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