smooth_operator 1.3.0 → 1.8.0

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