superstore 2.4.3 → 3.0.0

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