smooth_operator 1.2.9 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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