friendly-attributes 0.7.1.1 → 0.7.2

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.
@@ -0,0 +1,210 @@
1
+ require 'spec_helper'
2
+
3
+ describe FriendlyAttributes::DetailsDelegator do
4
+ DetailsDelegator = FriendlyAttributes::DetailsDelegator
5
+
6
+ let(:details_delegator) { FriendlyAttributes::DetailsDelegator.new(friendly_model, ar_model, attributes, options, &initializer) }
7
+ let(:initializer) { proc {} }
8
+ let(:attributes) { {} }
9
+ let(:options) { {} }
10
+
11
+ let(:friendly_model) { mock_friendly_model }
12
+ let(:ar_model) { Class.new { include ActiveRecordFake } }
13
+
14
+ let(:ar_instance) { ar_model.new(:id => 42) }
15
+ let(:friendly_instance) { mock(friendly_model) }
16
+
17
+ describe "class methods" do
18
+ describe ".friendly_model_name" do
19
+ it { DetailsDelegator.friendly_model_name(UserDetails).should == :user_details }
20
+ end
21
+
22
+ describe ".friendly_model_ivar" do
23
+ it { DetailsDelegator.friendly_model_ivar(:user_details).should == :@user_details_ivar }
24
+ end
25
+
26
+ describe ".friendly_model_reader" do
27
+ it { DetailsDelegator.friendly_model_reader(:user_details).should == :load_user_details }
28
+ it { DetailsDelegator.friendly_model_reader(UserDetails).should == :load_user_details }
29
+ end
30
+ end
31
+
32
+ describe "initialization" do
33
+ shared_examples_for "DetailsDelegator initialization" do
34
+ context "DetailsDelegator attr_readers" do
35
+ subject { details_delegator }
36
+
37
+ before(:each) do
38
+ details_delegator
39
+ end
40
+
41
+ its(:active_record_model) { should == ar_model }
42
+ its(:friendly_model) { should == friendly_model }
43
+ its(:attributes) { should == attributes }
44
+ end
45
+
46
+ context "#setup_delegated_attributes" do
47
+ let(:attributes) do
48
+ {
49
+ String => :foo,
50
+ Integer => [:bar, :baz]
51
+ }
52
+ end
53
+
54
+ it "delegates the attributes passed in the options" do
55
+ details_delegator.should_receive(:delegated_attribute).with(:foo, String)
56
+ details_delegator.should_receive(:delegated_attribute).with(:bar, Integer)
57
+ details_delegator.should_receive(:delegated_attribute).with(:baz, Integer)
58
+ details_delegator.setup_delegated_attributes
59
+ end
60
+ end
61
+
62
+ context "the Friendly model" do
63
+ before(:each) do
64
+ details_delegator
65
+ end
66
+
67
+ it "includes Friendly::Document" do
68
+ friendly_model.ancestors.should include(Friendly::Document)
69
+ end
70
+
71
+ context "with defaults" do
72
+ it "adds the active_record_id attribute" do
73
+ friendly_model.attributes.should include(:active_record_id)
74
+ end
75
+
76
+ it "adds an index to active_record_id" do
77
+ friendly_model.storage_proxy.index_for_fields([:active_record_id]).should be_an_instance_of(Friendly::Index)
78
+ end
79
+
80
+ it "sets the active_record_key" do
81
+ friendly_model.active_record_key.should == :active_record_id
82
+ end
83
+ end
84
+
85
+ context "with options" do
86
+ let(:options) { { :active_record_key => :user_id } }
87
+
88
+ it "adds the active_record_id attribute" do
89
+ friendly_model.attributes.should include(:user_id)
90
+ end
91
+
92
+ it "adds an index to active_record_id" do
93
+ friendly_model.storage_proxy.index_for_fields([:user_id]).should be_an_instance_of(Friendly::Index)
94
+ end
95
+
96
+ it "sets the active_record_key" do
97
+ friendly_model.active_record_key.should == :user_id
98
+ end
99
+ end
100
+ end
101
+
102
+ context "the ActiveRecord model" do
103
+ it "installs the update_friendly_models callback after_save" do
104
+ ar_model.should_receive(:after_save).with(:update_friendly_models)
105
+ details_delegator
106
+ end
107
+
108
+ it "installs the destroy_friendly_models callback after_destroy" do
109
+ ar_model.should_receive(:after_destroy).with(:destroy_friendly_models)
110
+ details_delegator
111
+ end
112
+
113
+ describe "friendly model reader method" do
114
+ before(:each) do
115
+ details_delegator
116
+ end
117
+
118
+ it "is defined" do
119
+ ar_instance.should respond_to(DetailsDelegator.friendly_model_reader(friendly_model))
120
+ end
121
+
122
+ it "finds or builds and memoizes the associated Friendly model" do
123
+ ar_instance.should_receive(:find_or_build_and_memoize_details).with(friendly_model)
124
+ ar_instance.send(DetailsDelegator.friendly_model_reader(friendly_model))
125
+ end
126
+ end
127
+
128
+ describe "cattr_accessor friendly_attributes_configuration" do
129
+ context "when no Configuration exists" do
130
+ it "creates and assigns a new Configuration with the delegator added" do
131
+ details_delegator
132
+
133
+ ar_model.friendly_attributes_configuration.should be_an_instance_of(FriendlyAttributes::Configuration)
134
+ ar_model.friendly_attributes_configuration.friendly_models.should == [friendly_model]
135
+ end
136
+ end
137
+
138
+ context "when Configuration already exists" do
139
+ let(:existing_details_delegator) { FriendlyAttributes::DetailsDelegator.new(other_friendly_model, ar_model, attributes, options, &initializer) }
140
+ let(:other_friendly_model) { mock_friendly_model }
141
+
142
+ before(:each) do
143
+ existing_details_delegator
144
+ end
145
+
146
+ it "adds to the existing configuration" do
147
+ existing_configuration = ar_model.friendly_attributes_configuration
148
+
149
+ expect do
150
+ details_delegator
151
+ end.to change { ar_model.friendly_attributes_configuration.friendly_models }.
152
+ from([other_friendly_model]).
153
+ to([other_friendly_model, friendly_model])
154
+
155
+ ar_model.friendly_attributes_configuration.should == existing_configuration
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+ context "missing initialization block" do
163
+ let(:details_delegator) { FriendlyAttributes::DetailsDelegator.new(friendly_model, ar_model, attributes, options) }
164
+ it_should_behave_like "DetailsDelegator initialization"
165
+ end
166
+ end
167
+
168
+ describe "#delegated_method" do
169
+ before(:each) do
170
+ details_delegator
171
+ details_delegator.delegated_method(:some_method)
172
+ ar_instance.stub(DetailsDelegator.friendly_model_reader(friendly_model) => friendly_instance)
173
+ end
174
+
175
+ it "delegates the method to Friendly model" do
176
+ bar = mock
177
+ friendly_instance.should_receive(:some_method).with(:foo).and_return(bar)
178
+ ar_instance.some_method(:foo).should == bar
179
+ end
180
+ end
181
+
182
+ describe "#delegated_attribute" do
183
+ before(:each) do
184
+ details_delegator
185
+ details_delegator.delegated_attribute(:some_attribute, String)
186
+ ar_instance.stub(DetailsDelegator.friendly_model_reader(friendly_model) => friendly_instance)
187
+ end
188
+
189
+ it "adds an attribute to the Friendly model" do
190
+ friendly_model.attributes.should include(:some_attribute)
191
+ friendly_model.attributes[:some_attribute].type.should == String
192
+ end
193
+
194
+ it "delegates the reader to Friendly model" do
195
+ bar = mock
196
+ friendly_instance.should_receive(:some_attribute).and_return(bar)
197
+ ar_instance.some_attribute.should == bar
198
+ end
199
+
200
+ it "delegates the writer to Friendly model" do
201
+ bar = mock
202
+ friendly_instance.should_receive(:some_attribute=).with(:value)
203
+ ar_instance.some_attribute = :value
204
+ end
205
+
206
+ it "adds the attribute to the configuration" do
207
+ ar_instance.friendly_attributes_configuration.attributes.should == { :some_attribute => friendly_model }
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,240 @@
1
+ require 'spec_helper'
2
+
3
+ describe FriendlyAttributes::InstanceMethods do
4
+ DetailsDelegator = FriendlyAttributes::DetailsDelegator unless defined?(DetailsDelegator)
5
+
6
+ let(:object) { klass.new.tap { |o| o.stub(:friendly_attributes_configuration => configuration) } }
7
+ let(:configuration) { mock(FriendlyAttributes::Configuration) }
8
+ let(:klass) {
9
+ Class.new {
10
+ include ActiveRecordFake
11
+ include FriendlyAttributes::InstanceMethods
12
+ }
13
+ }
14
+ let(:user_details) { double(UserDetails).as_null_object }
15
+ let(:user_second_details) { double(UserSecondDetails).as_null_object }
16
+ let(:friendly_model_instances) { [user_details, user_second_details] }
17
+
18
+ describe "reading and writing Friendly attributes" do
19
+ let(:object_details) { double(FriendlyAttributes::Base, :foo => foo, :foo= => foo) }
20
+ let(:foo) { mock("object_details.foo") }
21
+
22
+ before(:each) do
23
+ object.stub(:friendly_instance_for_attribute => object_details)
24
+ end
25
+
26
+ describe "#read_friendly_attribute" do
27
+ it "finds the FriendlyAttributes instance for the specified attribute" do
28
+ object.
29
+ should_receive(:friendly_instance_for_attribute).
30
+ with(:foo).
31
+ and_return(object_details)
32
+
33
+ object.read_friendly_attribute(:foo)
34
+ end
35
+
36
+ it "reads the attribute from the FriendlyAttributes instance" do
37
+ object_details.should_receive(:foo).and_return(foo)
38
+ object.read_friendly_attribute(:foo).should == foo
39
+ end
40
+ end
41
+
42
+ describe "#write_friendly_attribute" do
43
+ it "finds the FriendlyAttributes instance for the specified attribute" do
44
+ object.
45
+ should_receive(:friendly_instance_for_attribute).
46
+ with(:foo).
47
+ and_return(object_details)
48
+
49
+ object.write_friendly_attribute(:foo, "bar")
50
+ end
51
+
52
+ it "writes the attribute on the FriendlyAttributes instance" do
53
+ object_details.should_receive(:foo=).with("bar").and_return(foo)
54
+ object.write_friendly_attribute(:foo, "bar").should == foo
55
+ end
56
+
57
+ end
58
+
59
+ end
60
+
61
+ describe "#friendly_instance_for_attribute" do
62
+ it "reads the value for the friendly model from the instance" do
63
+ configuration.should_receive(:model_for_attribute).with(:foo).and_return(UserDetails)
64
+ object.should_receive(DetailsDelegator.friendly_model_reader(UserDetails)).and_return(user_details)
65
+ object.friendly_instance_for_attribute(:foo).should == user_details
66
+ end
67
+ end
68
+
69
+ describe "#update_friendly_models" do
70
+ it "updates each loaded friendly model instance if needed" do
71
+ object.should_receive(:present_friendly_instances).and_return(friendly_model_instances)
72
+ friendly_model_instances.each do |instance|
73
+ instance.should_receive(:update_if_changed_with_model).with(object.id)
74
+ end
75
+ object.update_friendly_models
76
+ end
77
+ end
78
+
79
+ describe "#destroy_friendly_details" do
80
+ it "loads and destroys all associated friendly models" do
81
+ object.should_receive(:all_friendly_instances).and_return(friendly_model_instances)
82
+ friendly_model_instances.each do |instance|
83
+ instance.should_receive(:destroy).and_return(true)
84
+ end
85
+ object.destroy_friendly_models.should be_true
86
+ end
87
+ end
88
+
89
+ describe "#friendly_instance_present?" do
90
+ subject { object.friendly_instance_present?(friendly_model) }
91
+ let(:friendly_model) { UserDetails }
92
+
93
+ context "when friendly_details ivar is present" do
94
+ before(:each) do
95
+ object.instance_variable_set(DetailsDelegator.friendly_model_ivar(friendly_model), mock)
96
+ end
97
+
98
+ it { should be_true }
99
+ end
100
+
101
+ context "when friendly_details ivar is not present" do
102
+ it { should be_false }
103
+ end
104
+ end
105
+
106
+ describe "#friendly_instance_presence" do
107
+ subject { object.friendly_instance_presence(friendly_model) }
108
+ let(:friendly_model) { UserDetails }
109
+
110
+ context "when present" do
111
+ before(:each) do
112
+ object.instance_variable_set(DetailsDelegator.friendly_model_ivar(friendly_model), mock)
113
+ end
114
+
115
+ it "returns the instance using the model reader" do
116
+ object.should_receive(DetailsDelegator.friendly_model_reader(friendly_model)).and_return(user_details)
117
+ subject.should == user_details
118
+ end
119
+ end
120
+
121
+ context "when not present" do
122
+ it "returns nil" do
123
+ subject.should be_nil
124
+ end
125
+ end
126
+ end
127
+
128
+ describe "#all_friendly_instances" do
129
+ let(:friendly_models) { [UserDetails, UserSecondDetails] }
130
+
131
+ it "loads and returns all the Friendly model instances associated with the record" do
132
+ configuration.should_receive(:friendly_models).and_return(friendly_models)
133
+ object.should_receive(DetailsDelegator.friendly_model_reader(UserDetails)).and_return(user_details)
134
+ object.should_receive(DetailsDelegator.friendly_model_reader(UserSecondDetails)).and_return(user_second_details)
135
+ object.all_friendly_instances.should == [user_details, user_second_details]
136
+ end
137
+ end
138
+
139
+ describe "#present_friendly_instances" do
140
+ let(:friendly_models) { [UserDetails, UserSecondDetails] }
141
+
142
+ it "returns all the loaded Friendly model instances associated with the record" do
143
+ configuration.should_receive(:friendly_models).and_return(friendly_models)
144
+ object.should_receive(:friendly_instance_presence).with(UserDetails).and_return(user_details)
145
+ object.should_receive(:friendly_instance_presence).with(UserSecondDetails).and_return(nil)
146
+ object.present_friendly_instances.should == [user_details]
147
+ end
148
+ end
149
+
150
+ describe "#friendly_details_build_options" do
151
+ context "when friendly_details_build_options is not defined" do
152
+ it "returns and empty Hash" do
153
+ object.friendly_details_build_options.should == { }
154
+ object.friendly_details_build_options(UserDetails).should == { }
155
+ end
156
+ end
157
+
158
+ context "when friendly_details_build_options is defined" do
159
+ let(:foo) { mock("default friendly details build option") }
160
+
161
+ before(:each) do
162
+ _foo = foo
163
+
164
+ klass.class_eval do
165
+ define_method(:friendly_details_build_options) do |klass|
166
+ { :foo => _foo }
167
+ end
168
+ end
169
+ end
170
+
171
+ it "returns the options specified by friendly_details_build_options" do
172
+ object.friendly_details_build_options(UserSecondDetails).should == { :foo => foo }
173
+ end
174
+ end
175
+ end
176
+
177
+ describe "#changed?" do
178
+ subject { active_record.changed? }
179
+ let(:active_record) { User.create(:name => "Stan", :email => "stan@example.com") }
180
+
181
+ context "when the ActiveRecord model has changed," do
182
+ before(:each) do
183
+ active_record.email = "eric@example.com"
184
+ end
185
+
186
+ context "and the Friendly model has changed," do
187
+ before(:each) do
188
+ active_record.name = "Eric"
189
+ end
190
+
191
+ it { should be_true }
192
+ end
193
+
194
+ context "and the Friendly model has not changed" do
195
+ it { should be_true }
196
+ end
197
+ end
198
+
199
+ context "when the ActiveRecord model has not changed," do
200
+ context "and the Friendly model has changed" do
201
+ before(:each) do
202
+ active_record.name = "Eric"
203
+ end
204
+
205
+ it { should be_true }
206
+ end
207
+
208
+ context "and the Friendly model has not changed" do
209
+ it { should be_false }
210
+ end
211
+ end
212
+ end
213
+
214
+ describe "#find_or_build_and_memoize_details" do
215
+ let(:user) { @user }
216
+ let(:build_options) { mock(Hash) }
217
+ let(:user_details) { double(UserDetails) }
218
+
219
+ before(:each) do
220
+ @user = User.find(User.create(:name => "Stan", :email => "stan@example.com").id)
221
+ end
222
+
223
+ it "finds (or builds a new instance with build options) and memoizes the associated Friendly instance" do
224
+ user.
225
+ should_receive(:friendly_details_build_options).
226
+ with(UserDetails).
227
+ once.
228
+ and_return(build_options)
229
+
230
+ UserDetails.
231
+ should_receive(:find_or_build_by_active_record_id).
232
+ with(user.id, build_options).
233
+ once.
234
+ and_return(user_details)
235
+
236
+ user.find_or_build_and_memoize_details(UserDetails).should == user_details
237
+ user.find_or_build_and_memoize_details(UserDetails).should == user_details
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,152 @@
1
+ require 'spec_helper'
2
+
3
+ describe FriendlyAttributes do
4
+ use_database_cleanup
5
+
6
+ describe "definition" do
7
+ it { User.should have_friendly_attributes(String, :name, :through => UserDetails) }
8
+ it { User.should have_friendly_attributes(Integer, :second_int, :through => UserSecondDetails) }
9
+ it { User.new.should have_friendly_attributes(Integer, :shoe_size, :birth_year, :through => UserDetails) }
10
+ it { User.new.should have_friendly_attributes(Friendly::Boolean, :subscribed, :through => UserDetails) }
11
+ it { User.should_not have_friendly_attributes(String, :foo, :through => UserDetails) }
12
+ end
13
+
14
+ describe "creating" do
15
+ context "with Friendly attributes" do
16
+ context "when using just one Friendly model" do
17
+ let(:user) { User.create!(:name => "Stan", :email => "stan@example.com", :birth_year => 1984, :subscribed => true) }
18
+
19
+ it "creates only the required Friendly model" do
20
+ expect do
21
+ expect do
22
+ user
23
+ end.to change { UserDetails.count({}) }
24
+ end.to_not change { UserSecondDetails.count({}) }
25
+ end
26
+
27
+ it "sets the appropriate attributes on the ActiveRecord model and associated Friendly model" do
28
+ user_detail = user.load_user_details
29
+ user_detail.name.should == "Stan"
30
+ user_detail.birth_year.should == 1984
31
+ user_detail.shoe_size.should == 42
32
+ user_detail.subscribed.should be_true
33
+ end
34
+ end
35
+
36
+ context "when using multiple associated Friendly models" do
37
+ let(:user) { User.create!(:name => "Stan", :email => "stan@example.com", :birth_year => 1984, :second_int => 120, :subscribed => true) }
38
+
39
+ it "creates all the required Friendly models" do
40
+ expect do
41
+ expect do
42
+ user
43
+ end.to change { UserDetails.count({}) }
44
+ end.to change { UserSecondDetails.count({}) }
45
+ end
46
+
47
+ it "sets the appropriate attributes on the ActiveRecord model and associated Friendly model" do
48
+ user_detail = user.load_user_details
49
+ user_detail.name.should == "Stan"
50
+ user_detail.birth_year.should == 1984
51
+ user_detail.shoe_size.should == 42
52
+ user_detail.subscribed.should be_true
53
+
54
+ user_second_detail = user.load_user_second_details
55
+ user_second_detail.second_int.should == 120
56
+ end
57
+ end
58
+ end
59
+
60
+ context "without Friendly attributes" do
61
+ let(:user) { User.create(:email => "stan@example.com") }
62
+
63
+ it "does not create an associated Friendly model" do
64
+ expect do
65
+ user
66
+ end.to_not change { UserDetails.count({}) }
67
+ end
68
+ end
69
+ end
70
+
71
+ describe "reading" do
72
+ let(:user) { User.create!(:name => "Stan", :email => "stan@example.com", :birth_year => 1984, :subscribed => true) }
73
+
74
+ it "through the attr_reader" do
75
+ user.name.should == "Stan"
76
+ end
77
+
78
+ it "through read_friendly_attribute" do
79
+ user.read_friendly_attribute(:name).should == "Stan"
80
+ user.read_friendly_attribute(:birth_year).should == 1984
81
+ end
82
+ end
83
+
84
+ describe "updating" do
85
+ context "with Friendly attributes" do
86
+ let(:user) { User.create(:name => "Stan", :email => "stan@example.com", :second_int => 200) }
87
+
88
+ it "updates the attributes" do
89
+ user.update_attributes(:name => "Eric", :second_int => 123)
90
+ UserDetails.first({}).name.should == "Eric"
91
+ UserSecondDetails.first({}).second_int.should == 123
92
+ end
93
+ end
94
+
95
+ context "without Friendly attributes" do
96
+ let(:user) { User.create(:email => "stan@example.com") }
97
+
98
+ it "does not create an associated Details model, if no delegated attributes are updated" do
99
+ expect do
100
+ user.update_attributes(:email => "eric@example.com")
101
+ end.to_not change { UserDetails.count({}) }
102
+ end
103
+
104
+ it "creates an associated Details model, if delegated attributes are updated" do
105
+ expect do
106
+ user.update_attributes(:name => "Eric")
107
+ end.to change { UserDetails.count({}) }
108
+ user.name.should == "Eric"
109
+ end
110
+ end
111
+
112
+ context "with nested attributes" do
113
+ let(:user) { User.create(:name => "Stan Marsh", :email => "smarsh@example.com") }
114
+ let(:parent) { Parent.create(:users => [user]) }
115
+
116
+ before(:each) do
117
+ parent
118
+ parent.update_attributes(:users_attributes => users_attributes)
119
+ user.reload
120
+ end
121
+
122
+ context "when only changing Friendly attributes" do
123
+ let(:users_attributes) { { "0" => { "id" => user.id, "name" => "Eric Cartman" } } }
124
+
125
+ it "updates Friendly attributes through nested association" do
126
+ user.name.should == "Eric Cartman"
127
+ end
128
+ end
129
+
130
+ context "when changing both Friendly attributes and ActiveRecord attributes" do
131
+ let(:users_attributes) { { "0" => { "id" => user.id, "name" => "Eric Cartman", "email" => "eric@example.com" } } }
132
+
133
+ it "updates Friendly attributes through nested association" do
134
+ user.email.should == "eric@example.com"
135
+ user.name.should == "Eric Cartman"
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ describe "destroying" do
142
+ let(:user) { User.create(:name => "Stan", :email => "stan@example.com") }
143
+
144
+ it "destroys the associated Details model" do
145
+ user # create it first
146
+
147
+ expect do
148
+ user.destroy
149
+ end.to change { UserDetails.count({}) }.by(-1)
150
+ end
151
+ end
152
+ end