smooth_operator 1.2.9 → 1.3.0

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