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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: bff1f7868f9525ae36d1a003c33422d12fea5e8e
4
- data.tar.gz: eb8cfbe4048d28e3b7097bad7f51d36e8fde1aa5
2
+ SHA256:
3
+ metadata.gz: 4b50e7d651ea05c7214a0407aca4c530eee09f8ba6e8d867d636400f1a22bb42
4
+ data.tar.gz: 00d665bc855ad9cfcdf77ceaaefb6d674a5964611eb082ea0e0e0d0da880ac84
5
5
  SHA512:
6
- metadata.gz: 41d1fc61dcad51d35ac4feb6d46ff3cad2dd423ea39b1070b275f1cfd0a0a5f51279e10866e823a8ea4925f1d57fc42157b9bb3c10ad401acd8c688d300a5dcf
7
- data.tar.gz: fb7104cda38cc586278b0ac28ee1330ab1d97705e11bff14379d01f255d1fc12fadcded56b41d5b00cd236e630ad41f961dae7b35d1ff4adf73d5dcfc45ead6c
6
+ metadata.gz: d819bb08f1c070d18bdccd76caf941718947ea0c85191813f8896bc86d6c61d49f4306e3cea816514e05e635299d28bd663bc4515bb1ca90ae01bd7454db49bc
7
+ data.tar.gz: 014e964e51adb9c2791af3598435e6f7764b9e6bc80edab030f804c84944213eb68e423d7e9daf527f42c92ed98fa7da4421be5f55e4f1a315fb13904dfe3921
@@ -0,0 +1,40 @@
1
+ name: Ruby
2
+ on:
3
+ push:
4
+ branches: [ master ]
5
+ pull_request:
6
+ branches: [ master ]
7
+ jobs:
8
+ test:
9
+ runs-on: ubuntu-20.04
10
+ strategy:
11
+ matrix:
12
+ ruby: ['3.1']
13
+ env:
14
+ CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
15
+ services:
16
+ postgres:
17
+ image: postgres:12.8
18
+ options: >-
19
+ --health-cmd pg_isready
20
+ --health-interval 10s
21
+ --health-timeout 5s
22
+ --health-retries 5
23
+ ports:
24
+ - 5432:5432
25
+ env:
26
+ POSTGRES_USER: runner
27
+ POSTGRES_HOST_AUTH_METHOD: trust
28
+ steps:
29
+ - name: Checkout the repo
30
+ uses: actions/checkout@v2
31
+ - name: Install Ruby, bundler and the bundle
32
+ uses: ruby/setup-ruby@v1
33
+ with:
34
+ ruby-version: ${{ matrix.ruby }}
35
+ bundler-cache: true
36
+ - name: Run tests
37
+ run: bundle exec rake
38
+ - name: Publish code coverage
39
+ if: ${{ github.actor != 'dependabot[bot]' }}
40
+ uses: paambaati/codeclimate-action@v3.0.0
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
1
  .bundle
2
2
  Gemfile*.lock
3
3
  *.gem
4
+ coverage
data/.simplecov ADDED
@@ -0,0 +1,3 @@
1
+ SimpleCov.start 'test_frameworks' do
2
+ enable_coverage :branch
3
+ end
data/Gemfile CHANGED
@@ -1,11 +1,10 @@
1
- source "http://rubygems.org"
1
+ source 'https://rubygems.org'
2
2
  gemspec
3
3
 
4
4
  gem 'rake'
5
5
 
6
6
  group :test do
7
- gem 'rails'
8
7
  gem 'pg'
9
- gem 'activerecord', '~> 5.0.0'
10
8
  gem 'mocha', require: false
9
+ gem 'simplecov'
11
10
  end
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009 Koziarski Software Ltd
1
+ Copyright (c) 2009 Koziarski Software Ltd, 2022 Data Axle Inc
2
2
 
3
3
  Permission to use, copy, modify, and/or distribute this software for any
4
4
  purpose with or without fee is hereby granted, provided that the above
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2011 [Michael Koziarski]
1
+ Copyright (c) 2011 [Michael Koziarski], 2022 [Data Axle Inc]
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # Superstore
2
- [![Build Status](https://secure.travis-ci.org/data-axle/superstore.png?rvm=2.0.0)](http://travis-ci.org/data-axle/superstore)
2
+ [![Ruby](https://github.com/data-axle/superstore/actions/workflows/ruby.yml/badge.svg)](https://github.com/data-axle/superstore/actions/workflows/ruby.yml)
3
3
  [![Code Climate](https://codeclimate.com/github/data-axle/superstore/badges/gpa.svg)](https://codeclimate.com/github/data-axle/superstore)
4
4
  [![Gem](https://img.shields.io/gem/v/superstore.svg?maxAge=2592000)](https://rubygems.org/gems/superstore)
5
5
 
@@ -15,27 +15,18 @@ Superstore requires PostgreSQL 9.5 or above.
15
15
  Add the following to the `Gemfile`:
16
16
 
17
17
  ```ruby
18
- gem 'pg'
19
18
  gem 'superstore'
20
19
  ```
21
20
 
22
- Add a `config/superstore.yml`:
23
-
24
- ```yaml
25
- development:
26
- adapter: jsonb
27
- ```
28
-
29
21
  Superstore will share the existing ActiveRecord database connection.
30
22
 
31
23
  ## Defining Models
32
24
 
33
25
  ```ruby
34
26
  class Widget < Superstore::Base
35
- string :name
36
- string :description
37
- integer :price
38
- array :colors, unique: true
27
+ attribute :name, type: :string
28
+ attribute :price, type: :integer
29
+ attribute :colors, type: :array
39
30
 
40
31
  validates :name, presence: :true
41
32
 
@@ -69,24 +60,3 @@ widget.price_was
69
60
  widget.save
70
61
  widget.save!
71
62
  ```
72
-
73
- ## Finding records
74
-
75
- ```ruby
76
- widget = Widget.find(uuid)
77
- widget = Widget.first
78
- widgets = Widget.all
79
- Widget.find_each do |widget|
80
- # Codez
81
- end
82
- ```
83
-
84
- ## Scoping
85
-
86
- Some lightweight scoping features are available:
87
-
88
- ```ruby
89
- Widget.where('color' => 'red')
90
- Widget.select(['name', 'color'])
91
- Widget.limit(10)
92
- ```
@@ -1,10 +1,7 @@
1
1
  module Superstore
2
2
  module Adapters
3
3
  class AbstractAdapter
4
- attr_reader :config
5
- def initialize(config)
6
- @config = config
7
- @batch_statements = nil
4
+ def initialize
8
5
  end
9
6
 
10
7
  # Read records from a instance of Superstore::Scope
@@ -22,29 +19,6 @@ module Superstore
22
19
  # Delete rows by an array of ids
23
20
  def delete(table, ids) # abstract
24
21
  end
25
-
26
- def execute_batch(statements) # abstract
27
- end
28
-
29
- def batching?
30
- !@batch_statements.nil?
31
- end
32
-
33
- def batch
34
- @batch_statements = []
35
- yield
36
- execute_batch(@batch_statements) if @batch_statements.any?
37
- ensure
38
- @batch_statements = nil
39
- end
40
-
41
- def execute_batchable(statement)
42
- if @batch_statements
43
- @batch_statements << statement
44
- else
45
- execute statement
46
- end
47
- end
48
22
  end
49
23
  end
50
24
  end
@@ -4,86 +4,6 @@ require 'pg'
4
4
  module Superstore
5
5
  module Adapters
6
6
  class JsonbAdapter < AbstractAdapter
7
- class QueryBuilder
8
- def initialize(adapter, scope)
9
- @adapter = adapter
10
- @scope = scope
11
- end
12
-
13
- def to_query
14
- [
15
- "SELECT #{select_string}",
16
- from_string,
17
- where_string,
18
- order_string,
19
- limit_string
20
- ].delete_if(&:blank?) * ' '
21
- end
22
-
23
- def from_string
24
- "FROM #{@scope.klass.table_name}"
25
- end
26
-
27
- def select_string
28
- if @scope.select_values.empty?
29
- '*'
30
- elsif @scope.select_values == [@adapter.primary_key_column]
31
- @adapter.primary_key_column
32
- else
33
- selects = @scope.select_values.map { |key| "#{@adapter.quote(key)},document->>#{@adapter.quote(key)}" }
34
- "#{@adapter.primary_key_column}, json_build_object(#{selects * ','}) as document"
35
- end
36
- end
37
-
38
- def where_string
39
- wheres = where_values_as_strings
40
-
41
- if @scope.id_values.any?
42
- wheres << @adapter.create_ids_where_clause(@scope.id_values)
43
- end
44
-
45
- if wheres.any?
46
- "WHERE #{wheres * ' AND '}"
47
- end
48
- end
49
-
50
- def order_string
51
- if @scope.order_values.any?
52
- orders = @scope.order_values.join(', ')
53
- "ORDER BY #{orders}"
54
- elsif @scope.id_values.many?
55
- id_orders = @scope.id_values.map { |id| "ID=#{@adapter.quote(id)} DESC" }.join(',')
56
- "ORDER BY #{id_orders}"
57
- end
58
- end
59
-
60
- def limit_string
61
- if @scope.limit_value
62
- "LIMIT #{@scope.limit_value}"
63
- end
64
- end
65
-
66
- def where_values_as_strings
67
- @scope.where_values.map do |where_value|
68
- if where_value.is_a?(Hash)
69
- key = where_value.keys.first
70
- value = where_value.values.first
71
-
72
- if value.nil?
73
- "(document->>'#{key}') IS NULL"
74
- elsif value.is_a?(Array)
75
- typecasted_values = value.map { |v| "'#{v}'" }.join(',')
76
- "document->>'#{key}' IN (#{typecasted_values})"
77
- else
78
- "document->>'#{key}' = '#{value}'"
79
- end
80
- else
81
- where_value
82
- end
83
- end
84
- end
85
- end
86
-
87
7
  PRIMARY_KEY_COLUMN = 'id'.freeze
88
8
  def primary_key_column
89
9
  PRIMARY_KEY_COLUMN
@@ -105,39 +25,10 @@ module Superstore
105
25
  connection.execute statement
106
26
  end
107
27
 
108
- def to_ids(scope)
109
- statement = QueryBuilder.new(self, scope.select(primary_key_column)).to_query
110
- connection.select_values(statement)
111
- end
112
-
113
- def select(scope)
114
- statement = QueryBuilder.new(self, scope).to_query
115
-
116
- connection.execute(statement).each do |result|
117
- yield result[primary_key_column], Oj.compat_load(result['document'])
118
- end
119
- end
120
-
121
- def scroll(scope, batch_size)
122
- statement = QueryBuilder.new(self, scope).to_query
123
- cursor_name = "cursor_#{SecureRandom.hex(6)}"
124
- fetch_sql = "FETCH FORWARD #{batch_size} FROM #{cursor_name}"
125
-
126
- connection.transaction do
127
- connection.execute "DECLARE #{cursor_name} NO SCROLL CURSOR FOR (#{statement})"
128
-
129
- while (batch = connection.execute(fetch_sql)).any?
130
- batch.each do |result|
131
- yield result[primary_key_column], Oj.compat_load(result['document'])
132
- end
133
- end
134
- end
135
- end
136
-
137
28
  def insert(table, id, attributes)
138
29
  not_nil_attributes = attributes.reject { |key, value| value.nil? }
139
30
  statement = "INSERT INTO #{table} (#{primary_key_column}, document) VALUES (#{quote(id)}, #{to_quoted_jsonb(not_nil_attributes)})"
140
- execute_batchable statement
31
+ execute statement
141
32
  end
142
33
 
143
34
  def update(table, id, attributes)
@@ -157,31 +48,13 @@ module Superstore
157
48
 
158
49
  statement = "UPDATE #{table} SET document = #{value_update} WHERE #{primary_key_column} = #{quote(id)}"
159
50
 
160
- execute_batchable statement
51
+ execute statement
161
52
  end
162
53
 
163
54
  def delete(table, ids)
164
55
  statement = "DELETE FROM #{table} WHERE #{create_ids_where_clause(ids)}"
165
56
 
166
- execute_batchable statement
167
- end
168
-
169
- def execute_batch(statements)
170
- connection.transaction do
171
- execute(statements * ";\n")
172
- end
173
- end
174
-
175
- def create_table(table_name, options = {})
176
- ActiveRecord::Migration.create_table table_name, id: false do |t|
177
- t.string :id, null: false
178
- t.jsonb :document, null: false
179
- end
180
- connection.execute "ALTER TABLE \"#{table_name}\" ADD CONSTRAINT #{table_name}_pkey PRIMARY KEY (id)"
181
- end
182
-
183
- def drop_table(table_name)
184
- ActiveRecord::Migration.drop_table table_name
57
+ execute statement
185
58
  end
186
59
 
187
60
  def create_ids_where_clause(ids)
@@ -204,9 +77,8 @@ module Superstore
204
77
  "ARRAY[#{quoted_fields}]"
205
78
  end
206
79
 
207
- OJ_OPTIONS = {mode: :compat}
208
80
  def to_quoted_jsonb(data)
209
- "#{quote(Oj.dump(data, OJ_OPTIONS))}::JSONB"
81
+ "#{quote(JSON.generate(data))}::JSONB"
210
82
  end
211
83
  end
212
84
  end
@@ -7,6 +7,7 @@ module Superstore
7
7
  def initialize(owner, reflection)
8
8
  @owner = owner
9
9
  @reflection = reflection
10
+ reset
10
11
  end
11
12
 
12
13
  def association_class
@@ -33,6 +34,11 @@ module Superstore
33
34
  def loaded!
34
35
  @loaded = true
35
36
  end
37
+
38
+ def reset
39
+ @loaded = false
40
+ @target = nil
41
+ end
36
42
  end
37
43
  end
38
44
  end
@@ -0,0 +1,20 @@
1
+ module Superstore
2
+ module Associations
3
+ class AssociationScope < ActiveRecord::Relation
4
+ def initialize(klass, association)
5
+ super(klass)
6
+ @association = association
7
+ end
8
+
9
+ def exec_queries
10
+ super.each { |r| @association.set_inverse_instance r }
11
+ end
12
+
13
+ def <<(*records)
14
+ if loaded?
15
+ @records = @records + records
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -17,6 +17,8 @@ module Superstore
17
17
  end
18
18
  end
19
19
 
20
+ def belongs_to?; true; end
21
+
20
22
  private
21
23
 
22
24
  def get_record
@@ -32,4 +34,4 @@ module Superstore
32
34
 
33
35
  end
34
36
  end
35
- end
37
+ end
@@ -35,4 +35,4 @@ module Superstore::Associations::Builder
35
35
  end
36
36
  end
37
37
  end
38
- end
38
+ end
@@ -19,12 +19,25 @@ module Superstore
19
19
  self.target = relation
20
20
  end
21
21
 
22
+ def set_inverse_instance(record)
23
+ return unless reflection.inverse_name
24
+
25
+ inverse = record.association(reflection.inverse_name)
26
+ inverse.target = owner
27
+ end
28
+
22
29
  private
23
30
 
31
+ def inverse_of
32
+ return unless reflection.inverse_name
33
+
34
+ @inverse_of ||= association_class.reflect_on_association reflection.inverse_name
35
+ end
36
+
24
37
  def load_collection
25
- association_class.where(reflection.foreign_key => owner.try(reflection.primary_key))
38
+ AssociationScope.new(association_class, self).where("document ->> '#{reflection.foreign_key}' = '#{owner.try(reflection.primary_key)}'")
26
39
  end
27
40
 
28
41
  end
29
42
  end
30
- end
43
+ end
@@ -18,7 +18,7 @@ module Superstore
18
18
  when :has_one
19
19
  Superstore::Associations::HasOne
20
20
  end
21
-
21
+
22
22
  end
23
23
 
24
24
  def instance_variable_name
@@ -45,12 +45,18 @@ module Superstore
45
45
  options[:polymorphic]
46
46
  end
47
47
 
48
+ def belongs_to?; false; end
49
+
48
50
  def class_name
49
51
  @class_name ||= (options[:class_name] || name.to_s.classify)
50
52
  end
51
53
 
54
+ def inverse_name
55
+ options[:inverse_of]
56
+ end
57
+
52
58
  private
53
-
59
+
54
60
  def derive_foreign_key
55
61
  case macro
56
62
  when :has_many, :has_one
@@ -2,7 +2,12 @@ module Superstore
2
2
  module Associations
3
3
  extend ActiveSupport::Concern
4
4
 
5
- module ClassMethods
5
+ included do
6
+ include ActiveRecord::Associations
7
+ extend ClassOverrides
8
+ end
9
+
10
+ module ClassOverrides
6
11
  # === Options
7
12
  # [:class_name]
8
13
  # Use if the class cannot be inferred from the association
@@ -13,7 +18,7 @@ module Superstore
13
18
  # end
14
19
  # class Truck < Superstore::Base
15
20
  # end
16
- def belongs_to(name, options = {})
21
+ def belongs_to(name, **options)
17
22
  if options.delete(:superstore)
18
23
  Superstore::Associations::Builder::BelongsTo.build(self, name, options)
19
24
  else
@@ -21,7 +26,7 @@ module Superstore
21
26
  end
22
27
  end
23
28
 
24
- def has_many(name, options = {})
29
+ def has_many(name, **options)
25
30
  if options.delete(:superstore)
26
31
  Superstore::Associations::Builder::HasMany.build(self, name, options)
27
32
  else
@@ -29,7 +34,7 @@ module Superstore
29
34
  end
30
35
  end
31
36
 
32
- def has_one(name, options = {})
37
+ def has_one(name, **options)
33
38
  if options.delete(:superstore)
34
39
  Superstore::Associations::Builder::HasOne.build(self, name, options)
35
40
  else
@@ -0,0 +1,7 @@
1
+ module Superstore
2
+ module AttributeAssignment
3
+ def _assign_attribute(k, v)
4
+ public_send("#{k}=", v) if respond_to?("#{k}=")
5
+ end
6
+ end
7
+ end
@@ -1,25 +1,34 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Superstore
2
4
  module AttributeMethods
3
5
  module PrimaryKey
4
6
  extend ActiveSupport::Concern
5
7
 
8
+ included do
9
+ attribute :id, type: :string
10
+ include AttributeOverrides
11
+ end
12
+
6
13
  module ClassMethods
7
- PRIMARY_KEY = 'id'
8
14
  def primary_key
9
- PRIMARY_KEY
15
+ 'id'
10
16
  end
11
17
  end
12
18
 
13
- def id
14
- @id ||= self.class._generate_key(self)
15
- end
16
-
17
- def id=(id)
18
- @id = id
19
- end
19
+ module AttributeOverrides
20
+ def id
21
+ value = super
22
+ if value.nil?
23
+ value = self.class._generate_key(self)
24
+ @attributes.write_from_user(self.class.primary_key, value)
25
+ end
26
+ value
27
+ end
20
28
 
21
- def attributes
22
- super.update(self.class.primary_key => id)
29
+ def attributes
30
+ super.update(self.class.primary_key => id)
31
+ end
23
32
  end
24
33
  end
25
34
  end