activerecord-typedstore 1.4.0 → 1.5.1
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.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +14 -31
- data/README.md +6 -3
- data/activerecord-typedstore.gemspec +2 -5
- data/gemfiles/Gemfile.ar-6.1 +0 -1
- data/gemfiles/{Gemfile.ar-5.2 → Gemfile.ar-7.0} +1 -1
- data/gemfiles/{Gemfile.ar-master → Gemfile.ar-edge} +0 -1
- data/lib/active_record/typed_store/behavior.rb +19 -0
- data/lib/active_record/typed_store/dsl.rb +41 -5
- data/lib/active_record/typed_store/extension.rb +8 -7
- data/lib/active_record/typed_store/type.rb +1 -6
- data/lib/active_record/typed_store/version.rb +1 -1
- data/spec/active_record/typed_store_spec.rb +97 -1
- data/spec/spec_helper.rb +6 -9
- data/spec/support/models.rb +49 -79
- metadata +11 -55
- data/gemfiles/Gemfile.ar-5.0 +0 -6
- data/gemfiles/Gemfile.ar-6.0 +0 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5f56fb7b6456937890a85e139a296727a04b2c87a2452f45810782849378357a
|
|
4
|
+
data.tar.gz: 17e694f93e4db21a4b5a08c7795285fd10aaf9bc203f74376a14356873c84802
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 02d3ce60427903e2c039818c4c77368a42b20c4f9fbf5bf167357e0e932d01c717f5eea6172ea03170b018a468b6b8f0a00d59fd49073a38eb620a27e05870c3
|
|
7
|
+
data.tar.gz: 96ff55688ac6c1b2d9f56d9a45e796b4c633e6d3d1dd89cdbdb14025044d97c9f5deefce7a53494f35ff5a227f340e8ec9d9fba70b7d861fe488ee6f52ad98fe
|
data/.github/workflows/ruby.yml
CHANGED
|
@@ -5,43 +5,26 @@ on: [push, pull_request]
|
|
|
5
5
|
jobs:
|
|
6
6
|
build:
|
|
7
7
|
runs-on: ubuntu-latest
|
|
8
|
-
name:
|
|
8
|
+
name: ${{ matrix.ruby }} / Rails ${{ matrix.rails }} / TZ ${{ matrix.timezone_aware }}
|
|
9
9
|
strategy:
|
|
10
|
+
fail-fast: false
|
|
10
11
|
matrix:
|
|
11
|
-
ruby: [2.
|
|
12
|
-
|
|
13
|
-
exclude:
|
|
14
|
-
- ruby: 2.6
|
|
15
|
-
gemfile: Gemfile.ar-6.0
|
|
16
|
-
- ruby: 2.6
|
|
17
|
-
gemfile: Gemfile.ar-6.1
|
|
18
|
-
- ruby: 2.6
|
|
19
|
-
gemfile: Gemfile.ar-master
|
|
12
|
+
ruby: ['2.7', '3.0', '3.1']
|
|
13
|
+
rails: ['6.1', '7.0', 'edge']
|
|
20
14
|
timezone_aware: [0, 1]
|
|
21
15
|
env:
|
|
22
|
-
BUNDLE_GEMFILE: gemfiles
|
|
16
|
+
BUNDLE_GEMFILE: gemfiles/Gemfile.ar-${{ matrix.rails }}
|
|
23
17
|
TIMEZONE_AWARE: ${{ matrix.timezone_aware }}
|
|
24
18
|
POSTGRES: 1
|
|
25
19
|
MYSQL: 1
|
|
26
20
|
POSTGRES_JSON: 1
|
|
27
21
|
steps:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
sudo systemctl start mysql.service
|
|
38
|
-
mysql -uroot -proot -e 'create database typed_store_test;'
|
|
39
|
-
- name: Start PostgresQL and create DB
|
|
40
|
-
run: |
|
|
41
|
-
sudo sed -i s/md5/trust/g /etc/postgresql/*/main/pg_hba.conf
|
|
42
|
-
sudo systemctl start postgresql.service
|
|
43
|
-
sudo -u postgres createuser --createdb --superuser ${USER}
|
|
44
|
-
createdb typed_store_test
|
|
45
|
-
echo "POSTGRES_URL=postgres://localhost/typed_store_test" >> $GITHUB_ENV
|
|
46
|
-
- name: Ruby Tests
|
|
47
|
-
run: bundle exec rake
|
|
22
|
+
- name: Check out code
|
|
23
|
+
uses: actions/checkout@v3
|
|
24
|
+
- name: Set up Ruby ${{ matrix.ruby }}
|
|
25
|
+
uses: ruby/setup-ruby@v1
|
|
26
|
+
with:
|
|
27
|
+
ruby-version: ${{ matrix.ruby }}
|
|
28
|
+
bundler-cache: true
|
|
29
|
+
- name: Ruby Tests
|
|
30
|
+
run: bundle exec rake
|
data/README.md
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
# ActiveRecord::TypedStore
|
|
2
2
|
|
|
3
|
-
[](http://travis-ci.org/byroot/activerecord-typedstore)
|
|
4
|
-
[](https://codeclimate.com/github/byroot/activerecord-typedstore)
|
|
5
|
-
[](https://coveralls.io/r/byroot/activerecord-typedstore)
|
|
6
3
|
[](http://badge.fury.io/rb/activerecord-typedstore)
|
|
7
4
|
|
|
8
5
|
[ActiveRecord::Store](http://api.rubyonrails.org/classes/ActiveRecord/Store.html) but with typed attributes.
|
|
@@ -79,6 +76,12 @@ shop.save
|
|
|
79
76
|
shop.reload
|
|
80
77
|
shop.settings[:unknown] # => 'Hello World'
|
|
81
78
|
|
|
79
|
+
# You can group attributes with a prefix or suffix
|
|
80
|
+
typed_store(:browser, prefix: true) { |s| s.string :ip } # => #browser_ip
|
|
81
|
+
typed_store(:browser, prefix: :web) { |s| s.string :ip } # => #web_ip
|
|
82
|
+
typed_store(:browser, suffix: true) { |s| s.string :ip } # => #ip_browser
|
|
83
|
+
typed_store(:browser, suffix: :web) { |s| s.string :ip } # => #ip_web
|
|
84
|
+
|
|
82
85
|
# If you only want type casting and default handling without accessors
|
|
83
86
|
|
|
84
87
|
# you can disable them store wide
|
|
@@ -18,14 +18,11 @@ Gem::Specification.new do |spec|
|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
19
19
|
spec.require_paths = ['lib']
|
|
20
20
|
|
|
21
|
-
spec.add_dependency 'activerecord', '>=
|
|
21
|
+
spec.add_dependency 'activerecord', '>= 6.1'
|
|
22
22
|
|
|
23
23
|
spec.add_development_dependency 'bundler'
|
|
24
|
-
spec.add_development_dependency 'rake'
|
|
24
|
+
spec.add_development_dependency 'rake'
|
|
25
25
|
spec.add_development_dependency 'rspec', '~> 3'
|
|
26
|
-
spec.add_development_dependency 'coveralls', '~> 0'
|
|
27
26
|
spec.add_development_dependency 'sqlite3', '~> 1'
|
|
28
|
-
spec.add_development_dependency 'pg', ENV.fetch('PG_VERSION', '~> 0.18')
|
|
29
|
-
spec.add_development_dependency 'mysql2', '> 0.3'
|
|
30
27
|
spec.add_development_dependency 'database_cleaner', '~> 1'
|
|
31
28
|
end
|
data/gemfiles/Gemfile.ar-6.1
CHANGED
|
@@ -71,5 +71,24 @@ module ActiveRecord::TypedStore
|
|
|
71
71
|
super
|
|
72
72
|
end
|
|
73
73
|
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def attribute_names_for_partial_inserts
|
|
78
|
+
# Contrary to all vanilla Rails types, typedstore attribute have an inherent default
|
|
79
|
+
# value that doesn't match the database column default.
|
|
80
|
+
# As such we need to insert them on partial inserts even if they weren't changed.
|
|
81
|
+
super | self.class.typed_stores.keys.map(&:to_s)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def attribute_names_for_partial_updates
|
|
85
|
+
# On partial updates we shouldn't need to force stores to be persisted. However since
|
|
86
|
+
# we weren't persisting them for a while on insertion, we now need to gracefully deal
|
|
87
|
+
# with existing records that may have been persisted with a `NULL` store
|
|
88
|
+
# We use `blank?` as an heuristic to detect these.
|
|
89
|
+
super | self.class.typed_stores.keys.map(&:to_s).select do |store|
|
|
90
|
+
@attributes.key?(store) && @attributes[store].value_before_type_cast.blank?
|
|
91
|
+
end
|
|
92
|
+
end
|
|
74
93
|
end
|
|
75
94
|
end
|
|
@@ -6,10 +6,38 @@ module ActiveRecord::TypedStore
|
|
|
6
6
|
class DSL
|
|
7
7
|
attr_reader :fields, :coder
|
|
8
8
|
|
|
9
|
-
def initialize(
|
|
10
|
-
@coder = options.fetch(:coder) { default_coder(
|
|
11
|
-
@
|
|
12
|
-
@
|
|
9
|
+
def initialize(store_name, options)
|
|
10
|
+
@coder = options.fetch(:coder) { default_coder(store_name) }
|
|
11
|
+
@store_name = store_name
|
|
12
|
+
@prefix =
|
|
13
|
+
case options[:prefix]
|
|
14
|
+
when String, Symbol
|
|
15
|
+
"#{options[:prefix]}_"
|
|
16
|
+
when true
|
|
17
|
+
"#{store_name}_"
|
|
18
|
+
when false, nil
|
|
19
|
+
""
|
|
20
|
+
else
|
|
21
|
+
raise ArgumentError, "Unexpected type for prefix option. Expected string, symbol, or boolean"
|
|
22
|
+
end
|
|
23
|
+
@suffix =
|
|
24
|
+
case options[:suffix]
|
|
25
|
+
when String, Symbol
|
|
26
|
+
"_#{options[:suffix]}"
|
|
27
|
+
when true
|
|
28
|
+
"_#{store_name}"
|
|
29
|
+
when false, nil
|
|
30
|
+
""
|
|
31
|
+
else
|
|
32
|
+
raise ArgumentError, "Unexpected type for suffix option. Expected string, symbol, or boolean"
|
|
33
|
+
end
|
|
34
|
+
@accessors = if options[:accessors] == false
|
|
35
|
+
{}
|
|
36
|
+
elsif options[:accessors].is_a?(Array)
|
|
37
|
+
options[:accessors].each_with_object({}) do |accessor_name, hash|
|
|
38
|
+
hash[accessor_name] = accessor_key_for(accessor_name)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
13
41
|
@fields = {}
|
|
14
42
|
yield self
|
|
15
43
|
end
|
|
@@ -25,7 +53,9 @@ module ActiveRecord::TypedStore
|
|
|
25
53
|
end
|
|
26
54
|
|
|
27
55
|
def accessors
|
|
28
|
-
@accessors || @fields.values.select(&:accessor).
|
|
56
|
+
@accessors || @fields.values.select(&:accessor).each_with_object({}) do |field, hash|
|
|
57
|
+
hash[field.name] = accessor_key_for(field.name)
|
|
58
|
+
end
|
|
29
59
|
end
|
|
30
60
|
|
|
31
61
|
delegate :keys, to: :@fields
|
|
@@ -37,5 +67,11 @@ module ActiveRecord::TypedStore
|
|
|
37
67
|
end
|
|
38
68
|
end
|
|
39
69
|
alias_method :date_time, :datetime
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def accessor_key_for(name)
|
|
74
|
+
"#{@prefix}#{name}#{@suffix}"
|
|
75
|
+
end
|
|
40
76
|
end
|
|
41
77
|
end
|
|
@@ -14,9 +14,10 @@ module ActiveRecord::TypedStore
|
|
|
14
14
|
class_attribute :typed_stores, :store_accessors, instance_accessor: false
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
+
store_options = options.slice(:prefix, :suffix)
|
|
17
18
|
dsl = DSL.new(store_attribute, options, &block)
|
|
18
19
|
self.typed_stores = (self.typed_stores || {}).merge(store_attribute => dsl)
|
|
19
|
-
self.store_accessors = typed_stores.each_value.flat_map
|
|
20
|
+
self.store_accessors = typed_stores.each_value.flat_map { |d| d.accessors.values }.map { |a| -a.to_s }.to_set
|
|
20
21
|
|
|
21
22
|
typed_klass = TypedHash.create(dsl.fields.values)
|
|
22
23
|
const_set("#{store_attribute}_hash".camelize, typed_klass)
|
|
@@ -31,20 +32,20 @@ module ActiveRecord::TypedStore
|
|
|
31
32
|
Type.new(typed_klass, dsl.coder, subtype)
|
|
32
33
|
end
|
|
33
34
|
end
|
|
34
|
-
store_accessor(store_attribute, dsl.accessors)
|
|
35
|
+
store_accessor(store_attribute, dsl.accessors.keys, **store_options)
|
|
35
36
|
|
|
36
|
-
dsl.accessors.each do |accessor_name|
|
|
37
|
-
define_method("#{
|
|
37
|
+
dsl.accessors.each do |accessor_name, accessor_key|
|
|
38
|
+
define_method("#{accessor_key}_changed?") do
|
|
38
39
|
send("#{store_attribute}_changed?") &&
|
|
39
40
|
send(store_attribute)[accessor_name] != send("#{store_attribute}_was")[accessor_name]
|
|
40
41
|
end
|
|
41
42
|
|
|
42
|
-
define_method("#{
|
|
43
|
+
define_method("#{accessor_key}_was") do
|
|
43
44
|
send("#{store_attribute}_was")[accessor_name]
|
|
44
45
|
end
|
|
45
46
|
|
|
46
|
-
define_method("restore_#{
|
|
47
|
-
send("#{
|
|
47
|
+
define_method("restore_#{accessor_key}!") do
|
|
48
|
+
send("#{accessor_key}=", send("#{accessor_name}_was"))
|
|
48
49
|
end
|
|
49
50
|
end
|
|
50
51
|
end
|
|
@@ -41,12 +41,7 @@ module ActiveRecord::TypedStore
|
|
|
41
41
|
|
|
42
42
|
def changed_in_place?(raw_old_value, value)
|
|
43
43
|
return false if value.nil?
|
|
44
|
-
|
|
45
|
-
raw_new_value = serialize(value)
|
|
46
|
-
else
|
|
47
|
-
# 4.2 capability
|
|
48
|
-
raw_new_value = type_cast_for_database(value)
|
|
49
|
-
end
|
|
44
|
+
raw_new_value = serialize(value)
|
|
50
45
|
raw_old_value.nil? != raw_new_value.nil? || raw_old_value != raw_new_value
|
|
51
46
|
end
|
|
52
47
|
end
|
|
@@ -696,6 +696,82 @@ shared_examples 'a store' do |retain_type = true, settings_type = :text|
|
|
|
696
696
|
|
|
697
697
|
end
|
|
698
698
|
|
|
699
|
+
describe 'with prefix true' do
|
|
700
|
+
|
|
701
|
+
it 'defines prefixed accessors' do
|
|
702
|
+
expect(model).to respond_to :prefixed_settings_language
|
|
703
|
+
expect(model).to respond_to :prefixed_settings_language=
|
|
704
|
+
end
|
|
705
|
+
|
|
706
|
+
it 'does not define unprefixed accessors' do
|
|
707
|
+
expect(model).not_to respond_to :language
|
|
708
|
+
expect(model).not_to respond_to :language=
|
|
709
|
+
end
|
|
710
|
+
|
|
711
|
+
it 'can be updated' do
|
|
712
|
+
model.update(prefixed_settings_language: 'en')
|
|
713
|
+
expect(model.reload.prefixed_settings_language).to be == 'en'
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
end
|
|
717
|
+
|
|
718
|
+
describe 'with custom prefix' do
|
|
719
|
+
|
|
720
|
+
it 'defines prefixed accessors' do
|
|
721
|
+
expect(model).to respond_to :custom_language
|
|
722
|
+
expect(model).to respond_to :custom_language=
|
|
723
|
+
end
|
|
724
|
+
|
|
725
|
+
it 'does not define unprefixed accessors' do
|
|
726
|
+
expect(model).not_to respond_to :language
|
|
727
|
+
expect(model).not_to respond_to :language=
|
|
728
|
+
end
|
|
729
|
+
|
|
730
|
+
it 'can be updated' do
|
|
731
|
+
model.update(custom_language: 'en')
|
|
732
|
+
expect(model.reload.custom_language).to be == 'en'
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
end
|
|
736
|
+
|
|
737
|
+
describe 'with suffix true' do
|
|
738
|
+
|
|
739
|
+
it 'defines suffixed accessors' do
|
|
740
|
+
expect(model).to respond_to :language_suffixed_settings
|
|
741
|
+
expect(model).to respond_to :language_suffixed_settings=
|
|
742
|
+
end
|
|
743
|
+
|
|
744
|
+
it 'does not define unprefixed accessors' do
|
|
745
|
+
expect(model).not_to respond_to :language
|
|
746
|
+
expect(model).not_to respond_to :language=
|
|
747
|
+
end
|
|
748
|
+
|
|
749
|
+
it 'can be updated' do
|
|
750
|
+
model.update(language_suffixed_settings: 'en')
|
|
751
|
+
expect(model.reload.language_suffixed_settings).to be == 'en'
|
|
752
|
+
end
|
|
753
|
+
|
|
754
|
+
end
|
|
755
|
+
|
|
756
|
+
describe 'with custom suffix' do
|
|
757
|
+
|
|
758
|
+
it 'defines suffixed accessors' do
|
|
759
|
+
expect(model).to respond_to :language_custom
|
|
760
|
+
expect(model).to respond_to :language_custom=
|
|
761
|
+
end
|
|
762
|
+
|
|
763
|
+
it 'does not define unprefixed accessors' do
|
|
764
|
+
expect(model).not_to respond_to :language
|
|
765
|
+
expect(model).not_to respond_to :language=
|
|
766
|
+
end
|
|
767
|
+
|
|
768
|
+
it 'can be updated' do
|
|
769
|
+
model.update(language_custom: 'en')
|
|
770
|
+
expect(model.reload.language_custom).to be == 'en'
|
|
771
|
+
end
|
|
772
|
+
|
|
773
|
+
end
|
|
774
|
+
|
|
699
775
|
describe '`any` attributes' do
|
|
700
776
|
|
|
701
777
|
it 'accept any type' do
|
|
@@ -849,7 +925,7 @@ describe YamlTypedStoreModel do
|
|
|
849
925
|
|
|
850
926
|
it 'nested hashes are not serialized as HashWithIndifferentAccess' do
|
|
851
927
|
model = described_class.create!
|
|
852
|
-
expect(model.settings_before_type_cast).not_to include('HashWithIndifferentAccess')
|
|
928
|
+
expect(model.settings_before_type_cast.to_s).not_to include('HashWithIndifferentAccess')
|
|
853
929
|
end
|
|
854
930
|
end
|
|
855
931
|
|
|
@@ -878,3 +954,23 @@ describe InheritedTypedStoreModel do
|
|
|
878
954
|
expect(model.settings[:new_attribute]).to be == '42'
|
|
879
955
|
end
|
|
880
956
|
end
|
|
957
|
+
|
|
958
|
+
describe DirtyTrackingModel do
|
|
959
|
+
it 'stores the default on creation' do
|
|
960
|
+
model = DirtyTrackingModel.create!
|
|
961
|
+
expect(model.settings_before_type_cast).to_not be_blank
|
|
962
|
+
end
|
|
963
|
+
|
|
964
|
+
it 'handles loaded records having uninitialized defaults' do
|
|
965
|
+
model = DirtyTrackingModel.create!
|
|
966
|
+
DirtyTrackingModel.update_all("settings = NULL") # bypass validation
|
|
967
|
+
model = DirtyTrackingModel.find(model.id)
|
|
968
|
+
expect(model.settings_changed?).to be false
|
|
969
|
+
expect(model.changes).to be_empty
|
|
970
|
+
|
|
971
|
+
model.update!(title: "Hello")
|
|
972
|
+
|
|
973
|
+
expect(model.settings_changed?).to be false
|
|
974
|
+
expect(model.changes).to be_empty
|
|
975
|
+
end
|
|
976
|
+
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -2,21 +2,18 @@ lib = File.expand_path('../lib', __FILE__)
|
|
|
2
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
3
|
|
|
4
4
|
require 'database_cleaner'
|
|
5
|
-
|
|
6
|
-
require 'simplecov'
|
|
7
|
-
require 'coveralls'
|
|
8
|
-
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([
|
|
9
|
-
SimpleCov::Formatter::HTMLFormatter,
|
|
10
|
-
Coveralls::SimpleCov::Formatter
|
|
11
|
-
])
|
|
12
|
-
SimpleCov.start
|
|
13
|
-
|
|
14
5
|
require 'activerecord-typedstore'
|
|
15
6
|
|
|
16
7
|
Dir[File.expand_path(File.join(File.dirname(__FILE__), 'support', '**', '*.rb'))].each { |f| require f }
|
|
17
8
|
|
|
18
9
|
Time.zone = 'UTC'
|
|
19
10
|
|
|
11
|
+
if ActiveRecord.respond_to?(:yaml_column_permitted_classes)
|
|
12
|
+
ActiveRecord.yaml_column_permitted_classes |= ['Date', 'Time', 'BigDecimal']
|
|
13
|
+
elsif ActiveRecord::Base.respond_to?(:yaml_column_permitted_classes)
|
|
14
|
+
ActiveRecord::Base.yaml_column_permitted_classes |= ['Date', 'Time', 'BigDecimal']
|
|
15
|
+
end
|
|
16
|
+
|
|
20
17
|
RSpec.configure do |config|
|
|
21
18
|
config.order = 'random'
|
|
22
19
|
end
|
data/spec/support/models.rb
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
require 'active_record'
|
|
2
|
+
require 'base64'
|
|
2
3
|
require 'json'
|
|
3
4
|
require 'yaml'
|
|
4
5
|
|
|
6
|
+
ENV["RAILS_ENV"] = "test"
|
|
7
|
+
|
|
5
8
|
ActiveRecord::Base.time_zone_aware_attributes = ENV['TIMEZONE_AWARE'] != '0'
|
|
9
|
+
credentials = { 'database' => 'typed_store_test', 'username' => 'typed_store', 'password' => 'typed_store' }
|
|
6
10
|
ActiveRecord::Base.configurations = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
test: {
|
|
12
|
+
'test_sqlite3' => { 'adapter' => 'sqlite3', 'database' => '/tmp/typed_store.db' },
|
|
13
|
+
}
|
|
10
14
|
}
|
|
11
15
|
|
|
12
|
-
def define_columns(t)
|
|
16
|
+
def define_columns(t, array: false)
|
|
13
17
|
t.integer :no_default
|
|
14
18
|
|
|
15
19
|
t.string :name, default: '', null: false
|
|
@@ -37,16 +41,12 @@ def define_columns(t)
|
|
|
37
41
|
t.decimal :total_price, default: 4.2, null: false, precision: 16, scale: 2
|
|
38
42
|
t.decimal :shipping_cost, precision: 16, scale: 2
|
|
39
43
|
|
|
40
|
-
t.
|
|
41
|
-
|
|
42
|
-
if t.respond_to?(:name) && t.name =~ /sqlite|mysql/
|
|
43
|
-
# native sqlite cannot automatically cast array to yaml
|
|
44
|
+
if t.is_a?(ActiveRecord::TypedStore::DSL)
|
|
45
|
+
t.integer :grades, array: true
|
|
44
46
|
t.string :tags, array: true, null: false, default: [].to_yaml
|
|
45
|
-
else
|
|
46
|
-
t.string :tags, array: true, null: false, default: []
|
|
47
|
-
end
|
|
48
47
|
|
|
49
|
-
|
|
48
|
+
t.string :nickname, blank: false, default: 'Please enter your nickname'
|
|
49
|
+
end
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
def define_store_with_no_attributes(**options)
|
|
@@ -74,37 +74,28 @@ def define_store_with_attributes(**options)
|
|
|
74
74
|
end
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
end
|
|
77
|
+
def define_stores_with_prefix_and_suffix(**options)
|
|
78
|
+
typed_store(:prefixed_settings, prefix: true, **options) { |t| t.any :language }
|
|
79
|
+
typed_store(:suffixed_settings, suffix: true, **options) { |t| t.any :language }
|
|
80
|
+
typed_store(:custom_prefixed_settings, prefix: :custom, **options) { |t| t.any :language }
|
|
81
|
+
typed_store(:custom_suffixed_settings, suffix: :custom, **options) { |t| t.any :language }
|
|
82
|
+
end
|
|
84
83
|
|
|
84
|
+
MigrationClass = ActiveRecord::Migration["#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}"]
|
|
85
|
+
class CreateAllTables < MigrationClass
|
|
85
86
|
def self.up
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations.configs_for(env_name: "test", name: :test_sqlite3))
|
|
88
|
+
create_table(:sqlite3_regular_ar_models, force: true) { |t| define_columns(t); t.text :untyped_settings }
|
|
89
|
+
create_table(:yaml_typed_store_models, force: true) { |t| %i[settings explicit_settings partial_settings untyped_settings prefixed_settings suffixed_settings custom_prefixed_settings custom_suffixed_settings].each { |column| t.text column}; t.string :regular_column }
|
|
90
|
+
create_table(:json_typed_store_models, force: true) { |t| %i[settings explicit_settings partial_settings untyped_settings prefixed_settings suffixed_settings custom_prefixed_settings custom_suffixed_settings].each { |column| t.text column}; t.string :regular_column }
|
|
91
|
+
create_table(:marshal_typed_store_models, force: true) { |t| %i[settings explicit_settings partial_settings untyped_settings prefixed_settings suffixed_settings custom_prefixed_settings custom_suffixed_settings].each { |column| t.text column}; t.string :regular_column }
|
|
90
92
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
create_table(:dirty_tracking_models, force: true) do |t|
|
|
94
|
+
t.string :title
|
|
95
|
+
t.text :settings
|
|
94
96
|
|
|
95
|
-
|
|
96
|
-
recreate_table(:postgres_hstore_typed_store_models) { |t| t.hstore :settings; t.text :untyped_settings }
|
|
97
|
-
|
|
98
|
-
if ENV['POSTGRES_JSON']
|
|
99
|
-
recreate_table(:postgres_json_typed_store_models) { |t| t.json :settings; t.text :explicit_settings; t.text :partial_settings; t.text :untyped_settings }
|
|
100
|
-
end
|
|
97
|
+
t.timestamps
|
|
101
98
|
end
|
|
102
|
-
|
|
103
|
-
ActiveRecord::Base.establish_connection(:test_sqlite3)
|
|
104
|
-
recreate_table(:sqlite3_regular_ar_models) { |t| define_columns(t); t.text :untyped_settings }
|
|
105
|
-
recreate_table(:yaml_typed_store_models) { |t| t.text :settings; t.text :explicit_settings; t.text :partial_settings; t.text :untyped_settings }
|
|
106
|
-
recreate_table(:json_typed_store_models) { |t| t.text :settings; t.text :explicit_settings; t.text :partial_settings; t.text :untyped_settings }
|
|
107
|
-
recreate_table(:marshal_typed_store_models) { |t| t.text :settings; t.text :explicit_settings; t.text :partial_settings; t.text :untyped_settings }
|
|
108
99
|
end
|
|
109
100
|
end
|
|
110
101
|
ActiveRecord::Migration.verbose = true
|
|
@@ -129,44 +120,6 @@ class ColumnCoder
|
|
|
129
120
|
|
|
130
121
|
end
|
|
131
122
|
|
|
132
|
-
module AsJson
|
|
133
|
-
extend self
|
|
134
|
-
|
|
135
|
-
def load(value)
|
|
136
|
-
value
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
def dump(value)
|
|
140
|
-
value.as_json
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
if ENV['MYSQL']
|
|
146
|
-
class MysqlRegularARModel < ActiveRecord::Base
|
|
147
|
-
establish_connection :test_mysql
|
|
148
|
-
store :untyped_settings, accessors: [:title]
|
|
149
|
-
end
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
if ENV['POSTGRES']
|
|
153
|
-
class PostgresqlRegularARModel < ActiveRecord::Base
|
|
154
|
-
establish_connection ENV['POSTGRES_URL'] || :test_postgresql
|
|
155
|
-
store :untyped_settings, accessors: [:title]
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
if ENV['POSTGRES_JSON']
|
|
159
|
-
class PostgresJsonTypedStoreModel < ActiveRecord::Base
|
|
160
|
-
establish_connection ENV['POSTGRES_URL'] || :test_postgresql
|
|
161
|
-
store :untyped_settings, accessors: [:title]
|
|
162
|
-
|
|
163
|
-
define_store_with_attributes(coder: ColumnCoder.new(AsJson))
|
|
164
|
-
define_store_with_no_attributes(coder: ColumnCoder.new(AsJson))
|
|
165
|
-
define_store_with_partial_attributes(coder: ColumnCoder.new(AsJson))
|
|
166
|
-
end
|
|
167
|
-
end
|
|
168
|
-
end
|
|
169
|
-
|
|
170
123
|
class Sqlite3RegularARModel < ActiveRecord::Base
|
|
171
124
|
establish_connection :test_sqlite3
|
|
172
125
|
store :untyped_settings, accessors: [:title]
|
|
@@ -176,9 +129,15 @@ class YamlTypedStoreModel < ActiveRecord::Base
|
|
|
176
129
|
establish_connection :test_sqlite3
|
|
177
130
|
store :untyped_settings, accessors: [:title]
|
|
178
131
|
|
|
132
|
+
after_update :read_active
|
|
133
|
+
def read_active
|
|
134
|
+
enabled
|
|
135
|
+
end
|
|
136
|
+
|
|
179
137
|
define_store_with_attributes
|
|
180
138
|
define_store_with_no_attributes
|
|
181
139
|
define_store_with_partial_attributes
|
|
140
|
+
define_stores_with_prefix_and_suffix
|
|
182
141
|
end
|
|
183
142
|
|
|
184
143
|
class InheritedTypedStoreModel < YamlTypedStoreModel
|
|
@@ -196,6 +155,7 @@ class JsonTypedStoreModel < ActiveRecord::Base
|
|
|
196
155
|
define_store_with_attributes(coder: ColumnCoder.new(JSON))
|
|
197
156
|
define_store_with_no_attributes(coder: ColumnCoder.new(JSON))
|
|
198
157
|
define_store_with_partial_attributes(coder: ColumnCoder.new(JSON))
|
|
158
|
+
define_stores_with_prefix_and_suffix(coder: ColumnCoder.new(JSON))
|
|
199
159
|
end
|
|
200
160
|
|
|
201
161
|
module MarshalCoder
|
|
@@ -218,6 +178,7 @@ class MarshalTypedStoreModel < ActiveRecord::Base
|
|
|
218
178
|
define_store_with_attributes(coder: ColumnCoder.new(MarshalCoder))
|
|
219
179
|
define_store_with_no_attributes(coder: ColumnCoder.new(MarshalCoder))
|
|
220
180
|
define_store_with_partial_attributes(coder: ColumnCoder.new(MarshalCoder))
|
|
181
|
+
define_stores_with_prefix_and_suffix(coder: ColumnCoder.new(MarshalCoder))
|
|
221
182
|
end
|
|
222
183
|
|
|
223
184
|
Models = [
|
|
@@ -227,6 +188,15 @@ Models = [
|
|
|
227
188
|
JsonTypedStoreModel,
|
|
228
189
|
MarshalTypedStoreModel
|
|
229
190
|
]
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
191
|
+
|
|
192
|
+
class DirtyTrackingModel < ActiveRecord::Base
|
|
193
|
+
after_update :read_active
|
|
194
|
+
|
|
195
|
+
typed_store(:settings) do |f|
|
|
196
|
+
f.boolean :active, default: false, null: false
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def read_active
|
|
200
|
+
active
|
|
201
|
+
end
|
|
202
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: activerecord-typedstore
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.5.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jean Boussier
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2022-11-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|
|
@@ -16,14 +16,14 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
19
|
+
version: '6.1'
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
26
|
+
version: '6.1'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: bundler
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -42,16 +42,16 @@ dependencies:
|
|
|
42
42
|
name: rake
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
|
-
- - "
|
|
45
|
+
- - ">="
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '
|
|
47
|
+
version: '0'
|
|
48
48
|
type: :development
|
|
49
49
|
prerelease: false
|
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
51
|
requirements:
|
|
52
|
-
- - "
|
|
52
|
+
- - ">="
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '
|
|
54
|
+
version: '0'
|
|
55
55
|
- !ruby/object:Gem::Dependency
|
|
56
56
|
name: rspec
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -66,20 +66,6 @@ dependencies:
|
|
|
66
66
|
- - "~>"
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
68
|
version: '3'
|
|
69
|
-
- !ruby/object:Gem::Dependency
|
|
70
|
-
name: coveralls
|
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
|
72
|
-
requirements:
|
|
73
|
-
- - "~>"
|
|
74
|
-
- !ruby/object:Gem::Version
|
|
75
|
-
version: '0'
|
|
76
|
-
type: :development
|
|
77
|
-
prerelease: false
|
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
-
requirements:
|
|
80
|
-
- - "~>"
|
|
81
|
-
- !ruby/object:Gem::Version
|
|
82
|
-
version: '0'
|
|
83
69
|
- !ruby/object:Gem::Dependency
|
|
84
70
|
name: sqlite3
|
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -94,34 +80,6 @@ dependencies:
|
|
|
94
80
|
- - "~>"
|
|
95
81
|
- !ruby/object:Gem::Version
|
|
96
82
|
version: '1'
|
|
97
|
-
- !ruby/object:Gem::Dependency
|
|
98
|
-
name: pg
|
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
|
100
|
-
requirements:
|
|
101
|
-
- - "~>"
|
|
102
|
-
- !ruby/object:Gem::Version
|
|
103
|
-
version: '0.18'
|
|
104
|
-
type: :development
|
|
105
|
-
prerelease: false
|
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
-
requirements:
|
|
108
|
-
- - "~>"
|
|
109
|
-
- !ruby/object:Gem::Version
|
|
110
|
-
version: '0.18'
|
|
111
|
-
- !ruby/object:Gem::Dependency
|
|
112
|
-
name: mysql2
|
|
113
|
-
requirement: !ruby/object:Gem::Requirement
|
|
114
|
-
requirements:
|
|
115
|
-
- - ">"
|
|
116
|
-
- !ruby/object:Gem::Version
|
|
117
|
-
version: '0.3'
|
|
118
|
-
type: :development
|
|
119
|
-
prerelease: false
|
|
120
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
-
requirements:
|
|
122
|
-
- - ">"
|
|
123
|
-
- !ruby/object:Gem::Version
|
|
124
|
-
version: '0.3'
|
|
125
83
|
- !ruby/object:Gem::Dependency
|
|
126
84
|
name: database_cleaner
|
|
127
85
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -151,11 +109,9 @@ files:
|
|
|
151
109
|
- README.md
|
|
152
110
|
- Rakefile
|
|
153
111
|
- activerecord-typedstore.gemspec
|
|
154
|
-
- gemfiles/Gemfile.ar-5.0
|
|
155
|
-
- gemfiles/Gemfile.ar-5.2
|
|
156
|
-
- gemfiles/Gemfile.ar-6.0
|
|
157
112
|
- gemfiles/Gemfile.ar-6.1
|
|
158
|
-
- gemfiles/Gemfile.ar-
|
|
113
|
+
- gemfiles/Gemfile.ar-7.0
|
|
114
|
+
- gemfiles/Gemfile.ar-edge
|
|
159
115
|
- lib/active_record/typed_store.rb
|
|
160
116
|
- lib/active_record/typed_store/behavior.rb
|
|
161
117
|
- lib/active_record/typed_store/dsl.rb
|
|
@@ -190,7 +146,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
190
146
|
- !ruby/object:Gem::Version
|
|
191
147
|
version: '0'
|
|
192
148
|
requirements: []
|
|
193
|
-
rubygems_version: 3.
|
|
149
|
+
rubygems_version: 3.3.7
|
|
194
150
|
signing_key:
|
|
195
151
|
specification_version: 4
|
|
196
152
|
summary: Add type casting and full method attributes support to АctiveRecord store
|
data/gemfiles/Gemfile.ar-5.0
DELETED