smooth_operator 1.2.9 → 1.3.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 +78 -22
- data/console.rb +2 -0
- data/lib/smooth_operator/array_with_meta_data.rb +20 -8
- data/lib/smooth_operator/{relation → associations}/association_reflection.rb +8 -8
- data/lib/smooth_operator/{relation/array_relation.rb → associations/has_many_relation.rb} +3 -13
- data/lib/smooth_operator/{relation → associations}/reflection.rb +2 -2
- data/lib/smooth_operator/associations.rb +110 -0
- data/lib/smooth_operator/attribute_assignment.rb +52 -60
- data/lib/smooth_operator/cookie_jar.rb +21 -0
- data/lib/smooth_operator/delegation.rb +13 -34
- data/lib/smooth_operator/finder_methods.rb +26 -17
- data/lib/smooth_operator/helpers.rb +14 -8
- data/lib/smooth_operator/http_methods.rb +17 -0
- data/lib/smooth_operator/internal_data.rb +45 -0
- data/lib/smooth_operator/open_struct.rb +11 -26
- data/lib/smooth_operator/operator.rb +65 -59
- data/lib/smooth_operator/operators/connection_wrapper.rb +15 -0
- data/lib/smooth_operator/operators/faraday.rb +6 -6
- data/lib/smooth_operator/operators/typhoeus.rb +22 -12
- data/lib/smooth_operator/options.rb +30 -0
- data/lib/smooth_operator/persistence.rb +64 -61
- data/lib/smooth_operator/remote_call/base.rb +7 -6
- data/lib/smooth_operator/resource_name.rb +46 -0
- data/lib/smooth_operator/schema.rb +21 -0
- data/lib/smooth_operator/serialization.rb +80 -36
- data/lib/smooth_operator/translation.rb +21 -12
- data/lib/smooth_operator/type_casting.rb +127 -0
- data/lib/smooth_operator/validations.rb +25 -3
- data/lib/smooth_operator/version.rb +1 -1
- data/lib/smooth_operator.rb +55 -5
- data/smooth_operator.gemspec +5 -5
- data/spec/smooth_operator/attribute_assignment_spec.rb +5 -14
- data/spec/smooth_operator/finder_methods_spec.rb +4 -9
- data/spec/smooth_operator/persistence_spec.rb +27 -19
- data/spec/smooth_operator/remote_call_spec.rb +104 -84
- data/spec/smooth_operator/{model_schema_spec.rb → resource_name_spec.rb} +1 -1
- data/spec/support/models/address.rb +8 -10
- data/spec/support/models/comment.rb +2 -0
- data/spec/support/models/post.rb +7 -7
- data/spec/support/models/user.rb +10 -13
- data/spec/support/models/user_with_address_and_posts.rb +9 -17
- data/spec/support/test_server.rb +7 -7
- metadata +25 -25
- data/lib/smooth_operator/attribute_methods.rb +0 -78
- 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/lib/smooth_operator/model_schema.rb +0 -81
- data/lib/smooth_operator/relation/associations.rb +0 -102
- data/spec/smooth_operator/attributes_dirty_spec.rb +0 -53
@@ -7,28 +7,20 @@ module SmoothOperator
|
|
7
7
|
|
8
8
|
attr_reader :last_remote_call
|
9
9
|
|
10
|
-
def
|
11
|
-
|
12
|
-
end
|
13
|
-
|
14
|
-
def reload(relative_path = nil, data = {}, options = {})
|
15
|
-
raise 'UnknownPath' if Helpers.blank?(relative_path) && (!respond_to?(self.class.primary_key) || Helpers.blank?(get_primary_key))
|
10
|
+
def new_record?(ignore_cache = false)
|
11
|
+
return @new_record if !ignore_cache && defined?(@new_record)
|
16
12
|
|
17
|
-
|
18
|
-
block_given? ? yield(remote_call) : remote_call.status
|
19
|
-
end
|
13
|
+
@new_record = Helpers.has_primary_key?(self)
|
20
14
|
end
|
21
15
|
|
22
|
-
def
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
end
|
16
|
+
def marked_for_destruction?(ignore_cache = false)
|
17
|
+
if !ignore_cache && defined?(@marked_for_destruction)
|
18
|
+
return @marked_for_destruction
|
19
|
+
end
|
27
20
|
|
28
|
-
|
29
|
-
return @marked_for_destruction if !bypass_cache && defined?(@marked_for_destruction)
|
21
|
+
_destroy = internal_data_get(self.class.destroy_key)
|
30
22
|
|
31
|
-
@marked_for_destruction =
|
23
|
+
@marked_for_destruction = TypeCasting::TRUE_VALUES.include?(_destroy)
|
32
24
|
end
|
33
25
|
|
34
26
|
def destroyed?
|
@@ -41,102 +33,113 @@ module SmoothOperator
|
|
41
33
|
!(new_record? || destroyed?)
|
42
34
|
end
|
43
35
|
|
44
|
-
def
|
45
|
-
|
36
|
+
def known_attribute?(attribute)
|
37
|
+
super ||
|
38
|
+
[self.class.primary_key, self.class.destroy_key].include?(attribute.to_s)
|
39
|
+
end
|
46
40
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
51
48
|
end
|
52
49
|
end
|
53
50
|
|
54
|
-
def save
|
55
|
-
|
51
|
+
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
|
58
|
+
|
56
59
|
block_given? ? yield(remote_call) : remote_call.status
|
57
|
-
end
|
60
|
+
end
|
58
61
|
end
|
59
62
|
|
60
63
|
def destroy(relative_path = nil, data = {}, options = {})
|
61
64
|
return false unless persisted?
|
62
65
|
|
63
|
-
|
66
|
+
make_a_persistence_call(:destroy, relative_path, data, options) do |remote_call|
|
64
67
|
@destroyed = true if remote_call.status
|
65
68
|
|
66
69
|
block_given? ? yield(remote_call) : remote_call.status
|
67
70
|
end
|
68
71
|
end
|
69
72
|
|
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')
|
77
|
+
end
|
70
78
|
|
71
79
|
protected ######################### PROTECTED ##################
|
72
80
|
|
73
|
-
def
|
74
|
-
|
75
|
-
@new_record = false if remote_call.status
|
81
|
+
def internal_data_for_server
|
82
|
+
data = self.class.get_option(:internal_data_for_server, false)
|
76
83
|
|
77
|
-
|
84
|
+
if data == false
|
85
|
+
serializable_hash.dup.tap { |hash| hash.delete(self.class.primary_key) }
|
86
|
+
else
|
87
|
+
data
|
78
88
|
end
|
79
89
|
end
|
80
90
|
|
81
|
-
def
|
82
|
-
|
83
|
-
|
91
|
+
def resource_data_for_server(data)
|
92
|
+
resource_data =
|
93
|
+
self.class.get_option(:resource_data_for_server, false, data)
|
94
|
+
|
95
|
+
if resource_data == false
|
96
|
+
data = Helpers.stringify_keys(data)
|
97
|
+
resource_data = Helpers.stringify_keys(internal_data_for_server)
|
98
|
+
|
99
|
+
{ self.class.resource_name.to_s => resource_data }.merge(data)
|
100
|
+
else
|
101
|
+
resource_data
|
84
102
|
end
|
85
103
|
end
|
86
104
|
|
87
|
-
|
105
|
+
private ##################### PRIVATE ##################
|
106
|
+
|
107
|
+
def make_a_persistence_call(method, relative_path, data, options)
|
88
108
|
options ||= {}
|
89
109
|
|
90
|
-
http_verb = options[:http_verb] || self.class.
|
110
|
+
http_verb = options[:http_verb] || self.class.http_verb_for(method)
|
91
111
|
|
92
112
|
make_the_call(http_verb, relative_path, data, options) do |remote_call|
|
93
113
|
@last_remote_call = remote_call
|
94
114
|
|
95
115
|
if !@last_remote_call.error? && @last_remote_call.parsed_response.is_a?(Hash)
|
96
|
-
assign_attributes @last_remote_call.parsed_response,
|
116
|
+
assign_attributes @last_remote_call.parsed_response, data_from_server: true
|
97
117
|
end
|
98
118
|
|
99
119
|
yield(remote_call)
|
100
120
|
end
|
101
121
|
end
|
102
122
|
|
103
|
-
def data_with_object_attributes(data, options)
|
104
|
-
data = Helpers.stringify_keys(data)
|
105
|
-
|
106
|
-
hash = serializable_hash(options[:serializable_options]).dup
|
107
|
-
|
108
|
-
hash.delete(self.class.primary_key)
|
109
|
-
|
110
|
-
{ self.class.resource_name => hash }.merge(data)
|
111
|
-
end
|
112
|
-
|
113
|
-
|
114
123
|
module ClassMethods
|
115
124
|
|
116
125
|
METHODS_VS_HTTP_VERBS = { reload: :get, create: :post, update: :put, destroy: :delete }
|
117
126
|
|
118
|
-
def
|
119
|
-
|
127
|
+
def http_verb_for(method)
|
128
|
+
get_option "#{method}_http_verb".to_sym, METHODS_VS_HTTP_VERBS[method]
|
120
129
|
end
|
121
130
|
|
122
131
|
def primary_key
|
123
|
-
|
132
|
+
get_option :primary_key, 'id'
|
124
133
|
end
|
125
134
|
|
126
|
-
attr_writer :primary_key
|
127
|
-
|
128
135
|
def destroy_key
|
129
|
-
|
130
|
-
end
|
131
|
-
|
132
|
-
attr_writer :destroy_key
|
133
|
-
|
134
|
-
METHODS_VS_HTTP_VERBS.keys.each do |method|
|
135
|
-
define_method("#{method}_http_verb=") { |http_verb| methods_vs_http_verbs[method] = http_verb }
|
136
|
+
get_option :destroy_key, '_destroy'
|
136
137
|
end
|
137
138
|
|
138
139
|
def create(attributes = nil, relative_path = nil, data = {}, options = {})
|
139
|
-
new(attributes).tap
|
140
|
+
new(attributes).tap do |object|
|
141
|
+
object.save(relative_path, data, options)
|
142
|
+
end
|
140
143
|
end
|
141
144
|
|
142
145
|
end
|
@@ -1,5 +1,6 @@
|
|
1
|
-
|
1
|
+
require "smooth_operator/cookie_jar"
|
2
2
|
|
3
|
+
module SmoothOperator
|
3
4
|
module RemoteCall
|
4
5
|
|
5
6
|
class Base
|
@@ -54,7 +55,7 @@ module SmoothOperator
|
|
54
55
|
begin
|
55
56
|
JSON.parse(body)
|
56
57
|
rescue JSON::ParserError
|
57
|
-
|
58
|
+
body
|
58
59
|
end
|
59
60
|
end
|
60
61
|
|
@@ -62,14 +63,14 @@ module SmoothOperator
|
|
62
63
|
error? ? nil : ok?
|
63
64
|
end
|
64
65
|
|
65
|
-
def objects
|
66
|
-
object.respond_to?(:length) ? object : []
|
67
|
-
end
|
68
|
-
|
69
66
|
def data
|
70
67
|
object.nil? ? parsed_response : object
|
71
68
|
end
|
72
69
|
|
70
|
+
def cookie(header_field = 'set-cookie')
|
71
|
+
CookieJar.new.parse(headers[header_field])
|
72
|
+
end
|
73
|
+
|
73
74
|
end
|
74
75
|
end
|
75
76
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module SmoothOperator
|
2
|
+
module ResourceName
|
3
|
+
|
4
|
+
def resources_name
|
5
|
+
get_option :resources_name, self.resource_name.pluralize
|
6
|
+
end
|
7
|
+
|
8
|
+
def resource_name
|
9
|
+
get_option :resource_name, self.model_name.to_s.underscore
|
10
|
+
end
|
11
|
+
|
12
|
+
def model_name
|
13
|
+
return '' if custom_model_name == :none
|
14
|
+
|
15
|
+
if defined? ActiveModel
|
16
|
+
smooth_model_name
|
17
|
+
else
|
18
|
+
custom_model_name ||= name.split('::').last.underscore.capitalize
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def model_name=(name)
|
23
|
+
@custom_model_name = name
|
24
|
+
end
|
25
|
+
|
26
|
+
def custom_model_name
|
27
|
+
Helpers.get_instance_variable(self, :custom_model_name, nil)
|
28
|
+
end
|
29
|
+
|
30
|
+
protected ############## PROTECTED #############
|
31
|
+
|
32
|
+
def smooth_model_name
|
33
|
+
@_model_name ||= begin
|
34
|
+
namespace ||= self.parents.detect do |n|
|
35
|
+
n.respond_to?(:use_relative_model_naming?) && n.use_relative_model_naming?
|
36
|
+
end
|
37
|
+
|
38
|
+
ActiveModel::Name.new(self, namespace, custom_model_name).tap do |model_name|
|
39
|
+
def model_name.human(options = {}); SmoothOperator::Translation::HelperMethods.translate("models.#{i18n_key}", options); end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module SmoothOperator
|
2
|
+
module Schema
|
3
|
+
|
4
|
+
def schema(structure)
|
5
|
+
internal_structure.merge! Helpers.stringify_keys(structure)
|
6
|
+
end
|
7
|
+
|
8
|
+
def internal_structure
|
9
|
+
Helpers.get_instance_variable(self, :internal_structure, {})
|
10
|
+
end
|
11
|
+
|
12
|
+
def known_attribute?(attribute)
|
13
|
+
internal_structure.has_key?(attribute.to_s)
|
14
|
+
end
|
15
|
+
|
16
|
+
def attribute_type(attribute)
|
17
|
+
internal_structure[attribute.to_s]
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -5,13 +5,12 @@ module SmoothOperator
|
|
5
5
|
def to_hash(options = nil)
|
6
6
|
Helpers.symbolyze_keys(serializable_hash(options) || {})
|
7
7
|
end
|
8
|
-
|
9
|
-
# alias :attributes :to_hash
|
8
|
+
|
10
9
|
def attributes; to_hash; end
|
11
|
-
|
10
|
+
|
12
11
|
def to_json(options = nil)
|
13
12
|
require 'json' unless defined? JSON
|
14
|
-
|
13
|
+
|
15
14
|
JSON(serializable_hash(options) || {})
|
16
15
|
end
|
17
16
|
|
@@ -22,58 +21,103 @@ module SmoothOperator
|
|
22
21
|
def serializable_hash(options = nil)
|
23
22
|
hash = {}
|
24
23
|
options ||= {}
|
24
|
+
method_names = HelperMethods.method_names(self, options)
|
25
|
+
attribute_names = HelperMethods.attribute_names(self, options)
|
26
|
+
|
27
|
+
attribute_names.each do |attribute_name|
|
28
|
+
attribute_name, attribute_value = HelperMethods
|
29
|
+
.serialize_normal_or_rails_way(self, attribute_name, options)
|
25
30
|
|
26
|
-
|
27
|
-
hash[attribute_name] = read_attribute_for_hashing(attribute_name, options)
|
31
|
+
hash[attribute_name] = attribute_value
|
28
32
|
end
|
29
33
|
|
30
|
-
method_names
|
31
|
-
hash[method_name.to_s] = send(method_name)
|
34
|
+
method_names.each do |method_name|
|
35
|
+
hash[method_name.to_s] = HelperMethods.serialize_normal_attribute(send(method_name), method_name, options[method_name])
|
32
36
|
end
|
33
37
|
|
34
38
|
hash
|
35
39
|
end
|
36
40
|
|
41
|
+
module HelperMethods
|
42
|
+
|
43
|
+
extend self
|
44
|
+
|
45
|
+
def attribute_names(object, options)
|
46
|
+
attribute_names = object.internal_data.keys.sort
|
47
|
+
|
48
|
+
if only = options[:only]
|
49
|
+
white_list = [*only].map(&:to_s)
|
37
50
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
def attribute_names(options)
|
42
|
-
attribute_names = internal_data.keys.sort
|
51
|
+
attribute_names &= white_list
|
52
|
+
elsif except = options[:except]
|
53
|
+
black_list = [*except].map(&:to_s)
|
43
54
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
attribute_names
|
55
|
+
attribute_names -= black_list
|
56
|
+
end
|
57
|
+
|
58
|
+
attribute_names
|
48
59
|
end
|
49
60
|
|
50
|
-
|
51
|
-
|
61
|
+
def method_names(object, options)
|
62
|
+
[*options[:methods]].select { |n| object.respond_to?(n) }
|
63
|
+
end
|
52
64
|
|
53
|
-
|
54
|
-
|
55
|
-
|
65
|
+
def serialize_normal_or_rails_way(object, attribute_name, options)
|
66
|
+
_attribute_name, attribute_sym = attribute_name, attribute_name.to_sym
|
67
|
+
|
68
|
+
reflection = object.class.respond_to?(:reflect_on_association) &&
|
69
|
+
object.class.reflect_on_association(attribute_sym)
|
70
|
+
|
71
|
+
attribute_options = options[attribute_sym]
|
56
72
|
|
57
|
-
|
58
|
-
|
73
|
+
if reflection && reflection.rails_serialization?
|
74
|
+
attribute_value = serialize_has_many_attribute(object, reflection, attribute_name, attribute_options)
|
59
75
|
|
60
|
-
|
76
|
+
_attribute_name = "#{attribute_name}_attributes"
|
77
|
+
end
|
61
78
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
attribute_to_hash(object, _options)
|
79
|
+
attribute_value ||= serialize_normal_attribute(object, attribute_name, attribute_options)
|
80
|
+
|
81
|
+
[_attribute_name, attribute_value]
|
66
82
|
end
|
67
|
-
end
|
68
83
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
84
|
+
def serialize_has_many_attribute(parent_object, reflection, attribute_name, options)
|
85
|
+
return nil unless reflection.has_many?
|
86
|
+
|
87
|
+
object = parent_object.read_attribute_for_serialization(attribute_name)
|
88
|
+
|
89
|
+
object.reduce({}) do |hash, array_entry|
|
90
|
+
id = Helpers.present?(array_entry.id) ? array_entry.id : Helpers.generated_id
|
91
|
+
|
92
|
+
hash[id.to_s] = attribute_to_hash(array_entry, options)
|
93
|
+
|
94
|
+
hash
|
95
|
+
end
|
74
96
|
end
|
97
|
+
|
98
|
+
def serialize_normal_attribute(parent_object, attribute_name, options)
|
99
|
+
if parent_object.respond_to?(:read_attribute_for_serialization)
|
100
|
+
object = parent_object.read_attribute_for_serialization(attribute_name)
|
101
|
+
else
|
102
|
+
object = parent_object
|
103
|
+
end
|
104
|
+
|
105
|
+
if object.is_a?(Array)
|
106
|
+
object.map { |array_entry| attribute_to_hash(array_entry, options) }
|
107
|
+
else
|
108
|
+
attribute_to_hash(object, options)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def attribute_to_hash(object, options = nil)
|
113
|
+
if object.respond_to?(:serializable_hash)
|
114
|
+
Helpers.symbolyze_keys(object.serializable_hash(options))
|
115
|
+
else
|
116
|
+
object
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
75
120
|
end
|
76
121
|
|
77
122
|
end
|
78
|
-
|
79
123
|
end
|
@@ -2,23 +2,32 @@ module SmoothOperator
|
|
2
2
|
module Translation
|
3
3
|
|
4
4
|
def human_attribute_name(attribute_key_name, options = {})
|
5
|
-
|
5
|
+
HelperMethods.translate(
|
6
|
+
"attributes.#{model_name.i18n_key}.#{attribute_key_name}",
|
7
|
+
options
|
8
|
+
)
|
6
9
|
end
|
7
10
|
|
8
|
-
|
11
|
+
module HelperMethods
|
9
12
|
|
10
|
-
|
11
|
-
no_translation = "-- no translation --"
|
13
|
+
extend self
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
def translate(namespace = '', options = {})
|
16
|
+
no_translation = "-- no translation --"
|
17
|
+
|
18
|
+
defaults = ["smooth_operator.#{namespace}".to_sym]
|
19
|
+
defaults << "activerecord.#{namespace}".to_sym
|
20
|
+
defaults << options[:default] if options[:default]
|
21
|
+
defaults.flatten!
|
22
|
+
defaults << no_translation
|
23
|
+
|
24
|
+
options = { count: 1, default: defaults }
|
25
|
+
.merge!(options.except(:default))
|
26
|
+
|
27
|
+
I18n.translate(defaults.shift, options)
|
28
|
+
end
|
18
29
|
|
19
|
-
options = { count: 1, default: defaults }.merge!(options.except(:default))
|
20
|
-
I18n.translate(defaults.shift, options)
|
21
30
|
end
|
22
31
|
|
23
32
|
end
|
24
|
-
end
|
33
|
+
end
|
@@ -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.nil?
|
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,37 @@
|
|
1
1
|
module SmoothOperator
|
2
|
-
|
3
2
|
module Validations
|
4
3
|
|
5
4
|
def valid?(context = nil)
|
6
|
-
Helpers.blank?(
|
5
|
+
Helpers.blank?(induced_errors)
|
7
6
|
end
|
8
7
|
|
9
8
|
def invalid?
|
10
9
|
!valid?
|
11
10
|
end
|
12
11
|
|
13
|
-
|
12
|
+
def induced_errors
|
13
|
+
@induced_errors ||= {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def clear_induced_errors
|
17
|
+
@induced_errors = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def induce_errors(value)
|
21
|
+
@induced_errors = value
|
22
|
+
end
|
14
23
|
|
24
|
+
def self.included(base)
|
25
|
+
base.extend(ClassMethods)
|
26
|
+
end
|
27
|
+
|
28
|
+
module ClassMethods
|
29
|
+
|
30
|
+
def errors_key
|
31
|
+
get_option :errors_key, 'errors'
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
15
37
|
end
|