active_typed_store 2.3.0 → 2.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 14bd114be57f358f5e2f39a9ee2c554d2bc150e0b9df39b269081c2b1efc543e
4
- data.tar.gz: a7addc73c5a5f5e2147f0b216e4109152aa5d5db44487971de45face6ece273f
3
+ metadata.gz: 89b2c39e6bd9afe68635f920a123392538160c0eb6bbe9484738ced7600b58fa
4
+ data.tar.gz: 30ed4d583b2fdb4ebbb4480b479826a54088f6fb2467dc87ba99e393c6c2270a
5
5
  SHA512:
6
- metadata.gz: 2992b768f9374caf908ed316e5f2fd3fcd37de8495bf89988e27e47726be73c2678ccf7827f6299d901db6c282818993f0027b1113028df07cf79130f387024f
7
- data.tar.gz: a41889669cb6d36feac7b129f66d65c747ceac9b0e08ff625fb5d598d6175757a8aa089ab1ee78725a3dcdb289745cac2a5efc09aee5fc2a8e0c16e24f445319
6
+ metadata.gz: a54b0a60a62d550f6493ac64f36c42d9a5f52eb207572b47b269b2cdd77815a2659b01e16648f1ed53a0ee943b698515ed04a3eb5ed0fcce7ef94b3eda942366
7
+ data.tar.gz: d57e21409b72efcc79086186aeb4ee8ea542380738cfdd2ddff94892dcb215b9ac05f509188dc5671e799e92f57b9aadabeb9b4f1473c3137f0b8d0f4f9b888b
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ActiveTypedStore
2
2
 
3
- `active_typed_store` is a lightweight (__105 lines of code__) and highly performant gem (see [benchmarks](#benchmarks))
3
+ `active_typed_store` is a lightweight (__~100 lines of code [1](https://github.com/corp-gp/active_typed_store/blob/master/lib/active_typed_store/attrs.rb) [2](https://github.com/corp-gp/active_typed_store/blob/master/lib/active_typed_store/store.rb)__) 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.
@@ -80,6 +80,69 @@ class Model < ActiveRecord::Base
80
80
  end
81
81
  ```
82
82
 
83
+ ### Active Values
84
+
85
+ ```ruby
86
+ class Model < ActiveRecord::Base
87
+ typed_store(:params) do
88
+ attr :name, :string
89
+ attr :parcel, Parcel::TYPE, default: Parcel.new
90
+ attr :parcels, Parcel::ARRAY_TYPE, default: []
91
+
92
+ # if types registered in ActiveRecord, you can use as symbol name
93
+ # ActiveRecord::Type.register(:parcel, ParcelType)
94
+ # ActiveRecord::Type.register(:parcel_array, ParcelArrayType)
95
+ # attr :parcel, :parcel, default: Parcel.new
96
+ # attr :parcels, :parcel_array, default: []
97
+ end
98
+ end
99
+
100
+ record = Model.new
101
+ record.parcel.weight = 10
102
+ record.parcel.height # => 0 is default value
103
+ record.parcels << Parcel.new(weight: 10, height: 20)
104
+ ```
105
+
106
+ Example Active Value defined class:
107
+ ```ruby
108
+ class Parcel
109
+ include ActiveModel::Attributes
110
+ include ActiveModel::AttributeAssignment
111
+
112
+ attribute :height, :float, default: 0
113
+ attribute :weight, :float
114
+
115
+ def initialize(attributes = {})
116
+ super()
117
+ assign_attributes(attributes)
118
+ end
119
+
120
+ def inspect = "<Parcel #{attributes.map { |k, v| "#{k}=#{v}" }.join(' ')}>"
121
+ def as_json = attributes.compact
122
+
123
+ class Type < ActiveRecord::Type::Json
124
+ def cast(value)
125
+ case value
126
+ when Hash then Parcel.new(value)
127
+ when Parcel then value
128
+ end
129
+ end
130
+ end
131
+
132
+ class ArrayType < ActiveRecord::Type::Json
133
+ def cast(value)
134
+ case value
135
+ when Array then value.map { it.is_a?(Parcel) ? it : Parcel.new(it) }
136
+ end
137
+ end
138
+ end
139
+
140
+ # Memoize types to enable sharing across models or attributes, reducing memory usage and optimizing YJIT warm-up.
141
+ TYPE = Type.new
142
+ ARRAY_TYPE = ArrayType.new
143
+ end
144
+ ```
145
+
83
146
  ### Hash safety
84
147
  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
148
 
@@ -91,16 +154,16 @@ class Model < ActiveRecord::Base
91
154
  end
92
155
 
93
156
  record = Model.new(price: 1)
94
- record["price"] # 1
95
- record[:price] # raises "Symbol keys are not allowed `:price` (ActiveTypedStore::SymbolKeysDisallowed)"
157
+ record.params["price"] # 1
158
+ record.params[:price] # raises "Symbol keys are not allowed `:price` (ActiveTypedStore::SymbolKeysDisallowed)"
96
159
 
97
160
  # initializers/active_type_store.rb
98
161
  ActiveTypeStore.configure do |config|
99
162
  config.hash_safety = false # default :disallow_symbol_keys
100
163
  end
101
164
 
102
- record["price"] # 1
103
- record[:price] # nil - isn't the expected behavior for most applications
165
+ record.params["price"] # 1
166
+ record.params[:price] # nil - isn't the expected behavior for most applications
104
167
  ```
105
168
 
106
169
  ### Benchmarks
@@ -37,7 +37,7 @@ module ActiveTypedStore
37
37
  store_module.define_method(:"#{field}=") do |value|
38
38
  v = (type.respond_to?(:cast) ? type.cast(value) : type[value]) unless value.nil?
39
39
  write_store_attribute(store_attribute, field, v)
40
- self[store_attribute].delete(field) if v.nil?
40
+ read_attribute(store_attribute).delete(field) if v.nil?
41
41
  end
42
42
  end
43
43
 
@@ -50,18 +50,19 @@ module ActiveTypedStore
50
50
  casted_val =
51
51
  if val.nil? && !default.nil?
52
52
  v = default.dup
53
- self[store_attribute][field] = v
54
- clear_attribute_change(store_attribute)
55
- self[store_attribute][field] = v
53
+ is_changed = attribute_changed?(store_attribute)
54
+ read_attribute(store_attribute)[field] = v
55
+ clear_attribute_change(store_attribute) unless is_changed
56
+ read_attribute(store_attribute)[field] = v
56
57
  elsif val.nil?
57
58
  return nil
58
59
  elsif instance_variable_get(ivar_prev).eql?(val)
59
60
  return val
60
61
  elsif type.respond_to?(:cast)
61
62
  casted = type.cast(val)
62
- casted.eql?(val) ? val : (self[store_attribute][field] = casted)
63
+ casted.eql?(val) ? val : (read_attribute(store_attribute)[field] = casted)
63
64
  else
64
- self[store_attribute][field] = type[val]
65
+ read_attribute(store_attribute)[field] = type[val]
65
66
  end
66
67
 
67
68
  instance_variable_set(ivar_prev, casted_val)
@@ -31,7 +31,7 @@ module ActiveTypedStore
31
31
  end
32
32
 
33
33
  def self.prepare(object, attribute)
34
- object.public_send :"#{attribute}=", {} unless object.read_attribute(attribute)
34
+ object.send :"#{attribute}=", {} unless object.read_attribute(attribute)
35
35
  end
36
36
  end
37
37
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveTypedStore
4
- VERSION = "2.3.0"
4
+ VERSION = "2.4.0"
5
5
  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: 2.3.0
4
+ version: 2.4.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: 2025-05-29 00:00:00.000000000 Z
11
+ date: 2025-11-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel