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
@@ -1,6 +1,6 @@
1
1
  module Superstore
2
2
  module Types
3
- class JsonType < BaseType
3
+ class JsonType < ActiveModel::Type::Value
4
4
  end
5
5
  end
6
6
  end
@@ -0,0 +1,51 @@
1
+ module Superstore
2
+ module Types
3
+ class RangeType < ActiveModel::Type::Value
4
+ class_attribute :subtype
5
+
6
+ def serialize(range)
7
+ if range
8
+ [
9
+ serialize_for_open_ended(range.begin),
10
+ serialize_for_open_ended(range.end)
11
+ ]
12
+ end
13
+ end
14
+
15
+ def deserialize(range_tuple)
16
+ if range_tuple.is_a? Range
17
+ range_tuple
18
+ elsif range_tuple.is_a?(Array)
19
+ range = convert_min(:deserialize, range_tuple[0]) .. convert_max(:deserialize, range_tuple[1])
20
+ cast_value(range)
21
+ end
22
+ end
23
+
24
+ def cast_value(value)
25
+ if value.is_a?(Range) && value.begin < value.end
26
+ value
27
+ elsif value.is_a?(Array) && value.size == 2
28
+ begin
29
+ array = convert_min(:cast_value, value[0])..convert_max(:cast_value, value[1])
30
+ cast_value(array)
31
+ rescue ArgumentError
32
+ end
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def serialize_for_open_ended(value)
39
+ subtype.serialize(value)
40
+ end
41
+
42
+ def convert_min(method, value)
43
+ subtype.send(method, value)
44
+ end
45
+
46
+ def convert_max(method, value)
47
+ subtype.send(method, value)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -1,8 +1,8 @@
1
1
  module Superstore
2
2
  module Types
3
- class StringType < BaseType
4
- def encode(str)
5
- raise ArgumentError.new("#{str.inspect} is not a String") unless str.kind_of?(String)
3
+ class StringType < ActiveModel::Type::Value
4
+ def serialize(str)
5
+ return if str.nil?
6
6
 
7
7
  unless str.encoding == Encoding::UTF_8
8
8
  (str.frozen? ? str.dup : str).force_encoding('UTF-8')
@@ -11,7 +11,7 @@ module Superstore
11
11
  end
12
12
  end
13
13
 
14
- def typecast(value)
14
+ def cast_value(value)
15
15
  value.to_s
16
16
  end
17
17
  end
@@ -1,16 +1,18 @@
1
1
  module Superstore
2
2
  module Types
3
- class TimeType < BaseType
4
- def encode(time)
5
- raise ArgumentError.new("#{time.inspect} does not respond to #to_time") unless time.is_a?(Time) || time.respond_to?(:to_time)
6
- time = time.to_time unless time.is_a?(Time)
7
- time.utc.xmlschema(6)
3
+ class TimeType < ActiveModel::Type::Value
4
+ def serialize(time)
5
+ time.utc.xmlschema(6) if time
8
6
  end
9
7
 
10
- def decode(str)
11
- Time.parse(str).in_time_zone if str
12
- rescue
8
+ def deserialize(str)
9
+ Time.rfc3339(str).in_time_zone if str
10
+ rescue ArgumentError
11
+ Time.parse(str).in_time_zone rescue nil
12
+ end
13
13
 
14
+ def cast_value(value)
15
+ value.to_time.in_time_zone rescue nil
14
16
  end
15
17
  end
16
18
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'superstore'
5
- s.version = '2.4.4'
5
+ s.version = '2.5.0'
6
6
  s.description = 'ActiveModel-based JSONB document store'
7
7
  s.summary = 'ActiveModel for JSONB documents'
8
8
  s.authors = ['Michael Koziarski', 'Infogroup']
@@ -18,9 +18,10 @@ Gem::Specification.new do |s|
18
18
  s.test_files = `git ls-files -- {test}/*`.split("\n")
19
19
  s.require_paths = ['lib']
20
20
 
21
- s.add_runtime_dependency('activemodel', '>= 3.0')
21
+ s.add_runtime_dependency('activemodel', '>= 5.2')
22
+ s.add_runtime_dependency('activerecord', '>= 5.2')
22
23
  s.add_runtime_dependency('globalid')
23
- s.add_runtime_dependency('oj')
24
24
 
25
25
  s.add_development_dependency('bundler')
26
+ s.add_development_dependency('rails')
26
27
  end
@@ -1,6 +1,8 @@
1
1
  class JsonbInitializer
2
2
  def self.initialize!
3
- Superstore::Base.adapter.create_table('issues')
3
+ ActiveRecord::Migration.create_table :issues, id: :string do |t|
4
+ t.jsonb :document, null: false
5
+ end
4
6
  end
5
7
  end
6
8
 
@@ -2,19 +2,22 @@ class User < ActiveRecord::Base
2
2
  end
3
3
 
4
4
  class Label < ActiveRecord::Base
5
+ belongs_to :issue
5
6
  end
6
7
 
7
8
  class Issue < Superstore::Base
8
- string :description
9
- string :title
10
- json :comments
9
+ attribute :description, type: :string
10
+ attribute :title, type: :string
11
+ attribute :parent_issue_id, type: :string
12
+ attribute :comments, type: :json
11
13
 
12
14
  before_create { self.description ||= 'funny' }
13
15
 
14
- has_many :labels
16
+ has_many :labels, inverse_of: :issue
17
+ has_many :children_issues, class_name: 'Issue', foreign_key: :parent_issue_id, inverse_of: :parent_issue, superstore: true
18
+ belongs_to :parent_issue, class_name: 'Issue', superstore: true
15
19
 
16
20
  def self.for_key key
17
21
  where_ids(key)
18
22
  end
19
23
  end
20
-
@@ -13,12 +13,16 @@ require 'support/pg'
13
13
  require 'support/jsonb'
14
14
  require 'support/models'
15
15
 
16
+ def MiniTest.filter_backtrace(bt)
17
+ bt
18
+ end
19
+
16
20
  module Superstore
17
21
  class TestCase < ActiveSupport::TestCase
18
22
  def temp_object(&block)
19
23
  Class.new(Superstore::Base) do
20
24
  self.table_name = 'issues'
21
- string :force_save
25
+ attribute :force_save, type: :string
22
26
  before_save { self.force_save = 'junk' }
23
27
 
24
28
  def self.name
@@ -34,7 +38,7 @@ module Superstore
34
38
  class TestCase < Superstore::TestCase
35
39
  attr_accessor :type
36
40
  setup do
37
- @type = self.class.name.sub(/Test$/, '').constantize.new(Issue)
41
+ @type = self.class.name.sub(/Test$/, '').constantize.new
38
42
  end
39
43
  end
40
44
  end
@@ -1,6 +1,4 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class Superstore::Adapters::AdapterTest < Superstore::TestCase
4
- test 'create_table' do
5
- end
6
- end
4
+ end
@@ -3,7 +3,7 @@ require 'test_helper'
3
3
  class Superstore::Associations::BelongsTest < Superstore::TestCase
4
4
  class TestObject < Superstore::Base
5
5
  self.table_name = 'issues'
6
- string :user_id
6
+ attribute :user_id, type: :string
7
7
  belongs_to :user, primary_key: :special_id
8
8
  end
9
9
 
@@ -4,7 +4,7 @@ class Superstore::Associations::HasManyTest < Superstore::TestCase
4
4
  class TestObject < Issue
5
5
  end
6
6
 
7
- test 'has_many' do
7
+ test 'has_many active_record association' do
8
8
  issue = TestObject.create!
9
9
  label = Label.create! name: 'important', issue_id: issue.id
10
10
 
@@ -13,10 +13,18 @@ class Superstore::Associations::HasManyTest < Superstore::TestCase
13
13
 
14
14
  test 'create supports preloaded records' do
15
15
  issue = TestObject.create!
16
- issue.labels = Label.all.to_a
16
+ issue.labels = Label.all
17
17
 
18
18
  issue.labels.create! name: 'blue'
19
19
 
20
20
  assert_equal 1, issue.labels.size
21
21
  end
22
+
23
+ test 'has_many superstore association' do
24
+ parent_issue = Issue.create!
25
+ child_issue = Issue.create! parent_issue: parent_issue
26
+
27
+ assert_equal [child_issue], parent_issue.children_issues
28
+ assert_equal parent_issue.object_id, parent_issue.children_issues.first.parent_issue.object_id
29
+ end
22
30
  end
@@ -3,7 +3,7 @@ require 'test_helper'
3
3
  class Superstore::AttributeMethods::DirtyTest < Superstore::TestCase
4
4
  test 'save clears dirty' do
5
5
  record = temp_object do
6
- string :name
6
+ attribute :name, type: :string
7
7
  end.new name: 'foo'
8
8
 
9
9
  assert record.changed?
@@ -16,7 +16,7 @@ class Superstore::AttributeMethods::DirtyTest < Superstore::TestCase
16
16
 
17
17
  test 'reload clears dirty' do
18
18
  record = temp_object do
19
- string :name
19
+ attribute :name, type: :string
20
20
  end.create! name: 'foo'
21
21
 
22
22
  record.name = 'bar'
@@ -27,9 +27,9 @@ class Superstore::AttributeMethods::DirtyTest < Superstore::TestCase
27
27
  assert !record.changed?
28
28
  end
29
29
 
30
- test 'typecast float before dirty check' do
30
+ test 'cast_value float before dirty check' do
31
31
  record = temp_object do
32
- float :price
32
+ attribute :price, type: :float
33
33
  end.create(price: 5.01)
34
34
 
35
35
  record.price = '5.01'
@@ -39,9 +39,9 @@ class Superstore::AttributeMethods::DirtyTest < Superstore::TestCase
39
39
  assert record.changed?
40
40
  end
41
41
 
42
- test 'typecast boolean before dirty check' do
42
+ test 'cast_value boolean before dirty check' do
43
43
  record = temp_object do
44
- boolean :awesome
44
+ attribute :awesome, type: :boolean
45
45
  end.create(awesome: false)
46
46
 
47
47
  record.awesome = false
@@ -51,20 +51,9 @@ class Superstore::AttributeMethods::DirtyTest < Superstore::TestCase
51
51
  assert record.changed?
52
52
  end
53
53
 
54
- test 'unapplied_changes' do
55
- record = temp_object do
56
- float :price
57
- string :color
58
- end.create(price: 5.01, color: 'green')
59
-
60
- record.color = 'blue'
61
-
62
- assert_equal({'color' => 'blue'}, record.unapplied_changes)
63
- end
64
-
65
54
  test 'write_attribute' do
66
55
  object = temp_object do
67
- string :name
56
+ attribute :name, type: :string
68
57
  end
69
58
 
70
59
  expected = {"name"=>[nil, "foo"]}
@@ -83,7 +72,7 @@ class Superstore::AttributeMethods::DirtyTest < Superstore::TestCase
83
72
 
84
73
  test 'dirty and restore to original value' do
85
74
  object = temp_object do
86
- string :name
75
+ attribute :name, type: :string
87
76
  end
88
77
 
89
78
  record = object.create(name: 'foo')
@@ -19,7 +19,7 @@ class Superstore::AttributeMethods::PrimaryKeyTest < Superstore::TestCase
19
19
  end
20
20
 
21
21
  test 'attributes' do
22
- issue = Issue.new
22
+ issue = Issue.new(id: 'lol')
23
23
 
24
24
  assert_not_nil issue.attributes['id']
25
25
  end
@@ -12,10 +12,6 @@ class Superstore::AttributeMethodsTest < Superstore::TestCase
12
12
  assert_equal 'foo', issue.read_attribute(:description)
13
13
  end
14
14
 
15
- test 'read primary_key' do
16
- refute_nil Issue.new[:id]
17
- end
18
-
19
15
  test 'hash accessor aliases' do
20
16
  issue = Issue.new
21
17
 
@@ -34,32 +30,24 @@ class Superstore::AttributeMethodsTest < Superstore::TestCase
34
30
  assert_equal 'foo', issue.description
35
31
  end
36
32
 
37
- class ChildIssue < Issue
38
- def title=(val)
39
- self[:title] = val + ' lol'
40
- end
41
- end
33
+ class ModelWithOverride < Superstore::Base
34
+ attribute :title, type: :string
42
35
 
43
- test 'override' do
44
- issue = ChildIssue.new(title: 'hey')
45
-
46
- assert_equal 'hey lol', issue.title
36
+ def title=(v)
37
+ super "#{v} lol"
38
+ end
47
39
  end
48
40
 
49
- class ReservedWord < Superstore::Base
50
- self.table_name = 'issues'
51
- string :system
52
- end
41
+ test 'override' do
42
+ issue = ModelWithOverride.new(title: 'hey')
53
43
 
54
- test 'reserved words' do
55
- r = ReservedWord.new(system: 'hello')
56
- assert_equal 'hello', r.system
44
+ assert_equal 'hey lol', issue.title
57
45
  end
58
46
 
59
47
  test 'has_attribute?' do
60
- refute Issue.new.attribute_exists?(:description)
48
+ refute Issue.new.has_attribute?(:unknown)
49
+ assert Issue.new.has_attribute?(:description)
61
50
  assert Issue.new(description: nil).has_attribute?(:description)
62
- assert Issue.new(description: false).has_attribute?(:description)
63
51
  assert Issue.new(description: 'hey').has_attribute?(:description)
64
52
  end
65
53
  end
@@ -1,18 +1,19 @@
1
1
  require 'test_helper'
2
2
 
3
- class Superstore::AttributeMethods::TypecastingTest < Superstore::TestCase
3
+ class Superstore::AttributesTest < Superstore::TestCase
4
4
  class TestIssue < Superstore::Base
5
5
  self.table_name = 'issues'
6
6
 
7
- boolean :enabled
8
- float :rating
9
- integer :price
10
- json :orders
11
- string :name
7
+ attribute :enabled, type: :boolean
8
+ attribute :rating, type: :float
9
+ attribute :price, type: :integer
10
+ attribute :orders, type: :json
11
+ attribute :name, type: :string
12
+ attribute :age_range, type: :integer_range
12
13
  end
13
14
 
14
15
  class TestChildIssue < TestIssue
15
- string :description
16
+ attribute :description, type: :string
16
17
  end
17
18
 
18
19
  test 'attributes not shared' do
@@ -21,15 +22,6 @@ class Superstore::AttributeMethods::TypecastingTest < Superstore::TestCase
21
22
  assert_nothing_raised { TestChildIssue.new.description }
22
23
  end
23
24
 
24
- test 'typecast_attribute' do
25
- assert_equal 1, TestIssue.typecast_attribute('price', 1)
26
- assert_equal 1, TestIssue.typecast_attribute(:price, 1)
27
-
28
- assert_raise NoMethodError do
29
- TestIssue.typecast_attribute('wtf', 1)
30
- end
31
- end
32
-
33
25
  test 'boolean attribute' do
34
26
  issue = TestIssue.create! enabled: '1'
35
27
  assert_equal true, issue.enabled
@@ -76,29 +68,11 @@ class Superstore::AttributeMethods::TypecastingTest < Superstore::TestCase
76
68
  assert_equal '42', issue.name
77
69
  end
78
70
 
79
- test 'multiple attributes definition' do
80
- class MultipleAttributesIssue < Superstore::Base
81
- self.table_name = 'issues'
82
- end
71
+ test 'integer_range' do
72
+ issue = TestIssue.create! age_range: ['70', nil]
73
+ assert_equal 70..Float::INFINITY, issue.age_range
83
74
 
84
- assert_nothing_raised {
85
- MultipleAttributesIssue.string :hello, :greetings, :bye
86
- }
87
- issue = MultipleAttributesIssue.new :hello => 'hey', :greetings => 'how r u', :bye => 'see ya'
88
-
89
- assert_equal 'how r u', issue.greetings
90
- end
91
-
92
- test 'multiple attributes with options' do
93
- class MultipleAttributesIssue < Superstore::Base
94
- self.table_name = 'issues'
95
- end
96
-
97
- MultipleAttributesIssue.expects(:attribute).with(:hello, { :unique => :true, :type => :string })
98
- MultipleAttributesIssue.expects(:attribute).with(:world, { :unique => :true, :type => :string })
99
-
100
- class MultipleAttributesIssue < Superstore::Base
101
- string :hello, :world, :unique => :true
102
- end
75
+ issue = TestIssue.find issue.id
76
+ assert_equal 70..Float::INFINITY, issue.age_range
103
77
  end
104
78
  end