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.
Files changed (52) hide show
  1. data/CHANGES.md +15 -0
  2. data/MIT-LICENSE.txt +19 -0
  3. data/README.md +295 -0
  4. data/VERSION.yml +4 -0
  5. data/init.rb +3 -0
  6. data/lib/core_ext/date.rb +10 -0
  7. data/lib/core_ext/object.rb +5 -0
  8. data/lib/core_ext/string.rb +19 -0
  9. data/lib/core_ext/symbol.rb +15 -0
  10. data/lib/core_ext/time.rb +11 -0
  11. data/lib/couch_potato.rb +40 -0
  12. data/lib/couch_potato/database.rb +106 -0
  13. data/lib/couch_potato/persistence.rb +98 -0
  14. data/lib/couch_potato/persistence/attachments.rb +31 -0
  15. data/lib/couch_potato/persistence/callbacks.rb +60 -0
  16. data/lib/couch_potato/persistence/dirty_attributes.rb +49 -0
  17. data/lib/couch_potato/persistence/ghost_attributes.rb +22 -0
  18. data/lib/couch_potato/persistence/json.rb +46 -0
  19. data/lib/couch_potato/persistence/magic_timestamps.rb +13 -0
  20. data/lib/couch_potato/persistence/properties.rb +52 -0
  21. data/lib/couch_potato/persistence/simple_property.rb +83 -0
  22. data/lib/couch_potato/persistence/validation.rb +18 -0
  23. data/lib/couch_potato/view/base_view_spec.rb +24 -0
  24. data/lib/couch_potato/view/custom_view_spec.rb +27 -0
  25. data/lib/couch_potato/view/custom_views.rb +44 -0
  26. data/lib/couch_potato/view/model_view_spec.rb +63 -0
  27. data/lib/couch_potato/view/properties_view_spec.rb +39 -0
  28. data/lib/couch_potato/view/raw_view_spec.rb +25 -0
  29. data/lib/couch_potato/view/view_query.rb +44 -0
  30. data/rails/init.rb +7 -0
  31. data/spec/attachments_spec.rb +23 -0
  32. data/spec/callbacks_spec.rb +271 -0
  33. data/spec/create_spec.rb +22 -0
  34. data/spec/custom_view_spec.rb +149 -0
  35. data/spec/default_property_spec.rb +34 -0
  36. data/spec/destroy_spec.rb +29 -0
  37. data/spec/fixtures/address.rb +9 -0
  38. data/spec/fixtures/person.rb +6 -0
  39. data/spec/property_spec.rb +101 -0
  40. data/spec/spec.opts +4 -0
  41. data/spec/spec_helper.rb +29 -0
  42. data/spec/unit/attributes_spec.rb +48 -0
  43. data/spec/unit/callbacks_spec.rb +33 -0
  44. data/spec/unit/couch_potato_spec.rb +20 -0
  45. data/spec/unit/create_spec.rb +58 -0
  46. data/spec/unit/customs_views_spec.rb +15 -0
  47. data/spec/unit/database_spec.rb +50 -0
  48. data/spec/unit/dirty_attributes_spec.rb +131 -0
  49. data/spec/unit/string_spec.rb +13 -0
  50. data/spec/unit/view_query_spec.rb +9 -0
  51. data/spec/update_spec.rb +40 -0
  52. metadata +153 -0
@@ -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,9 @@
1
+ class Address
2
+ include CouchPotato::Persistence
3
+
4
+ property :street
5
+ property :city
6
+ property :state
7
+ property :zip
8
+ property :country
9
+ end
@@ -0,0 +1,6 @@
1
+ class Person
2
+ include CouchPotato::Persistence
3
+
4
+ property :name
5
+ property :ship_address, :type => Address
6
+ 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
@@ -0,0 +1,4 @@
1
+ --colour
2
+ --format progress
3
+ --loadby mtime
4
+ --reverse
@@ -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
+