superstore 2.4.4 → 2.5.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 (95) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +4 -6
  3. data/Gemfile +2 -3
  4. data/{Gemfile-rails4.2 → Gemfile.rails6} +2 -3
  5. data/README.md +4 -34
  6. data/lib/superstore.rb +17 -18
  7. data/lib/superstore/adapters/abstract_adapter.rb +1 -27
  8. data/lib/superstore/adapters/jsonb_adapter.rb +4 -132
  9. data/lib/superstore/associations.rb +6 -1
  10. data/lib/superstore/associations/association.rb +6 -0
  11. data/lib/superstore/associations/association_scope.rb +20 -0
  12. data/lib/superstore/associations/belongs_to.rb +3 -1
  13. data/lib/superstore/associations/has_many.rb +15 -2
  14. data/lib/superstore/associations/reflection.rb +8 -2
  15. data/lib/superstore/attribute_assignment.rb +7 -0
  16. data/lib/superstore/attribute_methods.rb +1 -109
  17. data/lib/superstore/attribute_methods/primary_key.rb +20 -11
  18. data/lib/superstore/attributes.rb +13 -0
  19. data/lib/superstore/base.rb +8 -33
  20. data/lib/superstore/core.rb +7 -65
  21. data/lib/superstore/model_schema.rb +35 -0
  22. data/lib/superstore/persistence.rb +31 -115
  23. data/lib/superstore/railtie.rb +3 -11
  24. data/lib/superstore/relation/scrolling.rb +48 -0
  25. data/lib/superstore/timestamp.rb +13 -0
  26. data/lib/superstore/types.rb +11 -9
  27. data/lib/superstore/types/array_type.rb +3 -7
  28. data/lib/superstore/types/boolean_type.rb +7 -12
  29. data/lib/superstore/types/date_range_type.rb +7 -0
  30. data/lib/superstore/types/date_type.rb +7 -10
  31. data/lib/superstore/types/float_type.rb +3 -11
  32. data/lib/superstore/types/geo_point_type.rb +30 -0
  33. data/lib/superstore/types/integer_range_type.rb +19 -0
  34. data/lib/superstore/types/integer_type.rb +8 -14
  35. data/lib/superstore/types/json_type.rb +1 -1
  36. data/lib/superstore/types/range_type.rb +51 -0
  37. data/lib/superstore/types/string_type.rb +4 -4
  38. data/lib/superstore/types/time_type.rb +10 -8
  39. data/superstore.gemspec +4 -3
  40. data/test/support/jsonb.rb +3 -1
  41. data/test/support/models.rb +8 -5
  42. data/test/test_helper.rb +6 -2
  43. data/test/unit/adapters/adapter_test.rb +1 -3
  44. data/test/unit/associations/belongs_to_test.rb +1 -1
  45. data/test/unit/associations/has_many_test.rb +10 -2
  46. data/test/unit/attribute_methods/dirty_test.rb +8 -19
  47. data/test/unit/attribute_methods/primary_key_test.rb +1 -1
  48. data/test/unit/attribute_methods_test.rb +10 -22
  49. data/test/unit/{attribute_methods/typecasting_test.rb → attributes_test.rb} +13 -39
  50. data/test/unit/base_test.rb +4 -0
  51. data/test/unit/caching_test.rb +1 -1
  52. data/test/unit/callbacks_test.rb +4 -4
  53. data/test/unit/core_test.rb +9 -19
  54. data/test/unit/persistence_test.rb +17 -54
  55. data/test/unit/{scope/batches_test.rb → relation/scrolling_test.rb} +9 -5
  56. data/test/unit/serialization_test.rb +10 -2
  57. data/test/unit/{timestamps_test.rb → timestamp_test.rb} +5 -5
  58. data/test/unit/types/array_type_test.rb +3 -18
  59. data/test/unit/types/boolean_type_test.rb +7 -21
  60. data/test/unit/types/date_range_type_test.rb +28 -0
  61. data/test/unit/types/date_type_test.rb +15 -6
  62. data/test/unit/types/float_type_test.rb +4 -19
  63. data/test/unit/types/geo_point_type_test.rb +24 -0
  64. data/test/unit/types/integer_range_type_test.rb +28 -0
  65. data/test/unit/types/integer_type_test.rb +7 -16
  66. data/test/unit/types/string_type_test.rb +9 -13
  67. data/test/unit/types/time_type_test.rb +17 -11
  68. data/test/unit/validations_test.rb +2 -2
  69. metadata +39 -39
  70. data/lib/superstore/attribute_methods/definition.rb +0 -17
  71. data/lib/superstore/attribute_methods/dirty.rb +0 -52
  72. data/lib/superstore/attribute_methods/typecasting.rb +0 -53
  73. data/lib/superstore/caching.rb +0 -13
  74. data/lib/superstore/callbacks.rb +0 -29
  75. data/lib/superstore/connection.rb +0 -24
  76. data/lib/superstore/errors.rb +0 -10
  77. data/lib/superstore/inspect.rb +0 -25
  78. data/lib/superstore/model.rb +0 -38
  79. data/lib/superstore/schema.rb +0 -20
  80. data/lib/superstore/scope.rb +0 -73
  81. data/lib/superstore/scope/batches.rb +0 -27
  82. data/lib/superstore/scope/finder_methods.rb +0 -51
  83. data/lib/superstore/scope/query_methods.rb +0 -52
  84. data/lib/superstore/scoping.rb +0 -30
  85. data/lib/superstore/timestamps.rb +0 -19
  86. data/lib/superstore/type.rb +0 -16
  87. data/lib/superstore/types/base_type.rb +0 -23
  88. data/lib/superstore/validations.rb +0 -44
  89. data/test/unit/attribute_methods/definition_test.rb +0 -16
  90. data/test/unit/inspect_test.rb +0 -26
  91. data/test/unit/schema_test.rb +0 -15
  92. data/test/unit/scope/finder_methods_test.rb +0 -62
  93. data/test/unit/scope/query_methods_test.rb +0 -37
  94. data/test/unit/scoping_test.rb +0 -7
  95. data/test/unit/types/base_type_test.rb +0 -11
@@ -2,140 +2,56 @@ 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", {})
41
- 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)
22
+ if Rails.version >= '6.0'
23
+ def instantiate_instance_of(klass, attributes, column_types = {}, &block)
24
+ if attributes[superstore_column].is_a?(String)
25
+ attributes = JSON.parse(attributes[superstore_column]).merge('id' => attributes['id'])
51
26
  end
52
- end
53
- encoded
54
- end
55
27
 
56
- private
57
-
58
- def quote_columns(column_names)
59
- column_names.map { |name| "'#{name}'" }
28
+ super(klass, attributes, column_types, &block)
60
29
  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
30
+ private :instantiate_instance_of
31
+ else
32
+ def instantiate(attributes, column_types = {}, &block)
33
+ if attributes[superstore_column].is_a?(String)
34
+ attributes = JSON.parse(attributes[superstore_column]).merge('id' => attributes['id'])
69
35
  end
70
36
 
71
- result
37
+ super(attributes, column_types, &block)
72
38
  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
106
-
107
- alias update_attributes update
108
-
109
- def update!(attributes)
110
- self.attributes = attributes
111
- save!
112
- end
113
-
114
- alias update_attributes! update!
115
-
116
- def reload
117
- clear_association_cache
118
- @attributes = self.class.find(id).instance_variable_get('@attributes')
119
- self
120
- end
121
-
122
- private
123
-
124
- def create_or_update
125
- new_record? ? create_self : update_self
126
39
  end
127
40
 
128
- def create_self
129
- write :insert_record
41
+ def serialize_attributes(attributes)
42
+ serialized = {}
43
+ attributes.each do |attr_name, value|
44
+ next if attr_name == primary_key
45
+ serialized[attr_name] = attribute_types[attr_name].serialize(value)
46
+ end
47
+ serialized
130
48
  end
131
49
 
132
- def update_self
133
- write :update_record
134
- end
50
+ private
135
51
 
136
- def write(method)
137
- @new_record = false
138
- self.class.send(method, id, unapplied_changes)
139
- end
52
+ def adapter
53
+ @adapter ||= Superstore::Adapters::JsonbAdapter.new
54
+ end
55
+ end
140
56
  end
141
57
  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
@@ -0,0 +1,13 @@
1
+ module Superstore
2
+ module Timestamp
3
+ extend ActiveSupport::Concern
4
+
5
+ class_methods do
6
+ def inherited(child)
7
+ super
8
+ child.attribute :created_at, type: :time
9
+ child.attribute :updated_at, type: :time
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,9 +1,11 @@
1
- Superstore::Type.register(:array, Superstore::Types::ArrayType)
2
- Superstore::Type.register(:boolean, Superstore::Types::BooleanType)
3
- Superstore::Type.register(:date, Superstore::Types::DateType)
4
- Superstore::Type.register(:float, Superstore::Types::FloatType)
5
- Superstore::Type.register(:geo_point, Superstore::Types::JsonType)
6
- Superstore::Type.register(:integer, Superstore::Types::IntegerType)
7
- Superstore::Type.register(:json, Superstore::Types::JsonType)
8
- Superstore::Type.register(:time, Superstore::Types::TimeType)
9
- Superstore::Type.register(:string, Superstore::Types::StringType)
1
+ ActiveRecord::Type.register(:superstore_array, Superstore::Types::ArrayType)
2
+ ActiveRecord::Type.register(:superstore_boolean, Superstore::Types::BooleanType)
3
+ ActiveRecord::Type.register(:superstore_date, Superstore::Types::DateType)
4
+ ActiveRecord::Type.register(:superstore_date_range, Superstore::Types::DateRangeType)
5
+ ActiveRecord::Type.register(:superstore_float, Superstore::Types::FloatType)
6
+ ActiveRecord::Type.register(:superstore_geo_point, Superstore::Types::GeoPointType)
7
+ ActiveRecord::Type.register(:superstore_integer, Superstore::Types::IntegerType)
8
+ ActiveRecord::Type.register(:superstore_integer_range, Superstore::Types::IntegerRangeType)
9
+ ActiveRecord::Type.register(:superstore_json, Superstore::Types::JsonType)
10
+ ActiveRecord::Type.register(:superstore_time, Superstore::Types::TimeType)
11
+ ActiveRecord::Type.register(:superstore_string, Superstore::Types::StringType)
@@ -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 < ActiveModel::Type::Value
4
+ def cast_value(value)
5
+ Array(value)
10
6
  end
11
7
  end
12
8
  end
@@ -1,20 +1,15 @@
1
1
  module Superstore
2
2
  module Types
3
- class BooleanType < BaseType
3
+ class BooleanType < ActiveModel::Type::Value
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 < ActiveModel::Type::Value
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 < ActiveModel::Type::Value
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 < ActiveModel::Type::Value
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
@@ -0,0 +1,19 @@
1
+ module Superstore
2
+ module Types
3
+ class IntegerRangeType < RangeType
4
+ self.subtype = IntegerType.new
5
+
6
+ def serialize_for_open_ended(value)
7
+ value.abs == Float::INFINITY ? nil : super
8
+ end
9
+
10
+ def convert_min(method, value)
11
+ value.nil? ? -Float::INFINITY : super
12
+ end
13
+
14
+ def convert_max(method, value)
15
+ value.nil? ? Float::INFINITY : super
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,19 +1,13 @@
1
1
  module Superstore
2
2
  module Types
3
- class IntegerType < BaseType
4
- REGEX = /\A[-+]?\d+\Z/
5
- def encode(int)
6
- raise ArgumentError.new("#{int.inspect} is not an Integer.") unless int.kind_of?(Integer)
7
-
8
- int
9
- end
10
-
11
- def decode(str)
12
- str.to_i unless str.empty?
13
- end
14
-
15
- def typecast(value)
16
- value.to_i
3
+ class IntegerType < ActiveModel::Type::Value
4
+ def cast_value(value)
5
+ if value.is_a?(String)
6
+ Integer(value, 10)
7
+ else
8
+ Integer(value)
9
+ end
10
+ rescue
17
11
  end
18
12
  end
19
13
  end