To avoid relying on the filesystem for modified times.
Unified · Split
| @@ -2,6 +2,7 @@ module Docs · 1 unmodified line | ||
| 2 | 2 | class Doc |
| 3 | 3 | INDEX_FILENAME = 'index.json' |
| 4 | 4 | DB_FILENAME = 'db.json' |
| 5 | + META_FILENAME = 'meta.json' | |
| 5 | 6 | |
| 6 | 7 | class << self |
| 7 | 8 | include Instrumentable |
| @@ -73,6 +74,10 @@ module Docs · 65 unmodified lines | ||
| 73 | 74 | File.join path, DB_FILENAME |
| 74 | 75 | end |
| 75 | 76 | |
| 77 | + def meta_path | |
| 78 | + File.join path, META_FILENAME | |
| 79 | + end | |
| 80 | + | |
| 76 | 81 | def as_json |
| 77 | 82 | json = { name: name, slug: slug, type: type } |
| 78 | 83 | json[:links] = links if links.present? |
| @@ -107,6 +112,7 @@ module Docs · 28 unmodified lines | ||
| 107 | 112 | if index.present? |
| 108 | 113 | store_index(store, INDEX_FILENAME, index) |
| 109 | 114 | store_index(store, DB_FILENAME, pages) |
| 115 | + store_meta(store) | |
| 110 | 116 | true |
| 111 | 117 | else |
| 112 | 118 | false |
| @@ -131,8 +137,16 @@ module Docs · 18 unmodified lines | ||
| 131 | 137 | instrument "#{filename.remove('.json')}.doc", before: old_json, after: new_json |
| 132 | 138 | store.write(filename, new_json) |
| 133 | 139 | end |
| 140 | + | |
| 141 | + def store_meta(store) | |
| 142 | + json = as_json | |
| 143 | + json[:mtime] = Time.now.to_i | |
| 144 | + json[:db_size] = store.size(DB_FILENAME) | |
| 145 | + store.write(META_FILENAME, json.to_json) | |
| 146 | + end | |
| 134 | 147 | end |
| 135 | 148 | |
| 149 | + | |
| 136 | 150 | def initialize |
| 137 | 151 | raise NotImplementedError, "#{self.class} is an abstract class and cannot be instantiated." if self.class.abstract |
| 138 | 152 | end |
| @@ -14,32 +14,14 @@ module Docs · 13 unmodified lines | ||
| 14 | 14 | end |
| 15 | 15 | |
| 16 | 16 | def as_json |
| 17 | − indexed_docs.map do |doc| | |
| 18 | − json = doc.as_json | |
| 19 | − json[:mtime] = doc_mtime(doc) | |
| 20 | − json[:db_size] = doc_db_size(doc) | |
| 21 | − json | |
| 17 | + @docs.each_with_object [] do |doc, result| | |
| 18 | + next unless @store.exist?(doc.meta_path) | |
| 19 | + result << JSON.parse(@store.read(doc.meta_path)) | |
| 22 | 20 | end |
| 23 | 21 | end |
| 24 | 22 | |
| 25 | 23 | def to_json |
| 26 | 24 | JSON.generate(as_json) |
| 27 | 25 | end |
| 28 | − | |
| 29 | − private | |
| 30 | − | |
| 31 | − def indexed_docs | |
| 32 | − @docs.select do |doc| | |
| 33 | − @store.exist?(doc.index_path) && @store.exist?(doc.db_path) | |
| 34 | − end | |
| 35 | − end | |
| 36 | − | |
| 37 | − def doc_mtime(doc) | |
| 38 | − [@store.mtime(doc.index_path).to_i, @store.mtime(doc.db_path).to_i].max | |
| 39 | − end | |
| 40 | − | |
| 41 | − def doc_db_size(doc) | |
| 42 | − @store.size(doc.db_path) | |
| 43 | − end | |
| 44 | 26 | end |
| 45 | 27 | end |
| @@ -131,6 +131,13 @@ class DocsDocTest < MiniTest::Spec · 130 unmodified lines | ||
| 131 | 131 | end |
| 132 | 132 | end |
| 133 | 133 | |
| 134 | + describe ".meta_path" do | |
| 135 | + it "returns .path + ::META_FILENAME" do | |
| 136 | + stub(doc).path { 'path' } | |
| 137 | + assert_equal File.join('path', Docs::Doc::META_FILENAME), doc.meta_path | |
| 138 | + end | |
| 139 | + end | |
| 140 | + | |
| 134 | 141 | describe ".new" do |
| 135 | 142 | it "raises an error when .abstract is true" do |
| 136 | 143 | doc.abstract = true |
| @@ -265,12 +272,14 @@ class DocsDocTest < MiniTest::Spec · 128 unmodified lines | ||
| 265 | 272 | mock(store).write(page[:store_path], page[:output]) |
| 266 | 273 | mock(store).write('index.json', anything) |
| 267 | 274 | mock(store).write('db.json', anything) |
| 275 | + mock(store).write('meta.json', anything) | |
| 268 | 276 | doc.store_pages(store) |
| 269 | 277 | end |
| 270 | 278 | |
| 271 | 279 | it "stores an index that contains all the pages' entries" do |
| 272 | 280 | stub(store).write(page[:store_path], page[:output]) |
| 273 | 281 | stub(store).write('db.json', anything) |
| 282 | + stub(store).write('meta.json', anything) | |
| 274 | 283 | mock(store).write('index.json', anything) do |path, json| |
| 275 | 284 | json = JSON.parse(json) |
| 276 | 285 | assert_equal pages.length, json['entries'].length |
| @@ -282,6 +291,7 @@ class DocsDocTest < MiniTest::Spec · 5 unmodified lines | ||
| 282 | 291 | it "stores a db that contains all the pages, indexed by path" do |
| 283 | 292 | stub(store).write(page[:store_path], page[:output]) |
| 284 | 293 | stub(store).write('index.json', anything) |
| 294 | + stub(store).write('meta.json', anything) | |
| 285 | 295 | mock(store).write('db.json', anything) do |path, json| |
| 286 | 296 | json = JSON.parse(json) |
| 287 | 297 | assert_equal page[:output], json[page[:path]] |
| @@ -289,6 +299,17 @@ class DocsDocTest < MiniTest::Spec · 1 unmodified line | ||
| 289 | 299 | doc.store_pages(store) |
| 290 | 300 | end |
| 291 | 301 | |
| 302 | + it "stores a meta file that contains the doc's metadata" do | |
| 303 | + stub(store).write(page[:store_path], page[:output]) | |
| 304 | + stub(store).write('index.json', anything) | |
| 305 | + stub(store).write('db.json', anything) | |
| 306 | + mock(store).write('meta.json', anything) do |path, json| | |
| 307 | + json = JSON.parse(json) | |
| 308 | + assert_equal %w(name slug type mtime db_size).sort, json.keys.sort | |
| 309 | + end | |
| 310 | + doc.store_pages(store) | |
| 311 | + end | |
| 312 | + | |
| 292 | 313 | it "replaces the .path directory before storing the files" do |
| 293 | 314 | stub(doc).path { 'path' } |
| 294 | 315 | stub(store).write { assert false } |
| @@ -41,17 +41,12 @@ class ManifestTest < MiniTest::Spec · 40 unmodified lines | ||
| 41 | 41 | end |
| 42 | 42 | |
| 43 | 43 | describe "#as_json" do |
| 44 | − let :index_path do | |
| 45 | − 'index_path' | |
| 46 | − end | |
| 47 | − | |
| 48 | − let :db_path do | |
| 49 | − 'db_path' | |
| 44 | + let :meta_path do | |
| 45 | + 'meta_path' | |
| 50 | 46 | end |
| 51 | 47 | |
| 52 | 48 | before do |
| 53 | − stub(doc).index_path { index_path } | |
| 54 | − stub(doc).db_path { db_path } | |
| 49 | + stub(doc).meta_path { meta_path } | |
| 55 | 50 | end |
| 56 | 51 | |
| 57 | 52 | it "returns an array" do |
| @@ -59,46 +54,22 @@ class ManifestTest < MiniTest::Spec · 1 unmodified line | ||
| 59 | 54 | assert_instance_of Array, manifest.as_json |
| 60 | 55 | end |
| 61 | 56 | |
| 62 | − context "when the doc has an index and a db" do | |
| 57 | + context "when the doc has a meta file" do | |
| 63 | 58 | before do |
| 64 | − stub(store).exist?(index_path) { true } | |
| 65 | − stub(store).exist?(db_path) { true } | |
| 59 | + stub(store).exist?(meta_path) { true } | |
| 60 | + stub(store).read(meta_path) { '{"name":"Test"}' } | |
| 66 | 61 | end |
| 67 | 62 | |
| 68 | − it "includes the doc's JSON representation" do | |
| 63 | + it "includes the doc's meta representation" do | |
| 69 | 64 | json = manifest.as_json |
| 70 | 65 | assert_equal 1, json.length |
| 71 | − assert_empty doc.as_json.keys - json.first.keys | |
| 72 | − end | |
| 73 | − | |
| 74 | − it "adds an :mtime attribute with the greatest of the index and db files' mtime" do | |
| 75 | − mtime_index = Time.now - 1 | |
| 76 | − mtime_db = Time.now - 2 | |
| 77 | − stub(store).mtime(index_path) { mtime_index } | |
| 78 | − stub(store).mtime(db_path) { mtime_db } | |
| 79 | − assert_equal mtime_index.to_i, manifest.as_json.first[:mtime] | |
| 80 | − mtime_index, mtime_db = mtime_db, mtime_index | |
| 81 | − assert_equal mtime_db.to_i, manifest.as_json.first[:mtime] | |
| 82 | − end | |
| 83 | − | |
| 84 | − it "adds a :db_size attribute" do | |
| 85 | − stub(store).size(db_path) { 42 } | |
| 86 | − assert_equal 42, manifest.as_json.first[:db_size] | |
| 87 | − end | |
| 88 | − end | |
| 89 | − | |
| 90 | − context "when the doc doesn't have an index" do | |
| 91 | − it "doesn't include the doc" do | |
| 92 | − stub(store).exist?(db_path) { true } | |
| 93 | − stub(store).exist?(index_path) { false } | |
| 94 | − assert_empty manifest.as_json | |
| 66 | + assert_equal 'Test', json[0]['name'] | |
| 95 | 67 | end |
| 96 | 68 | end |
| 97 | 69 | |
| 98 | − context "when the doc doesn't have a db" do | |
| 70 | + context "when the doc doesn't have a meta file" do | |
| 99 | 71 | it "doesn't include the doc" do |
| 100 | − stub(store).exist?(index_path) { true } | |
| 101 | − stub(store).exist?(db_path) { false } | |
| 72 | + stub(store).exist?(meta_path) { false } | |
| 102 | 73 | assert_empty manifest.as_json |
| 103 | 74 | end |
| 104 | 75 | end |