smooth_operator 1.3.0 → 1.8.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 (60) hide show
  1. checksums.yaml +8 -8
  2. data/Gemfile +0 -5
  3. data/README.md +11 -308
  4. data/console.rb +3 -28
  5. data/lib/smooth_operator.rb +7 -75
  6. data/lib/smooth_operator/array_with_meta_data.rb +21 -20
  7. data/lib/smooth_operator/attribute_assignment.rb +53 -57
  8. data/lib/smooth_operator/delegation.rb +34 -15
  9. data/lib/smooth_operator/finder_methods.rb +17 -33
  10. data/lib/smooth_operator/helpers.rb +7 -37
  11. data/lib/smooth_operator/internal_attribute.rb +49 -0
  12. data/lib/smooth_operator/model_schema.rb +72 -0
  13. data/lib/smooth_operator/open_struct.rb +4 -7
  14. data/lib/smooth_operator/operator.rb +64 -102
  15. data/lib/smooth_operator/persistence.rb +64 -94
  16. data/lib/smooth_operator/remote_call.rb +70 -0
  17. data/lib/smooth_operator/serialization.rb +33 -89
  18. data/lib/smooth_operator/translation.rb +13 -26
  19. data/lib/smooth_operator/type_converter.rb +69 -0
  20. data/lib/smooth_operator/validations.rb +3 -25
  21. data/lib/smooth_operator/version.rb +1 -1
  22. data/smooth_operator.gemspec +5 -9
  23. data/spec/factories/user_factory.rb +4 -5
  24. data/spec/smooth_operator/attribute_assignment_spec.rb +11 -145
  25. data/spec/smooth_operator/delegation_spec.rb +54 -57
  26. data/spec/smooth_operator/finder_methods_spec.rb +2 -91
  27. data/spec/smooth_operator/{resource_name_spec.rb → model_schema_spec.rb} +2 -2
  28. data/spec/smooth_operator/operator_spec.rb +1 -1
  29. data/spec/smooth_operator/persistence_spec.rb +20 -140
  30. data/spec/smooth_operator/serialization_spec.rb +4 -28
  31. data/spec/spec_helper.rb +9 -7
  32. data/spec/support/models/address.rb +0 -9
  33. data/spec/support/models/post.rb +3 -9
  34. data/spec/support/models/user.rb +7 -30
  35. data/spec/support/models/user_with_address_and_posts.rb +12 -20
  36. data/spec/support/test_server.rb +7 -63
  37. metadata +18 -55
  38. data/lib/smooth_operator/associations.rb +0 -110
  39. data/lib/smooth_operator/associations/association_reflection.rb +0 -79
  40. data/lib/smooth_operator/associations/has_many_relation.rb +0 -45
  41. data/lib/smooth_operator/associations/reflection.rb +0 -41
  42. data/lib/smooth_operator/cookie_jar.rb +0 -21
  43. data/lib/smooth_operator/http_methods.rb +0 -17
  44. data/lib/smooth_operator/internal_data.rb +0 -45
  45. data/lib/smooth_operator/operators/connection_wrapper.rb +0 -15
  46. data/lib/smooth_operator/operators/faraday.rb +0 -75
  47. data/lib/smooth_operator/operators/typhoeus.rb +0 -87
  48. data/lib/smooth_operator/options.rb +0 -30
  49. data/lib/smooth_operator/remote_call/base.rb +0 -76
  50. data/lib/smooth_operator/remote_call/errors/connection_failed.rb +0 -20
  51. data/lib/smooth_operator/remote_call/errors/timeout.rb +0 -20
  52. data/lib/smooth_operator/remote_call/faraday.rb +0 -19
  53. data/lib/smooth_operator/remote_call/typhoeus.rb +0 -19
  54. data/lib/smooth_operator/resource_name.rb +0 -46
  55. data/lib/smooth_operator/schema.rb +0 -21
  56. data/lib/smooth_operator/type_casting.rb +0 -127
  57. data/spec/require_helper.rb +0 -11
  58. data/spec/smooth_operator/remote_call_spec.rb +0 -340
  59. data/spec/smooth_operator/validations_spec.rb +0 -42
  60. data/spec/support/models/comment.rb +0 -5
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MmY1MDZiMTA3ZGQ4OThhNWZlNDA1YzgzNmRhZTg3YzE2OWQ0ZjM1ZQ==
4
+ MjU3NWQ2OTMyMjE4ODBjZGMzZWJmMmRlMzcxZDBmMWZjYmU5YjYwOQ==
5
5
  data.tar.gz: !binary |-
6
- M2VlYzc3M2ZlZmZiM2I4ZTI3ZmE4NTUyYzIzMzEwNTMwNTAwZTY5OQ==
6
+ Y2E5MDEyYmYxODcwNmQ4MzY2MWFlMmViYTQ3Y2QxN2FlOWU2ZTJiOA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- ZmQ5NDFiMDAxYzhhNjQ0NjQzMjdlMTQ4MTM0OTg5MTRmOGZmOTlkM2YwZWJh
10
- YzJhZDBmYjRlMzdjOGJiYmFmNTBkZjBmODk0Mzc4MmRmNTcxZDdhZTUwMTE5
11
- MDQ4ZjU3YTM5YTYwMDI0Yjc4ODI2MjZmYTIwMGU4OGUwMGE5NGY=
9
+ NzlmOGZhNzVhYTVkOTE0YTI4OGZkMjhjNDlkYWE1NWEzZmE0YmIzY2RmMGVk
10
+ NzcyYTlmY2ZhZmNmZmU4MzIyOWY4ZjU2MmFlYTU4ZjRmNzI2OTczYmM3MGNi
11
+ ZDUyMGZkOGYwZDBkZThmNTE5ZTQxMTkxN2JjZWRiYTFlNzg4Nzk=
12
12
  data.tar.gz: !binary |-
13
- NTEzZjYwMzQyN2IxMWRmMDc4ZjFlZGZmY2M4ZjY1Zjk4OTM5NTY3OGRkZTEw
14
- OGJhMjNiYjVjOWRmMzQwZDJiZjM4ZGUwMjQ1YTVkZjZiNDhmMTkwMjEyZjhm
15
- YmIxMmRmMzExMjQ2ZmFjNTNhMThjZjBiZmIwMmRjOGJkNWI0NGM=
13
+ YWI0YjkzNDVmN2JhMmY0MzYxMmE4NGQ1ZDAxMDM1NzZkYThiZjZhMmYxZmE2
14
+ NzIzYTQ5MzA0NDg4NzE5ZmM2ODgzOTFmYjkwNzMxYjZiMTdmZjRjZDUzYjM4
15
+ YzQzZTc3NzJkM2I2OGM5MzFhYTQ0NDc5MWU1MGI0NmE0MzkzNGM=
data/Gemfile CHANGED
@@ -3,15 +3,10 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in smooth_operator.gemspec
4
4
  gemspec
5
5
 
6
- gem 'simplecov', :require => false, :group => :test
7
-
8
6
  group :development, :test do
9
7
  gem "pry"
10
8
  gem "sinatra"
11
- gem "typhoeus"
12
- gem "activesupport"
13
9
  gem "sinatra-contrib"
14
10
  gem "rspec", "~> 3.0.0.beta1"
15
11
  gem "factory_girl", "~> 4.0"
16
- gem "ethon", :git => 'https://github.com/goncalvesjoao/ethon'
17
12
  end
data/README.md CHANGED
@@ -1,23 +1,14 @@
1
- # SmoothOperator [![Code Climate](https://codeclimate.com/repos/536a7b9f6956801228014b02/badges/13f79897976274a9de33/gpa.png)](https://codeclimate.com/repos/536a7b9f6956801228014b02/feed)
1
+ # SmoothOperator
2
2
 
3
3
  Ruby gem, that mimics the ActiveRecord behaviour but through external API's.
4
4
  It's a lightweight and flexible alternative to ActiveResource, that responds to a REST API like you expect it too.
5
5
 
6
- Be sure to check out this micro-services example: https://github.com/goncalvesjoao/micro-services-example
6
+ Depends only on Faraday gem, no need for ActiveSupport or any other Active* gem.
7
7
 
8
- Where a Rails4 app lists/creates/edits and destroys blog posts from a Padrino (aka Sinatra) app, using SmoothOperator::Rails instead of ActiveRecord::Base classes.
8
+ Although if I18n is present it will respond to .human_attribute_name method and if ActiveModel is present it will make use of 'ActiveModel::Name' to improve .model_name method.
9
9
 
10
- This micro-services example will also feature other cool stuff like:
11
- - parallel requests;
12
- - using HTTP PATCH verb for saving instead of PUT;
13
- - form errors with simple_form gem;
14
- - nested objects using cocoon gem;
15
- - endless-pagination with kaminari gem
16
- - and others...
17
10
 
18
- ---
19
-
20
- ## 1) Installation
11
+ ## Installation
21
12
 
22
13
  Add this line to your application's Gemfile:
23
14
 
@@ -31,303 +22,15 @@ Or install it yourself as:
31
22
 
32
23
  $ gem install smooth_operator
33
24
 
34
- ---
35
-
36
- ## 2) Usage and Examples
37
-
38
- ```ruby
39
- class MyBlogResource < SmoothOperator::Base
40
-
41
- # HTTP BASIC AUTH
42
- options endpoint_user: 'admin',
43
- endpoint_pass: 'admin',
44
- endpoint: 'http://myblog.com/api/v0'
45
-
46
- # OR
47
- # smooth_operator_options
48
- end
49
-
50
- class Post < MyBlogResource
51
- end
52
- ```
53
-
54
- ---
55
-
56
- ### 2.1) Creating a .new 'Post' and #save it
57
-
58
- ```ruby
59
- post = Post.new(body: 'my first post', author: 'John Doe')
60
-
61
- post.new_record? # true
62
- post.persisted? # false
63
- post.body # 'my first post'
64
- post.author # 'John Doe'
65
- post.something_else # will raise NoMethodError
66
-
67
- save_result = post.save # will make a http POST call to 'http://myblog.com/api/v0/posts'
68
- # with `{ post: { body: 'my first post', author: 'John Doe' } }`
69
-
70
- post.last_remote_call # will contain a SmoothOperator::RemoteCall instance containing relevant information about the save remote call.
71
-
72
- # If the server response is positive (http code between 200 and 299):
73
- save_result # true
74
- post.new_record? # false
75
- post.persisted? # true
76
- # server response contains { id: 1 } on its body
77
- post.id # 1
78
-
79
- # If the server response is negative (http code between 400 and 499):
80
- save_result # false
81
- post.new_record? # true
82
- post.persisted? # false
83
- # server response contains { errors: { body: ['must be less then 10 letters'] } }
84
- post.errors.body # Array
85
-
86
- # If the server response is an error (http code between 500 and 599), or the connection was broke:
87
- save_result # nil
88
- post.new_record? # true
89
- post.persisted? # false
90
- # server response contains { errors: { body: ['must be less then 10 letters'] } }
91
- post.errors # will raise NoMethodError
92
-
93
- # In the positive and negative server response comes with a json,
94
- # e.g. { id: 1 }, post will reflect that new data
95
- post.id # 1
96
-
97
- # In case of error and the server response contains a json,
98
- # e.g. { id: 1 }, post will NOT reflect that data
99
- post.id # raise NoMethodError
100
-
101
- ```
102
-
103
- ---
104
-
105
- ### 2.2) Editing an existing record
106
- ```ruby
107
- post = Post.find(2)
108
-
109
- post.body = 'editing my second page'
110
-
111
- post.save
112
- ```
113
-
114
- ---
115
-
116
- ### 2.3) Customize #save 'url', 'params' and 'options'
117
- ```ruby
118
- post = Post.new(id: 2, body: 'editing my second page')
119
-
120
- post.new_record? # false
121
- post.persisted? # true
122
-
123
- post.save("save_and_add_to_list", { admin: true, post: { author: 'Agent Smith', list_id: 1 } }, { timeout: 1 })
124
- # Will make a PUT to 'http://myblog.com/api/v0/posts/2/save_and_add_to_list'
125
- # with { admin: true, post: { body: 'editing my second page', list_id: 1 } }
126
- # and will only wait 1sec for the server to respond.
127
-
128
- post.save('/#{post.id}/save_and_add_to_list')
129
- # Will make a PUT to 'http://myblog.com/api/v0/posts/2/save_and_add_to_list'
130
-
131
- post.save('/save_and_add_to_list')
132
- # Will make a PUT to 'http://myblog.com/api/v0/posts/save_and_add_to_list'
133
- ```
134
-
135
- ---
136
-
137
- ### 2.4) Saving using HTTP Patch verb
138
- ```ruby
139
- class Page < MyBlogResource
140
- options update_http_verb: 'patch'
141
- # OR
142
- #smooth_operator_options update_http_verb: 'patch'
143
- end
144
-
145
- page = Page.find(2)
146
-
147
- page.body = 'editing my second page'
148
-
149
- page.save # will make a http PATCH call to 'http://myblog.com/api/v0/pages/2'
150
- # with `{ page: { body: 'editing my second page' } }`
151
- ```
152
-
153
- ---
154
-
155
- ### 2.5) Retrieving remote objects - 'index' REST action
156
-
157
- ```ruby
158
- remote_call = Page.find(:all) # Will make a GET call to 'http://myblog.com/api/v0/pages'
159
- # and will return a SmoothOperator::RemoteCall instance
160
-
161
- pages = remote_call.data
162
-
163
- # If the server response is positive (http code between 200 and 299, or 304):
164
- remote_call.ok? # true
165
- remote_call.not_processed? # false
166
- remote_call.error? # false
167
- remote_call.status # true
168
- pages = remote_call.data # array of Page instances
169
- remote_call.http_status # server_response code
170
-
171
- # If the server response is unprocessed entity (http code 422):
172
- remote_call.ok? # false
173
- remote_call.not_processed? # true
174
- remote_call.error? # false
175
- remote_call.status # false
176
- remote_call.http_status # server_response code
177
-
178
- # If the server response is client error (http code between 400..499, except 422):
179
- remote_call.ok? # false
180
- remote_call.not_processed? # false
181
- remote_call.error? # true
182
- remote_call.status # nil
183
- remote_call.http_status # server_response code
184
-
185
- # If the server response is server error (http code between 500 and 599), or the connection broke:
186
- remote_call.ok? # false
187
- remote_call.not_processed? # false
188
- remote_call.error? # true
189
- remote_call.status # nil
190
- remote_call.http_status # server_response code or 0 if connection broke
191
- ```
192
-
193
- ---
194
-
195
- ### 2.6) Retrieving remote objects - 'show' REST action
196
-
197
- ```ruby
198
- remote_call = Page.find(2) # Will make a GET call to 'http://myblog.com/api/v0/pages/2'
199
- # and will return a SmoothOperator::RemoteCall instance
200
-
201
- service_down = remote_call.error?
202
-
203
- page = remote_call.data
204
- ```
205
-
206
- ---
207
-
208
- ### 2.7) Retrieving remote objects - custom query
209
- ```ruby
210
- remote_call = Page.find('my_pages', { q: body_contains: 'link' }, { endpoint_user: 'admin', endpoint_pass: 'new_password' })
211
- # will make a GET call to 'http://myblog.com/api/v0/pages/my_pages?q={body_contains="link"}'
212
- # and will change the HTTP BASIC AUTH credentials to user: 'admin' and pass: 'new_password' for this connection only.
213
-
214
- @service_down = remote_call.error?
215
-
216
- # If the server json response is an Array [{ id: 1 }, { id: 2 }]
217
- @pages = remote.data # will return an array with 2 Page's instances
218
- @pages[0].id # 1
219
- @pages[1].id # 2
220
-
221
- # If the server json response is a Hash { id: 3 }
222
- @page = remote.data # will return a single Page instance
223
- @page.id # 3
224
-
225
- # If the server json response is Hash with a key called 'pages' { current_page: 1, total_pages: 3, limit_value: 10, pages: [{ id: 4 }, { id: 5 }] }
226
- @pages = remote.data # will return a single ArrayWithMetaData instance, that will allow you to access to both the Page's instances array and the metadata.
227
-
228
- # @pages is now a valid object to work with kaminari
229
- @pages.total_pages # 3
230
- @pages.current_page # 1
231
- @pages.limit_value # 10
232
-
233
- @pages[0].id # 4
234
- @pages[1].id # 5
235
- ```
236
-
237
- ### 2.8) Keeping your session alive - custom HTTP Headers
238
-
239
- Controllers
240
- ApplicationController
241
- ```ruby
242
- ```
243
-
244
- Models
245
- SmoothResource
246
- ```ruby
247
- class SmoothResource < SmoothOperator::Rails
248
-
249
- options headers: :custom_headers
250
-
251
- def self.custom_headers
252
- {
253
- cookie: current_user.blog_cookie,
254
- "X_CSRF_TOKEN" => current_user.blog_auth_token
255
- }
256
- end
257
-
258
- protected ############## PROTECTED #################
259
-
260
- def self.current_user
261
- User.current_user
262
- end
263
-
264
- end
265
- ```
266
-
267
- ---
268
-
269
- ## 3) Methods
270
-
271
- ---
272
-
273
- ### 3.1) Persistence methods
274
-
275
- Methods | Behaviour | Arguments | Return
276
- ------- | --------- | ------ | ---------
277
- .create | Generates a new instance of the class with *attributes and calls #save with the rest of its arguments| Hash attributes = nil, String relative_path = nil, Hash data = {}, Hash options = {} | Class instance
278
- #new_record? | Returns @new_record if defined, else populates it with true if #id is present or false if blank. | - | Boolean
279
- #destroyed?| Returns @destroyed if defined, else populates it with false. | - | Boolean
280
- #persisted?| Returns true if both #new_record? and #destroyed? return false, else returns false. | - | Boolean
281
- #save | if #new_record? makes a HTTP POST, else a PUT call. If !#new_record? and relative_path is blank, sets relative_path = id.to_s. If the server POST response is positive, sets @new_record = false. See 4.2) for more behaviour info. | String relative_path = nil, Hash data = {}, Hash options = {} | Boolean or Nil
282
- #save! | Executes the same behaviour as #save, but will raise RecordNotSaved if the returning value is not true | String relative_path = nil, Hash data = {}, Hash options = {} | Boolean or Nil
283
- #destroy | Does nothing if !persisted? else makes a HTTP DELETE call. If server response it positive, sets @destroyed = true. If relative_path is blank, sets relative_path = id.to_s. See 4.2) for more behaviour info. | String relative_path = nil, Hash data = {}, Hash options = {} | Boolean or Nil
284
-
285
- ---
286
-
287
- ### 3.2) Finder methods
288
-
289
- Methods | Behaviour | Arguments | Return
290
- ------- | --------- | ------ | ---------
291
- .find | If relative_path == :all, sets relative_path = ''. Makes a Get call and initiates Class objects with the server's response data. See 4.3) and 4.4) for more behaviour info. | String relative_path, Hash data = {}, Hash options = {} | Class instance, Array of Class instances or an ArrayWithMetaData instance
292
-
293
- ---
294
-
295
- ### 3.3) Operator methods
296
- ...
297
-
298
- ---
299
-
300
- ### 3.3) Remote call methods
301
- ...
302
-
303
- ---
304
-
305
- ## 4) Behaviours
306
-
307
- ---
308
-
309
- ### 4.1) Delegation behaviour
310
- ...
311
-
312
- ---
313
-
314
- ### 4.2) Persistent operator behaviour
315
- ...
316
-
317
- ---
318
-
319
- ### 4.3) Operator behaviour
320
- ...
321
25
 
322
- ---
26
+ ## Usage
323
27
 
324
- ### 4.4) Remote call behaviour
325
- ...
28
+ TODO: Write usage instructions here
326
29
 
327
- ---
328
30
 
329
- ## 4) TODO
31
+ ## TODO
330
32
 
331
- 1. Finish "Methods" and "Behaviours" documentation;
332
- 2. ModelSchema specs;
333
- 3. Cache.
33
+ 1. FinderMethods specs
34
+ 2. serialization_specs to test the json options for nested classes
35
+ 3. model_schema_specs
36
+ 4. Cache
data/console.rb CHANGED
@@ -3,34 +3,9 @@
3
3
  $LOAD_PATH << './'
4
4
  $LOAD_PATH << './lib'
5
5
 
6
- require 'spec/require_helper'
6
+ require "spec/spec_helper"
7
7
 
8
- FactoryGirl.find_definitions
9
-
10
- LocalhostServer.new(TestServer.new, 4567)
11
-
12
- # user = nil
13
-
14
- # hydra = Typhoeus::Hydra::hydra
15
-
16
- # user = User::Base.new(id: 1)
17
- # user.reload(nil, nil, { hydra: hydra })
18
-
19
- # User::Base.find(1, nil, { hydra: hydra }) do |remote_call|
20
- # user = remote_call.data
21
- # end
22
-
23
- #User::Base.post('', { user: { age: 1, posts: [{ body: 'post1' }, 2] } })
24
-
25
- #user = UserWithAddressAndPosts::Son.new(FactoryGirl.attributes_for(:user_with_address_and_posts))
26
- #user.save('', { status: 200 })
27
-
28
- # "[{\"patient_id\"=>33, \"messages\"=>[{\"id\"=>\"53722c20cb38247c36000003\", \"title\"=>\"Joao Goncalves\", \"created_at\"=>\"2014-05-13T14:28:48Z\"}, {\"id\"=>\"53722bfccb382485d5000002\", \"title\"=>\"Joao Goncalves\", \"created_at\"=>\"2014-05-13T14:28:12Z\"}, {\"id\"=>\"53722b91cb3824e913000001\", \"title\"=>\"Joao Goncalves\", \"created_at\"=>\"2014-05-13T14:26:25Z\"}]}]"
29
-
30
- post = Post.new(comments: [{ id: 1, name: '1' }, { id: 2, name: '2' }], address: { id: 1, name: 'address' })
31
-
32
- comments_attributes = { "0" => { id: 1, name: '3' }, "1" => { name: '4' } }
33
-
34
- comments_with_errors = { "0" => { id: 1, name: '3', errors: { body: ["can't be blank"] } }, "1" => { name: '4', errors: { body: ["can't be blank"] } } }
8
+ #User.post('', { user: { age: 1, posts: [{ body: 'post1' }, 2] } })
35
9
 
36
10
  binding.pry
11
+
@@ -1,94 +1,26 @@
1
- require "smooth_operator/schema"
1
+ # require 'active_model'
2
+
2
3
  require "smooth_operator/version"
3
4
  require "smooth_operator/helpers"
4
5
  require "smooth_operator/operator"
6
+ require "smooth_operator/remote_call"
5
7
  require "smooth_operator/persistence"
6
8
  require "smooth_operator/translation"
7
9
  require "smooth_operator/open_struct"
8
- require "smooth_operator/http_methods"
9
- require "smooth_operator/associations"
10
10
  require "smooth_operator/finder_methods"
11
11
 
12
12
  module SmoothOperator
13
+
13
14
  class Base < OpenStruct
14
15
 
15
- extend Schema
16
- extend HttpMethods
17
- extend Associations
16
+ extend Operator
18
17
  extend FinderMethods
19
18
  extend Translation if defined? I18n
20
19
 
21
- include Operator
22
- include HttpMethods
23
20
  include Persistence
24
- include FinderMethods
25
-
26
- options strict_behaviour: true
27
-
28
- def self.smooth_operator?
29
- true
30
- end
31
-
32
- end
33
-
34
- if defined?(ActiveModel)
35
- class Rails < Base
36
-
37
- include ActiveModel::Validations
38
- include ActiveModel::Validations::Callbacks
39
- include ActiveModel::Conversion
40
-
41
- options unknown_hash_class: SmoothOperator::OpenStruct
42
-
43
- validate :validate_induced_errors, :validate_nested_objects
44
-
45
- def column_for_attribute(attribute_name)
46
- type = self.class.attribute_type(attribute_name)
47
-
48
- ActiveRecord::ConnectionAdapters::Column.new(attribute_name.to_sym, type, type)
49
- end
50
-
51
- def save(relative_path = nil, data = {}, options = {})
52
- return false unless before_save
53
-
54
- clear_induced_errors
55
-
56
- save_result = valid? ? super : false
57
-
58
- after_save if valid? && save_result
59
-
60
- save_result
61
- end
62
-
63
- def before_save
64
- true
65
- end
66
-
67
- def after_save; end
68
-
69
- def self.model_name
70
- smooth_model_name
71
- end
72
-
73
- protected ################# PROTECTED ###################
74
-
75
- def validate_induced_errors
76
- induced_errors.each do |key, value|
77
- [*value].each do |_value|
78
- self.errors.add(key, _value) unless self.errors.added?(key, _value)
79
- end
80
- end
81
-
82
- Helpers.blank?(induced_errors)
83
- end
84
-
85
- def validate_nested_objects
86
- all_nested_objects = self.class.reflections.keys
87
- .map { |association| send(association) }.flatten.compact
88
21
 
89
- all_nested_objects.map { |nested_object| nested_object.valid? }.all?
90
- end
22
+ attr_reader :last_remote_call
91
23
 
92
- end
93
24
  end
25
+
94
26
  end