smooth_operator 1.20.10 → 1.21.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/README.md +2 -2
- data/lib/smooth_operator/array_with_meta_data.rb +20 -8
- data/lib/smooth_operator/attribute_assignment.rb +41 -54
- data/lib/smooth_operator/delegation.rb +14 -33
- data/lib/smooth_operator/finder_methods.rb +26 -17
- data/lib/smooth_operator/helpers.rb +10 -8
- data/lib/smooth_operator/internal_data.rb +45 -0
- data/lib/smooth_operator/open_struct.rb +9 -27
- data/lib/smooth_operator/operator.rb +11 -12
- data/lib/smooth_operator/persistence.rb +52 -41
- data/lib/smooth_operator/relation/array_relation.rb +2 -12
- data/lib/smooth_operator/relation/association_reflection.rb +2 -6
- data/lib/smooth_operator/relation/associations.rb +3 -3
- data/lib/smooth_operator/remote_call/base.rb +0 -4
- data/lib/smooth_operator/{model_name.rb → resource_name.rb} +2 -2
- data/lib/smooth_operator/schema.rb +9 -20
- data/lib/smooth_operator/serialization.rb +11 -11
- data/lib/smooth_operator/translation.rb +21 -12
- data/lib/smooth_operator/type_casting.rb +127 -0
- data/lib/smooth_operator/validations.rb +19 -3
- data/lib/smooth_operator/version.rb +1 -1
- data/lib/smooth_operator.rb +24 -3
- data/spec/smooth_operator/attribute_assignment_spec.rb +4 -13
- data/spec/smooth_operator/finder_methods_spec.rb +4 -9
- data/spec/smooth_operator/persistence_spec.rb +3 -15
- data/spec/smooth_operator/{model_name_spec.rb → resource_name_spec.rb} +1 -1
- data/spec/support/models/address.rb +0 -2
- data/spec/support/models/user.rb +3 -3
- data/spec/support/models/user_with_address_and_posts.rb +6 -14
- metadata +7 -12
- data/lib/smooth_operator/attribute_methods.rb +0 -92
- data/lib/smooth_operator/attributes/base.rb +0 -107
- data/lib/smooth_operator/attributes/dirty.rb +0 -29
- data/lib/smooth_operator/attributes/normal.rb +0 -15
- data/lib/smooth_operator/blank_slate.rb +0 -7
- data/spec/smooth_operator/attributes_dirty_spec.rb +0 -53
@@ -0,0 +1,127 @@
|
|
1
|
+
module SmoothOperator
|
2
|
+
module TypeCasting
|
3
|
+
|
4
|
+
# RIPPED FROM RAILS
|
5
|
+
TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON'].to_set
|
6
|
+
FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].to_set
|
7
|
+
|
8
|
+
extend self
|
9
|
+
|
10
|
+
def cast_to_type(name, value, parent_object)
|
11
|
+
type, known_attribute, unknown_hash_class = extract_args(parent_object.class, name)
|
12
|
+
|
13
|
+
return Helpers.duplicate(value) if known_attribute && type.nil?
|
14
|
+
|
15
|
+
case value
|
16
|
+
when Array
|
17
|
+
value.map { |array_entry| cast_to_type(name, array_entry, parent_object) }
|
18
|
+
when Hash
|
19
|
+
type.nil? ? new_unknown_hash(value, unknown_hash_class, parent_object) : type.new(value, parent_object: parent_object)
|
20
|
+
else
|
21
|
+
convert(value, type)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
protected ##################### PROTECTED ########################
|
26
|
+
|
27
|
+
def extract_args(parent_object_class, name)
|
28
|
+
known_attribute, attribute_type = false, false
|
29
|
+
|
30
|
+
if parent_object_class.respond_to?(:known_attribute?)
|
31
|
+
known_attribute = parent_object_class.known_attribute?(name)
|
32
|
+
end
|
33
|
+
|
34
|
+
if parent_object_class.respond_to?(:attribute_type)
|
35
|
+
attribute_type = parent_object_class.attribute_type(name)
|
36
|
+
end
|
37
|
+
|
38
|
+
[
|
39
|
+
attribute_type,
|
40
|
+
known_attribute,
|
41
|
+
parent_object_class.unknown_hash_class
|
42
|
+
]
|
43
|
+
end
|
44
|
+
|
45
|
+
def convert(value, type)
|
46
|
+
case type
|
47
|
+
|
48
|
+
when :string, :text, String
|
49
|
+
value.to_s
|
50
|
+
|
51
|
+
when :int, :integer, Integer, Fixnum
|
52
|
+
to_int(value)
|
53
|
+
|
54
|
+
when :date, Date
|
55
|
+
to_date(value)
|
56
|
+
|
57
|
+
when :float, Float
|
58
|
+
to_float(value)
|
59
|
+
|
60
|
+
when :bool, :boolean
|
61
|
+
to_boolean(value)
|
62
|
+
|
63
|
+
when :datetime, :date_time, DateTime
|
64
|
+
to_datetime(value)
|
65
|
+
|
66
|
+
else
|
67
|
+
Helpers.duplicate(value)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_date(string)
|
72
|
+
return string if string.is_a?(Date)
|
73
|
+
|
74
|
+
Date.parse(string) rescue nil
|
75
|
+
end
|
76
|
+
|
77
|
+
def to_datetime(string)
|
78
|
+
return string if string.is_a?(DateTime)
|
79
|
+
|
80
|
+
DateTime.parse(string) rescue nil
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_boolean(string)
|
84
|
+
value = string.to_s.downcase
|
85
|
+
|
86
|
+
TRUE_VALUES.include?(value) ? true : FALSE_VALUES.include?(value) ? false : nil
|
87
|
+
end
|
88
|
+
|
89
|
+
def to_int(string)
|
90
|
+
return string if string.is_a?(Fixnum)
|
91
|
+
|
92
|
+
to_float(string).to_i
|
93
|
+
end
|
94
|
+
|
95
|
+
def to_float(string)
|
96
|
+
return string if string.is_a?(Float)
|
97
|
+
|
98
|
+
return 0 if string.nil? || !(string.is_a?(String) || string.is_a?(Fixnum))
|
99
|
+
|
100
|
+
value = string.to_s.gsub(',', '.').scan(/-*\d+[.]*\d*/).flatten.map(&:to_f).first
|
101
|
+
|
102
|
+
value.nil? ? 0 : value
|
103
|
+
end
|
104
|
+
|
105
|
+
def new_unknown_hash(hash, unknown_hash_class, parent_object)
|
106
|
+
if unknown_hash_class == :none
|
107
|
+
hash
|
108
|
+
else
|
109
|
+
unknown_hash_class.new(cast_params(hash, unknown_hash_class, parent_object))
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
private ################### PRIVATE #####################
|
114
|
+
|
115
|
+
def cast_params(attributes, unknown_hash_class, parent_object)
|
116
|
+
hash = {}
|
117
|
+
|
118
|
+
attributes.each do |key, value|
|
119
|
+
hash[key] = cast_to_type(key, value, parent_object)
|
120
|
+
end
|
121
|
+
|
122
|
+
hash
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
@@ -1,15 +1,31 @@
|
|
1
1
|
module SmoothOperator
|
2
|
-
|
3
2
|
module Validations
|
4
3
|
|
5
4
|
def valid?(context = nil)
|
6
|
-
Helpers.blank?(
|
5
|
+
Helpers.blank?(_internal_errors)
|
7
6
|
end
|
8
7
|
|
9
8
|
def invalid?
|
10
9
|
!valid?
|
11
10
|
end
|
12
11
|
|
13
|
-
|
12
|
+
def _internal_errors
|
13
|
+
@_internal_errors ||= {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.included(base)
|
17
|
+
base.extend(ClassMethods)
|
18
|
+
end
|
19
|
+
|
20
|
+
module ClassMethods
|
14
21
|
|
22
|
+
def errors_key
|
23
|
+
Helpers.get_instance_variable(self, :errors_key, 'errors')
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_writer :errors_key
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
15
31
|
end
|
data/lib/smooth_operator.rb
CHANGED
@@ -9,16 +9,18 @@ require "smooth_operator/finder_methods"
|
|
9
9
|
require "smooth_operator/relation/associations"
|
10
10
|
|
11
11
|
module SmoothOperator
|
12
|
-
class Base < OpenStruct
|
12
|
+
class Base < OpenStruct
|
13
13
|
|
14
|
+
extend Schema
|
14
15
|
extend FinderMethods
|
16
|
+
extend Operator::HttpMethods
|
15
17
|
extend Relation::Associations
|
16
18
|
extend Translation if defined? I18n
|
17
19
|
|
18
|
-
include Schema
|
19
20
|
include Operator
|
20
21
|
include Persistence
|
21
22
|
include FinderMethods
|
23
|
+
include Operator::HttpMethods
|
22
24
|
|
23
25
|
self.strict_behaviour = true
|
24
26
|
|
@@ -36,11 +38,30 @@ module SmoothOperator
|
|
36
38
|
include ActiveModel::Conversion
|
37
39
|
|
38
40
|
def column_for_attribute(attribute_name)
|
39
|
-
type =
|
41
|
+
type = self.class.attribute_type(attribute_name)
|
40
42
|
|
41
43
|
ActiveRecord::ConnectionAdapters::Column.new(attribute_name.to_sym, type, type)
|
42
44
|
end
|
43
45
|
|
46
|
+
def save(relative_path = nil, data = {}, options = {})
|
47
|
+
# clear_server_errors
|
48
|
+
return false unless before_save
|
49
|
+
|
50
|
+
save_result = valid? ? super : false
|
51
|
+
|
52
|
+
# import_server_errors
|
53
|
+
|
54
|
+
after_save if valid? && save_result
|
55
|
+
|
56
|
+
save_result
|
57
|
+
end
|
58
|
+
|
59
|
+
def before_save
|
60
|
+
true
|
61
|
+
end
|
62
|
+
|
63
|
+
def after_save; end
|
64
|
+
|
44
65
|
end
|
45
66
|
end
|
46
67
|
end
|
@@ -8,15 +8,6 @@ describe SmoothOperator::AttributeAssignment do
|
|
8
8
|
describe "receiving data from server" do
|
9
9
|
subject { User::Base.new }
|
10
10
|
|
11
|
-
context "when receiving the option 'from_server = true'" do
|
12
|
-
before { subject.assign_attributes({}, from_server: true) }
|
13
|
-
|
14
|
-
it "#has_data_from_server and #from_server should return true" do
|
15
|
-
expect(subject.has_data_from_server).to be true
|
16
|
-
expect(subject.from_server).to be true
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
11
|
context "when receiving a Hash with meta_data on it" do
|
21
12
|
before { subject.assign_attributes({ user: attributes_for(:user), status: 1 }) }
|
22
13
|
|
@@ -84,16 +75,16 @@ describe SmoothOperator::AttributeAssignment do
|
|
84
75
|
end
|
85
76
|
end
|
86
77
|
|
87
|
-
context "when the .unknown_hash_class is set to SmoothOperator::OpenStruct
|
78
|
+
context "when the .unknown_hash_class is set to SmoothOperator::OpenStruct" do
|
88
79
|
subject { User::UnknownHashClass::OpenStructBase.new(address: { street: 'something', postal_code: { code: '123' } }) }
|
89
80
|
|
90
|
-
it "a new instance of SmoothOperator::OpenStruct
|
81
|
+
it "a new instance of SmoothOperator::OpenStruct will be initialized with that hash" do
|
91
82
|
address = subject.address
|
92
83
|
|
93
|
-
expect(address).to be_instance_of(SmoothOperator::OpenStruct
|
84
|
+
expect(address).to be_instance_of(SmoothOperator::OpenStruct)
|
94
85
|
expect(address.street).to eq('something')
|
95
86
|
|
96
|
-
expect(address.postal_code).to be_instance_of(SmoothOperator::OpenStruct
|
87
|
+
expect(address.postal_code).to be_instance_of(SmoothOperator::OpenStruct)
|
97
88
|
expect(address.postal_code.code).to eq('123')
|
98
89
|
end
|
99
90
|
end
|
@@ -8,11 +8,6 @@ shared_examples_for "finder method" do
|
|
8
8
|
it "the instance class should be populated with the returned hash" do
|
9
9
|
expect(user.attributes).to eq(attributes_for(:user_with_address_and_posts))
|
10
10
|
end
|
11
|
-
|
12
|
-
it "#has_data_from_server and #from_server should return true" do
|
13
|
-
expect(user.has_data_from_server).to be true
|
14
|
-
expect(user.from_server).to be true
|
15
|
-
end
|
16
11
|
end
|
17
12
|
|
18
13
|
describe SmoothOperator::FinderMethods do
|
@@ -39,13 +34,13 @@ describe SmoothOperator::FinderMethods do
|
|
39
34
|
|
40
35
|
describe ".find" do
|
41
36
|
context "when the server returns a single hash" do
|
42
|
-
let(:user) { subject.find(5).
|
37
|
+
let(:user) { subject.find(5).data }
|
43
38
|
|
44
39
|
it_behaves_like "finder method"
|
45
40
|
end
|
46
41
|
|
47
42
|
context "when the server returns a hash with meta_data" do
|
48
|
-
let(:user) { subject.find("5/with_metadata").
|
43
|
+
let(:user) { subject.find("5/with_metadata").data }
|
49
44
|
|
50
45
|
it_behaves_like "finder method"
|
51
46
|
|
@@ -61,7 +56,7 @@ describe SmoothOperator::FinderMethods do
|
|
61
56
|
context "when the server returns an array" do
|
62
57
|
it "it should return a RemoteCall instance an array that contains a subject's class instance, one for every array's entry" do
|
63
58
|
remote_call = subject.find(:all)
|
64
|
-
users = remote_call.
|
59
|
+
users = remote_call.data
|
65
60
|
|
66
61
|
expect(users).to be_instance_of(Array)
|
67
62
|
expect(users[0]).to be_instance_of(subject)
|
@@ -70,7 +65,7 @@ describe SmoothOperator::FinderMethods do
|
|
70
65
|
|
71
66
|
it "if any of the array entries is not a hash, it shall not be converted or alteread" do
|
72
67
|
remote_call = subject.find('misc_array')
|
73
|
-
users = remote_call.
|
68
|
+
users = remote_call.data
|
74
69
|
|
75
70
|
expect(users).to be_instance_of(Array)
|
76
71
|
expect(users[0]).to be_instance_of(subject)
|
@@ -133,13 +133,6 @@ describe SmoothOperator::Persistence, helpers: :persistence do
|
|
133
133
|
subject { new_user }
|
134
134
|
let(:method_to_execute) { :reload }
|
135
135
|
|
136
|
-
context "before calling #reload" do
|
137
|
-
it "#has_data_from_server and #from_server should return false" do
|
138
|
-
expect(subject.from_server).to be_falsey
|
139
|
-
expect(subject.has_data_from_server).to be_falsey
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
136
|
context "when subject doesn't has an id" do
|
144
137
|
it "it should raise 'UnknownPath'" do
|
145
138
|
expect{ subject.reload }.to raise_error 'UnknownPath'
|
@@ -155,11 +148,6 @@ describe SmoothOperator::Persistence, helpers: :persistence do
|
|
155
148
|
it "it should fetch server data" do
|
156
149
|
expect(subject.attributes).to eq(attributes_for(:user_with_address_and_posts))
|
157
150
|
end
|
158
|
-
|
159
|
-
it "#has_data_from_server and #from_server should return true" do
|
160
|
-
expect(subject.from_server).to be true
|
161
|
-
expect(subject.has_data_from_server).to be true
|
162
|
-
end
|
163
151
|
end
|
164
152
|
|
165
153
|
context "when calling #reload on a nested object" do
|
@@ -194,7 +182,7 @@ describe SmoothOperator::Persistence, helpers: :persistence do
|
|
194
182
|
end
|
195
183
|
|
196
184
|
describe ".create" do
|
197
|
-
|
185
|
+
|
198
186
|
subject { created_subject }
|
199
187
|
let(:method_arguments) { [] }
|
200
188
|
|
@@ -263,7 +251,7 @@ describe SmoothOperator::Persistence, helpers: :persistence do
|
|
263
251
|
expect(subject.destroyed?).to be(true)
|
264
252
|
end
|
265
253
|
end
|
266
|
-
|
254
|
+
|
267
255
|
context "after a failed execution of #destroy" do
|
268
256
|
subject { existing_user }
|
269
257
|
before { subject.destroy('', { status: 422 }) }
|
@@ -387,7 +375,7 @@ describe SmoothOperator::Persistence, helpers: :persistence do
|
|
387
375
|
end
|
388
376
|
|
389
377
|
describe "#destroy" do
|
390
|
-
|
378
|
+
|
391
379
|
let(:method_to_execute) { :destroy }
|
392
380
|
let(:method_arguments) { [] }
|
393
381
|
|
data/spec/support/models/user.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module User
|
2
|
-
|
2
|
+
|
3
3
|
class Base < SmoothOperator::Base
|
4
4
|
|
5
5
|
self.resource_name = 'user'
|
@@ -19,9 +19,9 @@ module User
|
|
19
19
|
end
|
20
20
|
|
21
21
|
module UnknownHashClass
|
22
|
-
|
22
|
+
|
23
23
|
class OpenStructBase < User::Base
|
24
|
-
self.unknown_hash_class = SmoothOperator::OpenStruct
|
24
|
+
self.unknown_hash_class = SmoothOperator::OpenStruct
|
25
25
|
end
|
26
26
|
|
27
27
|
class None < User::Base
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module UserWithAddressAndPosts
|
2
|
-
|
2
|
+
|
3
3
|
class Father < User::Base
|
4
|
-
|
4
|
+
|
5
5
|
self.resource_name = 'user'
|
6
6
|
|
7
7
|
schema(
|
@@ -36,10 +36,10 @@ module UserWithAddressAndPosts
|
|
36
36
|
self.update_http_verb = :patch
|
37
37
|
|
38
38
|
end
|
39
|
-
|
39
|
+
|
40
40
|
|
41
41
|
module UserBlackListed
|
42
|
-
|
42
|
+
|
43
43
|
class Father < ::UserWithAddressAndPosts::Son
|
44
44
|
|
45
45
|
attributes_black_list_add "last_name"
|
@@ -55,7 +55,7 @@ module UserWithAddressAndPosts
|
|
55
55
|
end
|
56
56
|
|
57
57
|
module UserWhiteListed
|
58
|
-
|
58
|
+
|
59
59
|
class Father < ::UserWithAddressAndPosts::Son
|
60
60
|
|
61
61
|
attributes_white_list_add "id"
|
@@ -69,7 +69,7 @@ module UserWithAddressAndPosts
|
|
69
69
|
end
|
70
70
|
|
71
71
|
end
|
72
|
-
|
72
|
+
|
73
73
|
class UserWithMyMethod < UserWithAddressAndPosts::Son
|
74
74
|
|
75
75
|
def my_method
|
@@ -78,12 +78,4 @@ module UserWithAddressAndPosts
|
|
78
78
|
|
79
79
|
end
|
80
80
|
|
81
|
-
class DirtyAttributes < UserWithAddressAndPosts::Son
|
82
|
-
|
83
|
-
self.dirty_attributes
|
84
|
-
|
85
|
-
self.unknown_hash_class = SmoothOperator::OpenStruct::Dirty
|
86
|
-
|
87
|
-
end
|
88
|
-
|
89
81
|
end
|