document_mapper 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +22 -0
- data/README.textile +120 -0
- data/lib/document_mapper.rb +14 -0
- data/lib/document_mapper/attribute_methods.rb +25 -0
- data/lib/document_mapper/constants.rb +12 -0
- data/lib/document_mapper/core_ext/symbol.rb +9 -0
- data/lib/document_mapper/document.rb +94 -0
- data/lib/document_mapper/errors.rb +4 -0
- data/lib/document_mapper/query.rb +54 -0
- data/lib/document_mapper/selector.rb +12 -0
- data/lib/document_mapper/version.rb +3 -0
- data/lib/document_mapper/yaml_parsing.rb +24 -0
- data/test/document_mapper/core_ext/symbol_test.rb +29 -0
- data/test/document_mapper/document_mapper_test.rb +253 -0
- data/test/document_mapper/selector_test.rb +16 -0
- data/test/documents/2010-08-08-test-document-file.textile +8 -0
- data/test/documents/2010-08-09-another-test-document.textile +10 -0
- data/test/documents/document_with_date_in_yaml.textile +7 -0
- data/test/documents/document_without_date.textile +7 -0
- data/test/test_base.rb +11 -0
- metadata +94 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2010 Ralph von der Heyden
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.textile
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
h1. Document Mapper
|
2
|
+
|
3
|
+
Document mapper is an object mapper for plain text documents. The documents look like the ones used in "jekyll":http://github.com/mojombo/jekyll, "toto":http://github.com/cloudhead/toto or "Serious":http://github.com/colszowka/serious. They consist of a preambel written in YAML (also called YAML front matter), and some content in the format you prefer, e.g. Textile. This enables you to write documents in your favorite editor and access the content and metadata in your Ruby scripts.
|
4
|
+
|
5
|
+
|
6
|
+
h2. Step-by-step tutorial
|
7
|
+
|
8
|
+
Documents look somehow like this. The part between the @---@s is the YAML front matter. After the second @---@, there is one blank line, followed by the content of the file. All items in the YAML front matter and the content are accessible by Document Mapper.
|
9
|
+
|
10
|
+
<pre><code>---
|
11
|
+
id: 1
|
12
|
+
title: Ruby is great
|
13
|
+
tags: [programming, software]
|
14
|
+
number_of_foos: 42
|
15
|
+
status: published
|
16
|
+
---
|
17
|
+
|
18
|
+
I like Ruby.
|
19
|
+
</code></pre>
|
20
|
+
|
21
|
+
|
22
|
+
In order to access the values in the front matter, you have to create a class that includes @DocumentMapper@.
|
23
|
+
|
24
|
+
<pre><code>require 'document_mapper'
|
25
|
+
class MyDocument
|
26
|
+
include DocumentMapper::Document
|
27
|
+
end
|
28
|
+
</code></pre>
|
29
|
+
|
30
|
+
|
31
|
+
h3. Initializing single documents
|
32
|
+
|
33
|
+
<pre><code>doc = MyDocument.from_file('./documents/document-file.textile')
|
34
|
+
</code></pre>
|
35
|
+
|
36
|
+
|
37
|
+
h3. Accessing the attributes of single documents
|
38
|
+
|
39
|
+
<pre><code>doc.title # => "Ruby is great"
|
40
|
+
doc.tags # => ["programming", "software"]
|
41
|
+
doc.content # => "I like Ruby."
|
42
|
+
</code></pre>
|
43
|
+
|
44
|
+
|
45
|
+
h3. Date recognition
|
46
|
+
|
47
|
+
You can either set the date of a document in the YAML front matter, or you can use the file name, if you want to. A file named @2010-08-07-test-document-file.textile@ will return a date like this:
|
48
|
+
|
49
|
+
<pre><code>doc.date # => #<Date: 2010-08-08 (4910833/2,0,2299161)>
|
50
|
+
doc.date.to_s # => "2010-08-08"
|
51
|
+
doc.year # => 2010
|
52
|
+
doc.month # => 08
|
53
|
+
doc.day # => 07
|
54
|
+
</code></pre>
|
55
|
+
|
56
|
+
|
57
|
+
h3. Working with directories
|
58
|
+
|
59
|
+
As an example let's assume we have a directory called "documents" containing the following files:
|
60
|
+
|
61
|
+
<pre><code>documents/
|
62
|
+
|-foo.textile
|
63
|
+
|-bar.textile
|
64
|
+
</code></pre>
|
65
|
+
|
66
|
+
|
67
|
+
In order to work with a whole directory of files, we have to use the @directory@ method:
|
68
|
+
|
69
|
+
<pre><code>require 'document_mapper'
|
70
|
+
class MyDocument
|
71
|
+
include DocumentMapper::Document
|
72
|
+
self.directory = 'documents'
|
73
|
+
end
|
74
|
+
</code></pre>
|
75
|
+
|
76
|
+
Now we can receive all available documents or filter like that:
|
77
|
+
|
78
|
+
<pre><code>MyDocument.all
|
79
|
+
MyDocument.first
|
80
|
+
MyDocument.last
|
81
|
+
MyDocument.limit(2)
|
82
|
+
MyDocument.offset(2)
|
83
|
+
MyDocument.where(:title => 'Some title').first
|
84
|
+
MyDocument.where(:status => 'published').all
|
85
|
+
MyDocument.where(:year => 2010).all
|
86
|
+
</code></pre>
|
87
|
+
|
88
|
+
Not all of the documents in the directory need to have all of the attributes. You can add single attributes to single documents, and the queries will only return those documents where the attributes match.
|
89
|
+
|
90
|
+
The document queries do support more operators than just equality. The following operators are available:
|
91
|
+
|
92
|
+
<pre><code>MyDocument.where(:year.gt => 2010)
|
93
|
+
MyDocument.where(:year.gte => 2010)
|
94
|
+
MyDocument.where(:year.in => [2010,2011])
|
95
|
+
MyDocument.where(:year.lt => 2010)
|
96
|
+
MyDocument.where(:year.lte => 2010)
|
97
|
+
</code></pre>
|
98
|
+
|
99
|
+
|
100
|
+
h3. Chaining
|
101
|
+
|
102
|
+
Chaining works with all available query methods, e.g.:
|
103
|
+
|
104
|
+
<pre><code>MyDocument.where(:status => 'published').where(:title => 'Some title').limit(2).all
|
105
|
+
</code></pre>
|
106
|
+
|
107
|
+
|
108
|
+
h3. Reloading
|
109
|
+
|
110
|
+
If any of the files change, you must manually reload them:
|
111
|
+
|
112
|
+
<pre><code>MyDocument.reload
|
113
|
+
</code></pre>
|
114
|
+
|
115
|
+
|
116
|
+
h2. Author
|
117
|
+
|
118
|
+
Written by "Ralph von der Heyden":http://rvdh.de. Don't hesitate to contact me if you have any further questions.
|
119
|
+
|
120
|
+
Follow me on "Twitter":http://twitter.com/ralph!
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'active_support/core_ext/class'
|
2
|
+
require 'active_support/concern'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
require 'document_mapper/constants'
|
6
|
+
require 'document_mapper/errors'
|
7
|
+
require 'document_mapper/attribute_methods'
|
8
|
+
require 'document_mapper/yaml_parsing'
|
9
|
+
require 'document_mapper/document'
|
10
|
+
require 'document_mapper/query'
|
11
|
+
require 'document_mapper/selector'
|
12
|
+
require 'document_mapper/version'
|
13
|
+
|
14
|
+
require 'document_mapper/core_ext/symbol'
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module DocumentMapper
|
2
|
+
module AttributeMethods
|
3
|
+
module Read
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
# Undefine id so it can be used as an attribute name
|
8
|
+
undef_method(:id) if method_defined?(:id)
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def define_read_method(attr_name)
|
13
|
+
access_code = "attributes['#{attr_name}']"
|
14
|
+
generated_attribute_methods.module_eval("def #{attr_name}; #{access_code}; end", __FILE__, __LINE__)
|
15
|
+
|
16
|
+
%w(year month day).each do |attr_name|
|
17
|
+
access_code = "date.#{attr_name} if date"
|
18
|
+
generated_attribute_methods.module_eval("def #{attr_name}; #{access_code}; end", __FILE__, __LINE__)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
|
3
|
+
module DocumentMapper
|
4
|
+
module Document
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
include ActiveModel::AttributeMethods
|
7
|
+
include AttributeMethods::Read
|
8
|
+
include YamlParsing
|
9
|
+
|
10
|
+
attr_accessor :attributes, :content, :directory, :file_path
|
11
|
+
|
12
|
+
included do
|
13
|
+
@@documents = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def ==(other_document)
|
17
|
+
return false unless other_document.is_a? Document
|
18
|
+
self.file_path == other_document.file_path
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
def reset
|
23
|
+
@@documents = []
|
24
|
+
end
|
25
|
+
|
26
|
+
def reload
|
27
|
+
self.reset
|
28
|
+
self.directory = @@directory
|
29
|
+
end
|
30
|
+
|
31
|
+
def from_file(file_path)
|
32
|
+
if !File.exist? file_path
|
33
|
+
raise FileNotFoundError
|
34
|
+
end
|
35
|
+
self.new.tap do |document|
|
36
|
+
document.file_path = File.expand_path(file_path)
|
37
|
+
document.read_yaml
|
38
|
+
@@documents << document
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def directory=(new_directory)
|
43
|
+
raise FileNotFoundError unless File.directory?(new_directory)
|
44
|
+
self.reset
|
45
|
+
@@directory = Dir.new File.expand_path(new_directory)
|
46
|
+
@@directory.each do |file|
|
47
|
+
next if ['.', '..'].include? file
|
48
|
+
self.from_file [@@directory.path, file].join('/')
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def select(options = {})
|
53
|
+
documents = @@documents.dup
|
54
|
+
options[:where].each do |selector, selector_value|
|
55
|
+
documents.select! do |document|
|
56
|
+
next unless document.respond_to? selector.attribute
|
57
|
+
document_value = document.send(selector.attribute)
|
58
|
+
operator = REVERSE_OPERATOR_MAPPING[selector.operator]
|
59
|
+
selector_value.send operator, document_value
|
60
|
+
end
|
61
|
+
end
|
62
|
+
documents
|
63
|
+
end
|
64
|
+
|
65
|
+
def where(hash)
|
66
|
+
Query.new(self).where(hash)
|
67
|
+
end
|
68
|
+
|
69
|
+
def sort(field)
|
70
|
+
Query.new(self).sort(field)
|
71
|
+
end
|
72
|
+
|
73
|
+
def offset(number)
|
74
|
+
Query.new(self).offset(number)
|
75
|
+
end
|
76
|
+
|
77
|
+
def limit(number)
|
78
|
+
Query.new(self).limit(number)
|
79
|
+
end
|
80
|
+
|
81
|
+
def all
|
82
|
+
@@documents
|
83
|
+
end
|
84
|
+
|
85
|
+
def first
|
86
|
+
@@documents.first
|
87
|
+
end
|
88
|
+
|
89
|
+
def last
|
90
|
+
@@documents.last
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module DocumentMapper
|
2
|
+
class Query
|
3
|
+
def initialize(model)
|
4
|
+
@model = model
|
5
|
+
@where = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def where(constraints_hash)
|
9
|
+
differentiator = ->(key, value){ key.is_a? Selector }
|
10
|
+
selector_hash = constraints_hash.select &differentiator
|
11
|
+
symbol_hash = constraints_hash.reject &differentiator
|
12
|
+
symbol_hash.each do |attribute, value|
|
13
|
+
selector = Selector.new(:attribute => attribute, :operator => 'equal')
|
14
|
+
selector_hash.update({ selector => value })
|
15
|
+
end
|
16
|
+
@where.merge! selector_hash
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def sort(field)
|
21
|
+
@sort = field
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def offset(number)
|
26
|
+
@offset = number
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def limit(number)
|
31
|
+
@limit = number
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def first
|
36
|
+
self.all.first
|
37
|
+
end
|
38
|
+
|
39
|
+
def last
|
40
|
+
self.all.last
|
41
|
+
end
|
42
|
+
|
43
|
+
def all
|
44
|
+
result = @model.select(:where => @where, :sort => @sort)
|
45
|
+
if @offset.present?
|
46
|
+
result = result.last(result.size - @offset)
|
47
|
+
end
|
48
|
+
if @limit.present?
|
49
|
+
result = result.first(@limit)
|
50
|
+
end
|
51
|
+
result
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module DocumentMapper
|
2
|
+
class Selector
|
3
|
+
attr_reader :attribute, :operator
|
4
|
+
|
5
|
+
def initialize(opts = {})
|
6
|
+
unless VALID_OPERATORS.include? opts[:operator]
|
7
|
+
raise OperatorNotSupportedError
|
8
|
+
end
|
9
|
+
@attribute, @operator = opts[:attribute], opts[:operator]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module DocumentMapper
|
2
|
+
module YamlParsing
|
3
|
+
def read_yaml
|
4
|
+
@content = File.read(file_path)
|
5
|
+
|
6
|
+
if @content =~ /^(---\s*\n.*?\n?)^(---\s*$\n?)/m
|
7
|
+
@content = @content[($1.size + $2.size)..-1]
|
8
|
+
self.attributes = YAML.load($1)
|
9
|
+
end
|
10
|
+
|
11
|
+
self.attributes ||= {}
|
12
|
+
if !self.attributes.has_key? 'date'
|
13
|
+
begin
|
14
|
+
match = File.basename(@file_path).match(/(\d{4})-(\d{1,2})-(\d{1,2}).*/)
|
15
|
+
self.attributes['date'] = Date.new(match[1].to_i, match[2].to_i, match[3].to_i)
|
16
|
+
rescue NoMethodError => err
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
self.class.define_attribute_methods self.attributes.keys
|
21
|
+
self.attributes.keys.each { |attr| self.class.define_read_method attr }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.join(File.expand_path(__FILE__), '..', '..', '..', 'test_base')
|
2
|
+
include DocumentMapper
|
3
|
+
|
4
|
+
describe Symbol do
|
5
|
+
it 'should create a selector from a valid operator' do
|
6
|
+
selector = :my_attribute.gte
|
7
|
+
assert_equal 'gte', selector.operator
|
8
|
+
assert_equal :my_attribute, selector.attribute
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should not raise an error on valid operators' do
|
12
|
+
begin
|
13
|
+
:my_attribute.equal
|
14
|
+
:my_attribute.gt
|
15
|
+
:my_attribute.gte
|
16
|
+
:my_attribute.in
|
17
|
+
:my_attribute.lt
|
18
|
+
:my_attribute.lte
|
19
|
+
rescue StandardError => e
|
20
|
+
assert false, 'Calling operator on symbol raised error'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should raise an error on invalid operators' do
|
25
|
+
assert_raises NoMethodError do
|
26
|
+
:my_attribute.not_supported
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,253 @@
|
|
1
|
+
require File.join(File.expand_path(__FILE__), '..', '..', 'test_base')
|
2
|
+
include DocumentMapper
|
3
|
+
|
4
|
+
describe MyDocument do
|
5
|
+
before do
|
6
|
+
MyDocument.reset
|
7
|
+
end
|
8
|
+
|
9
|
+
describe 'loading a document from file' do
|
10
|
+
before do
|
11
|
+
@file_path = sample_file_path_1
|
12
|
+
@document = MyDocument.from_file(@file_path)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should load the document from a yaml file' do
|
16
|
+
assert_equal 1, @document.attributes['id']
|
17
|
+
assert_equal 'Some fancy title', @document.attributes['title']
|
18
|
+
assert_equal ['ruby'], @document.attributes['tags']
|
19
|
+
assert_equal :published, @document.attributes['status']
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should specify attributes from the YAML header' do
|
23
|
+
assert_equal 1, @document.id
|
24
|
+
assert_equal 'Some fancy title', @document.title
|
25
|
+
assert_equal ['ruby'], @document.tags
|
26
|
+
assert_equal :published, @document.status
|
27
|
+
assert_equal File.expand_path(@file_path), @document.file_path
|
28
|
+
end
|
29
|
+
|
30
|
+
describe 'specifying the date of the document' do
|
31
|
+
it 'should get the date from the filename' do
|
32
|
+
assert_equal '2010-08-08', @document.date.to_s
|
33
|
+
assert_equal 2010, @document.date.year
|
34
|
+
assert_equal 8, @document.date.month
|
35
|
+
assert_equal 8, @document.date.day
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should delegate the day method to date' do
|
39
|
+
assert_equal 2010, @document.year
|
40
|
+
assert_equal 8, @document.month
|
41
|
+
assert_equal 8, @document.day
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should get the date from the yaml front matter if there is one' do
|
45
|
+
@document = sample_document_with_date_in_yaml
|
46
|
+
assert_equal 2011, @document.year
|
47
|
+
assert_equal 4, @document.month
|
48
|
+
assert_equal 5, @document.day
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should not freak out if there is no date' do
|
52
|
+
@document = sample_document_without_date
|
53
|
+
assert_equal nil, @document.date
|
54
|
+
assert_equal nil, @document.year
|
55
|
+
assert_equal nil, @document.month
|
56
|
+
assert_equal nil, @document.day
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe 'loading a documents directory' do
|
61
|
+
it 'should load all the documents in that directory' do
|
62
|
+
MyDocument.directory = 'test/documents'
|
63
|
+
assert_equal [1,2,3,4], MyDocument.all.map(&:id)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe 'getting all/the first/the last MyDocument(s)' do
|
69
|
+
before do
|
70
|
+
@all_documents = [sample_document_1, sample_document_2]
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should return all documents' do
|
74
|
+
assert_equal @all_documents, MyDocument.all
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should return the first document' do
|
78
|
+
assert_equal @all_documents.first, MyDocument.first
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should return the last document' do
|
82
|
+
assert_equal @all_documents.last, MyDocument.last
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe 'using offset and limit' do
|
87
|
+
before do
|
88
|
+
MyDocument.directory = 'test/documents'
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'should limit the documents to the number specified' do
|
92
|
+
assert_equal [1,2], MyDocument.limit(2).all.map(&:id)
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'should offset the documents by the number specified' do
|
96
|
+
assert_equal [3,4], MyDocument.offset(2).all.map(&:id)
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'should support offset and limit at the same time' do
|
100
|
+
assert_equal [2,3], MyDocument.offset(1).limit(2).all.map(&:id)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe 'resetting the MyDocument class' do
|
105
|
+
it 'should clear all documents' do
|
106
|
+
one_document = sample_document_1
|
107
|
+
assert_equal [one_document], MyDocument.all
|
108
|
+
MyDocument.reset
|
109
|
+
assert_equal [], MyDocument.all
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe 'using where queries' do
|
114
|
+
before do
|
115
|
+
@document_1 = sample_document_1
|
116
|
+
@document_2 = sample_document_2
|
117
|
+
MyDocument.directory = 'test/documents'
|
118
|
+
end
|
119
|
+
|
120
|
+
describe 'with an equal operator' do
|
121
|
+
it 'should return the right documents' do
|
122
|
+
found_document = MyDocument.where(:title => @document_1.title).first
|
123
|
+
assert_equal @document_1, found_document
|
124
|
+
found_document = MyDocument.where(:title => @document_2.title).first
|
125
|
+
assert_equal @document_2, found_document
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'should be chainable' do
|
129
|
+
document_proxy = MyDocument.where(:title => @document_1.title)
|
130
|
+
document_proxy.where(:id => @document_1.id)
|
131
|
+
assert_equal @document_1, document_proxy.first
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'should work with dates' do
|
135
|
+
found_documents = MyDocument.where(:year => 2010).all
|
136
|
+
expected_documents = [sample_document_1, sample_document_2]
|
137
|
+
assert_equal expected_documents.map(&:id), found_documents.map(&:id)
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'should not be confused by attributes not present in all documents' do
|
141
|
+
MyDocument.directory = 'test/documents'
|
142
|
+
result = MyDocument.where(:seldom_attribute => 'is seldom').all
|
143
|
+
assert_equal [4], result.map(&:id)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
describe 'with a gt operator' do
|
148
|
+
it 'should return the right documents' do
|
149
|
+
selector = Selector.new :attribute => 'id', :operator => 'gt'
|
150
|
+
found_documents = MyDocument.where(selector => 2).all
|
151
|
+
assert_equal [3,4], found_documents.map(&:id)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe 'with a gte operator' do
|
156
|
+
it 'should return the right documents' do
|
157
|
+
selector = Selector.new :attribute => 'id', :operator => 'gte'
|
158
|
+
found_documents = MyDocument.where(selector => 2).all
|
159
|
+
assert_equal [2,3,4], found_documents.map(&:id)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
describe 'with an in operator' do
|
164
|
+
it 'should return the right documents' do
|
165
|
+
selector = Selector.new :attribute => 'id', :operator => 'in'
|
166
|
+
found_documents = MyDocument.where(selector => [2,3]).all
|
167
|
+
assert_equal [2,3], found_documents.map(&:id)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
describe 'with an lt operator' do
|
172
|
+
it 'should return the right documents' do
|
173
|
+
selector = Selector.new :attribute => 'id', :operator => 'lt'
|
174
|
+
found_documents = MyDocument.where(selector => 2).all
|
175
|
+
assert_equal [1], found_documents.map(&:id)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
describe 'with an lte operator' do
|
180
|
+
it 'should return the right documents' do
|
181
|
+
selector = Selector.new :attribute => 'id', :operator => 'lte'
|
182
|
+
found_documents = MyDocument.where(selector => 2).all
|
183
|
+
assert_equal [1,2], found_documents.map(&:id)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
describe 'with mixed operators' do
|
188
|
+
it 'should return the right documents' do
|
189
|
+
in_selector = Selector.new :attribute => 'id', :operator => 'in'
|
190
|
+
gt_selector = Selector.new :attribute => 'id', :operator => 'gt'
|
191
|
+
documents_proxy = MyDocument.where(in_selector => [2,3])
|
192
|
+
found_documents = documents_proxy.where(gt_selector => 2).all
|
193
|
+
assert_equal [3], found_documents.map(&:id)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
describe 'using multiple constrains in one where' do
|
198
|
+
it 'should return the right documents' do
|
199
|
+
selector = Selector.new :attribute => 'id', :operator => 'lte'
|
200
|
+
found_documents = MyDocument.where(selector => 2, :status => :published).all
|
201
|
+
assert_equal [1,2], found_documents.map(&:id)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
describe 'reloading the Document class' do
|
207
|
+
it 'should discover new documents' do
|
208
|
+
@file_path = 'test/documents/2011-04-26-new-stuff.textile'
|
209
|
+
File.open(@file_path, 'w') do |f|
|
210
|
+
f.write <<-EOS
|
211
|
+
---
|
212
|
+
id: 5
|
213
|
+
title: Some brand new document
|
214
|
+
---
|
215
|
+
|
216
|
+
Very new stuff.
|
217
|
+
EOS
|
218
|
+
end
|
219
|
+
MyDocument.reload
|
220
|
+
assert_equal [1,2,3,4,5].sort, MyDocument.all.map(&:id).sort
|
221
|
+
end
|
222
|
+
|
223
|
+
def teardown
|
224
|
+
File.delete @file_path
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def sample_file_path_1
|
229
|
+
'test/documents/2010-08-08-test-document-file.textile'
|
230
|
+
end
|
231
|
+
|
232
|
+
def sample_file_path_2
|
233
|
+
'test/documents/2010-08-09-another-test-document.textile'
|
234
|
+
end
|
235
|
+
|
236
|
+
def sample_document_1
|
237
|
+
MyDocument.from_file(sample_file_path_1)
|
238
|
+
end
|
239
|
+
|
240
|
+
def sample_document_2
|
241
|
+
MyDocument.from_file(sample_file_path_2)
|
242
|
+
end
|
243
|
+
|
244
|
+
def sample_document_with_date_in_yaml
|
245
|
+
file_path = 'test/documents/document_with_date_in_yaml.textile'
|
246
|
+
MyDocument.from_file(file_path)
|
247
|
+
end
|
248
|
+
|
249
|
+
def sample_document_without_date
|
250
|
+
file_path = 'test/documents/document_without_date.textile'
|
251
|
+
MyDocument.from_file(file_path)
|
252
|
+
end
|
253
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require File.join(File.expand_path(__FILE__), '..', '..', 'test_base')
|
2
|
+
include DocumentMapper
|
3
|
+
|
4
|
+
describe Selector do
|
5
|
+
it 'should initialize with an attribute and an operator' do
|
6
|
+
selector = Selector.new :attribute => 'author', :operator => 'equal'
|
7
|
+
assert_equal 'author', selector.attribute
|
8
|
+
assert_equal 'equal', selector.operator
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should raise an exception if the operator is not supported' do
|
12
|
+
assert_raises OperatorNotSupportedError do
|
13
|
+
selector = Selector.new :attribute => 'author', :operator => 'zomg'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/test/test_base.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'minitest/spec'
|
2
|
+
MiniTest::Unit.autorun
|
3
|
+
|
4
|
+
lib_dir = File.join(File.dirname(__FILE__), '..', 'lib')
|
5
|
+
$LOAD_PATH.unshift lib_dir unless $LOAD_PATH.include?(lib_dir)
|
6
|
+
require 'document_mapper'
|
7
|
+
TEST_DIR = File.dirname(__FILE__)
|
8
|
+
|
9
|
+
class MyDocument
|
10
|
+
include DocumentMapper::Document
|
11
|
+
end
|
metadata
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: document_mapper
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ralph von der Heyden
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-04-30 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: activesupport
|
17
|
+
prerelease: false
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ~>
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 3.0.0
|
24
|
+
type: :runtime
|
25
|
+
version_requirements: *id001
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: activemodel
|
28
|
+
prerelease: false
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ~>
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 3.0.0
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id002
|
37
|
+
description: " DocumentMapper is an object mapper for plain text documents. The documents look like the ones used in jekyll (http://github.com/mojombo/jekyll). They consist of a preambel written in YAML (also called YAML front matter), and some content in the format you prefer, e.g. Textile. This enables you to write documents in your favorite editor and access the content and metadata of these in your Ruby scripts.\n"
|
38
|
+
email: ralph@rvdh.de
|
39
|
+
executables: []
|
40
|
+
|
41
|
+
extensions: []
|
42
|
+
|
43
|
+
extra_rdoc_files: []
|
44
|
+
|
45
|
+
files:
|
46
|
+
- LICENSE
|
47
|
+
- README.textile
|
48
|
+
- lib/document_mapper/attribute_methods.rb
|
49
|
+
- lib/document_mapper/constants.rb
|
50
|
+
- lib/document_mapper/core_ext/symbol.rb
|
51
|
+
- lib/document_mapper/document.rb
|
52
|
+
- lib/document_mapper/errors.rb
|
53
|
+
- lib/document_mapper/query.rb
|
54
|
+
- lib/document_mapper/selector.rb
|
55
|
+
- lib/document_mapper/version.rb
|
56
|
+
- lib/document_mapper/yaml_parsing.rb
|
57
|
+
- lib/document_mapper.rb
|
58
|
+
- test/document_mapper/core_ext/symbol_test.rb
|
59
|
+
- test/document_mapper/document_mapper_test.rb
|
60
|
+
- test/document_mapper/selector_test.rb
|
61
|
+
- test/documents/2010-08-08-test-document-file.textile
|
62
|
+
- test/documents/2010-08-09-another-test-document.textile
|
63
|
+
- test/documents/document_with_date_in_yaml.textile
|
64
|
+
- test/documents/document_without_date.textile
|
65
|
+
- test/test_base.rb
|
66
|
+
homepage: http://github.com/ralph/document_mapper
|
67
|
+
licenses: []
|
68
|
+
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: "0"
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: "0"
|
86
|
+
requirements: []
|
87
|
+
|
88
|
+
rubyforge_project:
|
89
|
+
rubygems_version: 1.7.2
|
90
|
+
signing_key:
|
91
|
+
specification_version: 3
|
92
|
+
summary: DocumentMapper is an object mapper for plain text documents.
|
93
|
+
test_files: []
|
94
|
+
|