couch_potato 0.2.12

Sign up to get free protection for your applications and to get access to all the features.
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
+