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
@@ -1,43 +1,52 @@
1
1
  require 'smooth_operator/array_with_meta_data'
2
2
 
3
3
  module SmoothOperator
4
-
5
4
  module FinderMethods
6
5
 
7
6
  def find(relative_path, data = {}, options = {})
8
7
  relative_path = '' if relative_path == :all
9
8
 
10
9
  get(relative_path, data, options) do |remote_call|
11
- remote_call.object = build_object(remote_call.parsed_response, options) if remote_call.ok?
10
+ if remote_call.ok?
11
+ remote_call.object = HelperMethods
12
+ .build_object(self, remote_call.parsed_response, options)
13
+ end
12
14
 
13
15
  block_given? ? yield(remote_call) : remote_call
14
16
  end
15
17
  end
16
18
 
19
+ module HelperMethods
17
20
 
18
- protected #################### PROTECTED ##################
21
+ extend self
19
22
 
20
- def build_object(parsed_response, options, from_array = false)
21
- if parsed_response.is_a?(Array)
22
- parsed_response.map { |array_entry| build_object(array_entry, options, true) }
23
- elsif parsed_response.is_a?(Hash)
24
- if parsed_response.include?(object_class.resources_name) && !from_array
25
- ArrayWithMetaData.new(parsed_response, object_class)
23
+ def build_object(object, parsed_response, options, from_array = false)
24
+ if parsed_response.is_a?(Array)
25
+ parse_array(parsed_response, object, options)
26
+ elsif parsed_response.is_a?(Hash)
27
+ parse_hash(object, parsed_response, options, from_array)
26
28
  else
27
- object_class.new(parsed_response, from_server: true)
29
+ parsed_response
30
+ end
31
+ end
32
+
33
+ def parse_array(parsed_response, object, options)
34
+ parsed_response.map do |array_entry|
35
+ build_object(object, array_entry, options, true)
28
36
  end
29
- else
30
- parsed_response
31
37
  end
32
- end
33
38
 
39
+ def parse_hash(object, parsed_response, options, from_array)
40
+ object_class ||= object.class == Class ? object : object.class
34
41
 
35
- private #################### PRIVATE ##################
42
+ if parsed_response.include?(object_class.resources_name) && !from_array
43
+ ArrayWithMetaData.new(parsed_response.dup, object_class)
44
+ else
45
+ object_class.new(parsed_response, data_from_server: true)
46
+ end
47
+ end
36
48
 
37
- def object_class
38
- @object_class ||= self.class == Class ? self : self.class
39
49
  end
40
50
 
41
51
  end
42
-
43
52
  end
@@ -4,16 +4,22 @@ module SmoothOperator
4
4
 
5
5
  extend self
6
6
 
7
- def safe_call(object, method, *args)
8
- if object.respond_to?(method)
9
- object.send(method, *args)
10
- else
11
- false
12
- end
7
+ def generated_id
8
+ Time.now.to_f.to_s.split('.')[1]
9
+ end
10
+
11
+ def primary_key(object)
12
+ object.internal_data_get(object.class.primary_key)
13
+ end
14
+
15
+ def has_primary_key?(object)
16
+ blank? primary_key(object)
13
17
  end
14
18
 
15
19
  def super_method(object, method_name, *args)
16
- object.superclass.send(method_name, *args) if object.superclass.respond_to?(method_name)
20
+ if object.superclass.respond_to?(method_name)
21
+ object.superclass.send(method_name, *args)
22
+ end
17
23
  end
18
24
 
19
25
  def get_instance_variable(object, variable, default_value)
@@ -56,7 +62,7 @@ module SmoothOperator
56
62
  case object
57
63
  when String
58
64
  object.to_s == ''
59
- when Array
65
+ when Array, Hash
60
66
  object.empty?
61
67
  else
62
68
  object.nil?
@@ -0,0 +1,17 @@
1
+ module SmoothOperator
2
+ module HttpMethods
3
+
4
+ HTTP_VERBS = %w[get post put patch delete]
5
+
6
+ HTTP_VERBS.each do |method|
7
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
8
+ def #{method}(relative_path = '', params = {}, options = {})
9
+ make_the_call(:#{method}, relative_path, params, options) do |remote_call|
10
+ block_given? ? yield(remote_call) : remote_call
11
+ end
12
+ end
13
+ RUBY
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,45 @@
1
+ require 'smooth_operator/type_casting'
2
+
3
+ module SmoothOperator
4
+ module InternalData
5
+
6
+ def internal_data
7
+ @internal_data ||= {}
8
+ end
9
+
10
+ def known_attributes
11
+ return @known_attributes if defined?(@known_attributes)
12
+
13
+ schema_attributes = if self.class.respond_to?(:internal_structure)
14
+ self.class.internal_structure.keys
15
+ else
16
+ []
17
+ end
18
+
19
+ @known_attributes = Set.new(schema_attributes)
20
+ end
21
+
22
+ def known_attribute?(attribute)
23
+ known_attributes.include?(attribute.to_s)
24
+ end
25
+
26
+ def internal_data_get(attribute_name)
27
+ internal_data[attribute_name]
28
+ end
29
+
30
+ def internal_data_push(attribute_name, attribute_value)
31
+ attribute_name = attribute_name.to_s
32
+
33
+ known_attributes.add attribute_name
34
+
35
+ internal_data[attribute_name] = TypeCasting.cast_to_type(attribute_name, attribute_value, self)
36
+
37
+ if self.class.respond_to?(:smooth_operator?)
38
+ marked_for_destruction?(attribute_value) if attribute_name == self.class.destroy_key
39
+
40
+ new_record?(true) if attribute_name == self.class.primary_key
41
+ end
42
+ end
43
+
44
+ end
45
+ end
@@ -1,37 +1,22 @@
1
+ require "smooth_operator/options"
1
2
  require "smooth_operator/delegation"
2
3
  require "smooth_operator/validations"
3
- require "smooth_operator/model_schema"
4
+ require "smooth_operator/resource_name"
4
5
  require "smooth_operator/serialization"
5
- require "smooth_operator/attribute_methods"
6
+ require "smooth_operator/internal_data"
6
7
  require "smooth_operator/attribute_assignment"
7
8
 
8
9
  module SmoothOperator
9
- module OpenStruct
10
+ class OpenStruct
10
11
 
11
- class Base
12
+ extend Options
13
+ extend ResourceName
12
14
 
13
- include Delegation
14
- include Validations
15
- include ModelSchema
16
- include Serialization
17
- include AttributeMethods
18
- include AttributeAssignment
19
-
20
- def self.strict_behaviour=(value)
21
- @strict_behaviour = value
22
- end
23
-
24
- def self.strict_behaviour
25
- Helpers.get_instance_variable(self, :strict_behaviour, false)
26
- end
27
-
28
- end
29
-
30
- class Dirty < Base
31
-
32
- dirty_attributes
33
-
34
- end
15
+ include Delegation
16
+ include Validations
17
+ include InternalData
18
+ include Serialization
19
+ include AttributeAssignment
35
20
 
36
21
  end
37
22
  end
@@ -2,6 +2,7 @@ require "smooth_operator/remote_call/base"
2
2
  require "smooth_operator/operators/faraday"
3
3
  require "smooth_operator/operators/typhoeus"
4
4
  require "smooth_operator/remote_call/errors/timeout"
5
+ require "smooth_operator/operators/connection_wrapper"
5
6
  require "smooth_operator/remote_call/errors/connection_failed"
6
7
 
7
8
  module SmoothOperator
@@ -12,72 +13,67 @@ module SmoothOperator
12
13
 
13
14
  relative_path = resource_path(relative_path)
14
15
 
16
+ parent_object = _options[:parent_object]
17
+
15
18
  if !parent_object.nil? && options[:ignore_parent] != true
16
- options[:resources_name] ||= "#{parent_object.class.resources_name}/#{parent_object.get_primary_key}/#{self.class.resources_name}"
19
+ id = Helpers.primary_key(parent_object)
20
+
21
+ options[:resources_name] ||= "#{parent_object.class.resources_name}/#{id}/#{self.class.resources_name}"
17
22
  end
18
23
 
19
- self.class.make_the_call(http_verb, relative_path, data, options) do |remote_call|
24
+ call_args = before_request(http_verb, relative_path, data, options)
25
+
26
+ self.class.make_the_call(*call_args) do |remote_call|
20
27
  yield(remote_call)
21
28
  end
22
29
  end
23
30
 
24
- protected ######################## PROTECTED ###################
25
-
26
31
  def resource_path(relative_path)
27
32
  if Helpers.absolute_path?(relative_path)
28
33
  Helpers.remove_initial_slash(relative_path)
29
34
  elsif persisted?
30
- Helpers.present?(relative_path) ? "#{get_primary_key}/#{relative_path}" : get_primary_key.to_s
35
+ id = Helpers.primary_key(self)
36
+
37
+ Helpers.present?(relative_path) ? "#{id}/#{relative_path}" : id.to_s
31
38
  else
32
39
  relative_path
33
40
  end
34
41
  end
35
42
 
36
- ########################### MODULES BELLOW ###############################
37
-
38
- module HttpMethods
39
-
40
- HTTP_VERBS = %w[get post put patch delete]
43
+ def self.included(base)
44
+ base.extend(ClassMethods)
45
+ end
41
46
 
42
- HTTP_VERBS.each do |method|
43
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
44
- def #{method}(relative_path = '', params = {}, options = {})
45
- make_the_call(:#{method}, relative_path, params, options) do |remote_call|
46
- block_given? ? yield(remote_call) : remote_call
47
- end
48
- end
49
- RUBY
50
- end
47
+ protected ################# PROTECTED ####################
51
48
 
49
+ def before_request(http_verb, relative_path, data, options)
50
+ [http_verb, relative_path, data, options]
52
51
  end
53
52
 
54
53
  module ClassMethods
55
54
 
56
- OPTIONS = [:endpoint, :endpoint_user, :endpoint_pass, :timeout]
55
+ OPTIONS = [:endpoint, :endpoint_user, :endpoint_pass, :timeout, :server_name]
57
56
 
58
57
  OPTIONS.each do |option|
59
- define_method(option) { Helpers.get_instance_variable(self, option, '') }
58
+ define_method(option) { get_option option, '' }
60
59
  end
61
60
 
62
- attr_writer *OPTIONS
63
-
64
61
  def headers
65
- Helpers.get_instance_variable(self, :headers, {})
62
+ get_option :headers, {}
66
63
  end
67
64
 
68
- attr_writer :headers
65
+ def make_the_call(http_verb, relative_path = '', data = {}, options = {})
66
+ options = HelperMethods.populate_options(self, options)
69
67
 
68
+ resource_path = resource_path(relative_path, options)
70
69
 
71
- def make_the_call(http_verb, relative_path = '', data = {}, options = {})
72
- operator_args = operator_method_args(http_verb, relative_path, data, options)
70
+ http_verb, resource_path, data, options = before_request(http_verb, resource_path, data, options)
73
71
 
74
- if Helpers.present?(operator_args[4][:hydra])
75
- operator_call = Operators::Typhoeus
76
- else
77
- operator_call = Operators::Faraday
78
- end
72
+ params, data = *HelperMethods.strip_params(self, http_verb, data)
79
73
 
80
- operator_call.make_the_call(*operator_args) do |remote_call|
74
+ operator = HelperMethods.get_me_an_operator(options)
75
+
76
+ operator.make_the_call(http_verb, resource_path, params, data, options) do |remote_call|
81
77
  block_given? ? yield(remote_call) : remote_call
82
78
  end
83
79
  end
@@ -86,54 +82,64 @@ module SmoothOperator
86
82
  params
87
83
  end
88
84
 
89
- protected #################### PROTECTED ##################
85
+ def resource_path(relative_path, options)
86
+ resources_name = options[:resources_name] || self.resources_name
90
87
 
91
- def operator_method_args(http_verb, relative_path, data, options)
92
- options = populate_options(options)
88
+ if Helpers.present?(resources_name)
89
+ Helpers.present?(relative_path) ? "#{resources_name}/#{relative_path}" : resources_name
90
+ else
91
+ relative_path.to_s
92
+ end
93
+ end
93
94
 
94
- [http_verb, resource_path(relative_path, options), *strip_params(http_verb, data), options]
95
+ def generate_parallel_connection
96
+ Operators::Typhoeus.generate_parallel_connection
95
97
  end
96
98
 
97
- private #################### PRIVATE ##################
99
+ protected ################# PROTECTED ####################
98
100
 
99
- def populate_options(options)
100
- options ||= {}
101
+ def before_request(http_verb, relative_path, data, options)
102
+ [http_verb, relative_path, data, options]
103
+ end
101
104
 
102
- OPTIONS.each { |option| options[option] ||= send(option) }
105
+ end
103
106
 
104
- options[:headers] = headers.merge(options[:headers] || {})
107
+ module HelperMethods
105
108
 
106
- options
109
+ extend self
110
+
111
+ def get_me_an_operator(options)
112
+ if options[:parallel_connection].nil?
113
+ Operators::Faraday
114
+ else
115
+ options[:connection] = options.delete(:parallel_connection)
116
+ Operators::Typhoeus
117
+ end
107
118
  end
108
119
 
109
- def resource_path(relative_path, options)
110
- _resources_name = options[:resources_name] || self.resources_name
120
+ def populate_options(object, options)
121
+ options ||= {}
111
122
 
112
- if Helpers.present?(_resources_name)
113
- Helpers.present?(relative_path) ? "#{_resources_name}/#{relative_path}" : _resources_name
114
- else
115
- relative_path.to_s
123
+ ClassMethods::OPTIONS.each do |option|
124
+ options[option] ||= object.send(option)
116
125
  end
126
+
127
+ options[:headers] = object.headers.merge(options[:headers] || {})
128
+
129
+ options
117
130
  end
118
131
 
119
- def strip_params(http_verb, data)
132
+ def strip_params(object, http_verb, data)
120
133
  data ||= {}
121
134
 
122
135
  if [:get, :head, :delete].include?(http_verb)
123
- [query_string(data), nil]
136
+ [object.query_string(data), nil]
124
137
  else
125
- [query_string({}), data]
138
+ [object.query_string({}), data]
126
139
  end
127
140
  end
128
- end
129
-
130
- include HttpMethods
131
141
 
132
- def self.included(base)
133
- base.extend(ClassMethods)
134
- base.extend(HttpMethods)
135
142
  end
136
143
 
137
144
  end
138
-
139
145
  end
@@ -0,0 +1,15 @@
1
+ module SmoothOperator
2
+ class ConnectionWrapper
3
+
4
+ attr_reader :connection
5
+
6
+ def initialize(connection)
7
+ @connection = connection
8
+ end
9
+
10
+ def run
11
+ connection.run
12
+ end
13
+
14
+ end
15
+ end
@@ -17,11 +17,11 @@ module SmoothOperator
17
17
  def generate_connection(adapter = nil, options = nil)
18
18
  adapter ||= :net_http
19
19
 
20
- ::Faraday.new(url: options[:endpoint]) do |builder|
20
+ ConnectionWrapper.new(::Faraday.new(url: options[:endpoint]) do |builder|
21
21
  builder.options[:timeout] = options[:timeout].to_i unless Helpers.blank?(options[:timeout])
22
22
  builder.request :url_encoded
23
23
  builder.adapter adapter
24
- end
24
+ end)
25
25
  end
26
26
 
27
27
  def make_the_call(http_verb, resource_path, params, body, options)
@@ -49,8 +49,8 @@ module SmoothOperator
49
49
 
50
50
  def strip_options(options)
51
51
  request_options = options.delete(:request_options) || {}
52
-
53
- connection = options.delete(:connection) || generate_connection(nil, options)
52
+
53
+ connection = (options.delete(:connection) || generate_connection(nil, options)).connection
54
54
 
55
55
  [connection, request_options, options]
56
56
  end
@@ -61,9 +61,9 @@ module SmoothOperator
61
61
 
62
62
  def request_configuration(request, request_options, options, params, body)
63
63
  request_options.each { |key, value| request.options.send("#{key}=", value) }
64
-
64
+
65
65
  options[:headers].each { |key, value| request.headers[key] = value }
66
-
66
+
67
67
  params.each { |key, value| request.params[key] = value }
68
68
 
69
69
  request.body = body
@@ -9,27 +9,38 @@ module SmoothOperator
9
9
 
10
10
  extend self
11
11
 
12
+ def generate_parallel_connection
13
+ generate_connection
14
+ end
15
+
16
+ def generate_connection(adapter = nil, options = nil)
17
+ ConnectionWrapper.new(::Typhoeus::Hydra::hydra)
18
+ end
19
+
12
20
  def make_the_call(http_verb, resource_path, params, body, options)
13
21
  request = ::Typhoeus::Request.new *typhoeus_request_args(http_verb, resource_path, params, body, options)
14
-
15
- hydra = options[:hydra] || ::Typhoeus::Hydra::hydra
16
22
 
17
- _remote_call = nil
23
+ hydra = (options[:connection] || generate_connection).connection
24
+
25
+ request_result = nil
18
26
 
19
27
  hydra.queue(request)
20
28
 
21
- request.on_complete do |typhoeus_response|
22
- _remote_call = remote_call(typhoeus_response)
29
+ request.on_complete do |typhoeus_response|
30
+ remote_call = remote_call(typhoeus_response)
23
31
 
24
- yield(_remote_call) if block_given?
32
+ if block_given?
33
+ request_result = yield(remote_call)
34
+ else
35
+ request_result = remote_call
36
+ end
25
37
  end
26
38
 
27
- hydra.run if Helpers.blank?(options[:hydra])
39
+ hydra.run if Helpers.blank?(options[:connection])
28
40
 
29
- _remote_call
41
+ request_result
30
42
  end
31
43
 
32
-
33
44
  protected ################ PROTECTED ################
34
45
 
35
46
  def typhoeus_request_args(http_verb, relative_path, params, body, options)
@@ -46,7 +57,6 @@ module SmoothOperator
46
57
  end.new(typhoeus_response)
47
58
  end
48
59
 
49
-
50
60
  private ################### PRIVATE ###############
51
61
 
52
62
  def build_typhoeus_options(http_verb, params, body, options)
@@ -55,7 +65,7 @@ module SmoothOperator
55
65
  typhoeus_options[:timeout] = options[:timeout] if Helpers.present?(options[:timeout])
56
66
 
57
67
  typhoeus_options[:body] = body if Helpers.present?(body)
58
-
68
+
59
69
  typhoeus_options[:params] = params if Helpers.present?(params)
60
70
 
61
71
  typhoeus_options[:userpwd] = "#{options[:endpoint_user]}:#{options[:endpoint_pass]}" if Helpers.present?(options[:endpoint_user])
@@ -65,7 +75,7 @@ module SmoothOperator
65
75
 
66
76
  def url(options, relative_path)
67
77
  url = options[:endpoint]
68
-
78
+
69
79
  slice = url[-1] != '/' ? '/' : ''
70
80
 
71
81
  url = "#{url}#{slice}#{relative_path}" if Helpers.present?(relative_path)
@@ -0,0 +1,30 @@
1
+ module SmoothOperator
2
+ module Options
3
+
4
+ def get_option(option, default, *args)
5
+ return default unless config_options.include?(option)
6
+
7
+ _option = config_options[option]
8
+
9
+ case _option
10
+ when Symbol
11
+ respond_to?(_option) ? send(_option, *args) : _option
12
+ when Proc
13
+ _option.call(*args)
14
+ else
15
+ _option
16
+ end
17
+ end
18
+
19
+ def config_options
20
+ Helpers.get_instance_variable(self, :config_options, {})
21
+ end
22
+
23
+ def smooth_operator_options(options = {})
24
+ config_options.merge!(options)
25
+ end
26
+
27
+ alias_method :options, :smooth_operator_options
28
+
29
+ end
30
+ end