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.
Files changed (52) hide show
  1. checksums.yaml +8 -8
  2. data/README.md +78 -22
  3. data/console.rb +2 -0
  4. data/lib/smooth_operator/array_with_meta_data.rb +20 -8
  5. data/lib/smooth_operator/{relation → associations}/association_reflection.rb +8 -8
  6. data/lib/smooth_operator/{relation/array_relation.rb → associations/has_many_relation.rb} +3 -13
  7. data/lib/smooth_operator/{relation → associations}/reflection.rb +2 -2
  8. data/lib/smooth_operator/associations.rb +110 -0
  9. data/lib/smooth_operator/attribute_assignment.rb +52 -60
  10. data/lib/smooth_operator/cookie_jar.rb +21 -0
  11. data/lib/smooth_operator/delegation.rb +13 -34
  12. data/lib/smooth_operator/finder_methods.rb +26 -17
  13. data/lib/smooth_operator/helpers.rb +14 -8
  14. data/lib/smooth_operator/http_methods.rb +17 -0
  15. data/lib/smooth_operator/internal_data.rb +45 -0
  16. data/lib/smooth_operator/open_struct.rb +11 -26
  17. data/lib/smooth_operator/operator.rb +65 -59
  18. data/lib/smooth_operator/operators/connection_wrapper.rb +15 -0
  19. data/lib/smooth_operator/operators/faraday.rb +6 -6
  20. data/lib/smooth_operator/operators/typhoeus.rb +22 -12
  21. data/lib/smooth_operator/options.rb +30 -0
  22. data/lib/smooth_operator/persistence.rb +64 -61
  23. data/lib/smooth_operator/remote_call/base.rb +7 -6
  24. data/lib/smooth_operator/resource_name.rb +46 -0
  25. data/lib/smooth_operator/schema.rb +21 -0
  26. data/lib/smooth_operator/serialization.rb +80 -36
  27. data/lib/smooth_operator/translation.rb +21 -12
  28. data/lib/smooth_operator/type_casting.rb +127 -0
  29. data/lib/smooth_operator/validations.rb +25 -3
  30. data/lib/smooth_operator/version.rb +1 -1
  31. data/lib/smooth_operator.rb +55 -5
  32. data/smooth_operator.gemspec +5 -5
  33. data/spec/smooth_operator/attribute_assignment_spec.rb +5 -14
  34. data/spec/smooth_operator/finder_methods_spec.rb +4 -9
  35. data/spec/smooth_operator/persistence_spec.rb +27 -19
  36. data/spec/smooth_operator/remote_call_spec.rb +104 -84
  37. data/spec/smooth_operator/{model_schema_spec.rb → resource_name_spec.rb} +1 -1
  38. data/spec/support/models/address.rb +8 -10
  39. data/spec/support/models/comment.rb +2 -0
  40. data/spec/support/models/post.rb +7 -7
  41. data/spec/support/models/user.rb +10 -13
  42. data/spec/support/models/user_with_address_and_posts.rb +9 -17
  43. data/spec/support/test_server.rb +7 -7
  44. metadata +25 -25
  45. data/lib/smooth_operator/attribute_methods.rb +0 -78
  46. data/lib/smooth_operator/attributes/base.rb +0 -107
  47. data/lib/smooth_operator/attributes/dirty.rb +0 -29
  48. data/lib/smooth_operator/attributes/normal.rb +0 -15
  49. data/lib/smooth_operator/blank_slate.rb +0 -7
  50. data/lib/smooth_operator/model_schema.rb +0 -81
  51. data/lib/smooth_operator/relation/associations.rb +0 -102
  52. 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 get_primary_key
11
- get_internal_data(self.class.primary_key)
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
- persistence_call(:reload, relative_path, data, options) do |remote_call|
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 new_record?(bypass_cache = false)
23
- return @new_record if !bypass_cache && defined?(@new_record)
24
-
25
- @new_record = Helpers.blank?(get_primary_key)
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
- def marked_for_destruction?(bypass_cache = false)
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 = ["true", "1", true].include?(get_internal_data(self.class.destroy_key))
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 save(relative_path = nil, data = {}, options = {})
45
- data = data_with_object_attributes(data, options)
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
- if new_record?
48
- create(relative_path, data, options) { |remote_call| block_given? ? yield(remote_call) : remote_call.status }
49
- else
50
- update(relative_path, data, options) { |remote_call| block_given? ? yield(remote_call) : remote_call.status }
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!(relative_path = nil, data = {}, options = {})
55
- save(relative_path, data, options) do |remote_call|
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 || raise('RecordNotSaved')
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
- persistence_call(:destroy, relative_path, data, options) do |remote_call|
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 create(relative_path, data, options)
74
- persistence_call(:create, relative_path, data, options) do |remote_call|
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
- block_given? ? yield(remote_call) : remote_call
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 update(relative_path, data, options)
82
- persistence_call(:update, relative_path, data, options) do |remote_call|
83
- block_given? ? yield(remote_call) : remote_call
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
- def persistence_call(method, relative_path, data, options)
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.methods_vs_http_verbs[method]
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, from_server: true
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 methods_vs_http_verbs
119
- Helpers.get_instance_variable(self, :methods_vs_http_verbs, METHODS_VS_HTTP_VERBS.dup)
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
- Helpers.get_instance_variable(self, :primary_key, 'id')
132
+ get_option :primary_key, 'id'
124
133
  end
125
134
 
126
- attr_writer :primary_key
127
-
128
135
  def destroy_key
129
- Helpers.get_instance_variable(self, :destroy_key, '_destroy')
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 { |object| object.save(relative_path, data, options) }
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
- module SmoothOperator
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
- nil
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
- attribute_names(options).each do |attribute_name|
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(options).each do |method_name|
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
- protected ##################### PROTECTED ###################
39
-
40
- # TODO: COMPLEX METHOD
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
- if only = options[:only]
45
- attribute_names &= [*only].map(&:to_s)
46
- elsif except = options[:except]
47
- attribute_names -= [*except].map(&:to_s)
55
+ attribute_names -= black_list
56
+ end
57
+
58
+ attribute_names
48
59
  end
49
60
 
50
- attribute_names
51
- end
61
+ def method_names(object, options)
62
+ [*options[:methods]].select { |n| object.respond_to?(n) }
63
+ end
52
64
 
53
- def method_names(options)
54
- [*options[:methods]].select { |n| respond_to?(n) }
55
- end
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
- def read_attribute_for_hashing(attribute_name, options)
58
- object = read_attribute_for_serialization(attribute_name)
73
+ if reflection && reflection.rails_serialization?
74
+ attribute_value = serialize_has_many_attribute(object, reflection, attribute_name, attribute_options)
59
75
 
60
- _options = options[attribute_name] || options[attribute_name.to_sym]
76
+ _attribute_name = "#{attribute_name}_attributes"
77
+ end
61
78
 
62
- if object.is_a?(Array)
63
- object.map { |array_entry| attribute_to_hash(array_entry, _options) }
64
- else
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
- def attribute_to_hash(object, options = nil)
70
- if object.respond_to?(:serializable_hash)
71
- Helpers.symbolyze_keys(object.serializable_hash(options))
72
- else
73
- object
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
- _translate("attributes.#{model_name.i18n_key}.#{attribute_key_name}", options = {})
5
+ HelperMethods.translate(
6
+ "attributes.#{model_name.i18n_key}.#{attribute_key_name}",
7
+ options
8
+ )
6
9
  end
7
10
 
8
- private ###################### PRIVATE #########################
11
+ module HelperMethods
9
12
 
10
- def _translate(namespace = '', options = {})
11
- no_translation = "-- no translation --"
13
+ extend self
12
14
 
13
- defaults = ["smooth_operator.#{namespace}".to_sym]
14
- defaults << "activerecord.#{namespace}".to_sym
15
- defaults << options[:default] if options[:default]
16
- defaults.flatten!
17
- defaults << no_translation
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?(get_internal_data("errors"))
5
+ Helpers.blank?(induced_errors)
7
6
  end
8
7
 
9
8
  def invalid?
10
9
  !valid?
11
10
  end
12
11
 
13
- end
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
@@ -1,3 +1,3 @@
1
1
  module SmoothOperator
2
- VERSION = "1.2.9"
2
+ VERSION = '1.3.0'
3
3
  end