simply_stored 0.3.6 → 0.3.7

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