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
@@ -0,0 +1,72 @@
1
+ module SmoothOperator
2
+
3
+ module ModelSchema
4
+
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ def known_attributes
10
+ @known_attributes ||= self.class.known_attributes.dup
11
+ end
12
+
13
+ def internal_structure
14
+ @internal_structure ||= self.class.internal_structure.dup
15
+ end
16
+
17
+ def table_name
18
+ @table_name ||= self.class.table_name.dup
19
+ end
20
+
21
+ def model_name
22
+ @model_name ||= table_name.singularize
23
+ end
24
+
25
+
26
+ module ClassMethods
27
+
28
+ attr_writer :table_name
29
+
30
+ def table_name
31
+ @table_name ||= self.model_name.to_s.downcase.pluralize
32
+ end
33
+
34
+ def schema(structure)
35
+ internal_structure.merge! Helpers.stringify_keys(structure)
36
+
37
+ known_attributes.merge internal_structure.keys
38
+ end
39
+
40
+ def internal_structure
41
+ Helpers.get_instance_variable(self, :internal_structure, {})
42
+ end
43
+
44
+ def known_attributes
45
+ Helpers.get_instance_variable(self, :known_attributes, Set.new)
46
+ end
47
+
48
+ def model_name
49
+ if defined? ActiveModel
50
+ rails_model_name_method
51
+ else
52
+ name.split('::').last.underscore.capitalize
53
+ end
54
+ end
55
+
56
+
57
+ protected ############## PROTECTED #############
58
+
59
+ def rails_model_name_method
60
+ @_model_name ||= begin
61
+ namespace = self.parents.detect do |n|
62
+ n.respond_to?(:use_relative_model_naming?) && n.use_relative_model_naming?
63
+ end
64
+ ActiveModel::Name.new(self, namespace)
65
+ end
66
+ end
67
+
68
+ end
69
+
70
+ end
71
+
72
+ end
@@ -1,22 +1,19 @@
1
- require "smooth_operator/options"
2
1
  require "smooth_operator/delegation"
3
2
  require "smooth_operator/validations"
4
- require "smooth_operator/resource_name"
3
+ require "smooth_operator/model_schema"
5
4
  require "smooth_operator/serialization"
6
- require "smooth_operator/internal_data"
7
5
  require "smooth_operator/attribute_assignment"
8
6
 
9
7
  module SmoothOperator
10
- class OpenStruct
11
8
 
12
- extend Options
13
- extend ResourceName
9
+ class OpenStruct
14
10
 
15
11
  include Delegation
16
12
  include Validations
17
- include InternalData
13
+ include ModelSchema
18
14
  include Serialization
19
15
  include AttributeAssignment
20
16
 
21
17
  end
18
+
22
19
  end
@@ -1,145 +1,107 @@
1
- require "smooth_operator/remote_call/base"
2
- require "smooth_operator/operators/faraday"
3
- require "smooth_operator/operators/typhoeus"
4
- require "smooth_operator/remote_call/errors/timeout"
5
- require "smooth_operator/operators/connection_wrapper"
6
- require "smooth_operator/remote_call/errors/connection_failed"
1
+ require 'faraday'
2
+ # require 'typhoeus'
3
+ # require 'faraday_middleware'
4
+ # require 'typhoeus/adapters/faraday'
7
5
 
8
6
  module SmoothOperator
7
+
9
8
  module Operator
10
9
 
11
- def make_the_call(http_verb, relative_path = '', data = {}, options = {})
12
- options ||= {}
10
+ HTTP_VERBS = [:get, :post, :put, :patch, :delete]
13
11
 
14
- relative_path = resource_path(relative_path)
12
+ OPTIONS = [:endpoint, :endpoint_user, :endpoint_pass, :timeout]
15
13
 
16
- parent_object = _options[:parent_object]
17
14
 
18
- if !parent_object.nil? && options[:ignore_parent] != true
19
- id = Helpers.primary_key(parent_object)
15
+ attr_writer *OPTIONS
20
16
 
21
- options[:resources_name] ||= "#{parent_object.class.resources_name}/#{id}/#{self.class.resources_name}"
22
- end
17
+ OPTIONS.each { |option| define_method(option) { Helpers.get_instance_variable(self, option, '') } }
23
18
 
24
- call_args = before_request(http_verb, relative_path, data, options)
19
+ HTTP_VERBS.each { |http_verb| define_method(http_verb) { |relative_path = '', params = {}, options = {}| make_the_call(http_verb, relative_path, params, options) } }
25
20
 
26
- self.class.make_the_call(*call_args) do |remote_call|
27
- yield(remote_call)
28
- end
29
- end
30
21
 
31
- def resource_path(relative_path)
32
- if Helpers.absolute_path?(relative_path)
33
- Helpers.remove_initial_slash(relative_path)
34
- elsif persisted?
35
- id = Helpers.primary_key(self)
36
-
37
- Helpers.present?(relative_path) ? "#{id}/#{relative_path}" : id.to_s
38
- else
39
- relative_path
40
- end
22
+ def generate_parallel_connection
23
+ generate_connection(:typhoeus)
41
24
  end
42
25
 
43
- def self.included(base)
44
- base.extend(ClassMethods)
45
- end
46
-
47
- protected ################# PROTECTED ####################
48
-
49
- def before_request(http_verb, relative_path, data, options)
50
- [http_verb, relative_path, data, options]
51
- end
52
-
53
- module ClassMethods
54
-
55
- OPTIONS = [:endpoint, :endpoint_user, :endpoint_pass, :timeout, :server_name]
26
+ def generate_connection(adapter = nil, options = nil)
27
+ adapter ||= :net_http
28
+ options ||= {}
29
+ url, timeout = (options[:endpoint] || self.endpoint), (options[:timeout] || self.timeout)
56
30
 
57
- OPTIONS.each do |option|
58
- define_method(option) { get_option option, '' }
31
+ Faraday.new(url: url) do |builder|
32
+ builder.options.params_encoder = Faraday::NestedParamsEncoder # to properly encode arrays
33
+ builder.options.timeout = timeout unless Helpers.blank?(timeout)
34
+ builder.request :url_encoded
35
+ builder.adapter adapter
59
36
  end
37
+ end
60
38
 
61
- def headers
62
- get_option :headers, {}
63
- end
64
39
 
65
- def make_the_call(http_verb, relative_path = '', data = {}, options = {})
66
- options = HelperMethods.populate_options(self, options)
40
+ protected ################ PROTECTED ################
67
41
 
68
- resource_path = resource_path(relative_path, options)
42
+ def make_the_call(http_verb, relative_path = '', data = {}, options = {})
43
+ params, body = strip_params(http_verb, data)
69
44
 
70
- http_verb, resource_path, data, options = before_request(http_verb, resource_path, data, options)
45
+ connection, operator_options, options = strip_options(options)
71
46
 
72
- params, data = *HelperMethods.strip_params(self, http_verb, data)
47
+ relative_path = build_relative_path(relative_path, options)
73
48
 
74
- operator = HelperMethods.get_me_an_operator(options)
49
+ begin
50
+ set_basic_authentication(connection, operator_options)
75
51
 
76
- operator.make_the_call(http_verb, resource_path, params, data, options) do |remote_call|
77
- block_given? ? yield(remote_call) : remote_call
52
+ response = connection.send(http_verb) do |request|
53
+ operator_options.each { |key, value| request.options.send("#{key}=", value) }
54
+ params.each { |key, value| request.params[key] = value }
55
+
56
+ request.url relative_path
57
+ request.body = body
78
58
  end
79
- end
80
59
 
81
- def query_string(params)
82
- params
60
+ RemoteCall::Base.new(response)
61
+ rescue Faraday::ConnectionFailed
62
+ RemoteCall::ConnectionFailed.new
83
63
  end
64
+ end
84
65
 
85
- def resource_path(relative_path, options)
86
- resources_name = options[:resources_name] || self.resources_name
66
+ def query_string(options)
67
+ options
68
+ end
87
69
 
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
94
70
 
95
- def generate_parallel_connection
96
- Operators::Typhoeus.generate_parallel_connection
97
- end
71
+ private ################# PRIVATE ###################
98
72
 
99
- protected ################# PROTECTED ####################
73
+ def build_relative_path(relative_path, options)
74
+ table_name = options[:table_name] || self.table_name
100
75
 
101
- def before_request(http_verb, relative_path, data, options)
102
- [http_verb, relative_path, data, options]
76
+ if Helpers.present?(table_name)
77
+ Helpers.present?(relative_path) ? "#{table_name}/#{relative_path}" : table_name
78
+ else
79
+ relative_path
103
80
  end
104
-
105
81
  end
106
82
 
107
- module HelperMethods
108
-
109
- extend self
83
+ def strip_params(http_verb, data)
84
+ data ||= {}
110
85
 
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
118
- end
119
-
120
- def populate_options(object, options)
121
- options ||= {}
122
-
123
- ClassMethods::OPTIONS.each do |option|
124
- options[option] ||= object.send(option)
125
- end
86
+ ([:get, :head, :delete].include?(http_verb) ? [query_string(data), nil] : [query_string({}), data])
87
+ end
126
88
 
127
- options[:headers] = object.headers.merge(options[:headers] || {})
89
+ def strip_options(options)
90
+ options ||= {}
128
91
 
129
- options
130
- end
92
+ operator_options = options.delete(:operator_options) || {}
93
+ connection = options.delete(:connection) || generate_connection(nil, operator_options)
131
94
 
132
- def strip_params(object, http_verb, data)
133
- data ||= {}
95
+ [connection, operator_options, options]
96
+ end
134
97
 
135
- if [:get, :head, :delete].include?(http_verb)
136
- [object.query_string(data), nil]
137
- else
138
- [object.query_string({}), data]
139
- end
140
- end
98
+ def set_basic_authentication(connection, options)
99
+ endpoint_user = options[:endpoint_user] || self.endpoint_user
100
+ endpoint_pass = options[:endpoint_pass] || self.endpoint_pass
141
101
 
102
+ connection.basic_auth(endpoint_user, endpoint_pass) if Helpers.present?(endpoint_user)
142
103
  end
143
104
 
144
105
  end
106
+
145
107
  end
@@ -1,147 +1,117 @@
1
1
  module SmoothOperator
2
+
2
3
  module Persistence
3
4
 
4
5
  def self.included(base)
5
6
  base.extend(ClassMethods)
6
7
  end
7
8
 
8
- attr_reader :last_remote_call
9
-
10
- def new_record?(ignore_cache = false)
11
- return @new_record if !ignore_cache && defined?(@new_record)
12
-
13
- @new_record = Helpers.has_primary_key?(self)
14
- end
9
+ module ClassMethods
15
10
 
16
- def marked_for_destruction?(ignore_cache = false)
17
- if !ignore_cache && defined?(@marked_for_destruction)
18
- return @marked_for_destruction
11
+ def methods_http_verbs
12
+ @methods_http_verbs ||= { create: :post, save: :put, destroy: :delete }
19
13
  end
20
14
 
21
- _destroy = internal_data_get(self.class.destroy_key)
15
+ [:create, :save, :destroy].each do |method|
16
+ define_method("#{method}_http_verb=") { |http_verb| methods_http_verbs[method] = http_verb }
17
+ end
18
+
19
+ def create(attributes = nil, relative_path = nil, data = {}, options = {})
20
+ if attributes.is_a?(Array)
21
+ attributes.map { |array_entry| create(array_entry, relative_path, data, options) }
22
+ else
23
+ new(attributes).tap { |object| object.save(relative_path, data, options) }
24
+ end
25
+ end
22
26
 
23
- @marked_for_destruction = TypeCasting::TRUE_VALUES.include?(_destroy)
24
27
  end
25
28
 
26
- def destroyed?
27
- return @destroyed if defined?(@destroyed)
28
29
 
29
- @destroyed = false
30
+ def new_record?
31
+ return @new_record if defined?(@new_record)
32
+
33
+ @new_record = Helpers.blank?(get_internal_data("id"))
30
34
  end
31
-
32
- def persisted?
33
- !(new_record? || destroyed?)
35
+
36
+ def destroyed?
37
+ @destroyed || false
34
38
  end
35
39
 
36
- def known_attribute?(attribute)
37
- super ||
38
- [self.class.primary_key, self.class.destroy_key].include?(attribute.to_s)
40
+ def last_remote_call
41
+ @last_remote_call
39
42
  end
40
43
 
41
- def reload(relative_path = nil, data = {}, options = {})
42
- if Helpers.blank?(relative_path) && Helpers.has_primary_key?(self)
43
- raise 'UnknownPath'
44
- end
45
-
46
- make_a_persistence_call(:reload, relative_path, data, options) do |remote_call|
47
- block_given? ? yield(remote_call) : remote_call.status
48
- end
44
+ def persisted?
45
+ !(new_record? || destroyed?)
49
46
  end
50
47
 
51
48
  def save(relative_path = nil, data = {}, options = {})
52
- resource_data = resource_data_for_server(data)
53
-
54
- method = new_record? ? :create : :update
55
-
56
- make_a_persistence_call(method, relative_path, resource_data, options) do |remote_call|
57
- @new_record = false if method == :create && remote_call.status
49
+ create_or_update(relative_path, data, options)
50
+ end
58
51
 
59
- block_given? ? yield(remote_call) : remote_call.status
60
- end
52
+ def save!(relative_path = nil, data = {}, options = {})
53
+ create_or_update(relative_path, data, options) || raise('RecordNotSaved')
61
54
  end
62
55
 
63
56
  def destroy(relative_path = nil, data = {}, options = {})
64
57
  return false unless persisted?
65
58
 
66
- make_a_persistence_call(:destroy, relative_path, data, options) do |remote_call|
67
- @destroyed = true if remote_call.status
59
+ relative_path = "#{id}" if Helpers.blank?(relative_path)
60
+
61
+ success = make_remote_call(self.class.methods_http_verbs[:destroy], relative_path, data, options)
68
62
 
69
- block_given? ? yield(remote_call) : remote_call.status
70
- end
71
- end
63
+ @destroyed = true if success
72
64
 
73
- def save!(relative_path = nil, data = {}, options = {})
74
- save(relative_path, data, options) do |remote_call|
75
- block_given? ? yield(remote_call) : remote_call.status
76
- end || raise('RecordNotSaved')
65
+ success
77
66
  end
78
67
 
79
- protected ######################### PROTECTED ##################
80
68
 
81
- def internal_data_for_server
82
- data = self.class.get_option(:internal_data_for_server, false)
69
+ protected ######################### PROTECTED ##################
83
70
 
84
- if data == false
85
- serializable_hash.dup.tap { |hash| hash.delete(self.class.primary_key) }
86
- else
87
- data
88
- end
71
+ def create_or_update(relative_path, data, options)
72
+ new_record? ? create(relative_path, data, options) : update(relative_path, data, options)
89
73
  end
90
74
 
91
- def resource_data_for_server(data)
92
- resource_data =
93
- self.class.get_option(:resource_data_for_server, false, data)
75
+ def create(relative_path, data, options)
76
+ success = make_remote_call(self.class.methods_http_verbs[:create], relative_path, data, options)
94
77
 
95
- if resource_data == false
96
- data = Helpers.stringify_keys(data)
97
- resource_data = Helpers.stringify_keys(internal_data_for_server)
78
+ @new_record = false if success
98
79
 
99
- { self.class.resource_name.to_s => resource_data }.merge(data)
100
- else
101
- resource_data
102
- end
80
+ success
103
81
  end
104
82
 
105
- private ##################### PRIVATE ##################
106
-
107
- def make_a_persistence_call(method, relative_path, data, options)
108
- options ||= {}
109
-
110
- http_verb = options[:http_verb] || self.class.http_verb_for(method)
111
-
112
- make_the_call(http_verb, relative_path, data, options) do |remote_call|
113
- @last_remote_call = remote_call
114
-
115
- if !@last_remote_call.error? && @last_remote_call.parsed_response.is_a?(Hash)
116
- assign_attributes @last_remote_call.parsed_response, data_from_server: true
117
- end
83
+ def update(relative_path, data, options)
84
+ relative_path = "#{id}" if Helpers.blank?(relative_path)
118
85
 
119
- yield(remote_call)
120
- end
86
+ make_remote_call(self.class.methods_http_verbs[:save], relative_path, data, options)
121
87
  end
122
88
 
123
- module ClassMethods
124
89
 
125
- METHODS_VS_HTTP_VERBS = { reload: :get, create: :post, update: :put, destroy: :delete }
90
+ private ##################### PRIVATE ####################
126
91
 
127
- def http_verb_for(method)
128
- get_option "#{method}_http_verb".to_sym, METHODS_VS_HTTP_VERBS[method]
129
- end
92
+ def make_remote_call(http_verb, relative_path, data, options)
93
+ data, options = build_remote_call_args(http_verb, data, options)
130
94
 
131
- def primary_key
132
- get_option :primary_key, 'id'
133
- end
95
+ @last_remote_call = self.class.send(http_verb, relative_path, data, options)
134
96
 
135
- def destroy_key
136
- get_option :destroy_key, '_destroy'
97
+ returning_data = @last_remote_call.parsed_response
98
+
99
+ if !@last_remote_call.error? && returning_data.is_a?(Hash)
100
+ assign_attributes returning_data.include?(model_name) ? returning_data[model_name] : returning_data
137
101
  end
138
102
 
139
- def create(attributes = nil, relative_path = nil, data = {}, options = {})
140
- new(attributes).tap do |object|
141
- object.save(relative_path, data, options)
142
- end
143
- end
103
+ @last_remote_call.status
104
+ end
144
105
 
106
+ def build_remote_call_args(http_verb, data, options)
107
+ return [data, options] if http_verb == :delete
108
+
109
+ hash = serializable_hash(options[:serializable_options]).dup
110
+ hash.delete('id')
111
+
112
+ [{ model_name => hash }.merge(data), options]
145
113
  end
114
+
146
115
  end
116
+
147
117
  end