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.
- checksums.yaml +8 -8
- data/Gemfile +0 -5
- data/README.md +11 -308
- data/console.rb +3 -28
- data/lib/smooth_operator.rb +7 -75
- data/lib/smooth_operator/array_with_meta_data.rb +21 -20
- data/lib/smooth_operator/attribute_assignment.rb +53 -57
- data/lib/smooth_operator/delegation.rb +34 -15
- data/lib/smooth_operator/finder_methods.rb +17 -33
- data/lib/smooth_operator/helpers.rb +7 -37
- data/lib/smooth_operator/internal_attribute.rb +49 -0
- data/lib/smooth_operator/model_schema.rb +72 -0
- data/lib/smooth_operator/open_struct.rb +4 -7
- data/lib/smooth_operator/operator.rb +64 -102
- data/lib/smooth_operator/persistence.rb +64 -94
- data/lib/smooth_operator/remote_call.rb +70 -0
- data/lib/smooth_operator/serialization.rb +33 -89
- data/lib/smooth_operator/translation.rb +13 -26
- data/lib/smooth_operator/type_converter.rb +69 -0
- data/lib/smooth_operator/validations.rb +3 -25
- data/lib/smooth_operator/version.rb +1 -1
- data/smooth_operator.gemspec +5 -9
- data/spec/factories/user_factory.rb +4 -5
- data/spec/smooth_operator/attribute_assignment_spec.rb +11 -145
- data/spec/smooth_operator/delegation_spec.rb +54 -57
- data/spec/smooth_operator/finder_methods_spec.rb +2 -91
- data/spec/smooth_operator/{resource_name_spec.rb → model_schema_spec.rb} +2 -2
- data/spec/smooth_operator/operator_spec.rb +1 -1
- data/spec/smooth_operator/persistence_spec.rb +20 -140
- data/spec/smooth_operator/serialization_spec.rb +4 -28
- data/spec/spec_helper.rb +9 -7
- data/spec/support/models/address.rb +0 -9
- data/spec/support/models/post.rb +3 -9
- data/spec/support/models/user.rb +7 -30
- data/spec/support/models/user_with_address_and_posts.rb +12 -20
- data/spec/support/test_server.rb +7 -63
- metadata +18 -55
- data/lib/smooth_operator/associations.rb +0 -110
- data/lib/smooth_operator/associations/association_reflection.rb +0 -79
- data/lib/smooth_operator/associations/has_many_relation.rb +0 -45
- data/lib/smooth_operator/associations/reflection.rb +0 -41
- data/lib/smooth_operator/cookie_jar.rb +0 -21
- data/lib/smooth_operator/http_methods.rb +0 -17
- data/lib/smooth_operator/internal_data.rb +0 -45
- data/lib/smooth_operator/operators/connection_wrapper.rb +0 -15
- data/lib/smooth_operator/operators/faraday.rb +0 -75
- data/lib/smooth_operator/operators/typhoeus.rb +0 -87
- data/lib/smooth_operator/options.rb +0 -30
- data/lib/smooth_operator/remote_call/base.rb +0 -76
- data/lib/smooth_operator/remote_call/errors/connection_failed.rb +0 -20
- data/lib/smooth_operator/remote_call/errors/timeout.rb +0 -20
- data/lib/smooth_operator/remote_call/faraday.rb +0 -19
- data/lib/smooth_operator/remote_call/typhoeus.rb +0 -19
- data/lib/smooth_operator/resource_name.rb +0 -46
- data/lib/smooth_operator/schema.rb +0 -21
- data/lib/smooth_operator/type_casting.rb +0 -127
- data/spec/require_helper.rb +0 -11
- data/spec/smooth_operator/remote_call_spec.rb +0 -340
- data/spec/smooth_operator/validations_spec.rb +0 -42
- 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/
|
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
|
-
|
13
|
-
extend ResourceName
|
9
|
+
class OpenStruct
|
14
10
|
|
15
11
|
include Delegation
|
16
12
|
include Validations
|
17
|
-
include
|
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
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
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
|
-
|
12
|
-
options ||= {}
|
10
|
+
HTTP_VERBS = [:get, :post, :put, :patch, :delete]
|
13
11
|
|
14
|
-
|
12
|
+
OPTIONS = [:endpoint, :endpoint_user, :endpoint_pass, :timeout]
|
15
13
|
|
16
|
-
parent_object = _options[:parent_object]
|
17
14
|
|
18
|
-
|
19
|
-
id = Helpers.primary_key(parent_object)
|
15
|
+
attr_writer *OPTIONS
|
20
16
|
|
21
|
-
|
22
|
-
end
|
17
|
+
OPTIONS.each { |option| define_method(option) { Helpers.get_instance_variable(self, option, '') } }
|
23
18
|
|
24
|
-
|
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
|
32
|
-
|
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
|
44
|
-
|
45
|
-
|
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
|
-
|
58
|
-
|
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
|
-
|
66
|
-
options = HelperMethods.populate_options(self, options)
|
40
|
+
protected ################ PROTECTED ################
|
67
41
|
|
68
|
-
|
42
|
+
def make_the_call(http_verb, relative_path = '', data = {}, options = {})
|
43
|
+
params, body = strip_params(http_verb, data)
|
69
44
|
|
70
|
-
|
45
|
+
connection, operator_options, options = strip_options(options)
|
71
46
|
|
72
|
-
|
47
|
+
relative_path = build_relative_path(relative_path, options)
|
73
48
|
|
74
|
-
|
49
|
+
begin
|
50
|
+
set_basic_authentication(connection, operator_options)
|
75
51
|
|
76
|
-
|
77
|
-
|
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
|
-
|
82
|
-
|
60
|
+
RemoteCall::Base.new(response)
|
61
|
+
rescue Faraday::ConnectionFailed
|
62
|
+
RemoteCall::ConnectionFailed.new
|
83
63
|
end
|
64
|
+
end
|
84
65
|
|
85
|
-
|
86
|
-
|
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
|
-
|
96
|
-
Operators::Typhoeus.generate_parallel_connection
|
97
|
-
end
|
71
|
+
private ################# PRIVATE ###################
|
98
72
|
|
99
|
-
|
73
|
+
def build_relative_path(relative_path, options)
|
74
|
+
table_name = options[:table_name] || self.table_name
|
100
75
|
|
101
|
-
|
102
|
-
|
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
|
-
|
108
|
-
|
109
|
-
extend self
|
83
|
+
def strip_params(http_verb, data)
|
84
|
+
data ||= {}
|
110
85
|
|
111
|
-
|
112
|
-
|
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
|
-
|
89
|
+
def strip_options(options)
|
90
|
+
options ||= {}
|
128
91
|
|
129
|
-
|
130
|
-
|
92
|
+
operator_options = options.delete(:operator_options) || {}
|
93
|
+
connection = options.delete(:connection) || generate_connection(nil, operator_options)
|
131
94
|
|
132
|
-
|
133
|
-
|
95
|
+
[connection, operator_options, options]
|
96
|
+
end
|
134
97
|
|
135
|
-
|
136
|
-
|
137
|
-
|
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
|
-
|
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
|
-
|
17
|
-
|
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
|
-
|
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
|
-
|
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
|
33
|
-
|
35
|
+
|
36
|
+
def destroyed?
|
37
|
+
@destroyed || false
|
34
38
|
end
|
35
39
|
|
36
|
-
def
|
37
|
-
|
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
|
42
|
-
|
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
|
-
|
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
|
-
|
60
|
-
|
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
|
-
|
67
|
-
|
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
|
-
|
70
|
-
end
|
71
|
-
end
|
63
|
+
@destroyed = true if success
|
72
64
|
|
73
|
-
|
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
|
-
|
82
|
-
data = self.class.get_option(:internal_data_for_server, false)
|
69
|
+
protected ######################### PROTECTED ##################
|
83
70
|
|
84
|
-
|
85
|
-
|
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
|
92
|
-
|
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
|
-
|
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
|
-
|
100
|
-
else
|
101
|
-
resource_data
|
102
|
-
end
|
80
|
+
success
|
103
81
|
end
|
104
82
|
|
105
|
-
|
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
|
-
|
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
|
-
|
90
|
+
private ##################### PRIVATE ####################
|
126
91
|
|
127
|
-
|
128
|
-
|
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
|
-
|
132
|
-
get_option :primary_key, 'id'
|
133
|
-
end
|
95
|
+
@last_remote_call = self.class.send(http_verb, relative_path, data, options)
|
134
96
|
|
135
|
-
|
136
|
-
|
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
|
-
|
140
|
-
|
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
|