smooth_operator 1.2.9 → 1.3.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 (52) hide show
  1. checksums.yaml +8 -8
  2. data/README.md +78 -22
  3. data/console.rb +2 -0
  4. data/lib/smooth_operator/array_with_meta_data.rb +20 -8
  5. data/lib/smooth_operator/{relation → associations}/association_reflection.rb +8 -8
  6. data/lib/smooth_operator/{relation/array_relation.rb → associations/has_many_relation.rb} +3 -13
  7. data/lib/smooth_operator/{relation → associations}/reflection.rb +2 -2
  8. data/lib/smooth_operator/associations.rb +110 -0
  9. data/lib/smooth_operator/attribute_assignment.rb +52 -60
  10. data/lib/smooth_operator/cookie_jar.rb +21 -0
  11. data/lib/smooth_operator/delegation.rb +13 -34
  12. data/lib/smooth_operator/finder_methods.rb +26 -17
  13. data/lib/smooth_operator/helpers.rb +14 -8
  14. data/lib/smooth_operator/http_methods.rb +17 -0
  15. data/lib/smooth_operator/internal_data.rb +45 -0
  16. data/lib/smooth_operator/open_struct.rb +11 -26
  17. data/lib/smooth_operator/operator.rb +65 -59
  18. data/lib/smooth_operator/operators/connection_wrapper.rb +15 -0
  19. data/lib/smooth_operator/operators/faraday.rb +6 -6
  20. data/lib/smooth_operator/operators/typhoeus.rb +22 -12
  21. data/lib/smooth_operator/options.rb +30 -0
  22. data/lib/smooth_operator/persistence.rb +64 -61
  23. data/lib/smooth_operator/remote_call/base.rb +7 -6
  24. data/lib/smooth_operator/resource_name.rb +46 -0
  25. data/lib/smooth_operator/schema.rb +21 -0
  26. data/lib/smooth_operator/serialization.rb +80 -36
  27. data/lib/smooth_operator/translation.rb +21 -12
  28. data/lib/smooth_operator/type_casting.rb +127 -0
  29. data/lib/smooth_operator/validations.rb +25 -3
  30. data/lib/smooth_operator/version.rb +1 -1
  31. data/lib/smooth_operator.rb +55 -5
  32. data/smooth_operator.gemspec +5 -5
  33. data/spec/smooth_operator/attribute_assignment_spec.rb +5 -14
  34. data/spec/smooth_operator/finder_methods_spec.rb +4 -9
  35. data/spec/smooth_operator/persistence_spec.rb +27 -19
  36. data/spec/smooth_operator/remote_call_spec.rb +104 -84
  37. data/spec/smooth_operator/{model_schema_spec.rb → resource_name_spec.rb} +1 -1
  38. data/spec/support/models/address.rb +8 -10
  39. data/spec/support/models/comment.rb +2 -0
  40. data/spec/support/models/post.rb +7 -7
  41. data/spec/support/models/user.rb +10 -13
  42. data/spec/support/models/user_with_address_and_posts.rb +9 -17
  43. data/spec/support/test_server.rb +7 -7
  44. metadata +25 -25
  45. data/lib/smooth_operator/attribute_methods.rb +0 -78
  46. data/lib/smooth_operator/attributes/base.rb +0 -107
  47. data/lib/smooth_operator/attributes/dirty.rb +0 -29
  48. data/lib/smooth_operator/attributes/normal.rb +0 -15
  49. data/lib/smooth_operator/blank_slate.rb +0 -7
  50. data/lib/smooth_operator/model_schema.rb +0 -81
  51. data/lib/smooth_operator/relation/associations.rb +0 -102
  52. data/spec/smooth_operator/attributes_dirty_spec.rb +0 -53
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NzlkNzFkMjI1Y2UyZTRlNzYwNWIxYWJmMTIwYjMxM2RmNzYxZWQ2Yg==
4
+ MmY1MDZiMTA3ZGQ4OThhNWZlNDA1YzgzNmRhZTg3YzE2OWQ0ZjM1ZQ==
5
5
  data.tar.gz: !binary |-
6
- Njc2YzU4YTRjOTFiY2QxMmE1YjA3NDRkMWYxNzZmYWM5YzBlMzAxYQ==
6
+ M2VlYzc3M2ZlZmZiM2I4ZTI3ZmE4NTUyYzIzMzEwNTMwNTAwZTY5OQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- NmE0OGIxNDc4YTgxNDFmNjBhZDE3YzNjMzdhYmYzOWRmMzA4ZTU4NjgyZjQ1
10
- YmI3YmZhYzE3YzVkODliYzhkNzc5YjU1MzViMzBjZGIwN2IzZThmMjI4ZmZl
11
- ODNhYzI1MjY0NTY1ZTZhMTU2NjNlOTE1ZWU4MWRkYzY2ZWFmMTM=
9
+ ZmQ5NDFiMDAxYzhhNjQ0NjQzMjdlMTQ4MTM0OTg5MTRmOGZmOTlkM2YwZWJh
10
+ YzJhZDBmYjRlMzdjOGJiYmFmNTBkZjBmODk0Mzc4MmRmNTcxZDdhZTUwMTE5
11
+ MDQ4ZjU3YTM5YTYwMDI0Yjc4ODI2MjZmYTIwMGU4OGUwMGE5NGY=
12
12
  data.tar.gz: !binary |-
13
- ZDQxZDg3ZjhiMGJjNWE3ZjJhOTQyMjE2NzM1OGI5YWMwOTQ0M2QzN2VhMGFi
14
- YjcxMGZiZDg4ZDcyMTU3NzcyMTYyZTRkYTU2MThjNmRlYThlN2EzYzRmZjI4
15
- ZDAyM2M2ZjU1MjVmYWFhZWI4NWU0YmVhMDBhMTU5ZGU1MzZkMTI=
13
+ NTEzZjYwMzQyN2IxMWRmMDc4ZjFlZGZmY2M4ZjY1Zjk4OTM5NTY3OGRkZTEw
14
+ OGJhMjNiYjVjOWRmMzQwZDJiZjM4ZGUwMjQ1YTVkZjZiNDhmMTkwMjEyZjhm
15
+ YmIxMmRmMzExMjQ2ZmFjNTNhMThjZjBiZmIwMmRjOGJkNWI0NGM=
data/README.md CHANGED
@@ -1,11 +1,19 @@
1
- # SmoothOperator
1
+ # SmoothOperator [![Code Climate](https://codeclimate.com/repos/536a7b9f6956801228014b02/badges/13f79897976274a9de33/gpa.png)](https://codeclimate.com/repos/536a7b9f6956801228014b02/feed)
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
- Depends only on Faraday gem, no need for ActiveSupport or any other Active* gem.
6
+ Be sure to check out this micro-services example: https://github.com/goncalvesjoao/micro-services-example
7
7
 
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.
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.
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...
9
17
 
10
18
  ---
11
19
 
@@ -29,11 +37,14 @@ Or install it yourself as:
29
37
 
30
38
  ```ruby
31
39
  class MyBlogResource < SmoothOperator::Base
32
- self.endpoint = 'http://myblog.com/api/v0'
33
40
 
34
41
  # HTTP BASIC AUTH
35
- self.endpoint_user = 'admin'
36
- self.endpoint_pass = 'admin'
42
+ options endpoint_user: 'admin',
43
+ endpoint_pass: 'admin',
44
+ endpoint: 'http://myblog.com/api/v0'
45
+
46
+ # OR
47
+ # smooth_operator_options
37
48
  end
38
49
 
39
50
  class Post < MyBlogResource
@@ -109,10 +120,16 @@ post = Post.new(id: 2, body: 'editing my second page')
109
120
  post.new_record? # false
110
121
  post.persisted? # true
111
122
 
112
- post.save("#{post.id}/save_and_add_to_list", { admin: true, post: { author: 'Agent Smith', list_id: 1 } }, { timeout: 1 })
123
+ post.save("save_and_add_to_list", { admin: true, post: { author: 'Agent Smith', list_id: 1 } }, { timeout: 1 })
113
124
  # Will make a PUT to 'http://myblog.com/api/v0/posts/2/save_and_add_to_list'
114
125
  # with { admin: true, post: { body: 'editing my second page', list_id: 1 } }
115
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'
116
133
  ```
117
134
 
118
135
  ---
@@ -120,7 +137,9 @@ post.save("#{post.id}/save_and_add_to_list", { admin: true, post: { author: 'Age
120
137
  ### 2.4) Saving using HTTP Patch verb
121
138
  ```ruby
122
139
  class Page < MyBlogResource
123
- self.update_http_verb = :patch
140
+ options update_http_verb: 'patch'
141
+ # OR
142
+ #smooth_operator_options update_http_verb: 'patch'
124
143
  end
125
144
 
126
145
  page = Page.find(2)
@@ -139,9 +158,9 @@ page.save # will make a http PATCH call to 'http://myblog.com/api/v0/pages/2'
139
158
  remote_call = Page.find(:all) # Will make a GET call to 'http://myblog.com/api/v0/pages'
140
159
  # and will return a SmoothOperator::RemoteCall instance
141
160
 
142
- pages = remote_call.objects # 'pages = remote_call.data' also works
161
+ pages = remote_call.data
143
162
 
144
- # If the server response is positive (http code between 200 and 299):
163
+ # If the server response is positive (http code between 200 and 299, or 304):
145
164
  remote_call.ok? # true
146
165
  remote_call.not_processed? # false
147
166
  remote_call.error? # false
@@ -179,7 +198,9 @@ pages = remote_call.objects # 'pages = remote_call.data' also works
179
198
  remote_call = Page.find(2) # Will make a GET call to 'http://myblog.com/api/v0/pages/2'
180
199
  # and will return a SmoothOperator::RemoteCall instance
181
200
 
182
- page = remote_call.object # 'page = remote_call.data' also works
201
+ service_down = remote_call.error?
202
+
203
+ page = remote_call.data
183
204
  ```
184
205
 
185
206
  ---
@@ -190,24 +211,59 @@ remote_call = Page.find('my_pages', { q: body_contains: 'link' }, { endpoint_use
190
211
  # will make a GET call to 'http://myblog.com/api/v0/pages/my_pages?q={body_contains="link"}'
191
212
  # and will change the HTTP BASIC AUTH credentials to user: 'admin' and pass: 'new_password' for this connection only.
192
213
 
214
+ @service_down = remote_call.error?
215
+
193
216
  # If the server json response is an Array [{ id: 1 }, { id: 2 }]
194
- pages = remote.data # will return an array with 2 Page's instances
195
- pages[0].id # 1
196
- pages[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
197
220
 
198
221
  # If the server json response is a Hash { id: 3 }
199
- page = remote.data # will return a single Page instance
200
- page.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.
201
227
 
202
- # If the server json response is Hash with a key called 'pages' { page: 1, total: 3, pages: [{ id: 4 }, { id: 5 }] }
203
- 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.
204
- pages.page # 1
205
- pages.total # 3
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
206
232
 
207
- pages[0].id # 4
208
- pages[1].id # 5
233
+ @pages[0].id # 4
234
+ @pages[1].id # 5
209
235
  ```
210
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
+
211
267
  ---
212
268
 
213
269
  ## 3) Methods
data/console.rb CHANGED
@@ -31,4 +31,6 @@ post = Post.new(comments: [{ id: 1, name: '1' }, { id: 2, name: '2' }], address:
31
31
 
32
32
  comments_attributes = { "0" => { id: 1, name: '3' }, "1" => { name: '4' } }
33
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"] } } }
35
+
34
36
  binding.pry
@@ -1,22 +1,34 @@
1
1
  module SmoothOperator
2
- class ArrayWithMetaData < ::SimpleDelegator
2
+ class ArrayWithMetaData < SimpleDelegator
3
3
 
4
4
  attr_reader :meta_data, :internal_array
5
5
 
6
6
  def initialize(attributes, object_class)
7
- _attributes, _resources_name = attributes.dup, object_class.resources_name
7
+ resources_name = object_class.resources_name
8
8
 
9
- @internal_array = [*_attributes[_resources_name]].map { |array_entry| object_class.new(array_entry).tap { |object| object.reloaded = true } }
10
- _attributes.delete(_resources_name)
9
+ @internal_array = [*attributes[resources_name]].map do |array_entry|
10
+ object_class.new(array_entry)
11
+ end
11
12
 
12
- @meta_data = _attributes
13
+ attributes.delete(resources_name)
14
+
15
+ @meta_data = attributes
16
+
17
+ define_metada_methods
13
18
 
14
19
  super(@internal_array)
15
20
  end
16
21
 
17
- def method_missing(method, *args, &block)
18
- _method = method.to_s
19
- meta_data.include?(_method) ? meta_data[_method] : super
22
+ protected ############# PROTECTED ###################
23
+
24
+ def define_metada_methods
25
+ @meta_data.keys.each do |method|
26
+ instance_eval <<-RUBY, __FILE__, __LINE__ + 1
27
+ def #{method}
28
+ @meta_data['#{method}']
29
+ end
30
+ RUBY
31
+ end
20
32
  end
21
33
 
22
34
  end
@@ -1,15 +1,15 @@
1
- require "smooth_operator/relation/reflection"
1
+ require "smooth_operator/associations/reflection"
2
2
 
3
3
  module SmoothOperator
4
- module Relation
4
+ module Associations
5
5
  class AssociationReflection < Reflection
6
6
 
7
7
  attr_reader :related_reflection, :macro
8
8
 
9
9
  def initialize(association, related_reflection, options)
10
10
  super(association, options)
11
- @macro = options[:macro] || macro_default(association)
12
- @related_reflection = related_reflection
11
+
12
+ @related_reflection, @macro = related_reflection, options[:macro]
13
13
  end
14
14
 
15
15
  def primary_key
@@ -60,12 +60,12 @@ module SmoothOperator
60
60
  has_many?
61
61
  end
62
62
 
63
- private ################################# private
64
-
65
- def macro_default(association)
66
- Helpers.plural?(association) ? :has_many : :belongs_to
63
+ def rails_serialization?
64
+ options[:rails_serialization] == true
67
65
  end
68
66
 
67
+ private ################################# private
68
+
69
69
  def foreign_key_default
70
70
  if has_many? || has_one?
71
71
  "#{related_reflection.single_name}_id"
@@ -1,6 +1,6 @@
1
1
  module SmoothOperator
2
- module Relation
3
- class ArrayRelation < ::SimpleDelegator
2
+ module Associations
3
+ class HasManyRelation < SimpleDelegator
4
4
 
5
5
  attr_reader :object, :association
6
6
 
@@ -30,18 +30,8 @@ module SmoothOperator
30
30
 
31
31
  protected ############### PROTECTED ###############
32
32
 
33
- # def method_missing(method, *args, &block)
34
- # if get_array.respond_to?(method)
35
- # puts "if true #{method} - #{args}"
36
- # get_array.send(method, *args)
37
- # else
38
- # puts "if else #{method}"
39
- # super
40
- # end
41
- # end
42
-
43
33
  def get_array
44
- data = object.get_internal_data(association.to_s)
34
+ data = object.internal_data_get(association.to_s)
45
35
 
46
36
  data.nil? ? [] : [*data]
47
37
  end
@@ -1,5 +1,5 @@
1
1
  module SmoothOperator
2
- module Relation
2
+ module Associations
3
3
  class Reflection
4
4
 
5
5
  attr_reader :name, :klass, :options
@@ -14,7 +14,7 @@ module SmoothOperator
14
14
  if options.include?(:class_name) && options[:class_name].nil?
15
15
  @klass = nil
16
16
  elsif @klass.is_a?(String)
17
- @klass = @klass.constantize
17
+ @klass = @klass.constantize rescue OpenStruct
18
18
  end
19
19
  end
20
20
 
@@ -0,0 +1,110 @@
1
+ require "smooth_operator/associations/has_many_relation"
2
+ require "smooth_operator/associations/association_reflection"
3
+
4
+ module SmoothOperator
5
+ module Associations
6
+
7
+ def has_many(association, options = {})
8
+ accepts_nested_objects(association, :has_many, options)
9
+ end
10
+
11
+ def has_one(association, options = {})
12
+ accepts_nested_objects(association, :has_one, options)
13
+ end
14
+
15
+ def belongs_to(association, options = {})
16
+ accepts_nested_objects(association, :belongs_to, options)
17
+ end
18
+
19
+ def reflections
20
+ Helpers.get_instance_variable(self, :reflections, {})
21
+ end
22
+
23
+ def reflect_on_association(association)
24
+ reflections[association]
25
+ end
26
+
27
+ def reflect_on_all_associations(macro = nil)
28
+ macro ? reflections.values.select { |reflection| reflection.macro == macro } : reflections.values
29
+ end
30
+
31
+ def rails_serialization
32
+ get_option :rails_serialization, false
33
+ end
34
+
35
+ protected ###################### PROTECTED ###################
36
+
37
+ # TODO: THIS MUST GO TO A HELPER_METHODS MODULE
38
+
39
+ def accepts_nested_objects(association, macro, options = {})
40
+ options = parse_options(options, { macro: macro })
41
+
42
+ reflection = AssociationReflection.new(association, Reflection.new(name, {}), options)
43
+
44
+ schema(association => reflection.klass)
45
+
46
+ reflections.merge!(association => reflection)
47
+
48
+ if reflection.has_many?
49
+ define_has_many_association_method(reflection, association)
50
+ else
51
+ define_single_association_method(reflection, association)
52
+ end
53
+
54
+ self.send(:attr_reader, "#{association}_attributes".to_sym)
55
+
56
+ define_attributes_setter_methods(reflection, association)
57
+ end
58
+
59
+ private ####################### PRIVATE ######################
60
+
61
+ def define_has_many_association_method(reflection, association)
62
+ define_method(association) do
63
+ has_many_relation = instance_variable_get("@#{association}")
64
+
65
+ if has_many_relation.nil?
66
+ has_many_relation = HasManyRelation.new(self, association)
67
+
68
+ instance_variable_set("@#{association}", has_many_relation)
69
+ end
70
+
71
+ has_many_relation.send(:refresh)
72
+
73
+ has_many_relation
74
+ end
75
+ end
76
+
77
+ def define_single_association_method(reflection, association)
78
+ define_method(association) { internal_data_get(association.to_s) }
79
+
80
+ define_method("build_#{association}") do |attributes = {}|
81
+ new_instance = reflection.klass.new(attributes)
82
+
83
+ internal_data_push(association, new_instance)
84
+
85
+ new_instance
86
+ end
87
+ end
88
+
89
+ def define_attributes_setter_methods(reflection, association)
90
+ define_method("#{association}_attributes=") do |attributes|
91
+ instance_variable_set("@#{association}_attributes", attributes)
92
+
93
+ attributes = attributes.values if reflection.has_many?
94
+
95
+ internal_data_push(association.to_s, attributes)
96
+ end
97
+ end
98
+
99
+ def parse_options(options, default_options)
100
+ options = options.is_a?(Hash) ? options.merge(default_options) : default_options
101
+
102
+ if options[:rails_serialization].nil?
103
+ options[:rails_serialization] = rails_serialization
104
+ end
105
+
106
+ Helpers.symbolyze_keys(options)
107
+ end
108
+
109
+ end
110
+ end
@@ -1,87 +1,51 @@
1
- require 'smooth_operator/attributes/base'
2
- require 'smooth_operator/attributes/dirty'
3
- require 'smooth_operator/attributes/normal'
4
-
5
1
  module SmoothOperator
6
2
  module AttributeAssignment
7
3
 
8
- def self.included(base)
9
- base.extend(ClassMethods)
10
- end
11
-
12
- module ClassMethods
13
-
14
- attr_writer :unknown_hash_class
15
-
16
- def unknown_hash_class
17
- Helpers.get_instance_variable(self, :unknown_hash_class, ::OpenStruct)
18
- end
19
-
20
- def attributes_white_list
21
- Helpers.get_instance_variable(self, :attributes_white_list, Set.new)
22
- end
23
-
24
- def attributes_black_list
25
- Helpers.get_instance_variable(self, :attributes_black_list, Set.new)
26
- end
27
-
28
- def attributes_white_list_add(*getters)
29
- attributes_white_list.merge getters.map(&:to_s)
30
- end
31
-
32
- def attributes_black_list_add(*getters)
33
- attributes_black_list.merge getters.map(&:to_s)
34
- end
35
-
36
- def dirty_attributes
37
- @dirty_attributes = true
38
- end
39
-
40
- def dirty_attributes?
41
- @dirty_attributes
42
- end
43
-
44
- end
45
-
46
4
  def initialize(attributes = {}, options = {})
47
5
  @_options = {}
48
6
 
49
7
  before_initialize(attributes, options)
50
8
 
51
- assign_attributes attributes, options
9
+ assign_attributes(attributes, options)
52
10
 
53
11
  after_initialize(attributes, options)
54
12
  end
55
13
 
56
14
  attr_reader :_options, :_meta_data
57
15
 
16
+ def assign_attributes(attributes = {}, options = {})
17
+ attributes = _extract_attributes(attributes)
58
18
 
59
- def assign_attributes(_attributes = {}, options = {})
60
- return nil unless _attributes.is_a?(Hash)
19
+ return nil unless attributes.is_a?(Hash)
61
20
 
62
- attributes = _attributes = Helpers.stringify_keys(_attributes)
21
+ induce_errors(attributes.delete(self.class.errors_key))
63
22
 
64
- if _attributes.include?(self.class.resource_name)
65
- attributes = _attributes.delete(self.class.resource_name)
66
- @_meta_data = _attributes
67
- end
23
+ @_options.merge!(options) if options.is_a? Hash
68
24
 
69
- options.each { |key, value| @_options[key] = value } if options.is_a?(Hash)
25
+ attributes.each do |name, value|
26
+ next unless allowed_attribute(name)
70
27
 
71
- attributes.each { |name, value| push_to_internal_data(name, value) }
28
+ send("#{name}=", value)
29
+ end
72
30
  end
73
31
 
74
- def parent_object
75
- _options[:parent_object]
76
- end
32
+ protected ################# PROTECTED METHODS DOWN BELOW ###################
77
33
 
78
- def has_data_from_server
79
- _options[:from_server] == true
80
- end
34
+ def _extract_attributes(attributes)
35
+ return nil unless attributes.is_a?(Hash)
81
36
 
82
- alias :from_server :has_data_from_server
37
+ _attributes = Helpers.stringify_keys(attributes)
38
+
39
+ if _attributes.include?(self.class.resource_name)
40
+ attributes = _attributes.delete(self.class.resource_name)
41
+ @_meta_data = _attributes
42
+ else
43
+ attributes = _attributes
44
+ @_meta_data = {}
45
+ end
83
46
 
84
- protected #################### PROTECTED METHODS DOWN BELOW ######################
47
+ attributes
48
+ end
85
49
 
86
50
  def before_initialize(attributes, options); end
87
51
 
@@ -97,5 +61,33 @@ module SmoothOperator
97
61
  end
98
62
  end
99
63
 
64
+ def self.included(base)
65
+ base.extend(ClassMethods)
66
+ end
67
+
68
+ module ClassMethods
69
+
70
+ def unknown_hash_class
71
+ get_option :unknown_hash_class, ::OpenStruct
72
+ end
73
+
74
+ def attributes_white_list
75
+ Helpers.get_instance_variable(self, :attributes_white_list, Set.new)
76
+ end
77
+
78
+ def attributes_black_list
79
+ Helpers.get_instance_variable(self, :attributes_black_list, Set.new)
80
+ end
81
+
82
+ def attributes_white_list_add(*getters)
83
+ attributes_white_list.merge getters.map(&:to_s)
84
+ end
85
+
86
+ def attributes_black_list_add(*getters)
87
+ attributes_black_list.merge getters.map(&:to_s)
88
+ end
89
+
90
+ end
91
+
100
92
  end
101
93
  end
@@ -0,0 +1,21 @@
1
+ module SmoothOperator
2
+ class CookieJar < Hash
3
+
4
+ def to_s
5
+ self.map { |key, value| "#{key}=#{value}"}.join("; ")
6
+ end
7
+
8
+ def parse(*cookie_strings)
9
+ cookie_strings.each do |cookie_string|
10
+ next unless cookie_string.is_a?(String)
11
+
12
+ key, value = cookie_string.split('; ').first.split('=', 2)
13
+
14
+ self[key] = value
15
+ end
16
+
17
+ self
18
+ end
19
+
20
+ end
21
+ end
@@ -1,5 +1,4 @@
1
1
  module SmoothOperator
2
-
3
2
  module Delegation
4
3
 
5
4
  def respond_to?(method, include_private = false)
@@ -7,48 +6,28 @@ module SmoothOperator
7
6
  end
8
7
 
9
8
  def method_missing(method, *args, &block)
10
- method_type, method_name = *parse_method(method)
11
-
12
- result = case method_type
13
- when :was
14
- get_internal_data(method_name, :was)
15
- when :changed
16
- get_internal_data(method_name, :changed?)
17
- when :setter
18
- return push_to_internal_data(method_name, args.first)
9
+ method_name = method.to_s
10
+
11
+ if !! ((method.to_s) =~ /=$/) #setter method
12
+ internal_data_push(method_name[0..-2], args.first)
13
+ elsif !self.class.strict_behaviour || known_attribute?(method_name)
14
+ internal_data_get(method_name)
19
15
  else
20
- if !self.class.strict_behaviour || known_attribute?(method_name)
21
- return get_internal_data(method_name)
22
- end
16
+ super
23
17
  end
24
-
25
- result.nil? ? super : result
26
18
  end
27
19
 
28
-
29
- protected #################### PROTECTED ################
30
-
31
- def parse_method(method)
32
- method = method.to_s
33
-
34
- if method?(method, /=$/)
35
- [:setter, method[0..-2]]
36
- elsif method?(method, /_was$/)
37
- [:was, method[0..-5]]
38
- elsif method?(method, /_changed\?$/)
39
- [:changed, method[0..-10]]
40
- else
41
- [nil, method]
42
- end
20
+ def self.included(base)
21
+ base.extend(ClassMethods)
43
22
  end
44
23
 
24
+ module ClassMethods
45
25
 
46
- private #################### PRIVATE ################
26
+ def strict_behaviour
27
+ get_option :strict_behaviour, false
28
+ end
47
29
 
48
- def method?(method, regex)
49
- !! ((method.to_s) =~ regex)
50
30
  end
51
31
 
52
32
  end
53
-
54
33
  end