superstore 2.4.4 → 2.5.0

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