superstore 2.4.3 → 3.0.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 (101) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ruby.yml +40 -0
  3. data/.gitignore +1 -0
  4. data/.simplecov +3 -0
  5. data/Gemfile +2 -3
  6. data/LICENSE +1 -1
  7. data/MIT-LICENSE +1 -1
  8. data/README.md +4 -34
  9. data/lib/superstore/adapters/abstract_adapter.rb +1 -27
  10. data/lib/superstore/adapters/jsonb_adapter.rb +4 -132
  11. data/lib/superstore/associations/association.rb +6 -0
  12. data/lib/superstore/associations/association_scope.rb +20 -0
  13. data/lib/superstore/associations/belongs_to.rb +3 -1
  14. data/lib/superstore/associations/builder/association.rb +1 -1
  15. data/lib/superstore/associations/has_many.rb +15 -2
  16. data/lib/superstore/associations/reflection.rb +8 -2
  17. data/lib/superstore/associations.rb +9 -4
  18. data/lib/superstore/attribute_assignment.rb +7 -0
  19. data/lib/superstore/attribute_methods/primary_key.rb +20 -11
  20. data/lib/superstore/attribute_methods.rb +1 -109
  21. data/lib/superstore/attributes.rb +13 -0
  22. data/lib/superstore/base.rb +6 -34
  23. data/lib/superstore/core.rb +7 -65
  24. data/lib/superstore/model_schema.rb +35 -0
  25. data/lib/superstore/persistence.rb +26 -115
  26. data/lib/superstore/railtie.rb +3 -11
  27. data/lib/superstore/relation/scrolling.rb +48 -0
  28. data/lib/superstore/types/array_type.rb +3 -7
  29. data/lib/superstore/types/base.rb +9 -0
  30. data/lib/superstore/types/boolean_type.rb +7 -12
  31. data/lib/superstore/types/date_range_type.rb +7 -0
  32. data/lib/superstore/types/date_type.rb +7 -10
  33. data/lib/superstore/types/float_type.rb +3 -11
  34. data/lib/superstore/types/geo_point_type.rb +30 -0
  35. data/lib/superstore/types/integer_range_type.rb +15 -0
  36. data/lib/superstore/types/integer_type.rb +8 -14
  37. data/lib/superstore/types/json_type.rb +1 -1
  38. data/lib/superstore/types/range_type.rb +58 -0
  39. data/lib/superstore/types/string_type.rb +4 -4
  40. data/lib/superstore/types/time_type.rb +10 -8
  41. data/lib/superstore/types.rb +11 -9
  42. data/lib/superstore.rb +17 -19
  43. data/superstore.gemspec +8 -10
  44. data/test/support/jsonb.rb +3 -1
  45. data/test/support/models.rb +10 -4
  46. data/test/test_helper.rb +7 -3
  47. data/test/unit/adapters/adapter_test.rb +1 -3
  48. data/test/unit/associations/belongs_to_test.rb +1 -1
  49. data/test/unit/associations/has_many_test.rb +10 -2
  50. data/test/unit/attribute_methods/dirty_test.rb +8 -19
  51. data/test/unit/attribute_methods/primary_key_test.rb +1 -1
  52. data/test/unit/attribute_methods_test.rb +10 -22
  53. data/test/unit/{attribute_methods/typecasting_test.rb → attributes_test.rb} +13 -39
  54. data/test/unit/base_test.rb +4 -0
  55. data/test/unit/caching_test.rb +1 -1
  56. data/test/unit/callbacks_test.rb +4 -4
  57. data/test/unit/core_test.rb +15 -20
  58. data/test/unit/persistence_test.rb +36 -44
  59. data/test/unit/{scope/batches_test.rb → relation/scrolling_test.rb} +9 -5
  60. data/test/unit/serialization_test.rb +10 -2
  61. data/test/unit/{timestamps_test.rb → timestamp_test.rb} +5 -5
  62. data/test/unit/types/array_type_test.rb +3 -18
  63. data/test/unit/types/boolean_type_test.rb +7 -21
  64. data/test/unit/types/date_range_type_test.rb +29 -0
  65. data/test/unit/types/date_type_test.rb +15 -6
  66. data/test/unit/types/float_type_test.rb +4 -19
  67. data/test/unit/types/geo_point_type_test.rb +28 -0
  68. data/test/unit/types/integer_range_type_test.rb +28 -0
  69. data/test/unit/types/integer_type_test.rb +7 -16
  70. data/test/unit/types/string_type_test.rb +9 -13
  71. data/test/unit/types/time_type_test.rb +17 -11
  72. data/test/unit/validations_test.rb +2 -2
  73. metadata +39 -53
  74. data/.travis.yml +0 -13
  75. data/Gemfile-rails4.2 +0 -11
  76. data/lib/superstore/attribute_methods/definition.rb +0 -17
  77. data/lib/superstore/attribute_methods/dirty.rb +0 -52
  78. data/lib/superstore/attribute_methods/typecasting.rb +0 -53
  79. data/lib/superstore/caching.rb +0 -13
  80. data/lib/superstore/callbacks.rb +0 -29
  81. data/lib/superstore/connection.rb +0 -24
  82. data/lib/superstore/errors.rb +0 -10
  83. data/lib/superstore/inspect.rb +0 -25
  84. data/lib/superstore/model.rb +0 -38
  85. data/lib/superstore/schema.rb +0 -20
  86. data/lib/superstore/scope/batches.rb +0 -27
  87. data/lib/superstore/scope/finder_methods.rb +0 -51
  88. data/lib/superstore/scope/query_methods.rb +0 -52
  89. data/lib/superstore/scope.rb +0 -73
  90. data/lib/superstore/scoping.rb +0 -30
  91. data/lib/superstore/timestamps.rb +0 -19
  92. data/lib/superstore/type.rb +0 -16
  93. data/lib/superstore/types/base_type.rb +0 -23
  94. data/lib/superstore/validations.rb +0 -44
  95. data/test/unit/attribute_methods/definition_test.rb +0 -16
  96. data/test/unit/inspect_test.rb +0 -26
  97. data/test/unit/schema_test.rb +0 -15
  98. data/test/unit/scope/finder_methods_test.rb +0 -62
  99. data/test/unit/scope/query_methods_test.rb +0 -36
  100. data/test/unit/scoping_test.rb +0 -7
  101. data/test/unit/types/base_type_test.rb +0 -11
@@ -1,117 +1,9 @@
1
1
  module Superstore
2
2
  module AttributeMethods
3
3
  extend ActiveSupport::Concern
4
- include ActiveModel::AttributeMethods
5
4
 
6
5
  included do
7
- attribute_method_suffix("=")
8
-
9
- # (Alias for the protected read_attribute method).
10
- def [](attr_name)
11
- read_attribute(attr_name)
12
- end
13
-
14
- # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
15
- # (Alias for the protected write_attribute method).
16
- def []=(attr_name, value)
17
- write_attribute(attr_name, value)
18
- end
19
- end
20
-
21
- module ClassMethods
22
- def inherited(child_class)
23
- child_class.define_attribute_methods
24
- super
25
- end
26
-
27
- def define_attribute_methods
28
- return if attribute_methods_generated?
29
- super(attribute_definitions.keys)
30
- @attribute_methods_generated = true
31
- end
32
-
33
- def attribute_methods_generated?
34
- @attribute_methods_generated ||= false
35
- end
36
-
37
- def dangerous_attribute_method?(name)
38
- false
39
- end
40
-
41
- def has_attribute?(attr_name)
42
- attribute_definitions.key?(attr_name.to_s)
43
- end
44
- end
45
-
46
- def write_attribute(name, value)
47
- @attributes[name.to_s] = self.class.typecast_attribute(name, value)
48
- end
49
-
50
- def read_attribute(name)
51
- name = name.to_s unless name.is_a?(String)
52
-
53
- if name == self.class.primary_key
54
- send(name)
55
- else
56
- @attributes[name]
57
- end
58
- end
59
- alias_method :_read_attribute, :read_attribute
60
-
61
- def attribute_present?(attribute)
62
- value = _read_attribute(attribute)
63
- !value.nil? && !(value.respond_to?(:empty?) && value.empty?)
6
+ include PrimaryKey
64
7
  end
65
-
66
- def has_attribute?(name)
67
- @attributes.key?(name.to_s)
68
- end
69
- alias_method :attribute_exists?, :has_attribute?
70
-
71
- def attributes
72
- results = {}
73
- @attributes.each_key do |key|
74
- results[key] = read_attribute(key)
75
- end
76
- results
77
- end
78
-
79
- def attributes=(attributes)
80
- attributes.each do |(name, value)|
81
- send("#{name}=", value)
82
- end
83
- end
84
-
85
- def method_missing(method_id, *args, &block)
86
- self.class.define_attribute_methods unless self.class.attribute_methods_generated?
87
-
88
- match = if ActiveRecord.version >= Gem::Version.new('5.0')
89
- # Active Record 5.0
90
- matched_attribute_method(method_id.to_s)
91
- else
92
- # Active Record 4.2
93
- match_attribute_method?(method_id.to_s)
94
- end
95
- match ? attribute_missing(match, *args, &block) : super
96
- end
97
-
98
- def respond_to?(*args)
99
- self.class.define_attribute_methods unless self.class.attribute_methods_generated?
100
- super
101
- end
102
-
103
- protected
104
- def attribute_method?(name)
105
- !!attribute_definitions[name.to_s]
106
- end
107
-
108
- private
109
- def attribute(name)
110
- read_attribute(name)
111
- end
112
-
113
- def attribute=(name, value)
114
- write_attribute(name, value)
115
- end
116
8
  end
117
9
  end
@@ -0,0 +1,13 @@
1
+ module Superstore
2
+ module Attributes
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def attribute(name, options)
7
+ type_name = "superstore_#{options.fetch(:type)}".to_sym
8
+
9
+ super(name, type_name)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,44 +1,16 @@
1
- require 'set'
2
-
3
1
  require 'superstore/types'
4
2
 
5
3
  module Superstore
6
- class Base
7
-
8
- extend ActiveModel::Naming
9
- include ActiveModel::Conversion
10
- extend ActiveSupport::DescendantsTracker
11
- include ActiveModel::Serializers::JSON
12
- include GlobalID::Identification
13
-
14
- extend ActiveRecord::Delegation::DelegateCache
15
- extend ActiveRecord::ConnectionHandling
16
- include ActiveRecord::Persistence
17
- include ActiveRecord::ModelSchema
18
- include ActiveRecord::Inheritance
19
- include ActiveRecord::Attributes
20
- include ActiveRecord::Associations
21
- include ActiveRecord::AutosaveAssociation
22
- include ActiveRecord::Reflection
23
-
24
- include Model
4
+ class Base < ActiveRecord::Base
5
+ self.abstract_class = true
25
6
  include Core
26
- include Connection
27
- include Identity
28
- include Inspect
29
7
  include Persistence
8
+ include ModelSchema
9
+ include AttributeAssignment
10
+ include Attributes
30
11
  include AttributeMethods
31
- include Validations
32
- include AttributeMethods::Dirty
33
- include AttributeMethods::PrimaryKey
34
- include AttributeMethods::Typecasting
35
12
  include Associations
36
- include Callbacks
37
- include Timestamps
38
- include Scoping
39
- include Caching
40
13
 
14
+ include Identity
41
15
  end
42
16
  end
43
-
44
- ActiveSupport.run_load_hooks(:superstore, Superstore::Base)
@@ -2,76 +2,18 @@ module Superstore
2
2
  module Core
3
3
  extend ActiveSupport::Concern
4
4
 
5
- module ClassMethods
6
- def inspect
7
- if self == Base
8
- super
9
- else
10
- attr_list = attribute_definitions.keys * ', '
11
- "#{super}(#{attr_list.truncate(140 * 1.7337)})"
12
- end
13
- end
5
+ def inspect
6
+ inspection = ["#{self.class.primary_key}: #{id.inspect}"]
14
7
 
15
- def initialize_generated_modules # :nodoc:
16
- generated_association_methods
17
- end
8
+ (self.class.attribute_names - [self.class.primary_key]).each do |name|
9
+ value = send(name)
18
10
 
19
- def generated_association_methods
20
- @generated_association_methods ||= begin
21
- mod = const_set(:GeneratedAssociationMethods, Module.new)
22
- include mod
23
- mod
11
+ if value.present? || value === false
12
+ inspection << "#{name}: #{attribute_for_inspect(name)}"
24
13
  end
25
14
  end
26
15
 
27
- def arel_table # :nodoc:
28
- @arel_table ||= Arel::Table.new(table_name, self)
29
- end
30
-
31
- def subclass_from_attributes?(attrs)
32
- false
33
- end
34
- end
35
-
36
- def initialize(attributes=nil)
37
- @new_record = true
38
- @destroyed = false
39
- @association_cache = {}
40
-
41
- @attributes = {}
42
- self.attributes = attributes || {}
43
-
44
- yield self if block_given?
45
- end
46
-
47
- def initialize_dup(other)
48
- @attributes = other.attributes
49
- @attributes['created_at'] = nil
50
- @attributes['updated_at'] = nil
51
- @attributes.delete(self.class.primary_key)
52
- @id = nil
53
- @new_record = true
54
- @destroyed = false
55
- @association_cache = {}
56
- super
57
- end
58
-
59
- def to_param
60
- id
61
- end
62
-
63
- def hash
64
- id.hash
65
- end
66
-
67
- def ==(comparison_object)
68
- comparison_object.equal?(self) ||
69
- (comparison_object.instance_of?(self.class) &&
70
- comparison_object.id == id)
71
- end
72
-
73
- def eql?(comparison_object)
74
- self == (comparison_object)
16
+ "#<#{self.class} #{inspection * ', '}>"
75
17
  end
76
18
  end
77
19
  end
@@ -0,0 +1,35 @@
1
+ module Superstore
2
+ module ModelSchema
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :superstore_column, default: 'document'
7
+ end
8
+
9
+ module ClassMethods
10
+ def attributes_builder # :nodoc:
11
+ @attributes_builder ||= ActiveModel::AttributeSet::Builder.new(attribute_types, _default_attributes)
12
+ end
13
+
14
+ def load_schema! # :nodoc:
15
+ @columns_hash = {}
16
+
17
+ attributes_to_define_after_schema_loads.each do |name, (type, options)|
18
+ if type.is_a?(Symbol)
19
+ type = ActiveRecord::Type.lookup(type, **options.except(:default))
20
+ end
21
+
22
+ define_attribute(name, type, **options.slice(:default))
23
+ end
24
+ end
25
+
26
+ def attribute_names
27
+ attribute_types.keys
28
+ end
29
+
30
+ def column_names
31
+ attribute_names
32
+ end
33
+ end
34
+ end
35
+ end
@@ -2,140 +2,51 @@ module Superstore
2
2
  module Persistence
3
3
  extend ActiveSupport::Concern
4
4
 
5
- included do
6
- class_attribute :batch_statements
7
- end
8
-
9
5
  module ClassMethods
10
- def delete(ids)
11
- adapter.delete table_name, ids
12
- end
13
-
14
- def delete_all
15
- adapter.execute "TRUNCATE #{table_name}"
6
+ def find_by_id(id)
7
+ find_by(id: id)
16
8
  end
17
9
 
18
- def insert_record(id, attributes)
19
- adapter.insert table_name, id, encode_attributes(attributes)
20
- end
10
+ def _insert_record(attributes)
11
+ id = attributes.fetch(primary_key)
21
12
 
22
- def update_record(id, attributes)
23
- adapter.update table_name, id, encode_attributes(attributes)
13
+ adapter.insert table_name, id, serialize_attributes(attributes)
24
14
  end
25
15
 
26
- def batching?
27
- adapter.batching?
28
- end
16
+ def _update_record(attributes, constraints)
17
+ id = constraints.fetch(primary_key)
29
18
 
30
- def batch(&block)
31
- adapter.batch(&block)
19
+ adapter.update table_name, id, serialize_attributes(attributes)
32
20
  end
33
21
 
34
- def instantiate(id, attributes)
35
- allocate.tap do |object|
36
- object.instance_variable_set("@id", id) if id
37
- object.instance_variable_set("@new_record", false)
38
- object.instance_variable_set("@destroyed", false)
39
- object.instance_variable_set("@attributes", typecast_persisted_attributes(attributes))
40
- object.instance_variable_set("@association_cache", {})
22
+ def serialize_attributes(attributes)
23
+ serialized = {}
24
+ attributes.each do |attr_name, value|
25
+ next if attr_name == primary_key
26
+ serialized[attr_name] = attribute_types[attr_name].serialize(value)
41
27
  end
42
- end
43
-
44
- def encode_attributes(attributes)
45
- encoded = {}
46
- attributes.each do |column_name, value|
47
- if value.nil?
48
- encoded[column_name] = nil
49
- else
50
- encoded[column_name] = attribute_definitions[column_name].type.encode(value)
51
- end
52
- end
53
- encoded
28
+ serialized
54
29
  end
55
30
 
56
31
  private
57
32
 
58
- def quote_columns(column_names)
59
- column_names.map { |name| "'#{name}'" }
60
- end
61
-
62
- def typecast_persisted_attributes(attributes)
63
- result = {}
64
-
65
- attributes.each do |key, value|
66
- if definition = attribute_definitions[key]
67
- result[key] = definition.instantiate(value)
68
- end
33
+ def instantiate_instance_of(klass, attributes, column_types = {}, &block)
34
+ if attributes[superstore_column].is_a?(String)
35
+ attributes = JSON.parse(attributes[superstore_column]).merge('id' => attributes['id'])
69
36
  end
70
37
 
71
- result
72
- end
73
- end
74
-
75
- def new_record?
76
- @new_record
77
- end
78
-
79
- def destroyed?
80
- @destroyed
81
- end
82
-
83
- def persisted?
84
- !(new_record? || destroyed?)
85
- end
86
-
87
- def save(*)
88
- create_or_update
89
- end
90
-
91
- def destroy
92
- self.class.delete(id)
93
- @destroyed = true
94
- end
95
-
96
- def update_attribute(name, value)
97
- name = name.to_s
98
- send("#{name}=", value)
99
- save(validate: false)
100
- end
101
-
102
- def update(attributes)
103
- self.attributes = attributes
104
- save
105
- end
38
+ if inheritance_column && attribute_types.key?(inheritance_column)
39
+ klass = find_sti_class(attributes[inheritance_column])
40
+ end
106
41
 
107
- alias update_attributes update
42
+ attributes.each_key { |k, v| attributes.delete(k) unless klass.attribute_types.key?(k) }
108
43
 
109
- def update!(attributes)
110
- self.attributes = attributes
111
- save!
112
- end
113
-
114
- alias update_attributes! update!
44
+ super(klass, attributes, column_types, &block)
45
+ end
115
46
 
116
- def reload
117
- clear_association_cache
118
- @attributes = self.class.find(id).instance_variable_get('@attributes')
119
- self
47
+ def adapter
48
+ @adapter ||= Superstore::Adapters::JsonbAdapter.new
49
+ end
120
50
  end
121
-
122
- private
123
-
124
- def create_or_update
125
- new_record? ? create_self : update_self
126
- end
127
-
128
- def create_self
129
- write :insert_record
130
- end
131
-
132
- def update_self
133
- write :update_record
134
- end
135
-
136
- def write(method)
137
- @new_record = false
138
- self.class.send(method, id, unapplied_changes)
139
- end
140
51
  end
141
52
  end
@@ -1,17 +1,9 @@
1
1
  module Superstore
2
2
  class Railtie < Rails::Railtie
3
3
  initializer "superstore.config" do |app|
4
- ActiveSupport.on_load :superstore do
5
- pathname = Rails.root.join('config', 'superstore.yml')
6
- if pathname.exist?
7
- config = ERB.new(pathname.read).result
8
- config = YAML.load(config)
9
-
10
- if config = config[Rails.env]
11
- self.config = config.symbolize_keys!
12
- else
13
- raise "Missing environment #{Rails.env} in superstore.yml"
14
- end
4
+ ActiveSupport.on_load :active_record do
5
+ ActiveRecord::Relation.class_eval do
6
+ include Superstore::Relation::Scrolling
15
7
  end
16
8
  end
17
9
  end
@@ -0,0 +1,48 @@
1
+ module Superstore
2
+ module Relation
3
+ module Scrolling
4
+ def scroll_each(options = {})
5
+ batch_size = options[:batch_size] || 1000
6
+
7
+ scroll_results(batch_size) do |attributes|
8
+ yield klass.instantiate(attributes)
9
+ end
10
+ end
11
+
12
+ def scroll_in_batches(options = {})
13
+ batch_size = options[:batch_size] || 1000
14
+ batch = []
15
+
16
+ scroll_each(options) do |record|
17
+ batch << record
18
+
19
+ if batch.size == batch_size
20
+ yield batch
21
+ batch = []
22
+ end
23
+ end
24
+
25
+ yield(batch) if batch.any?
26
+ end
27
+
28
+ private
29
+
30
+ def scroll_results(batch_size)
31
+ statement = to_sql
32
+ cursor_name = "cursor_#{SecureRandom.hex(6)}"
33
+ fetch_sql = "FETCH FORWARD #{batch_size} FROM #{cursor_name}"
34
+
35
+ connection.transaction do
36
+ connection.execute "DECLARE #{cursor_name} NO SCROLL CURSOR FOR (#{statement})"
37
+
38
+ while (batch = connection.execute(fetch_sql)).any?
39
+ batch.each do |result|
40
+ yield result
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ end
47
+ end
48
+ end
@@ -1,12 +1,8 @@
1
1
  module Superstore
2
2
  module Types
3
- class ArrayType < BaseType
4
- def decode(val)
5
- val unless val.blank?
6
- end
7
-
8
- def typecast(value)
9
- value.to_a
3
+ class ArrayType < Base
4
+ def cast_value(value)
5
+ Array(value)
10
6
  end
11
7
  end
12
8
  end
@@ -0,0 +1,9 @@
1
+ module Superstore
2
+ module Types
3
+ class Base < ActiveModel::Type::Value
4
+ def type
5
+ self.class.name.demodulize.sub(/Type$/, '').underscore
6
+ end
7
+ end
8
+ end
9
+ end
@@ -1,20 +1,15 @@
1
1
  module Superstore
2
2
  module Types
3
- class BooleanType < BaseType
3
+ class BooleanType < Base
4
4
  TRUE_VALS = [true, 'true', '1']
5
- FALSE_VALS = [false, 'false', '0', '', nil]
6
- VALID_VALS = TRUE_VALS + FALSE_VALS
5
+ FALSE_VALS = [false, 'false', '0']
7
6
 
8
- def encode(bool)
9
- unless VALID_VALS.include?(bool)
10
- raise ArgumentError.new("#{bool.inspect} is not a Boolean")
7
+ def cast_value(value)
8
+ if TRUE_VALS.include?(value)
9
+ true
10
+ elsif FALSE_VALS.include?(value)
11
+ false
11
12
  end
12
-
13
- TRUE_VALS.include?(bool)
14
- end
15
-
16
- def decode(str)
17
- TRUE_VALS.include?(str)
18
13
  end
19
14
  end
20
15
  end
@@ -0,0 +1,7 @@
1
+ module Superstore
2
+ module Types
3
+ class DateRangeType < RangeType
4
+ self.subtype = DateType.new
5
+ end
6
+ end
7
+ end
@@ -1,21 +1,18 @@
1
1
  module Superstore
2
2
  module Types
3
- class DateType < BaseType
3
+ class DateType < Base
4
4
  FORMAT = '%Y-%m-%d'
5
- REGEX = /\A\d{4}-\d{2}-\d{2}\Z/
6
5
 
7
- def encode(value)
8
- raise ArgumentError.new("#{value.inspect} is not a Date") unless value.kind_of?(Date)
9
- value.strftime(FORMAT)
6
+ def serialize(value)
7
+ value.strftime(FORMAT) if value
10
8
  end
11
9
 
12
- def decode(str)
13
- return nil if str.empty?
14
- Date.parse(str)
10
+ def deserialize(str)
11
+ Date.strptime(str, FORMAT) if str
15
12
  end
16
13
 
17
- def typecast(value)
18
- value.to_date
14
+ def cast_value(value)
15
+ value.to_date rescue nil
19
16
  end
20
17
  end
21
18
  end
@@ -1,16 +1,8 @@
1
1
  module Superstore
2
2
  module Types
3
- class FloatType < BaseType
4
- def encode(float)
5
- float
6
- end
7
-
8
- def decode(str)
9
- str.to_f unless str.empty?
10
- end
11
-
12
- def typecast(value)
13
- value.to_f
3
+ class FloatType < Base
4
+ def cast_value(value)
5
+ Float(value) rescue nil
14
6
  end
15
7
  end
16
8
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Superstore
4
+ module Types
5
+ class GeoPointType < Base
6
+ def deserialize(value)
7
+ {lat: value[:lat] || value['lat'], lon: value[:lon] || value['lon']} if value
8
+ end
9
+
10
+ def cast_value(value)
11
+ case value
12
+ when String
13
+ cast_value value.split(/[,\s]+/)
14
+ when Array
15
+ to_float_or_nil(lat: value[0], lon: value[1])
16
+ when Hash
17
+ to_float_or_nil(lat: value[:lat] || value['lat'], lon: value[:lon] || value['lon'])
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def to_float_or_nil(coords)
24
+ if coords[:lat] && coords[:lon]
25
+ coords.transform_values!(&:to_f)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end