smooth_operator 0.4.4 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +9 -9
  2. data/.gitignore +2 -1
  3. data/.rspec +4 -0
  4. data/Gemfile +13 -0
  5. data/README.md +258 -10
  6. data/console.rb +44 -0
  7. data/lib/smooth_operator/array_with_meta_data.rb +31 -0
  8. data/lib/smooth_operator/attribute_assignment.rb +102 -0
  9. data/lib/smooth_operator/attribute_methods.rb +87 -0
  10. data/lib/smooth_operator/attributes/base.rb +107 -0
  11. data/lib/smooth_operator/attributes/dirty.rb +29 -0
  12. data/lib/smooth_operator/attributes/normal.rb +15 -0
  13. data/lib/smooth_operator/delegation.rb +60 -0
  14. data/lib/smooth_operator/finder_methods.rb +43 -0
  15. data/lib/smooth_operator/helpers.rb +79 -0
  16. data/lib/smooth_operator/model_schema.rb +81 -0
  17. data/lib/smooth_operator/open_struct.rb +37 -0
  18. data/lib/smooth_operator/operator.rb +145 -0
  19. data/lib/smooth_operator/operators/faraday.rb +75 -0
  20. data/lib/smooth_operator/operators/typhoeus.rb +77 -0
  21. data/lib/smooth_operator/persistence.rb +144 -0
  22. data/lib/smooth_operator/relation/array_relation.rb +13 -0
  23. data/lib/smooth_operator/relation/association_reflection.rb +75 -0
  24. data/lib/smooth_operator/relation/associations.rb +75 -0
  25. data/lib/smooth_operator/relation/reflection.rb +41 -0
  26. data/lib/smooth_operator/relation/single_relation.rb +14 -0
  27. data/lib/smooth_operator/remote_call/base.rb +80 -0
  28. data/lib/smooth_operator/remote_call/errors/connection_failed.rb +20 -0
  29. data/lib/smooth_operator/remote_call/errors/timeout.rb +20 -0
  30. data/lib/smooth_operator/remote_call/faraday.rb +19 -0
  31. data/lib/smooth_operator/remote_call/typhoeus.rb +19 -0
  32. data/lib/smooth_operator/serialization.rb +79 -0
  33. data/lib/smooth_operator/translation.rb +27 -0
  34. data/lib/smooth_operator/validations.rb +15 -0
  35. data/lib/smooth_operator/version.rb +1 -1
  36. data/lib/smooth_operator.rb +26 -5
  37. data/smooth_operator.gemspec +12 -3
  38. data/spec/factories/user_factory.rb +34 -0
  39. data/spec/require_helper.rb +11 -0
  40. data/spec/smooth_operator/attribute_assignment_spec.rb +351 -0
  41. data/spec/smooth_operator/attributes_dirty_spec.rb +53 -0
  42. data/spec/smooth_operator/delegation_spec.rb +139 -0
  43. data/spec/smooth_operator/finder_methods_spec.rb +105 -0
  44. data/spec/smooth_operator/model_schema_spec.rb +31 -0
  45. data/spec/smooth_operator/operator_spec.rb +46 -0
  46. data/spec/smooth_operator/persistence_spec.rb +424 -0
  47. data/spec/smooth_operator/remote_call_spec.rb +320 -0
  48. data/spec/smooth_operator/serialization_spec.rb +80 -0
  49. data/spec/smooth_operator/validations_spec.rb +42 -0
  50. data/spec/spec_helper.rb +25 -0
  51. data/spec/support/helpers/persistence_helper.rb +38 -0
  52. data/spec/support/localhost_server.rb +97 -0
  53. data/spec/support/models/address.rb +14 -0
  54. data/spec/support/models/comment.rb +3 -0
  55. data/spec/support/models/post.rb +13 -0
  56. data/spec/support/models/user.rb +41 -0
  57. data/spec/support/models/user_with_address_and_posts.rb +89 -0
  58. data/spec/support/test_server.rb +165 -0
  59. metadata +108 -18
  60. data/lib/smooth_operator/base.rb +0 -30
  61. data/lib/smooth_operator/core.rb +0 -218
  62. data/lib/smooth_operator/http_handlers/typhoeus/base.rb +0 -58
  63. data/lib/smooth_operator/http_handlers/typhoeus/orm.rb +0 -34
  64. data/lib/smooth_operator/http_handlers/typhoeus/remote_call.rb +0 -28
  65. data/lib/smooth_operator/operator/base.rb +0 -43
  66. data/lib/smooth_operator/operator/exceptions.rb +0 -64
  67. data/lib/smooth_operator/operator/orm.rb +0 -118
  68. data/lib/smooth_operator/operator/remote_call.rb +0 -84
@@ -0,0 +1,27 @@
1
+ module SmoothOperator
2
+
3
+ module Translation
4
+
5
+ def human_attribute_name(attribute_key_name, options = {})
6
+ _translate("attributes.#{model_name.i18n_key}.#{attribute_key_name}", options = {})
7
+ end
8
+
9
+
10
+ private ###################### PRIVATE #########################
11
+
12
+ def _translate(namespace = '', options = {})
13
+ no_translation = "-- no translation --"
14
+
15
+ defaults = ["smooth_operator.#{namespace}".to_sym]
16
+ defaults << "activerecord.#{namespace}".to_sym
17
+ defaults << options[:default] if options[:default]
18
+ defaults.flatten!
19
+ defaults << no_translation
20
+
21
+ options = { count: 1, default: defaults }.merge!(options.except(:default))
22
+ I18n.translate(defaults.shift, options)
23
+ end
24
+
25
+ end
26
+
27
+ end
@@ -0,0 +1,15 @@
1
+ module SmoothOperator
2
+
3
+ module Validations
4
+
5
+ def valid?(context = nil)
6
+ Helpers.blank?(get_internal_data("errors"))
7
+ end
8
+
9
+ def invalid?
10
+ !valid?
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -1,3 +1,3 @@
1
1
  module SmoothOperator
2
- VERSION = "0.4.4"
2
+ VERSION = "1.2.0"
3
3
  end
@@ -1,7 +1,28 @@
1
+ require "smooth_operator/version"
2
+ require "smooth_operator/helpers"
3
+ require "smooth_operator/operator"
4
+ require "smooth_operator/persistence"
5
+ require "smooth_operator/translation"
6
+ require "smooth_operator/open_struct"
7
+ require "smooth_operator/finder_methods"
8
+ require "smooth_operator/relation/associations"
9
+
1
10
  module SmoothOperator
2
- # Thank you Mario! But our princess is in another castle!
3
- end
11
+ class Base < OpenStruct::Base
4
12
 
5
- require "smooth_operator/version"
6
- require "smooth_operator/core"
7
- require "smooth_operator/base"
13
+ extend FinderMethods
14
+ extend Translation if defined? I18n
15
+
16
+ include Operator
17
+ include Persistence
18
+ include FinderMethods
19
+ include Relation::Associations
20
+
21
+ self.strict_behaviour = true
22
+
23
+ def self.smooth_operator?
24
+ true
25
+ end
26
+
27
+ end
28
+ end
@@ -1,6 +1,9 @@
1
1
  # coding: utf-8
2
+
2
3
  lib = File.expand_path('../lib', __FILE__)
4
+
3
5
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
+
4
7
  require 'smooth_operator/version'
5
8
 
6
9
  Gem::Specification.new do |spec|
@@ -9,7 +12,7 @@ Gem::Specification.new do |spec|
9
12
  spec.authors = ["João Gonçalves"]
10
13
  spec.email = ["goncalves.joao@gmail.com"]
11
14
  spec.description = %q{ActiveResource alternative}
12
- spec.summary = %q{Simple and fully customizable alternative to ActiveResource, based on httparty gem}
15
+ spec.summary = %q{Simple and fully customizable alternative to ActiveResource, using faraday gem to stablish remote calls"}
13
16
  spec.homepage = "https://github.com/goncalvesjoao/smooth_operator"
14
17
  spec.license = "MIT"
15
18
 
@@ -17,7 +20,13 @@ Gem::Specification.new do |spec|
17
20
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
21
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
22
  spec.require_paths = ["lib"]
20
-
23
+
21
24
  spec.add_development_dependency "bundler", "~> 1.3"
22
- spec.add_development_dependency "rake"
25
+
26
+ spec.add_dependency "json"
27
+ spec.add_dependency "faraday", "~> 0.8.9"
28
+ spec.add_dependency "typhoeus", "~> 0.6.8"
29
+
30
+ # this is necessary if you want to typhoeus to correctly encode arrays
31
+ # spec.add_dependency "ethon", :git => 'https://github.com/goncalvesjoao/ethon'
23
32
  end
@@ -0,0 +1,34 @@
1
+ FactoryGirl.define do
2
+
3
+ factory :user, class: User::Base do
4
+
5
+ id 1
6
+ admin true
7
+ last_name 'Doe'
8
+ first_name 'John'
9
+
10
+ trait :with_address_and_posts do
11
+ address { { street: 'my_street' } }
12
+ # posts [{ id: 1, body: 'post1' }, { id: 2, body: 'post2' }]
13
+ posts [{ id: 1 }, { id: 2 }]
14
+ end
15
+ factory :user_with_address_and_posts, traits: [:with_address_and_posts]
16
+
17
+ trait :has_my_method do
18
+ my_method 'my_method'
19
+ end
20
+ factory :user_with_my_method, traits: [:with_address_and_posts, :has_my_method]
21
+
22
+ end
23
+
24
+ factory :white_list, class: User::Base do
25
+ id 1
26
+ first_name 'John'
27
+ end
28
+
29
+ factory :black_list, class: User::Base do
30
+ admin true
31
+ last_name 'Doe'
32
+ end
33
+
34
+ end
@@ -0,0 +1,11 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
3
+
4
+ require 'bundler/setup'
5
+ require 'smooth_operator'
6
+
7
+ Bundler.require :test, :default
8
+
9
+ Dir.chdir("spec/") do
10
+ Dir["support/**/*.rb"].each { |file| require file }
11
+ end
@@ -0,0 +1,351 @@
1
+ require 'date'
2
+ require "spec_helper"
3
+
4
+ describe SmoothOperator::AttributeAssignment do
5
+
6
+ describe "#assign_attributes" do
7
+
8
+ describe "receiving data from server" do
9
+ subject { User::Base.new }
10
+
11
+ context "when receiving the option 'from_server = true'" do
12
+ before { subject.assign_attributes({}, from_server: true) }
13
+
14
+ it "#has_data_from_server and #from_server should return true" do
15
+ expect(subject.has_data_from_server).to be true
16
+ expect(subject.from_server).to be true
17
+ end
18
+ end
19
+
20
+ context "when receiving a Hash with meta_data on it" do
21
+ before { subject.assign_attributes({ user: attributes_for(:user), status: 1 }) }
22
+
23
+ it "#meta_data should reflect the receiving meta_data" do
24
+ expect(subject._meta_data).to eq({ "status" => 1 })
25
+ end
26
+
27
+ it "subject should NOT contain meta_data" do
28
+ expect{ subject.status }.to raise_error NoMethodError
29
+ end
30
+
31
+ it "subject should contain all other data" do
32
+ expect(subject.attributes).to eq(attributes_for(:user))
33
+ end
34
+ end
35
+
36
+ end
37
+
38
+ describe "white and black list" do
39
+ subject { UserWithAddressAndPosts::Son.new(attributes_for(:user_with_address_and_posts)) }
40
+
41
+ context "when there are no changes to attributes's white and black list" do
42
+ it 'it should return all attributes' do
43
+ expect(subject.to_hash).to eq(attributes_for(:user_with_address_and_posts))
44
+ end
45
+ end
46
+
47
+ context "when there are changes to attributes's white list" do
48
+ subject(:user_white_listed) { UserWithAddressAndPosts::UserWhiteListed::Son.new(attributes_for(:user_with_address_and_posts)) }
49
+
50
+ it 'it should return only the white listed' do
51
+ expect(user_white_listed.to_hash).to eq(attributes_for(:white_list))
52
+ end
53
+ end
54
+
55
+ context "when there are changes to attributes's black list" do
56
+ subject(:user_black_listed) { UserWithAddressAndPosts::UserBlackListed::Son.new(attributes_for(:user_with_address_and_posts)) }
57
+
58
+ it 'it should not return the black listed' do
59
+ expect(user_black_listed.to_hash).not_to include(attributes_for(:black_list))
60
+ end
61
+ end
62
+ end
63
+
64
+ context "when something other than a hash is introduced" do
65
+ it "should do nothing" do
66
+ [nil, '', [1, 2], 'test', 1, 2].each do |something_other_than_a_hash|
67
+ expect(User::Base.new(something_other_than_a_hash).internal_data).to eq({})
68
+ end
69
+ end
70
+ end
71
+
72
+ context "when one of the attribute's value, is an hash and is unknown to the schema" do
73
+ context "when the .unknown_hash_class is unused", current: true do
74
+ subject { User::Base.new(address: { street: 'something', postal_code: { code: '123' } }) }
75
+
76
+ it "a new instance of OpenStruct will be initialized with that hash" do
77
+ address = subject.address
78
+
79
+ expect(address).to be_instance_of(OpenStruct)
80
+ expect(address.street).to eq('something')
81
+
82
+ expect(address.postal_code).to be_instance_of(OpenStruct)
83
+ expect(address.postal_code.code).to eq('123')
84
+ end
85
+ end
86
+
87
+ context "when the .unknown_hash_class is set to SmoothOperator::OpenStruct::Base" do
88
+ subject { User::UnknownHashClass::OpenStructBase.new(address: { street: 'something', postal_code: { code: '123' } }) }
89
+
90
+ it "a new instance of SmoothOperator::OpenStruct::Base will be initialized with that hash" do
91
+ address = subject.address
92
+
93
+ expect(address).to be_instance_of(SmoothOperator::OpenStruct::Base)
94
+ expect(address.street).to eq('something')
95
+
96
+ expect(address.postal_code).to be_instance_of(SmoothOperator::OpenStruct::Base)
97
+ expect(address.postal_code.code).to eq('123')
98
+ end
99
+ end
100
+
101
+ context "when the .unknown_hash_class is set to :none" do
102
+ subject { User::UnknownHashClass::None.new(creator: { first_name: 'admin', address: { street: 'something' } }) }
103
+
104
+ it "the hash will be copied as it is" do
105
+ creator = subject.creator
106
+
107
+ expect(creator).to be_instance_of(Hash)
108
+ expect(creator[:first_name]).to eq('admin')
109
+
110
+ expect(creator[:address]).to be_instance_of(Hash)
111
+ expect(creator[:address][:street]).to eq('something')
112
+ end
113
+ end
114
+ end
115
+
116
+ context "when there is no declared schema" do
117
+ subject { User::Base.new(attributes_for(:user)) }
118
+ let(:expected_internal_data) { SmoothOperator::Helpers.stringify_keys(attributes_for(:user)) }
119
+
120
+ it "it should populate 'internal_data' with unaltered duplicate data from the received hash" do
121
+ expect(subject.to_hash).to eq(attributes_for(:user))
122
+ end
123
+
124
+ it "it should populate 'known_attributes' with the keys of the received hash" do
125
+ expect(subject.known_attributes.to_a).to match_array(expected_internal_data.keys)
126
+ end
127
+ end
128
+
129
+ context "when there is a known schema and the received hash has an attribute" do
130
+ subject { UserWithAddressAndPosts::Son }
131
+
132
+ context "that is declared (in schema) as an nil" do
133
+
134
+ it "when the attributes's value is '1', should return '1'" do
135
+ expect(subject.new(complex_field: '1').complex_field).to eq('1')
136
+ end
137
+
138
+ it "when the attributes's value is ['1', '2'], should return ['1', '2']" do
139
+ expect(subject.new(complex_field: ['1', '2']).complex_field).to eq(['1', '2'])
140
+ end
141
+
142
+ it "when the attributes's value is 1, should be converted to 1" do
143
+ expect(subject.new(complex_field: 1).complex_field).to eq(1)
144
+ end
145
+
146
+ it "when the attributes's value is { first_name: ['1', '2'] }, should be converted to { first_name: ['1', '2'] }" do
147
+ expect(subject.new(complex_field: { first_name: ['1', '2'] }).complex_field).to eq({ first_name: ['1', '2'] })
148
+ end
149
+
150
+ it "when the attributes's value is -1, should be converted to -1" do
151
+ expect(subject.new(complex_field: -1).complex_field).to eq(-1)
152
+ end
153
+
154
+ it "when the attributes's value is 0.35, should be converted to 0.35" do
155
+ expect(subject.new(complex_field: 0.35).complex_field).to eq(0.35)
156
+ end
157
+
158
+ end
159
+
160
+ context "that is declared (in schema) as an integer" do
161
+
162
+ it "when the attributes's value is '1', should be converted to 1" do
163
+ expect(subject.new(age: '1').age).to be(1)
164
+ end
165
+
166
+ it "when the attributes's value is '-1', should be converted to -1" do
167
+ expect(subject.new(age: '-1').age).to be(-1)
168
+ end
169
+
170
+ it "when the attributes's value is 's-10s', should be converted to -10" do
171
+ expect(subject.new(age: 's-10s').age).to be(-10)
172
+ end
173
+
174
+ it "when the attributes's value is ' 10s', should be converted to 10" do
175
+ expect(subject.new(age: ' 10s').age).to be(10)
176
+ end
177
+
178
+ it "when the attributes's value is 123, should be converted to 123" do
179
+ expect(subject.new(age: 123).age).to be(123)
180
+ end
181
+
182
+ it "when the attributes's value is -5, should be converted to -5" do
183
+ expect(subject.new(age: -5).age).to be(-5)
184
+ end
185
+
186
+ end
187
+
188
+ context "that is declared (in schema) as an float" do
189
+
190
+ it "when the attributes's value is '1', should be converted to 1" do
191
+ expect(subject.new(price: '1').price).to eq(1.0)
192
+ end
193
+
194
+ it "when the attributes's value is '-1', should be converted to -1" do
195
+ expect(subject.new(price: '-1').price).to eq(-1.0)
196
+ end
197
+
198
+ it "when the attributes's value is 's-10s', should be converted to -10" do
199
+ expect(subject.new(price: 's-10s').price).to eq(-10.0)
200
+ end
201
+
202
+ it "when the attributes's value is ' 10s', should be converted to 10" do
203
+ expect(subject.new(price: ' 10s').price).to eq(10.0)
204
+ end
205
+
206
+ it "when the attributes's value is 123, should be converted to 123" do
207
+ expect(subject.new(price: 123).price).to eq(123.0)
208
+ end
209
+
210
+ it "when the attributes's value is -5, should be converted to -5" do
211
+ expect(subject.new(price: -5).price).to eq(-5.0)
212
+ end
213
+
214
+ it "when the attributes's value is '12.3', should be converted to 12.3" do
215
+ expect(subject.new(price: '12.3').price).to eq(12.3)
216
+ end
217
+
218
+ it "when the attributes's value is 's12.3s', should be converted to 12.3" do
219
+ expect(subject.new(price: 's12.3s').price).to eq(12.3)
220
+ end
221
+
222
+ it "when the attributes's value is 's12,3s', should be converted to 12.3" do
223
+ expect(subject.new(price: 's12,3s').price).to eq(12.3)
224
+ end
225
+
226
+ it "when the attributes's value is 1.2, should be converted to 1.2" do
227
+ expect(subject.new(price: 1.2).price).to eq(1.2)
228
+ end
229
+
230
+ end
231
+
232
+ context "that is declared (in schema) as an boolean" do
233
+
234
+ it "when the attributes's value is true, should be converted to true" do
235
+ expect(subject.new(manager: true).manager).to be(true)
236
+ end
237
+
238
+ it "when the attributes's value is false, should be converted to false" do
239
+ expect(subject.new(manager: false).manager).to be(false)
240
+ end
241
+
242
+ it "when the attributes's value is 'true', should be converted to true" do
243
+ expect(subject.new(manager: 'true').manager).to be(true)
244
+ end
245
+
246
+ it "when the attributes's value is 'false', should be converted to false" do
247
+ expect(subject.new(manager: 'false').manager).to be(false)
248
+ end
249
+
250
+ it "when the attributes's value is '1', should be converted to true" do
251
+ expect(subject.new(manager: '1').manager).to be(true)
252
+ end
253
+
254
+ it "when the attributes's value is '0', should be converted to false" do
255
+ expect(subject.new(manager: '0').manager).to be(false)
256
+ end
257
+
258
+ it "when the attributes's value is '', should be converted to nil" do
259
+ expect(subject.new(manager: '').manager).to be_nil
260
+ end
261
+
262
+ it "when the attributes's value is 'something', should be converted to nil" do
263
+ expect(subject.new(manager: 'something').manager).to be_nil
264
+ end
265
+
266
+ end
267
+
268
+ context "that is declared (in schema) as an existing class" do
269
+
270
+ it "if the attribute's value is an hash a new instance of that class will be initialized with that hash" do
271
+ address = subject.new(address: { street: 'something' }).address
272
+
273
+ expect(address).to be_instance_of(Address)
274
+ expect(address.street).to eq('something')
275
+ end
276
+
277
+ it "if the attribute's value is not an hash, then that value will be simply cloned" do
278
+ expect(subject.new(address: 'something').address).to eq('something')
279
+ end
280
+
281
+ it "if the attribute's value is an array, a new instance of that class will be initialized for each array entry" do
282
+ posts = subject.new(posts: [{ body: 'post1' }, { body: 'post2' }]).posts
283
+
284
+ expect(posts.length).to be(2)
285
+
286
+ expect(posts[0]).to be_instance_of(Post)
287
+ expect(posts[0].body).to eq('post1')
288
+
289
+ expect(posts[1]).to be_instance_of(Post)
290
+ expect(posts[1].body).to eq('post2')
291
+ end
292
+
293
+ end
294
+
295
+ context "that is declared (in schema) as a date" do
296
+
297
+ it "if the attribute's value is a valid date string" do
298
+ dob = subject.new(dob: '2-2-2222').dob
299
+
300
+ expect(dob).to be_instance_of(Date)
301
+ expect(dob.day).to be(2)
302
+ expect(dob.month).to be(2)
303
+ expect(dob.year).to be(2222)
304
+ end
305
+
306
+ it "if the attribute's value is a valid date" do
307
+ date_now = DateTime.now
308
+ dob = subject.new(dob: date_now).dob
309
+
310
+ expect(dob).to be_instance_of(DateTime)
311
+ expect(dob).to eq(date_now)
312
+ end
313
+
314
+ it "if the attribute's value is an invalid date string, the returning value should be nil" do
315
+ expect(subject.new(dob: '2s-2-2222').dob).to be_nil
316
+ end
317
+
318
+ end
319
+
320
+ context "that is declared (in schema) as a datetime" do
321
+
322
+ it "if the attribute's value is a valid datetime string" do
323
+ date = subject.new(date: '2-2-2222 12:30').date
324
+
325
+ expect(date).to be_instance_of(DateTime)
326
+ expect(date.day).to be(2)
327
+ expect(date.month).to be(2)
328
+ expect(date.year).to be(2222)
329
+ expect(date.hour).to be(12)
330
+ expect(date.min).to be(30)
331
+ end
332
+
333
+ it "if the attribute's value is a valid datetime" do
334
+ date_now = DateTime.now
335
+ date = subject.new(date: date_now).date
336
+
337
+ expect(date).to be_instance_of(DateTime)
338
+ expect(date).to eq(date_now)
339
+ end
340
+
341
+ it "if the attribute's value is an invalid datetime string, the returning value should be nil" do
342
+ expect(subject.new(date: '2s-2-2222').date).to be_nil
343
+ end
344
+
345
+ end
346
+
347
+ end
348
+
349
+ end
350
+
351
+ end
@@ -0,0 +1,53 @@
1
+ require "spec_helper"
2
+
3
+ describe SmoothOperator::Attributes::Dirty do
4
+
5
+ subject { UserWithAddressAndPosts::DirtyAttributes.new(attributes_for(:user_with_address_and_posts)) }
6
+
7
+ context "when no changes are made to an attribute" do
8
+ it "checking if that attribute is changed, should return false" do
9
+ expect(subject.first_name_changed?).to be false
10
+ end
11
+
12
+ it "checking that attribute past value, should its original value" do
13
+ expect(subject.first_name_was).to eq('John')
14
+ end
15
+ end
16
+
17
+ context "when there are changes made to an attribute" do
18
+ before { subject.first_name = 'nhoJ' }
19
+
20
+ it "checking if that attribute is changed, should return true" do
21
+ expect(subject.first_name_changed?).to be true
22
+ end
23
+
24
+ it "checking that attribute past value, should its original value" do
25
+ expect(subject.first_name_was).to eq('John')
26
+ end
27
+
28
+ context "when there are changes to the changes made to an attribute" do
29
+ before { subject.first_name = 'no_name' }
30
+
31
+ it "checking if that attribute is changed, should return true" do
32
+ expect(subject.first_name_changed?).to be true
33
+ end
34
+
35
+ it "checking that attribute past value, should its first original value" do
36
+ expect(subject.first_name_was).to eq('John')
37
+ end
38
+ end
39
+ end
40
+
41
+ context "when there are changes made to a nested object" do
42
+ before { subject.address.street = 'my street' }
43
+
44
+ it "checking if the nested object as changed, should return false" do
45
+ expect(subject.address_changed?).to be false
46
+ end
47
+
48
+ it "checking if the nested object's attribute as changed, should return true" do
49
+ expect(subject.address.street_changed?).to be true
50
+ end
51
+ end
52
+
53
+ end