langalex-couch_potato 0.1

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 (40) hide show
  1. data/CREDITS +3 -0
  2. data/MIT-LICENSE.txt +19 -0
  3. data/init.rb +5 -0
  4. data/lib/core_ext/object.rb +5 -0
  5. data/lib/core_ext/time.rb +14 -0
  6. data/lib/couch_potato/active_record/compatibility.rb +9 -0
  7. data/lib/couch_potato/ordering.rb +88 -0
  8. data/lib/couch_potato/persistence/belongs_to_property.rb +44 -0
  9. data/lib/couch_potato/persistence/bulk_save_queue.rb +47 -0
  10. data/lib/couch_potato/persistence/callbacks.rb +96 -0
  11. data/lib/couch_potato/persistence/collection.rb +51 -0
  12. data/lib/couch_potato/persistence/external_collection.rb +54 -0
  13. data/lib/couch_potato/persistence/external_has_many_property.rb +68 -0
  14. data/lib/couch_potato/persistence/find.rb +21 -0
  15. data/lib/couch_potato/persistence/finder.rb +109 -0
  16. data/lib/couch_potato/persistence/inline_collection.rb +14 -0
  17. data/lib/couch_potato/persistence/inline_has_many_property.rb +39 -0
  18. data/lib/couch_potato/persistence/json.rb +46 -0
  19. data/lib/couch_potato/persistence/properties.rb +40 -0
  20. data/lib/couch_potato/persistence/simple_property.rb +33 -0
  21. data/lib/couch_potato/persistence.rb +186 -0
  22. data/lib/couch_potato/versioning.rb +46 -0
  23. data/lib/couch_potato.rb +20 -0
  24. data/spec/attributes_spec.rb +22 -0
  25. data/spec/belongs_to_spec.rb +37 -0
  26. data/spec/callbacks_spec.rb +229 -0
  27. data/spec/create_spec.rb +68 -0
  28. data/spec/destroy_spec.rb +24 -0
  29. data/spec/find_spec.rb +88 -0
  30. data/spec/finder_spec.rb +115 -0
  31. data/spec/has_many_spec.rb +178 -0
  32. data/spec/inline_collection_spec.rb +15 -0
  33. data/spec/ordering_spec.rb +94 -0
  34. data/spec/property_spec.rb +46 -0
  35. data/spec/reload_spec.rb +46 -0
  36. data/spec/spec.opts +4 -0
  37. data/spec/spec_helper.rb +31 -0
  38. data/spec/update_spec.rb +33 -0
  39. data/spec/versioning_spec.rb +149 -0
  40. metadata +132 -0
@@ -0,0 +1,39 @@
1
+ class InlineHasManyProperty
2
+ attr_accessor :name
3
+
4
+ def initialize(owner_clazz, name, options = {})
5
+ @name = name
6
+ getter = <<-GETTER
7
+ def #{name}
8
+ @#{name} ||= CouchPotato::Persistence::InlineCollection.new(#{item_class_name})
9
+ end
10
+ GETTER
11
+ owner_clazz.class_eval getter
12
+ end
13
+
14
+ def build(object, json)
15
+ object.send("#{name}").clear
16
+ json[name.to_s].each do |item|
17
+ item.delete 'ruby_class'
18
+ object.send("#{name}").build item
19
+ end
20
+ end
21
+
22
+ def save(object)
23
+
24
+ end
25
+
26
+ def serialize(json, object)
27
+ json[name.to_s] = object.send(name)
28
+ end
29
+
30
+ def destroy(object)
31
+
32
+ end
33
+
34
+ private
35
+
36
+ def item_class_name
37
+ @name.to_s.singularize.camelcase
38
+ end
39
+ end
@@ -0,0 +1,46 @@
1
+ module CouchPotato
2
+ module Persistence
3
+ module Json
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ def to_json(*args)
9
+ (self.class.properties).inject({}) do |props, property|
10
+ property.serialize(props, self)
11
+ props
12
+ end.merge('ruby_class' => self.class.name).merge(id_and_rev_json).merge(timestamps_json).to_json(*args)
13
+ end
14
+
15
+ private
16
+
17
+ def id_and_rev_json
18
+ [:_id, :_rev, :_deleted].inject({}) do |hash, key|
19
+ hash[key] = self.send(key) unless self.send(key).nil?
20
+ hash
21
+ end
22
+ end
23
+
24
+ def timestamps_json
25
+ [:created_at, :updated_at].inject({}) do |hash, key|
26
+ hash[key] = self.send(key).to_s unless self.send(key).nil?
27
+ hash
28
+ end
29
+ end
30
+
31
+ module ClassMethods
32
+ def json_create(json)
33
+ instance = self.new
34
+ instance.created_at = Time.parse(json['created_at'])
35
+ instance.updated_at = Time.parse(json['updated_at'])
36
+ instance._id = json['_id']
37
+ instance._rev = json['_rev']
38
+ properties.each do |property|
39
+ property.build(instance, json)
40
+ end
41
+ instance
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,40 @@
1
+ require File.dirname(__FILE__) + '/simple_property'
2
+ require File.dirname(__FILE__) + '/belongs_to_property'
3
+ require File.dirname(__FILE__) + '/inline_has_many_property'
4
+ require File.dirname(__FILE__) + '/external_has_many_property'
5
+
6
+ module CouchPotato
7
+ module Persistence
8
+ module Properties
9
+ def self.included(base)
10
+ base.extend ClassMethods
11
+ base.class_eval do
12
+ def self.properties
13
+ @@properties ||= {}
14
+ @@properties[self.name] ||= []
15
+ end
16
+ end
17
+ end
18
+
19
+ module ClassMethods
20
+ def property_names
21
+ properties.map(&:name)
22
+ end
23
+
24
+ def property(name, options = {})
25
+ clazz = options.delete(:class)
26
+ properties << (clazz || SimpleProperty).new(self, name, options)
27
+ end
28
+
29
+ def belongs_to(name)
30
+ property name, :class => BelongsToProperty
31
+ end
32
+
33
+ def has_many(name, options = {})
34
+ stored = options.delete(:stored)
35
+ property name, options.merge(:class => (stored == :inline ? InlineHasManyProperty : ExternalHasManyProperty))
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,33 @@
1
+ module CouchPotato
2
+ module Persistence
3
+ class SimpleProperty
4
+ attr_accessor :name
5
+
6
+ def initialize(owner_clazz, name, options = {})
7
+ self.name = name
8
+ owner_clazz.class_eval do
9
+ attr_accessor name
10
+ define_method "#{name}?" do
11
+ !self.send(name).nil? && !self.send(name).try(:blank?)
12
+ end
13
+ end
14
+ end
15
+
16
+ def build(object, json)
17
+ object.send "#{name}=", json.stringify_keys[name.to_s]
18
+ end
19
+
20
+ def save(object)
21
+
22
+ end
23
+
24
+ def destroy(object)
25
+
26
+ end
27
+
28
+ def serialize(json, object)
29
+ json[name] = object.send name
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,186 @@
1
+ require 'digest/md5'
2
+ require 'couchrest'
3
+ require 'validatable'
4
+ require File.dirname(__FILE__) + '/persistence/inline_collection'
5
+ require File.dirname(__FILE__) + '/persistence/external_collection'
6
+ require File.dirname(__FILE__) + '/persistence/properties'
7
+ require File.dirname(__FILE__) + '/persistence/callbacks'
8
+ require File.dirname(__FILE__) + '/persistence/json'
9
+ require File.dirname(__FILE__) + '/persistence/bulk_save_queue'
10
+ require File.dirname(__FILE__) + '/persistence/find'
11
+
12
+ module CouchPotato
13
+ module Persistence
14
+
15
+ class ValidationsFailedError < ::Exception; end
16
+ class UnsavedRecordError < ::Exception; end
17
+
18
+ def self.included(base)
19
+ base.send :extend, ClassMethods, Find
20
+ base.send :include, Callbacks, Properties, Validatable, Json
21
+ base.class_eval do
22
+ attr_accessor :_id, :_rev, :_attachments, :_deleted, :created_at, :updated_at
23
+ attr_reader :bulk_save_queue
24
+ alias_method :id, :_id
25
+ end
26
+ end
27
+
28
+ def initialize(attributes = {})
29
+ @bulk_save_queue = BulkSaveQueue.new
30
+ attributes.each do |name, value|
31
+ self.send("#{name}=", value)
32
+ end if attributes
33
+ end
34
+
35
+ def attributes=(hash)
36
+ hash.each do |attribute, value|
37
+ self.send "#{attribute}=", value
38
+ end
39
+ end
40
+
41
+ def attributes
42
+ self.class.properties.inject({}) do |res, property|
43
+ property.serialize(res, self)
44
+ res
45
+ end
46
+ end
47
+
48
+ def save!
49
+ save || raise(ValidationsFailedError.new(self.errors.full_messages))
50
+ end
51
+
52
+ def save
53
+ if new_document?
54
+ create_document
55
+ else
56
+ update_document
57
+ end
58
+ end
59
+
60
+ def destroy
61
+ run_callbacks(:before_destroy)
62
+ self._deleted = true
63
+ bulk_save_queue << self
64
+ destroy_dependent_objects
65
+ bulk_save_queue.save do |res|
66
+ self._id = nil
67
+ self._rev = nil
68
+ end
69
+ run_callbacks(:after_destroy)
70
+ end
71
+
72
+ def reload
73
+ raise(UnsavedRecordError.new) unless _id
74
+ json = self.class.db.get _id
75
+ self.class.properties.each do |property|
76
+ property.build self, json
77
+ end
78
+ end
79
+
80
+ def new_document?
81
+ _id.nil?
82
+ end
83
+
84
+ def to_param
85
+ _id
86
+ end
87
+
88
+ def [](name)
89
+ self.send name
90
+ end
91
+
92
+ def ==(other)
93
+ other.class == self.class && self.class.property_names.map{|name| self.send(name)} == self.class.property_names.map{|name| other.send(name)}
94
+ end
95
+
96
+ private
97
+
98
+ def create_document
99
+ run_callbacks :before_validation_on_save
100
+ run_callbacks :before_validation_on_create
101
+ return unless valid?
102
+ run_callbacks :before_save
103
+ run_callbacks :before_create
104
+ self.created_at = Time.now
105
+ self.updated_at = Time.now
106
+ self._id = self.class.db.server.next_uuid rescue Digest::MD5.hexdigest(rand(1000000000000).to_s) # only works with couchdb 0.9
107
+ bulk_save_queue << self
108
+ save_dependent_objects
109
+ bulk_save_queue.save do |res|
110
+ self._rev = extract_rev(res)
111
+ end
112
+ run_callbacks :after_save
113
+ run_callbacks :after_create
114
+ true
115
+ end
116
+
117
+ def extract_rev(res)
118
+ res['new_revs'].select{|hash| hash['id'] == self.id}.first['rev']
119
+ end
120
+
121
+ def update_document
122
+ run_callbacks(:before_validation_on_save)
123
+ run_callbacks(:before_validation_on_update)
124
+ return unless valid?
125
+ run_callbacks :before_save
126
+ run_callbacks :before_update
127
+ self.updated_at = Time.now
128
+ bulk_save_queue << self
129
+ save_dependent_objects
130
+ bulk_save_queue.save do |res|
131
+ self._rev = extract_rev(res)
132
+ end
133
+ run_callbacks :after_save
134
+ run_callbacks :after_update
135
+ true
136
+ end
137
+
138
+ def save_dependent_objects
139
+ self.class.properties.each do |property|
140
+ property.save(self)
141
+ end
142
+ end
143
+
144
+ def destroy_dependent_objects
145
+ self.class.properties.each do |property|
146
+ property.destroy(self)
147
+ end
148
+ end
149
+
150
+ module ClassMethods
151
+
152
+ def create!(attributes = {})
153
+ instance = self.new attributes
154
+ instance.save!
155
+ instance
156
+ end
157
+
158
+ def create(attributes = {})
159
+ instance = self.new attributes
160
+ instance.save
161
+ instance
162
+ end
163
+
164
+ def get(id)
165
+ begin
166
+ self.json_create db.get(id)
167
+ rescue(RestClient::ResourceNotFound)
168
+ nil
169
+ end
170
+ end
171
+
172
+ def db(name = nil)
173
+ ::CouchPotato::Persistence.Db(name)
174
+ end
175
+ end
176
+
177
+ def self.Db(database_name = nil)
178
+ database_name ||= CouchPotato::Config.database_name || raise('No Database configured. Set CouchPotato::Config.database_name')
179
+ full_url_to_database = database_name
180
+ if full_url_to_database !~ /^http:\/\//
181
+ full_url_to_database = "http://localhost:5984/#{database_name}"
182
+ end
183
+ CouchRest.database!(full_url_to_database)
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,46 @@
1
+ module CouchPotato
2
+ module Versioning
3
+ def self.included(base)
4
+ base.class_eval do
5
+ property :version
6
+ property :master_version_id
7
+ cattr_accessor :new_version_condition
8
+ before_create :set_version_default
9
+ before_update :prepare_for_new_version
10
+
11
+ def self.set_version_condition(lambda)
12
+ self.new_version_condition = lambda
13
+ end
14
+ end
15
+ end
16
+
17
+ def versions(version_no = nil)
18
+ if version_no
19
+ CouchPotato::Persistence::Finder.new.find(self.class, :version => version_no).first
20
+ elsif version == 1
21
+ [self]
22
+ else
23
+ CouchPotato::Persistence::Finder.new.find(self.class, :master_version_id => _id).sort_by(&:version)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def set_version_default
30
+ self.version ||= 1
31
+ end
32
+
33
+ def prepare_for_new_version
34
+ if new_version_condition.nil? || new_version_condition.call(self)
35
+ copy = self.class.get self._id
36
+ copy._id = nil
37
+ copy._rev = nil
38
+ copy.master_version_id = self._id
39
+ copy.save
40
+ self.master_version_id ||= self.id
41
+ self.version += 1
42
+ end
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,20 @@
1
+ require 'rubygems'
2
+ require 'active_support'
3
+ require 'json'
4
+ require 'json/add/core'
5
+ require 'json/add/rails'
6
+
7
+ require 'ostruct'
8
+
9
+ module CouchPotato
10
+ Config = OpenStruct.new
11
+ end
12
+
13
+ require File.dirname(__FILE__) + '/core_ext/object'
14
+ require File.dirname(__FILE__) + '/core_ext/time'
15
+
16
+ require File.dirname(__FILE__) + '/couch_potato/persistence'
17
+ require File.dirname(__FILE__) + '/couch_potato/versioning'
18
+ require File.dirname(__FILE__) + '/couch_potato/ordering'
19
+ require File.dirname(__FILE__) + '/couch_potato/active_record/compatibility'
20
+
@@ -0,0 +1,22 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe 'attributes=' do
4
+
5
+ class Plant
6
+ include CouchPotato::Persistence
7
+ property :leaf_count
8
+ end
9
+
10
+ it "should assign the attributes" do
11
+ plant = Plant.new
12
+ plant.attributes = {:leaf_count => 1}
13
+ plant.leaf_count.should == 1
14
+ end
15
+ end
16
+
17
+ describe "attributes" do
18
+ it "should return the attributes" do
19
+ plant = Plant.new :leaf_count => 1
20
+ plant.attributes .should == {:leaf_count => 1}
21
+ end
22
+ end
@@ -0,0 +1,37 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe 'belongs_to' do
4
+ before(:each) do
5
+ @commenter = Commenter.create!
6
+ end
7
+
8
+ it "should return nil by default" do
9
+ c = Comment.new :title => 'title'
10
+ c.commenter.should be_nil
11
+ c.commenter_id.should be_nil
12
+ end
13
+
14
+ it "should assign the parent object" do
15
+ c = Comment.new :title => 'title'
16
+ c.commenter = @commenter
17
+ c.commenter.should == @commenter
18
+ c.commenter_id.should == @commenter.id
19
+ end
20
+
21
+ it "should assign the parent object id" do
22
+ c = Comment.new :title => 'title'
23
+ c.commenter_id = @commenter.id
24
+ c.commenter.should == @commenter
25
+ c.commenter_id.should == @commenter.id
26
+ end
27
+
28
+ it "should persist the link to the parent object" do
29
+ c = Comment.new :title => 'title'
30
+ c.commenter_id = @commenter.id
31
+ c.save!
32
+ c = Comment.find c.id
33
+ c.commenter._id.should == @commenter.id
34
+ c.commenter_id.should == @commenter._id
35
+ end
36
+
37
+ end
@@ -0,0 +1,229 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ class CallbackRecorder
4
+ include CouchPotato::Persistence
5
+
6
+ property :required_property
7
+
8
+ validates_presence_of :required_property
9
+
10
+ [:before_validation_on_create,
11
+ :before_validation_on_save, :before_validation_on_update,
12
+ :before_save, :before_create, :before_create,
13
+ :after_save, :after_create, :after_create,
14
+ :before_update, :after_update,
15
+ :before_destroy, :after_destroy
16
+ ].each do |callback|
17
+ define_method callback do
18
+ callbacks << callback
19
+ end
20
+ self.send callback, callback
21
+ end
22
+
23
+ def callbacks
24
+ @callbacks ||= []
25
+ end
26
+
27
+ end
28
+
29
+ describe "multiple callbacks at once" do
30
+ class Monkey
31
+ include CouchPotato::Persistence
32
+ attr_accessor :eaten_banana, :eaten_apple
33
+
34
+ before_create :eat_apple, :eat_banana
35
+
36
+ private
37
+
38
+ def eat_banana
39
+ self.eaten_banana = true
40
+ end
41
+
42
+ def eat_apple
43
+ self.eaten_apple = true
44
+ end
45
+ end
46
+ it "should run all callback methods given to the callback method call" do
47
+ monkey = Monkey.create!
48
+ monkey.eaten_banana.should be_true
49
+ monkey.eaten_apple.should be_true
50
+ end
51
+ end
52
+
53
+ describe 'create callbacks' do
54
+
55
+ before(:each) do
56
+ @recorder = CallbackRecorder.new
57
+ end
58
+
59
+ describe "successful create" do
60
+ before(:each) do
61
+ @recorder.required_property = 1
62
+ end
63
+
64
+ it "should call before_validation_on_create" do
65
+ @recorder.save!
66
+ @recorder.callbacks.should include(:before_validation_on_create)
67
+ end
68
+
69
+ it "should call before_validation_on_save" do
70
+ @recorder.save!
71
+ @recorder.callbacks.should include(:before_validation_on_save)
72
+ end
73
+
74
+ it "should call before_save" do
75
+ @recorder.save!
76
+ @recorder.callbacks.should include(:before_save)
77
+ end
78
+
79
+ it "should call after_save" do
80
+ @recorder.save!
81
+ @recorder.callbacks.should include(:after_save)
82
+ end
83
+
84
+ it "should call before_create" do
85
+ @recorder.save!
86
+ @recorder.callbacks.should include(:before_create)
87
+ end
88
+
89
+ it "should call after_create" do
90
+ @recorder.save!
91
+ @recorder.callbacks.should include(:after_create)
92
+ end
93
+
94
+ end
95
+
96
+ describe "failed create" do
97
+
98
+ it "should call before_validation_on_create" do
99
+ @recorder.save
100
+ @recorder.callbacks.should include(:before_validation_on_create)
101
+ end
102
+
103
+ it "should call before_validation_on_save" do
104
+ @recorder.save
105
+ @recorder.callbacks.should include(:before_validation_on_save)
106
+ end
107
+
108
+ it "should not call before_save" do
109
+ @recorder.save
110
+ @recorder.callbacks.should_not include(:before_save)
111
+ end
112
+
113
+ it "should not call after_save" do
114
+ @recorder.save
115
+ @recorder.callbacks.should_not include(:after_save)
116
+ end
117
+
118
+ it "should not call before_create" do
119
+ @recorder.save
120
+ @recorder.callbacks.should_not include(:before_create)
121
+ end
122
+
123
+ it "should not call after_create" do
124
+ @recorder.save
125
+ @recorder.callbacks.should_not include(:after_create)
126
+ end
127
+
128
+ end
129
+
130
+
131
+ end
132
+
133
+ describe "update callbacks" do
134
+
135
+ before(:each) do
136
+ @recorder = CallbackRecorder.create! :required_property => 1
137
+ @recorder.callbacks.clear
138
+ end
139
+
140
+ describe "successful update" do
141
+
142
+ it "should call before_validation_on_update" do
143
+ @recorder.save!
144
+ @recorder.callbacks.should include(:before_validation_on_update)
145
+ end
146
+
147
+ it "should call before_validation_on_save" do
148
+ @recorder.save!
149
+ @recorder.callbacks.should include(:before_validation_on_save)
150
+ end
151
+
152
+ it "should call before_save" do
153
+ @recorder.save!
154
+ @recorder.callbacks.should include(:before_save)
155
+ end
156
+
157
+ it "should call after_save" do
158
+ @recorder.save!
159
+ @recorder.callbacks.should include(:after_save)
160
+ end
161
+
162
+ it "should call before_update" do
163
+ @recorder.save!
164
+ @recorder.callbacks.should include(:before_update)
165
+ end
166
+
167
+ it "should call after_update" do
168
+ @recorder.save!
169
+ @recorder.callbacks.should include(:after_update)
170
+ end
171
+
172
+ end
173
+
174
+ describe "failed update" do
175
+
176
+ before(:each) do
177
+ @recorder.required_property = nil
178
+ end
179
+
180
+ it "should call before_validation_on_update" do
181
+ @recorder.save
182
+ @recorder.callbacks.should include(:before_validation_on_update)
183
+ end
184
+
185
+ it "should call before_validation_on_save" do
186
+ @recorder.save
187
+ @recorder.callbacks.should include(:before_validation_on_save)
188
+ end
189
+
190
+ it "should not call before_save" do
191
+ @recorder.save
192
+ @recorder.callbacks.should_not include(:before_save)
193
+ end
194
+
195
+ it "should not call after_save" do
196
+ @recorder.save
197
+ @recorder.callbacks.should_not include(:after_save)
198
+ end
199
+
200
+ it "should not call before_update" do
201
+ @recorder.save
202
+ @recorder.callbacks.should_not include(:before_update)
203
+ end
204
+
205
+ it "should not call after_update" do
206
+ @recorder.save
207
+ @recorder.callbacks.should_not include(:after_update)
208
+ end
209
+
210
+ end
211
+
212
+ end
213
+
214
+ describe "destroy callbacks" do
215
+ before(:each) do
216
+ @recorder = CallbackRecorder.create! :required_property => 1
217
+ @recorder.callbacks.clear
218
+ end
219
+
220
+ it "should call before_destroy" do
221
+ @recorder.destroy
222
+ @recorder.callbacks.should include(:before_destroy)
223
+ end
224
+
225
+ it "should call after_destroy" do
226
+ @recorder.destroy
227
+ @recorder.callbacks.should include(:after_destroy)
228
+ end
229
+ end