smooth_operator 0.4.4 → 1.2.0

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 (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