active_typed_store 2.6.0 → 2.7.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.
- checksums.yaml +4 -4
- data/Gemfile.lock +7 -7
- data/README.md +25 -0
- data/lib/active_typed_store/encrypted_json.rb +46 -0
- data/lib/active_typed_store/store.rb +13 -4
- data/lib/active_typed_store/version.rb +1 -1
- metadata +14 -13
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e69cb858697a79e92cbfcfb8147a2c8c5861e94a693b7097ce8f961bc4973bf6
|
|
4
|
+
data.tar.gz: 934c15165efc0c5f04d59c2f831b262866abedd49ec1ab0efaf978417a58783e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9507a3d04171ca491053553829c22662c46f301efd01712ddf47173a07bcfb1867eec373ec7d9e8d9ec907216bc8fbd383ba2fa4e79e1953c6ed313de511eb9a
|
|
7
|
+
data.tar.gz: 010a2ac3ad8063d1ee9bab17b7437bd348ff2d78856cf970c23a6bb2603822c4c8f1dfa844e3456ec81d4b95c80c1de444b85bbf164f9a725ceaa31f095c13bf
|
data/Gemfile.lock
CHANGED
|
@@ -22,10 +22,10 @@ GIT
|
|
|
22
22
|
PATH
|
|
23
23
|
remote: .
|
|
24
24
|
specs:
|
|
25
|
-
active_typed_store (2.
|
|
26
|
-
activemodel
|
|
27
|
-
activerecord
|
|
28
|
-
activesupport
|
|
25
|
+
active_typed_store (2.7.0)
|
|
26
|
+
activemodel (>= 7.1)
|
|
27
|
+
activerecord (>= 7.1)
|
|
28
|
+
activesupport (>= 7.1)
|
|
29
29
|
|
|
30
30
|
GEM
|
|
31
31
|
remote: https://rubygems.org/
|
|
@@ -75,7 +75,6 @@ GEM
|
|
|
75
75
|
json (2.7.2)
|
|
76
76
|
language_server-protocol (3.17.0.3)
|
|
77
77
|
logger (1.6.1)
|
|
78
|
-
mini_portile2 (2.8.9)
|
|
79
78
|
minitest (5.25.1)
|
|
80
79
|
parallel (1.26.3)
|
|
81
80
|
parser (3.3.5.0)
|
|
@@ -131,8 +130,9 @@ GEM
|
|
|
131
130
|
ruby-progressbar (1.13.0)
|
|
132
131
|
rumoji (0.5.0)
|
|
133
132
|
securerandom (0.3.1)
|
|
134
|
-
sqlite3 (1.7.3)
|
|
135
|
-
|
|
133
|
+
sqlite3 (1.7.3-arm64-darwin)
|
|
134
|
+
sqlite3 (1.7.3-x86_64-darwin)
|
|
135
|
+
sqlite3 (1.7.3-x86_64-linux)
|
|
136
136
|
timeout (0.4.1)
|
|
137
137
|
tzinfo (2.0.6)
|
|
138
138
|
concurrent-ruby (~> 1.0)
|
data/README.md
CHANGED
|
@@ -143,6 +143,31 @@ class Parcel
|
|
|
143
143
|
end
|
|
144
144
|
```
|
|
145
145
|
|
|
146
|
+
### Encrypted store
|
|
147
|
+
|
|
148
|
+
`encrypted: true` keeps keys of the store readable and encrypts every value like Rails `encrypts`:
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
class SupplierImportSetting < ActiveRecord::Base
|
|
152
|
+
typed_store :marketplace_credentials, encrypted: true do
|
|
153
|
+
attr :wb_token, :string
|
|
154
|
+
attr :ozon_token, :string
|
|
155
|
+
attr :ozon_client_id, :string
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
setting.update!(wb_token: "secret", ozon_client_id: "123")
|
|
160
|
+
# in the database: {"wb_token": "{\"p\":\"...\",\"h\":{...}}", "ozon_client_id": "{\"p\":\"...\",\"h\":{...}}"}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
- Values are encrypted by `ActiveRecord::Encryption::EncryptedAttributeType` with the same scheme as `encrypts`:
|
|
164
|
+
[Active Record Encryption](https://guides.rubyonrails.org/active_record_encryption.html) must be configured,
|
|
165
|
+
`encrypts` options can be passed instead of `true`: `encrypted: { deterministic: true }`,
|
|
166
|
+
`encrypted: { previous: { key: "old key" } }`, `encrypted: { support_unencrypted_data: true }`.
|
|
167
|
+
- Any value type is supported: the value is JSON-encoded before encryption.
|
|
168
|
+
- Don't add `encrypts` for the store column or its accessors: it conflicts with typed_store or silently does nothing.
|
|
169
|
+
- Like with `encrypts`, decrypted values are in `changes`, `attributes` and the store hash.
|
|
170
|
+
|
|
146
171
|
### Hash safety
|
|
147
172
|
This gem assumes you're using a database that supports structured data types, such as `json` in `PostgreSQL` or `MySQL`, and leverages Rails' [store_accessor](https://edgeapi.rubyonrails.org/classes/ActiveRecord/Store.html) under the hood. However, there’s one caveat: JSON columns use a string-keyed hash and don’t support access via symbols. To avoid unexpected errors when accessing the hash, we raise an error if a symbol is used. You can disable this behavior by setting `config.hash_safety = false`.
|
|
148
173
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveTypedStore
|
|
4
|
+
module Store
|
|
5
|
+
# typed_store(:params, encrypted: true): keys are stored as is, every value is encrypted like Rails `encrypts`.
|
|
6
|
+
# Options are `encrypts` options: encrypted: { deterministic: true, previous: ..., support_unencrypted_data: true }
|
|
7
|
+
class EncryptedJson < CustomJson
|
|
8
|
+
# value is JSON-encoded before encryption to keep its type; not encrypted value (support_unencrypted_data)
|
|
9
|
+
# is returned as is, it may be not JSON
|
|
10
|
+
class ValueJson < ActiveRecord::Type::Json
|
|
11
|
+
def deserialize(value)
|
|
12
|
+
return value unless value.is_a?(::String)
|
|
13
|
+
|
|
14
|
+
ActiveSupport::JSON.decode(value)
|
|
15
|
+
rescue JSON::ParserError
|
|
16
|
+
value
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def initialize(scheme_options)
|
|
21
|
+
super()
|
|
22
|
+
@scheme_options = scheme_options
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def serialize(value) = super(value.is_a?(Hash) ? value.transform_values { value_type.serialize(_1) } : value)
|
|
26
|
+
|
|
27
|
+
def deserialize(value)
|
|
28
|
+
hash = super
|
|
29
|
+
hash.is_a?(Hash) ? hash.transform_values { value_type.deserialize(_1) } : hash
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# deserialize(serialize(value)) gives the same as plain json, but encrypts and decrypts every value
|
|
33
|
+
# on every assignment and dirty check
|
|
34
|
+
def cast(value) = CUSTOM_JSON.cast(value)
|
|
35
|
+
def changed_in_place?(raw_old_value, new_value) = deserialize(raw_old_value) != cast(new_value)
|
|
36
|
+
|
|
37
|
+
# built lazily like `encrypts` does: encryption config may be not loaded yet when the model is defined
|
|
38
|
+
private def value_type
|
|
39
|
+
@value_type ||= ActiveRecord::Encryption::EncryptedAttributeType.new(
|
|
40
|
+
scheme: ActiveRecord::Base.send(:scheme_for, **@scheme_options),
|
|
41
|
+
cast_type: ValueJson.new,
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -10,16 +10,25 @@ module ActiveTypedStore
|
|
|
10
10
|
|
|
11
11
|
CUSTOM_JSON = CustomJson.new
|
|
12
12
|
|
|
13
|
-
def typed_store(store_attribute, &)
|
|
13
|
+
def typed_store(store_attribute, encrypted: false, &block)
|
|
14
14
|
attrs = Attrs.new(store_attribute)
|
|
15
|
-
attrs.instance_eval(&)
|
|
15
|
+
attrs.instance_eval(&block)
|
|
16
|
+
|
|
17
|
+
json_type =
|
|
18
|
+
if encrypted
|
|
19
|
+
# not required on load: it loads ActiveRecord::Encryption
|
|
20
|
+
require_relative "encrypted_json"
|
|
21
|
+
EncryptedJson.new(encrypted == true ? {} : encrypted)
|
|
22
|
+
else
|
|
23
|
+
CUSTOM_JSON
|
|
24
|
+
end
|
|
16
25
|
|
|
17
26
|
define_singleton_method(:inherited) do |subclass|
|
|
18
27
|
super(subclass)
|
|
19
|
-
subclass.define_attribute(store_attribute.name,
|
|
28
|
+
subclass.define_attribute(store_attribute.name, json_type)
|
|
20
29
|
end
|
|
21
30
|
|
|
22
|
-
define_attribute store_attribute.name,
|
|
31
|
+
define_attribute store_attribute.name, json_type
|
|
23
32
|
store_accessor store_attribute, attrs.fields
|
|
24
33
|
|
|
25
34
|
if ActiveTypedStore.config.hash_safety == :disallow_symbol_keys
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: active_typed_store
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ermolaev Andrey
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activemodel
|
|
@@ -16,43 +16,43 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
19
|
+
version: '7.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: '7.1'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: activerecord
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
31
|
- - ">="
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '
|
|
33
|
+
version: '7.1'
|
|
34
34
|
type: :runtime
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
38
|
- - ">="
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '
|
|
40
|
+
version: '7.1'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
42
|
name: activesupport
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
45
|
- - ">="
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '
|
|
47
|
+
version: '7.1'
|
|
48
48
|
type: :runtime
|
|
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: '
|
|
55
|
-
description:
|
|
54
|
+
version: '7.1'
|
|
55
|
+
description:
|
|
56
56
|
email:
|
|
57
57
|
- andruhafirst@yandex.ru
|
|
58
58
|
executables: []
|
|
@@ -72,6 +72,7 @@ files:
|
|
|
72
72
|
- lib/active_typed_store/attrs.rb
|
|
73
73
|
- lib/active_typed_store/configuration.rb
|
|
74
74
|
- lib/active_typed_store/disallow_symbol_keys.rb
|
|
75
|
+
- lib/active_typed_store/encrypted_json.rb
|
|
75
76
|
- lib/active_typed_store/store.rb
|
|
76
77
|
- lib/active_typed_store/version.rb
|
|
77
78
|
homepage: https://github.com/corp-gp/active_typed_store
|
|
@@ -79,7 +80,7 @@ licenses:
|
|
|
79
80
|
- MIT
|
|
80
81
|
metadata:
|
|
81
82
|
rubygems_mfa_required: 'true'
|
|
82
|
-
post_install_message:
|
|
83
|
+
post_install_message:
|
|
83
84
|
rdoc_options: []
|
|
84
85
|
require_paths:
|
|
85
86
|
- lib
|
|
@@ -94,8 +95,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
94
95
|
- !ruby/object:Gem::Version
|
|
95
96
|
version: '0'
|
|
96
97
|
requirements: []
|
|
97
|
-
rubygems_version: 3.3.
|
|
98
|
-
signing_key:
|
|
98
|
+
rubygems_version: 3.0.3.1
|
|
99
|
+
signing_key:
|
|
99
100
|
specification_version: 4
|
|
100
101
|
summary: Typed store for active records json columns (postgresql, mysql, sqlite)
|
|
101
102
|
test_files: []
|