couch_potato 0.2.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGES.md +15 -0
- data/MIT-LICENSE.txt +19 -0
- data/README.md +295 -0
- data/VERSION.yml +4 -0
- data/init.rb +3 -0
- data/lib/core_ext/date.rb +10 -0
- data/lib/core_ext/object.rb +5 -0
- data/lib/core_ext/string.rb +19 -0
- data/lib/core_ext/symbol.rb +15 -0
- data/lib/core_ext/time.rb +11 -0
- data/lib/couch_potato.rb +40 -0
- data/lib/couch_potato/database.rb +106 -0
- data/lib/couch_potato/persistence.rb +98 -0
- data/lib/couch_potato/persistence/attachments.rb +31 -0
- data/lib/couch_potato/persistence/callbacks.rb +60 -0
- data/lib/couch_potato/persistence/dirty_attributes.rb +49 -0
- data/lib/couch_potato/persistence/ghost_attributes.rb +22 -0
- data/lib/couch_potato/persistence/json.rb +46 -0
- data/lib/couch_potato/persistence/magic_timestamps.rb +13 -0
- data/lib/couch_potato/persistence/properties.rb +52 -0
- data/lib/couch_potato/persistence/simple_property.rb +83 -0
- data/lib/couch_potato/persistence/validation.rb +18 -0
- data/lib/couch_potato/view/base_view_spec.rb +24 -0
- data/lib/couch_potato/view/custom_view_spec.rb +27 -0
- data/lib/couch_potato/view/custom_views.rb +44 -0
- data/lib/couch_potato/view/model_view_spec.rb +63 -0
- data/lib/couch_potato/view/properties_view_spec.rb +39 -0
- data/lib/couch_potato/view/raw_view_spec.rb +25 -0
- data/lib/couch_potato/view/view_query.rb +44 -0
- data/rails/init.rb +7 -0
- data/spec/attachments_spec.rb +23 -0
- data/spec/callbacks_spec.rb +271 -0
- data/spec/create_spec.rb +22 -0
- data/spec/custom_view_spec.rb +149 -0
- data/spec/default_property_spec.rb +34 -0
- data/spec/destroy_spec.rb +29 -0
- data/spec/fixtures/address.rb +9 -0
- data/spec/fixtures/person.rb +6 -0
- data/spec/property_spec.rb +101 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/unit/attributes_spec.rb +48 -0
- data/spec/unit/callbacks_spec.rb +33 -0
- data/spec/unit/couch_potato_spec.rb +20 -0
- data/spec/unit/create_spec.rb +58 -0
- data/spec/unit/customs_views_spec.rb +15 -0
- data/spec/unit/database_spec.rb +50 -0
- data/spec/unit/dirty_attributes_spec.rb +131 -0
- data/spec/unit/string_spec.rb +13 -0
- data/spec/unit/view_query_spec.rb +9 -0
- data/spec/update_spec.rb +40 -0
- metadata +153 -0
data/spec/create_spec.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe "create" do
|
4
|
+
before(:all) do
|
5
|
+
recreate_db
|
6
|
+
end
|
7
|
+
describe "succeeds" do
|
8
|
+
it "should store the class" do
|
9
|
+
@comment = Comment.new :title => 'my_title'
|
10
|
+
CouchPotato.database.save_document! @comment
|
11
|
+
CouchPotato.couchrest_database.get(@comment.id)['ruby_class'].should == 'Comment'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
describe "fails" do
|
15
|
+
it "should not store anything" do
|
16
|
+
@comment = Comment.new
|
17
|
+
CouchPotato.database.save_document @comment
|
18
|
+
CouchPotato.couchrest_database.documents['rows'].should be_empty
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,149 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
class Build
|
4
|
+
include CouchPotato::Persistence
|
5
|
+
|
6
|
+
property :state
|
7
|
+
property :time
|
8
|
+
|
9
|
+
view :timeline, :key => :time
|
10
|
+
view :count, :key => :time, :reduce => true
|
11
|
+
view :minimal_timeline, :key => :time, :properties => [:state], :type => :properties
|
12
|
+
view :key_array_timeline, :key => [:time, :state]
|
13
|
+
view :custom_timeline, :map => "function(doc) { emit(doc._id, {state: 'custom_' + doc.state}); }", :type => :custom
|
14
|
+
view :custom_timeline_returns_docs, :map => "function(doc) { emit(doc._id, null); }", :include_docs => true, :type => :custom
|
15
|
+
view :custom_with_reduce, :map => "function(doc) {if(doc.foreign_key) {emit(doc.foreign_key, 1);} else {emit(doc.id, 1)}}", :reduce => "function(key, values) {return({\"count\": sum(values)});}", :group => true, :type => :custom
|
16
|
+
view :raw, :type => :raw, :map => "function(doc) {emit(doc._id, doc.state)}"
|
17
|
+
view :filtered_raw, :type => :raw, :map => "function(doc) {emit(doc._id, doc.state)}", :results_filter => lambda{|res| res['rows'].map{|row| row['value']}}
|
18
|
+
view :with_view_options, :group => true, :key => :time
|
19
|
+
end
|
20
|
+
|
21
|
+
describe 'view' do
|
22
|
+
before(:each) do
|
23
|
+
recreate_db
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should return instances of the class" do
|
27
|
+
CouchPotato.database.save_document Build.new(:state => 'success', :time => '2008-01-01')
|
28
|
+
results = CouchPotato.database.view(Build.timeline)
|
29
|
+
results.map(&:class).should == [Build]
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should pass the view options to the view query" do
|
33
|
+
query = mock 'query'
|
34
|
+
CouchPotato::View::ViewQuery.stub!(:new).and_return(query)
|
35
|
+
query.should_receive(:query_view!).with(hash_including(:key => 1)).and_return('rows' => [])
|
36
|
+
CouchPotato.database.view Build.timeline(:key => 1)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should not return documents that don't have a matching ruby_class" do
|
40
|
+
CouchPotato.couchrest_database.save_doc({:time => 'x'})
|
41
|
+
CouchPotato.database.view(Build.timeline).should == []
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should count documents" do
|
45
|
+
CouchPotato.database.save_document Build.new(:state => 'success', :time => '2008-01-01')
|
46
|
+
CouchPotato.database.view(Build.count(:reduce => true)).should == 1
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should count zero documents" do
|
50
|
+
CouchPotato.database.view(Build.count(:reduce => true)).should == 0
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "properties defined" do
|
54
|
+
it "should assign the configured properties" do
|
55
|
+
CouchPotato.couchrest_database.save_doc(:state => 'success', :time => '2008-01-01', :ruby_class => 'Build')
|
56
|
+
CouchPotato.database.view(Build.minimal_timeline).first.state.should == 'success'
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should not assign the properties not configured" do
|
60
|
+
CouchPotato.couchrest_database.save_doc(:state => 'success', :time => '2008-01-01', :ruby_class => 'Build')
|
61
|
+
CouchPotato.database.view(Build.minimal_timeline).first.time.should be_nil
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should assign the id even if it is not configured" do
|
65
|
+
id = CouchPotato.couchrest_database.save_doc(:state => 'success', :time => '2008-01-01', :ruby_class => 'Build')['id']
|
66
|
+
CouchPotato.database.view(Build.minimal_timeline).first._id.should == id
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "no properties defined" do
|
71
|
+
it "should assign all properties to the objects by default" do
|
72
|
+
id = CouchPotato.couchrest_database.save_doc({:state => 'success', :time => '2008-01-01', :ruby_class => 'Build'})['id']
|
73
|
+
result = CouchPotato.database.view(Build.timeline).first
|
74
|
+
result.state.should == 'success'
|
75
|
+
result.time.should == '2008-01-01'
|
76
|
+
result._id.should == id
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "map function given" do
|
81
|
+
it "should still return instances of the class" do
|
82
|
+
CouchPotato.couchrest_database.save_doc({:state => 'success', :time => '2008-01-01'})
|
83
|
+
CouchPotato.database.view(Build.custom_timeline).map(&:class).should == [Build]
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should assign the properties from the value" do
|
87
|
+
CouchPotato.couchrest_database.save_doc({:state => 'success', :time => '2008-01-01'})
|
88
|
+
CouchPotato.database.view(Build.custom_timeline).map(&:state).should == ['custom_success']
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should assign the id" do
|
92
|
+
doc = CouchPotato.couchrest_database.save_doc({:state => 'success', :time => '2008-01-01'})
|
93
|
+
CouchPotato.database.view(Build.custom_timeline).map(&:_id).should == [doc['id']]
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should leave the other properties blank" do
|
97
|
+
CouchPotato.couchrest_database.save_doc({:state => 'success', :time => '2008-01-01'})
|
98
|
+
CouchPotato.database.view(Build.custom_timeline).map(&:time).should == [nil]
|
99
|
+
end
|
100
|
+
|
101
|
+
describe "that returns null documents" do
|
102
|
+
it "should return instances of the class" do
|
103
|
+
CouchPotato.couchrest_database.save_doc({:state => 'success', :time => '2008-01-01'})
|
104
|
+
CouchPotato.database.view(Build.custom_timeline_returns_docs).map(&:class).should == [Build]
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should assign the properties from the value" do
|
108
|
+
CouchPotato.couchrest_database.save_doc({:state => 'success', :time => '2008-01-01'})
|
109
|
+
CouchPotato.database.view(Build.custom_timeline_returns_docs).map(&:state).should == ['success']
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe "additional reduce function given" do
|
114
|
+
it "should still assign the id" do
|
115
|
+
doc = CouchPotato.couchrest_database.save_doc({})
|
116
|
+
CouchPotato.couchrest_database.save_doc({:foreign_key => doc['id']})
|
117
|
+
CouchPotato.database.view(Build.custom_with_reduce).map(&:_id).should == [doc['id']]
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe "with array as key" do
|
124
|
+
it "should create a map function with the composite key" do
|
125
|
+
CouchPotato::View::ViewQuery.should_receive(:new).with(anything, anything, anything, string_matching(/emit\(\[doc\['time'\], doc\['state'\]\]/), anything).and_return(stub('view query').as_null_object)
|
126
|
+
CouchPotato.database.view Build.key_array_timeline
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe "raw view" do
|
131
|
+
it "should return the raw data" do
|
132
|
+
CouchPotato.database.save_document Build.new(:state => 'success', :time => '2008-01-01')
|
133
|
+
CouchPotato.database.view(Build.raw)['rows'][0]['value'].should == 'success'
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should return filtred raw data" do
|
137
|
+
CouchPotato.database.save_document Build.new(:state => 'success', :time => '2008-01-01')
|
138
|
+
CouchPotato.database.view(Build.filtered_raw).should == ['success']
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should pass view options declared in the view declaration to the query" do
|
142
|
+
view_query = mock 'view_query'
|
143
|
+
CouchPotato::View::ViewQuery.stub!(:new).and_return(view_query)
|
144
|
+
view_query.should_receive(:query_view!).with(hash_including(:group => true)).and_return({'rows' => []})
|
145
|
+
CouchPotato.database.view(Build.with_view_options)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
class Test
|
4
|
+
include CouchPotato::Persistence
|
5
|
+
|
6
|
+
property :test, :default => 'Test value'
|
7
|
+
property :complex, :default => []
|
8
|
+
end
|
9
|
+
|
10
|
+
describe 'default properties' do
|
11
|
+
before(:all) do
|
12
|
+
recreate_db
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should use the default value if nothing is supplied" do
|
16
|
+
t = Test.new
|
17
|
+
|
18
|
+
t.test.should == 'Test value'
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should persist the default value if nothing is supplied" do
|
22
|
+
t = Test.new
|
23
|
+
CouchPotato.database.save_document! t
|
24
|
+
|
25
|
+
t = CouchPotato.database.load_document t.id
|
26
|
+
t.test.should == 'Test value'
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should not have the same default for two instances of the object" do
|
30
|
+
t = Test.new
|
31
|
+
t2 = Test.new
|
32
|
+
t.complex.object_id.should_not == t2.complex.object_id
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe 'destroy' do
|
4
|
+
before(:all) do
|
5
|
+
recreate_db
|
6
|
+
end
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
@comment = Comment.new :title => 'title'
|
10
|
+
CouchPotato.database.save_document! @comment
|
11
|
+
@comment_id = @comment.id
|
12
|
+
CouchPotato.database.destroy_document @comment
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should unset the id" do
|
16
|
+
@comment._id.should be_nil
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should unset the revision" do
|
20
|
+
@comment._rev.should be_nil
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should remove the document from the database" do
|
24
|
+
lambda {
|
25
|
+
CouchPotato.couchrest_database.get(@comment_id).should
|
26
|
+
}.should raise_error(RestClient::ResourceNotFound)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
require File.join(File.dirname(__FILE__), 'fixtures', 'address')
|
3
|
+
require File.join(File.dirname(__FILE__), 'fixtures', 'person')
|
4
|
+
|
5
|
+
class Watch
|
6
|
+
include CouchPotato::Persistence
|
7
|
+
|
8
|
+
property :time, :type => Time
|
9
|
+
property :overwritten_read
|
10
|
+
property :overwritten_write
|
11
|
+
|
12
|
+
def overwritten_read
|
13
|
+
super.to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
def overwritten_write=(value)
|
17
|
+
super value.to_s
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
describe 'properties' do
|
23
|
+
before(:all) do
|
24
|
+
recreate_db
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should allow me to overwrite read accessor and call super" do
|
28
|
+
Watch.new(:overwritten_read => 1).overwritten_read.should == '1'
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should allow me to overwrite write accessor and call super" do
|
32
|
+
Watch.new(:overwritten_write => 1).overwritten_write.should == '1'
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should return the property names" do
|
36
|
+
Comment.property_names.should == [:created_at, :updated_at, :title]
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should persist a string" do
|
40
|
+
c = Comment.new :title => 'my title'
|
41
|
+
CouchPotato.database.save_document! c
|
42
|
+
c = CouchPotato.database.load_document c.id
|
43
|
+
c.title.should == 'my title'
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should persist a number" do
|
47
|
+
c = Comment.new :title => 3
|
48
|
+
CouchPotato.database.save_document! c
|
49
|
+
c = CouchPotato.database.load_document c.id
|
50
|
+
c.title.should == 3
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should persist a hash" do
|
54
|
+
c = Comment.new :title => {'key' => 'value'}
|
55
|
+
CouchPotato.database.save_document! c
|
56
|
+
c = CouchPotato.database.load_document c.id
|
57
|
+
c.title.should == {'key' => 'value'}
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should persist a Time object" do
|
61
|
+
w = Watch.new :time => Time.now
|
62
|
+
CouchPotato.database.save_document! w
|
63
|
+
w = CouchPotato.database.load_document w.id
|
64
|
+
w.time.year.should == Time.now.year
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should persist an object" do
|
68
|
+
p = Person.new :name => 'Bob'
|
69
|
+
a = Address.new :city => 'Denver'
|
70
|
+
p.ship_address = a
|
71
|
+
CouchPotato.database.save_document! p
|
72
|
+
p = CouchPotato.database.load_document p.id
|
73
|
+
p.ship_address.should === a
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should persist null for a null " do
|
77
|
+
p = Person.new :name => 'Bob'
|
78
|
+
p.ship_address = nil
|
79
|
+
CouchPotato.database.save_document! p
|
80
|
+
p = CouchPotato.database.load_document p.id
|
81
|
+
p.ship_address.should be_nil
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "predicate" do
|
85
|
+
it "should return true if property set" do
|
86
|
+
Comment.new(:title => 'title').title?.should be_true
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should return false if property nil" do
|
90
|
+
Comment.new.title?.should be_false
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should return false if property false" do
|
94
|
+
Comment.new(:title => false).title?.should be_false
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should return false if property blank" do
|
98
|
+
Comment.new(:title => '').title?.should be_false
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'spec'
|
3
|
+
|
4
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
5
|
+
|
6
|
+
require 'couch_potato'
|
7
|
+
|
8
|
+
CouchPotato::Config.database_name = 'couch_potato_test'
|
9
|
+
|
10
|
+
|
11
|
+
class Comment
|
12
|
+
include CouchPotato::Persistence
|
13
|
+
|
14
|
+
validates_presence_of :title
|
15
|
+
|
16
|
+
property :title
|
17
|
+
end
|
18
|
+
|
19
|
+
def recreate_db
|
20
|
+
CouchPotato.couchrest_database.delete! rescue nil
|
21
|
+
CouchPotato.couchrest_database.server.create_db CouchPotato::Config.database_name
|
22
|
+
end
|
23
|
+
recreate_db
|
24
|
+
|
25
|
+
Spec::Matchers.define :string_matching do |regex|
|
26
|
+
match do |string|
|
27
|
+
string =~ regex
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
class Plant
|
4
|
+
include CouchPotato::Persistence
|
5
|
+
property :leaf_count
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "attributes" do
|
9
|
+
|
10
|
+
describe 'attributes=' do
|
11
|
+
it "should assign the attributes" do
|
12
|
+
plant = Plant.new
|
13
|
+
plant.attributes = {:leaf_count => 1}
|
14
|
+
plant.leaf_count.should == 1
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "attributes" do
|
19
|
+
it "should return the attributes" do
|
20
|
+
plant = Plant.new(:leaf_count => 1)
|
21
|
+
plant.attributes.should == {:leaf_count => 1, :created_at => nil, :updated_at => nil}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# useful when loading models from custom views
|
26
|
+
describe "accessing ghost attributes" do
|
27
|
+
it "should allow me to access attributes that are in the couchdb document but not defined as a property" do
|
28
|
+
plant = Plant.json_create({"ruby_class" => "Plant", "color" => "red", "leaf_count" => 1})
|
29
|
+
plant.color.should == 'red'
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should raise a no method error when trying to read attributes that are not in the document" do
|
33
|
+
plant = Plant.json_create({"ruby_class" => "Plant", "leaf_count" => 1})
|
34
|
+
lambda do
|
35
|
+
plant.length
|
36
|
+
end.should raise_error(NoMethodError)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should raise a no method error if the document hasn't been loaded from the database" do
|
40
|
+
plant = Plant.new
|
41
|
+
lambda do
|
42
|
+
plant.length
|
43
|
+
end.should raise_error(NoMethodError, /undefined method `length'/)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|