simply_stored 0.3.6 → 0.3.7

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/CHANGELOG.md CHANGED
@@ -1,6 +1,9 @@
1
1
  Changelog
2
2
  =============
3
3
 
4
+ - Pre-populate the parent object on has_many and has_one associations so that user.posts.first.user
5
+ doesn't reload the user.
6
+
4
7
  - Add support for associations not named after the class [akm]
5
8
 
6
9
  0.3.3
@@ -21,11 +21,12 @@ module SimplyStored
21
21
  descending = false
22
22
  end
23
23
 
24
- cached_results = cached_results = send("_get_cached_#{name}")
24
+ cached_results = send("_get_cached_#{name}")
25
25
  cache_key = _cache_key_for(local_options)
26
26
  if forced_reload || cached_results[cache_key].nil?
27
27
  cached_results[cache_key] = find_associated(options[:class_name], self.class, :with_deleted => with_deleted, :limit => limit, :descending => descending, :foreign_key => options[:foreign_key])
28
28
  instance_variable_set("@#{name}", cached_results)
29
+ self.class.set_parent_has_many_association_object(self, cached_results[cache_key])
29
30
  end
30
31
  cached_results[cache_key]
31
32
  end
@@ -146,6 +147,14 @@ module SimplyStored
146
147
  end
147
148
  end
148
149
 
150
+ def set_parent_has_many_association_object(parent, child_collection)
151
+ child_collection.each do |child|
152
+ if child.respond_to?("#{parent.class.name.to_s.singularize.downcase}=")
153
+ child.send("#{parent.class.name.to_s.singularize.camelize.downcase}=", parent)
154
+ end
155
+ end
156
+ end
157
+
149
158
  class Property
150
159
  attr_reader :name, :options
151
160
 
@@ -43,12 +43,20 @@ module SimplyStored
43
43
  end
44
44
 
45
45
  if forced_reload || instance_variable_get("@#{name}").nil?
46
- instance_variable_set("@#{name}", find_one_associated(options[:class_name], self.class, :with_deleted => with_deleted, :foreign_key => options[:foreign_key]))
46
+ found_object = find_one_associated(options[:class_name], self.class, :with_deleted => with_deleted, :foreign_key => options[:foreign_key])
47
+ instance_variable_set("@#{name}", found_object)
48
+ self.class.set_parent_has_one_association_object(self, found_object)
47
49
  end
48
50
  instance_variable_get("@#{name}")
49
51
  end
50
52
  end
51
53
 
54
+ def set_parent_has_one_association_object(parent, child)
55
+ if child.respond_to?("#{parent.class.name.to_s.singularize.downcase}=")
56
+ child.send("#{parent.class.name.to_s.singularize.camelize.downcase}=", parent)
57
+ end
58
+ end
59
+
52
60
  class Property
53
61
  attr_reader :name, :options
54
62
 
@@ -7,7 +7,7 @@ module SimplyStored
7
7
  end
8
8
 
9
9
  def ==(other)
10
- other._id == _id && other._rev == _rev
10
+ other.kind_of?(SimplyStored::Couch) && other._id == _id && other._rev == _rev
11
11
  end
12
12
 
13
13
  def eql?(other)
data/lib/simply_stored.rb CHANGED
@@ -4,7 +4,7 @@ require File.expand_path(File.dirname(__FILE__) + '/simply_stored/storage')
4
4
  require File.expand_path(File.dirname(__FILE__) + '/simply_stored/class_methods_base')
5
5
 
6
6
  module SimplyStored
7
- VERSION = '0.3.6'
7
+ VERSION = '0.3.7'
8
8
  class Error < RuntimeError; end
9
9
  class RecordNotFound < RuntimeError; end
10
10
  end
@@ -0,0 +1,23 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ begin
4
+ require 'active_model'
5
+
6
+ class CouchModel
7
+ include SimplyStored::Couch
8
+ property :name
9
+ validates_presence_of :name
10
+ end
11
+
12
+ class CouchActiveModelCompatibilityTest < Test::Unit::TestCase
13
+ def setup
14
+ @model = CouchModel.new
15
+ end
16
+ include ActiveModel::Lint::Tests
17
+ end
18
+
19
+ rescue LoadError
20
+ puts "ActiveModel not installed, skipping lint tests."
21
+ end
22
+
23
+
@@ -0,0 +1,163 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+ require File.expand_path(File.dirname(__FILE__) + '/../fixtures/couch')
3
+
4
+ class CouchBelongsToTest < Test::Unit::TestCase
5
+ context "with associations" do
6
+ setup do
7
+ CouchPotato::Config.database_name = 'simply_stored_test'
8
+ recreate_db
9
+ end
10
+
11
+ context "with belongs_to" do
12
+ should "generate a view for the association" do
13
+ assert Post.respond_to?(:association_post_belongs_to_user)
14
+ end
15
+
16
+ should "add the foreign key id to the referencing object" do
17
+ user = User.create(:title => "Mr.")
18
+ post = Post.create(:user => user)
19
+
20
+ post = Post.find(post.id)
21
+ assert_equal user.id, post.user_id
22
+ end
23
+
24
+ should "set also the foreign key id to nil if setting the referencing object to nil" do
25
+ user = User.create(:title => "Mr.")
26
+ post = Post.create(:user => user)
27
+ post.user = nil
28
+ post.save!
29
+ assert_nil post.reload.user
30
+ assert_nil post.reload.user_id
31
+ end
32
+
33
+ should "fetch the object from the database when requested through the getter" do
34
+ user = User.create(:title => "Mr.")
35
+ post = Post.create(:user => user)
36
+
37
+ post = Post.find(post.id)
38
+ assert_equal user, post.user
39
+ end
40
+
41
+ should "mark the referencing object as dirty" do
42
+ user = User.create(:title => "Mr.")
43
+ post = Post.create
44
+ post.user = user
45
+ assert post.dirty?
46
+ end
47
+
48
+ should "allow assigning a different object and store the id accordingly" do
49
+ user = User.create(:title => "Mr.")
50
+ user2 = User.create(:title => "Mrs.")
51
+ post = Post.create(:user => user)
52
+ post.user = user2
53
+ post.save
54
+
55
+ post = Post.find(post.id)
56
+ assert_equal user2, post.user
57
+ end
58
+
59
+ should "check the class and raise an error if not matching in belongs_to setter" do
60
+ post = Post.create
61
+ assert_raise(ArgumentError, 'expected Post got String') do
62
+ post.user = 'foo'
63
+ end
64
+ end
65
+
66
+ should 'not query for the object twice in getter' do
67
+ user = User.create(:title => "Mr.")
68
+ post = Post.create(:user => user)
69
+ post = Post.find(post.id)
70
+ User.expects(:find).returns "user"
71
+ post.user
72
+ User.expects(:find).never
73
+ post.user
74
+ end
75
+
76
+ should 'use cache in getter' do
77
+ post = Post.create
78
+ post.instance_variable_set("@user", 'foo')
79
+ assert_equal 'foo', post.user
80
+ end
81
+
82
+ should "ignore the cache if force_reload is given as an option" do
83
+ user = User.create(:name => 'Dude', :title => 'Mr.')
84
+ post = Post.create(:user => user)
85
+ post.reload
86
+ post.instance_variable_set("@user", 'foo')
87
+ assert_not_equal 'foo', post.user(:force_reload => true)
88
+ end
89
+
90
+ should 'set cache in setter' do
91
+ post = Post.create
92
+ user = User.create
93
+ assert_nil post.instance_variable_get("@user")
94
+ post.user = user
95
+ assert_equal user, post.instance_variable_get("@user")
96
+ end
97
+
98
+ should "not hit the database when the id column is empty" do
99
+ User.expects(:find).never
100
+ post = Post.create
101
+ post.user
102
+ end
103
+
104
+ should "know when the associated object changed" do
105
+ post = Post.create(:user => User.create(:title => "Mr."))
106
+ user2 = User.create(:title => "Mr.")
107
+ post.user = user2
108
+ assert post.user_changed?
109
+ end
110
+
111
+ should "not be changed when an association has not changed" do
112
+ post = Post.create(:user => User.create(:title => "Mr."))
113
+ assert !post.user_changed?
114
+ end
115
+
116
+ should "not be changed when assigned the same object" do
117
+ user = User.create(:title => "Mr.")
118
+ post = Post.create(:user => user)
119
+ post.user = user
120
+ assert !post.user_changed?
121
+ end
122
+
123
+ should "not be changed after saving" do
124
+ user = User.create(:title => "Mr.")
125
+ post = Post.new
126
+ post.user = user
127
+ assert post.user_changed?
128
+ post.save!
129
+ assert !post.user_changed?
130
+ end
131
+
132
+ should "handle a foreign_key of '' as nil" do
133
+ post = Post.create
134
+ post.user_id = ''
135
+
136
+ assert_nothing_raised do
137
+ assert_nil post.user
138
+ end
139
+ end
140
+
141
+ context "with aliased associations" do
142
+ should "allow different names for the same class" do
143
+ editor = User.create(:name => 'Editor', :title => 'Dr.')
144
+ author = User.create(:name => 'author', :title => 'Dr.')
145
+ assert_not_nil editor.id, editor.errors.inspect
146
+ assert_not_nil author.id, author.errors.inspect
147
+
148
+ doc = Document.create(:editor => editor, :author => author)
149
+ doc.save!
150
+ assert_equal editor.id, doc.editor_id
151
+ assert_equal author.id, doc.author_id
152
+ doc = Document.find(doc.id)
153
+ assert_not_nil doc.editor, doc.inspect
154
+ assert_not_nil doc.author
155
+ assert_equal editor.id, doc.editor.id
156
+ assert_equal author.id, doc.author.id
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+
163
+ end
@@ -0,0 +1,96 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+ require File.expand_path(File.dirname(__FILE__) + '/../fixtures/couch')
3
+
4
+ class CouchConflictHandlingTest < Test::Unit::TestCase
5
+ context "when handling conflicts" do
6
+ setup do
7
+ CouchPotato::Config.database_name = 'simply_stored_test'
8
+ recreate_db
9
+ @original = User.create(:name => 'Mickey Mouse', :title => "Dr.", :homepage => 'www.gmx.de')
10
+ @copy = User.find(@original.id)
11
+ User.auto_conflict_resolution_on_save = true
12
+ end
13
+
14
+ should "be able to save without modifications" do
15
+ assert @copy.save
16
+ end
17
+
18
+ should "be able to save when modification happen on different attributes" do
19
+ @original.name = "Pluto"
20
+ assert @original.save
21
+
22
+ @copy.title = 'Prof.'
23
+ assert_nothing_raised do
24
+ assert @copy.save
25
+ end
26
+
27
+ assert_equal "Pluto", @copy.reload.name
28
+ assert_equal "Prof.", @copy.reload.title
29
+ assert_equal "www.gmx.de", @copy.reload.homepage
30
+ end
31
+
32
+ should "be able to save when modification happen on different, multiple attributes - remote" do
33
+ @original.name = "Pluto"
34
+ @original.homepage = 'www.google.com'
35
+ assert @original.save
36
+
37
+ @copy.title = 'Prof.'
38
+ assert_nothing_raised do
39
+ assert @copy.save
40
+ end
41
+
42
+ assert_equal "Pluto", @copy.reload.name
43
+ assert_equal "Prof.", @copy.reload.title
44
+ assert_equal "www.google.com", @copy.reload.homepage
45
+ end
46
+
47
+ should "be able to save when modification happen on different, multiple attributes locally" do
48
+ @original.name = "Pluto"
49
+ assert @original.save
50
+
51
+ @copy.title = 'Prof.'
52
+ @copy.homepage = 'www.google.com'
53
+ assert_nothing_raised do
54
+ assert @copy.save
55
+ end
56
+
57
+ assert_equal "Pluto", @copy.reload.name
58
+ assert_equal "Prof.", @copy.reload.title
59
+ assert_equal "www.google.com", @copy.reload.homepage
60
+ end
61
+
62
+ should "re-raise the conflict if there is no merge possible" do
63
+ @original.name = "Pluto"
64
+ assert @original.save
65
+
66
+ @copy.name = 'Prof.'
67
+ assert_raise(RestClient::Conflict) do
68
+ assert @copy.save
69
+ end
70
+
71
+ assert_equal "Prof.", @copy.name
72
+ assert_equal "Pluto", @copy.reload.name
73
+ end
74
+
75
+ should "re-raise the conflict if retried several times" do
76
+ exception = RestClient::Conflict.new
77
+ CouchPotato.database.expects(:save_document).raises(exception).times(3)
78
+
79
+ @copy.name = 'Prof.'
80
+ assert_raise(RestClient::Conflict) do
81
+ assert @copy.save
82
+ end
83
+ end
84
+
85
+ should "not try to merge and re-save if auto_conflict_resolution_on_save is disabled" do
86
+ User.auto_conflict_resolution_on_save = false
87
+ exception = RestClient::Conflict.new
88
+ CouchPotato.database.expects(:save_document).raises(exception).times(1)
89
+
90
+ @copy.name = 'Prof.'
91
+ assert_raise(RestClient::Conflict) do
92
+ assert @copy.save
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,183 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+ require File.expand_path(File.dirname(__FILE__) + '/../fixtures/couch')
3
+
4
+ class CouchFinderTest < Test::Unit::TestCase
5
+ context "when finding instances" do
6
+ setup do
7
+ CouchPotato::Config.database_name = 'simply_stored_test'
8
+ recreate_db
9
+ end
10
+
11
+ context "with find(:all)" do
12
+ setup do
13
+ User.create(:title => "Mr.")
14
+ User.create(:title => "Mrs.")
15
+ end
16
+
17
+ should "return all instances" do
18
+ assert_equal 2, User.find(:all).size
19
+ end
20
+
21
+ should "allow a limit" do
22
+ assert_equal 1, User.find(:all, :limit => 1).size
23
+ end
24
+
25
+ should "allow to order the results" do
26
+ assert_not_equal User.find(:all).map(&:id), User.find(:all, :order => :desc).map(&:id)
27
+ assert_equal User.find(:all).map(&:id).reverse, User.find(:all, :order => :desc).map(&:id)
28
+ end
29
+ end
30
+
31
+ context "to find all instances" do
32
+ should 'generate a default find_all view' do
33
+ assert User.respond_to?(:all_documents)
34
+ end
35
+
36
+ should 'return all the users when calling all' do
37
+ User.create(:title => "Mr.")
38
+ User.create(:title => "Mrs.")
39
+ assert_equal 2, User.all.size
40
+ end
41
+ end
42
+
43
+ context "to find one instance" do
44
+ should 'return one user when calling first' do
45
+ user = User.create(:title => "Mr.")
46
+ assert_equal user, User.first
47
+ end
48
+
49
+ should 'understand the order' do
50
+ assert_nothing_raised do
51
+ User.first(:order => :desc)
52
+ end
53
+ end
54
+
55
+ should 'return nil when no user found' do
56
+ assert_nil User.first
57
+ end
58
+ end
59
+
60
+ context "when finding with just an identifier" do
61
+ should "find just one instance" do
62
+ user = User.create(:title => "Mr.")
63
+ assert User.find(user.id).kind_of?(User)
64
+ end
65
+
66
+ should 'raise an error when no record was found' do
67
+ assert_raises(SimplyStored::RecordNotFound) do
68
+ User.find('abc')
69
+ end
70
+ end
71
+
72
+ should 'tell you which class failed to load something' do
73
+ exception = nil
74
+ begin
75
+ User.find('abc')
76
+ rescue SimplyStored::RecordNotFound => e
77
+ exception = e
78
+ end
79
+ assert_equal "User could not be found with \"abc\"", exception.message
80
+ end
81
+
82
+ should 'raise an error when nil was specified' do
83
+ assert_raises(SimplyStored::Error) do
84
+ User.find(nil)
85
+ end
86
+ end
87
+
88
+ should 'raise an error when the record was not of the expected type' do
89
+ post = Post.create
90
+ assert_raises(SimplyStored::RecordNotFound) do
91
+ User.find(post.id)
92
+ end
93
+ end
94
+ end
95
+
96
+ context "with a find_by prefix" do
97
+ setup do
98
+ recreate_db
99
+ end
100
+
101
+ should "create a view for the called finder" do
102
+ User.find_by_name("joe")
103
+ assert User.respond_to?(:by_name)
104
+ end
105
+
106
+ should 'not create the view when it already exists' do
107
+ User.expects(:view).never
108
+ User.find_by_name_and_created_at("joe", 'foo')
109
+ end
110
+
111
+ should "create a method to prevent future loops through method_missing" do
112
+ assert !User.respond_to?(:find_by_title)
113
+ User.find_by_title("Mr.")
114
+ assert User.respond_to?(:find_by_title)
115
+ end
116
+
117
+ should "call the generated view and return the result" do
118
+ user = User.create(:homepage => "http://www.peritor.com", :title => "Mr.")
119
+ assert_equal user, User.find_by_homepage("http://www.peritor.com")
120
+ end
121
+
122
+ should 'find only one instance when using find_by' do
123
+ User.create(:title => "Mr.")
124
+ assert User.find_by_title("Mr.").is_a?(User)
125
+ end
126
+
127
+ should "raise an error if the parameters don't match" do
128
+ assert_raise(ArgumentError) do
129
+ User.find_by_title()
130
+ end
131
+
132
+ assert_raise(ArgumentError) do
133
+ User.find_by_title(1,2,3,4,5)
134
+ end
135
+ end
136
+ end
137
+
138
+ context "with a find_all_by prefix" do
139
+ should "create a view for the called finder" do
140
+ User.find_all_by_name("joe")
141
+ assert User.respond_to?(:by_name)
142
+ end
143
+
144
+ should 'not create the view when it already exists' do
145
+ User.expects(:view).never
146
+ User.find_all_by_name_and_created_at("joe", "foo")
147
+ end
148
+
149
+ should "create a method to prevent future loops through method_missing" do
150
+ assert !User.respond_to?(:find_all_by_foo_attribute)
151
+ User.find_all_by_foo_attribute("Mr.")
152
+ assert User.respond_to?(:find_all_by_foo_attribute)
153
+ end
154
+
155
+ should "call the generated view and return the result" do
156
+ user = User.create(:homepage => "http://www.peritor.com", :title => "Mr.")
157
+ assert_equal [user], User.find_all_by_homepage("http://www.peritor.com")
158
+ end
159
+
160
+ should "return an emtpy array if none found" do
161
+ recreate_db
162
+ assert_equal [], User.find_all_by_title('Mr. Magoooo')
163
+ end
164
+
165
+ should 'find all instances when using find_all_by' do
166
+ User.create(:title => "Mr.")
167
+ User.create(:title => "Mr.")
168
+ assert_equal 2, User.find_all_by_title("Mr.").size
169
+ end
170
+
171
+ should "raise an error if the parameters don't match" do
172
+ assert_raise(ArgumentError) do
173
+ User.find_all_by_title()
174
+ end
175
+
176
+ assert_raise(ArgumentError) do
177
+ User.find_all_by_title(1,2,3,4,5)
178
+ end
179
+ end
180
+ end
181
+ end
182
+
183
+ end