active_typed_store 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b7e95f3805fc3ef46bb5fa9ce567e2470396150130720d78d7617a1bd1b63d44
4
- data.tar.gz: 9d178c5936bab0bd8ec4c400102b7b784ea5a4666697e34a6efeebfd3703088a
3
+ metadata.gz: 81b1efdd481c9034944b0b3ad6acb8972492128a13197a5054d291ee1b2e23ae
4
+ data.tar.gz: 8af315be7e55e6af5bba99f126b1abf26217f3634aef8aae708abf0887270c5c
5
5
  SHA512:
6
- metadata.gz: 8c140d6d25206075d0db2493f54dbb468902b45a98a42748c5e56fe7e513b418d1c2ca527fdada93309718dd3f4d92050f66ddd067d5e879c62b0cadd22ba1bc
7
- data.tar.gz: ba3b3ce6ed5a4c7c17464765dbe9cfad4aa910fc48e03b3a89ddbaef5fba4a84b2ad57ac2aaad618a45bcf1b7ff59d93d25b0380158001443e6747b45e71bf69
6
+ metadata.gz: 7dd04ad99513208826305d50e04b558778800b4ce3dbc6873193d995eb638b48a36c84c20b3a9c57dda18af7503751b48d9af291c6d720314ddf60a988f41b8c
7
+ data.tar.gz: eb4103cf2a62dc935a4cb283896083710f66a60146a5e4086c4fbcf57d215d52e88f5102f744aded09fa4291e1e62afca3375cadc05cf5c2dcbf6e72e9652995
data/Gemfile.lock CHANGED
@@ -22,7 +22,7 @@ GIT
22
22
  PATH
23
23
  remote: .
24
24
  specs:
25
- active_typed_store (1.0.0)
25
+ active_typed_store (1.2.0)
26
26
  activemodel
27
27
  activerecord
28
28
  activesupport
@@ -141,6 +141,7 @@ GEM
141
141
 
142
142
  PLATFORMS
143
143
  arm64-darwin-21
144
+ arm64-darwin-23
144
145
  x86_64-darwin-21
145
146
  x86_64-linux
146
147
 
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ActiveTypedStore
2
2
 
3
- `active_typed_store` is a lightweight (__65 lines of code__) and highly performant gem (see [benchmarks](#benchmarks))
3
+ `active_typed_store` is a lightweight (__105 lines of code__) and highly performant gem (see [benchmarks](#benchmarks))
4
4
  designed to help you store and manage typed data in JSON format within database.
5
5
  This gem provides a simple, yet powerful way to ensure that your JSON data cast
6
6
  to specific types, enabling more structured and reliable use of JSON fields in your Rails models.
@@ -47,7 +47,7 @@ m.asap? # => true
47
47
  - `name` the name of the accessor to the store
48
48
  - `type` a symbol such as `:string` or `:integer`, or a type object to be used for the accessor
49
49
  - `options` (optional), a hash of cast type options such as:
50
- - `precision`, `limit`, `scale`
50
+ - `precision`, `limit`, `scale`
51
51
  - `default` the default value to use when no value is provided. Otherwise, the default will be nil
52
52
  - `array` specifies that the type should be an array
53
53
 
@@ -80,17 +80,41 @@ class Model < ActiveRecord::Base
80
80
  end
81
81
  ```
82
82
 
83
+ ### Hash safety
84
+ 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`.
85
+
86
+ ```ruby
87
+ class Model < ActiveRecord::Base
88
+ typed_store(:params) do
89
+ attr :price, :decimal
90
+ end
91
+ end
92
+
93
+ record = Model.new(price: 1)
94
+ record["price"] # 1
95
+ record[:price] # raises "Symbol keys are not allowed `:price` (ActiveTypedStore::SymbolKeysDisallowed)"
96
+
97
+ # initializers/active_type_store.rb
98
+ ActiveTypeStore.configure do |config|
99
+ config.hash_safety = false # default :disallow_symbol_keys
100
+ end
101
+
102
+ record["price"] # 1
103
+ record[:price] # nil - isn't the expected behavior for most applications
104
+ ```
105
+
83
106
  ### Benchmarks
84
107
  compare `active_typed_store` with other gems
85
108
  ```ruby
109
+ # ruby 3.3.5 arm64-darwin24
86
110
  # gem getter i/s setter i/s Lines of code
87
- # rails (without types): 27930.8 660 170
88
- # active_typed_store: 24318.5 - 1.15x slower 656 65
89
- # store_attribute: 23748.3 - 1.18x slower 639 276
90
- # store_model: 23324.4 - 1.20x slower 595 857
91
- # attr_json: 15541.4 - 1.80x slower 577 - 1.14x slower 1195
92
- # jsonb_accessor: 15000.1 - 1.86x slower 626 324
93
- ```
111
+ # active_typed_store: 28502.2 656 105
112
+ # rails (without types): 27350.5 660 170
113
+ # store_attribute: 24592.2 - 1.16x slower 639 276
114
+ # store_model: 22833.6 - 1.25x slower 595 857
115
+ # attr_json: 14000.4 - 2.03x slower 577 - 1.14x slower 1195
116
+ # jsonb_accessor: 13995.4 - 2.04x slower 626 324
117
+ ```
94
118
 
95
119
  ## License
96
120
 
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveTypedStore
4
+ class Configuration
5
+ attr_accessor :hash_safety
6
+
7
+ def initialize
8
+ self.hash_safety = :disallow_symbol_keys
9
+ end
10
+ end
11
+
12
+ module Configurable
13
+ attr_writer :config
14
+
15
+ def config
16
+ @config ||= ActiveTypedStore::Configuration.new
17
+ end
18
+
19
+ def configure
20
+ yield(config)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveTypedStore
4
+ class SymbolKeysDisallowed < StandardError; end
5
+
6
+ DEFAULT_PROC =
7
+ proc do |_hash, key|
8
+ raise(SymbolKeysDisallowed, "Symbol keys are not allowed `#{key.inspect}`") if key.is_a?(Symbol)
9
+ end
10
+
11
+ module DisallowSymbolKeys
12
+ def self.call!(hash)
13
+ return unless hash.is_a?(Hash)
14
+
15
+ hash.default_proc ||= DEFAULT_PROC
16
+ hash.each_value { call!(_1) }
17
+ end
18
+ end
19
+
20
+ # Same interface as ActiveRecord::Store::HashAccessor
21
+ # Optimized by using object.read_attribute(attribute) instead of object.send(attribute)
22
+ class StoreHashAccessor
23
+ def self.read(object, attribute, key)
24
+ prepare(object, attribute)
25
+ object.read_attribute(attribute)[key]
26
+ end
27
+
28
+ def self.write(object, attribute, key, value)
29
+ prepare(object, attribute)
30
+ object.read_attribute(attribute)[key] = value if value != object.read_attribute(attribute)[key]
31
+ end
32
+
33
+ def self.prepare(object, attribute)
34
+ object.public_send :"#{attribute}=", {} unless object.read_attribute(attribute)
35
+ end
36
+ end
37
+ end
@@ -6,7 +6,20 @@ module ActiveTypedStore
6
6
  attrs = Attrs.new(store_attribute)
7
7
  attrs.instance_eval(&)
8
8
 
9
- store store_attribute, accessors: attrs.fields, coder: JSON
9
+ store_accessor store_attribute, attrs.fields
10
+
11
+ if ActiveTypedStore.config.hash_safety == :disallow_symbol_keys
12
+ define_method store_attribute do
13
+ super().tap { ActiveTypedStore::DisallowSymbolKeys.call!(_1) }
14
+ end
15
+
16
+ define_method :store_accessor_for do |_store_attribute|
17
+ ActiveTypedStore::StoreHashAccessor
18
+ end
19
+
20
+ private :store_accessor_for
21
+ end
22
+
10
23
  include attrs.store_module
11
24
  end
12
25
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveTypedStore
4
- VERSION = "1.1.0"
4
+ VERSION = "1.2.0"
5
5
  end
@@ -1,10 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "active_typed_store/version"
4
+ require_relative "active_typed_store/configuration"
4
5
  require "active_support"
5
6
 
6
7
  ActiveSupport.on_load(:active_record) do
7
8
  require_relative "active_typed_store/store"
8
9
  require_relative "active_typed_store/attrs"
10
+ require_relative "active_typed_store/disallow_symbol_keys"
11
+
9
12
  ActiveSupport.on_load(:active_record) { extend ActiveTypedStore::Store }
10
13
  end
14
+
15
+ module ActiveTypedStore
16
+ extend ActiveTypedStore::Configurable
17
+ end
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: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ermolaev Andrey
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-10-11 00:00:00.000000000 Z
11
+ date: 2024-10-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -70,6 +70,8 @@ files:
70
70
  - bin/setup
71
71
  - lib/active_typed_store.rb
72
72
  - lib/active_typed_store/attrs.rb
73
+ - lib/active_typed_store/configuration.rb
74
+ - lib/active_typed_store/disallow_symbol_keys.rb
73
75
  - lib/active_typed_store/store.rb
74
76
  - lib/active_typed_store/version.rb
75
77
  homepage: https://github.com/corp-gp/active_typed_store