attr_json 2.2.0 → 2.3.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/.github/workflows/ci.yml +5 -2
- data/CHANGELOG.md +18 -1
- data/lib/attr_json/attribute_definition/registry.rb +43 -9
- data/lib/attr_json/attribute_definition.rb +3 -1
- data/lib/attr_json/model.rb +10 -8
- data/lib/attr_json/nested_attributes/builder.rb +2 -0
- data/lib/attr_json/nested_attributes/multiparameter_attribute_writer.rb +2 -0
- data/lib/attr_json/nested_attributes/writer.rb +2 -0
- data/lib/attr_json/nested_attributes.rb +2 -0
- data/lib/attr_json/record/query_builder.rb +2 -0
- data/lib/attr_json/record/query_scopes.rb +2 -0
- data/lib/attr_json/record.rb +8 -14
- data/lib/attr_json/serialization_coder_from_type.rb +2 -0
- data/lib/attr_json/type/array.rb +2 -0
- data/lib/attr_json/type/container_attribute.rb +2 -0
- data/lib/attr_json/type/model.rb +2 -0
- data/lib/attr_json/type/polymorphic_model.rb +2 -0
- data/lib/attr_json/version.rb +1 -1
- data/lib/attr_json.rb +21 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eb4f8078039ec81cc25e7a95318ff5e42cb5bf85a955b40326f60f9022af16a2
|
4
|
+
data.tar.gz: 635f08ce5fa34ce45dfdd5256ae0e81fdcc2d455f4525bad5f71f5b0c4391c6b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 22c35f674c7b5f3a4e91f348d20439df3e8fe175ca5d3c8938720a5e71739df2474cdddd5fdab5f0527f8751ed0fb6dee11ba7f75889c143daca92bd84afc86b
|
7
|
+
data.tar.gz: 636628f0083dd61c869de5b9eb65a0c11988560e26d5dcd8e822759788bdac4bdb3c6f0359e2257867ac91a61721fda32df54a36f51a001450ef883289be433a
|
data/.github/workflows/ci.yml
CHANGED
@@ -18,7 +18,10 @@ jobs:
|
|
18
18
|
tests:
|
19
19
|
services:
|
20
20
|
db:
|
21
|
-
|
21
|
+
# used to be 9.4, should work on 9.4, only moved to 10.0
|
22
|
+
# because of rails 7.1 bug (rails intends to support 9 too!)
|
23
|
+
# https://github.com/jrochkind/attr_json/issues/211
|
24
|
+
image: postgres:10.0
|
22
25
|
env:
|
23
26
|
POSTGRES_USER: postgres
|
24
27
|
POSTGRES_PASSWORD: postgres
|
@@ -49,7 +52,7 @@ jobs:
|
|
49
52
|
|
50
53
|
- gemfile: rails_7_0
|
51
54
|
ruby: 3.2
|
52
|
-
|
55
|
+
|
53
56
|
- gemfile: rails_7_1
|
54
57
|
ruby: '3.0'
|
55
58
|
|
data/CHANGELOG.md
CHANGED
@@ -4,7 +4,7 @@ Notable changes to this project will be documented in this file.
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
6
|
|
7
|
-
## [Unreleased](https://github.com/jrochkind/attr_json/compare/v2.
|
7
|
+
## [Unreleased](https://github.com/jrochkind/attr_json/compare/v2.3.0...HEAD)
|
8
8
|
|
9
9
|
### Fixed
|
10
10
|
|
@@ -22,6 +22,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
22
22
|
|
23
23
|
*
|
24
24
|
|
25
|
+
### Changed
|
26
|
+
|
27
|
+
*
|
28
|
+
|
29
|
+
*
|
30
|
+
|
31
|
+
*
|
32
|
+
|
33
|
+
## [2.3.0]((https://github.com/jrochkind/attr_json/compare/v2.2.0...v2.3.0))
|
34
|
+
|
35
|
+
|
36
|
+
### Changed
|
37
|
+
|
38
|
+
* Avoid private ActiveRecord API when lazily registering container attributes. (Compat with Rails post 7.1) https://github.com/jrochkind/attr_json/pull/214
|
39
|
+
|
40
|
+
* Freeze more strings to reduce String allocations https://github.com/jrochkind/attr_json/pull/216
|
41
|
+
|
25
42
|
## [2.2.0](https://github.com/jrochkind/attr_json/compare/v2.1.0...v2.2.0)
|
26
43
|
|
27
44
|
### Added
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'attr_json/attribute_definition'
|
2
4
|
|
3
5
|
module AttrJson
|
@@ -25,7 +27,10 @@ module AttrJson
|
|
25
27
|
def initialize(hash = {})
|
26
28
|
@name_to_definition = hash.dup
|
27
29
|
@store_key_to_definition = {}
|
28
|
-
|
30
|
+
|
31
|
+
@name_to_definition.values.each { |d| store_key_index!(d) }
|
32
|
+
|
33
|
+
@container_attributes_registered = Hash.new { Set.new }
|
29
34
|
end
|
30
35
|
|
31
36
|
def fetch(key, *args, &block)
|
@@ -46,12 +51,13 @@ module AttrJson
|
|
46
51
|
|
47
52
|
# Can return nil if none found.
|
48
53
|
def store_key_lookup(container_attribute, store_key)
|
49
|
-
@store_key_to_definition[container_attribute
|
50
|
-
@store_key_to_definition[container_attribute
|
54
|
+
@store_key_to_definition[AttrJson.efficient_to_s(container_attribute)] &&
|
55
|
+
@store_key_to_definition[AttrJson.efficient_to_s(container_attribute)][AttrJson.efficient_to_s(store_key)]
|
51
56
|
end
|
52
57
|
|
53
58
|
def definitions
|
54
|
-
|
59
|
+
# Since we are immutable, we can cache this to avoid allocation in a hot spot
|
60
|
+
@stored_definitions ||= @name_to_definition.values
|
55
61
|
end
|
56
62
|
|
57
63
|
# Returns all registered attributes as an array of symbols
|
@@ -60,7 +66,7 @@ module AttrJson
|
|
60
66
|
end
|
61
67
|
|
62
68
|
def container_attributes
|
63
|
-
@store_key_to_definition.keys.collect(
|
69
|
+
@store_key_to_definition.keys.collect { |s| AttrJson.efficient_to_s(s) }
|
64
70
|
end
|
65
71
|
|
66
72
|
# This is how you register additional definitions, as a non-mutating
|
@@ -73,6 +79,32 @@ module AttrJson
|
|
73
79
|
end
|
74
80
|
end
|
75
81
|
|
82
|
+
|
83
|
+
# We need to lazily set the container type only the FIRST time
|
84
|
+
#
|
85
|
+
# While also avoiding this triggering ActiveRecord to actually go to DB,
|
86
|
+
# we don't want DB connection forced on boot, that's a problem for many apps,
|
87
|
+
# including that may not have a DB connection in initial development.
|
88
|
+
# (#type_for_attribute forces DB connection)
|
89
|
+
#
|
90
|
+
# AND we need to call container attriubte on SUB-CLASS AGAIN, iff sub-class
|
91
|
+
# has any of it's own new registrations, to make sure we get the right type in
|
92
|
+
# sub-class!
|
93
|
+
#
|
94
|
+
# So we just keep track of whether we've registered ourselves, so we can
|
95
|
+
# first time we need to.
|
96
|
+
#
|
97
|
+
# While current implementation is simple, this has ended up a bit fragile,
|
98
|
+
# a different API that doesn't require us to do this implicitly lazily
|
99
|
+
# might be preferred! But this is what we got for now.
|
100
|
+
def register_container_attribute(attribute_name:, model:)
|
101
|
+
@container_attributes_registered[attribute_name.to_sym] << model
|
102
|
+
end
|
103
|
+
|
104
|
+
def container_attribute_registered?(attribute_name:, model:)
|
105
|
+
@container_attributes_registered[attribute_name.to_sym].include?(model)
|
106
|
+
end
|
107
|
+
|
76
108
|
protected
|
77
109
|
|
78
110
|
def add!(definition)
|
@@ -81,17 +113,19 @@ module AttrJson
|
|
81
113
|
end
|
82
114
|
@name_to_definition[definition.name.to_sym] = definition
|
83
115
|
store_key_index!(definition)
|
116
|
+
|
117
|
+
@stored_definitions = nil
|
84
118
|
end
|
85
119
|
|
86
120
|
def store_key_index!(definition)
|
87
|
-
container_hash = (@store_key_to_definition[definition.container_attribute
|
121
|
+
container_hash = (@store_key_to_definition[AttrJson.efficient_to_s(definition.container_attribute)] ||= {})
|
88
122
|
|
89
|
-
if container_hash.has_key?(definition.store_key
|
90
|
-
existing = container_hash[definition.store_key
|
123
|
+
if container_hash.has_key?(AttrJson.efficient_to_s(definition.store_key))
|
124
|
+
existing = container_hash[AttrJson.efficient_to_s(definition.store_key)]
|
91
125
|
raise ArgumentError, "Can't add, store key `#{definition.store_key}` conflicts with existing attribute: #{existing.original_args}"
|
92
126
|
end
|
93
127
|
|
94
|
-
container_hash[definition.store_key
|
128
|
+
container_hash[AttrJson.efficient_to_s(definition.store_key)] = definition
|
95
129
|
end
|
96
130
|
end
|
97
131
|
end
|
data/lib/attr_json/model.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'active_support/concern'
|
2
4
|
require 'active_model/type'
|
3
5
|
|
@@ -136,8 +138,8 @@ module AttrJson
|
|
136
138
|
def new_from_serializable(attributes = {})
|
137
139
|
attributes = attributes.collect do |key, value|
|
138
140
|
# store keys in arguments get translated to attribute names on initialize.
|
139
|
-
if attribute_def = self.attr_json_registry.store_key_lookup("", key
|
140
|
-
key = attribute_def.name
|
141
|
+
if attribute_def = self.attr_json_registry.store_key_lookup("".freeze, AttrJson.efficient_to_s(key))
|
142
|
+
key = AttrJson.efficient_to_s(attribute_def.name)
|
141
143
|
end
|
142
144
|
|
143
145
|
attr_type = self.attr_json_registry.has_attribute?(key) && self.attr_json_registry.type_for_attribute(key)
|
@@ -184,7 +186,7 @@ module AttrJson
|
|
184
186
|
|
185
187
|
# like the ActiveModel::Attributes method, hash with name keys, and ActiveModel::Type values
|
186
188
|
def attribute_types
|
187
|
-
attribute_names.collect { |name| [name
|
189
|
+
attribute_names.collect { |name| [AttrJson.efficient_to_s(name), attr_json_registry.type_for_attribute(name)]}.to_h
|
188
190
|
end
|
189
191
|
|
190
192
|
|
@@ -225,11 +227,11 @@ module AttrJson
|
|
225
227
|
|
226
228
|
_attr_jsons_module.module_eval do
|
227
229
|
define_method("#{name}=") do |value|
|
228
|
-
_attr_json_write(name
|
230
|
+
_attr_json_write(AttrJson.efficient_to_s(name), value)
|
229
231
|
end
|
230
232
|
|
231
233
|
define_method("#{name}") do
|
232
|
-
attributes[name
|
234
|
+
attributes[AttrJson.efficient_to_s(name)]
|
233
235
|
end
|
234
236
|
end
|
235
237
|
end
|
@@ -427,7 +429,7 @@ module AttrJson
|
|
427
429
|
|
428
430
|
def fill_in_defaults!
|
429
431
|
self.class.attr_json_registry.definitions.each do |definition|
|
430
|
-
if definition.has_default? && !attributes.has_key?(definition.name
|
432
|
+
if definition.has_default? && !attributes.has_key?(AttrJson.efficient_to_s(definition.name))
|
431
433
|
self.send("#{definition.name.to_s}=", definition.provide_default!)
|
432
434
|
end
|
433
435
|
end
|
@@ -435,10 +437,10 @@ module AttrJson
|
|
435
437
|
|
436
438
|
def _attr_json_write(key, value)
|
437
439
|
if attribute_def = self.class.attr_json_registry[key.to_sym]
|
438
|
-
attributes[key
|
440
|
+
attributes[AttrJson.efficient_to_s(key)] = attribute_def.cast(value)
|
439
441
|
else
|
440
442
|
# TODO, strict mode, ignore, raise, allow.
|
441
|
-
attributes[key
|
443
|
+
attributes[AttrJson.efficient_to_s(key)] = value
|
442
444
|
end
|
443
445
|
end
|
444
446
|
|
data/lib/attr_json/record.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'attr_json/attribute_definition'
|
2
4
|
require 'attr_json/attribute_definition/registry'
|
3
5
|
require 'attr_json/type/container_attribute'
|
@@ -160,22 +162,14 @@ module AttrJson
|
|
160
162
|
options.assert_valid_keys(AttributeDefinition::VALID_OPTIONS + [:validate, :accepts_nested_attributes])
|
161
163
|
container_attribute = options[:container_attribute]
|
162
164
|
|
163
|
-
# TODO arg check container_attribute make sure it exists. Hard cause
|
164
|
-
# schema isn't loaded yet when class def is loaded. Maybe not.
|
165
165
|
|
166
|
-
#
|
167
|
-
#
|
168
|
-
#
|
169
|
-
#
|
170
|
-
|
171
|
-
# We set default to empty hash, because that 'tricks' AR into knowing any
|
172
|
-
# application of defaults is a change that needs to be saved.
|
173
|
-
unless attributes_to_define_after_schema_loads[container_attribute.to_s] &&
|
174
|
-
attributes_to_define_after_schema_loads[container_attribute.to_s].first.is_a?(AttrJson::Type::ContainerAttribute) &&
|
175
|
-
attributes_to_define_after_schema_loads[container_attribute.to_s].first.model == self
|
176
|
-
# If this is already defined, but was for superclass, we need to define it again for
|
177
|
-
# this class.
|
166
|
+
# Make sure to "lazily" register attribute for *container* class if this is the first time
|
167
|
+
# this container attribute hsa been encountered for this specific class. The registry
|
168
|
+
# helps us keep track. Kinda messy, in future we may want a more explicit API
|
169
|
+
# that does not require us to implicitly track first-time per-container.
|
170
|
+
unless self.attr_json_registry.container_attribute_registered?(model: self, attribute_name: container_attribute.to_sym)
|
178
171
|
attribute container_attribute.to_sym, AttrJson::Type::ContainerAttribute.new(self, container_attribute), default: -> { {} }
|
172
|
+
self.attr_json_registry.register_container_attribute(model: self, attribute_name: container_attribute.to_sym)
|
179
173
|
end
|
180
174
|
|
181
175
|
self.attr_json_registry = attr_json_registry.with(
|
data/lib/attr_json/type/array.rb
CHANGED
data/lib/attr_json/type/model.rb
CHANGED
data/lib/attr_json/version.rb
CHANGED
data/lib/attr_json.rb
CHANGED
@@ -10,5 +10,25 @@ require 'attr_json/record/query_scopes'
|
|
10
10
|
require 'attr_json/type/polymorphic_model'
|
11
11
|
|
12
12
|
module AttrJson
|
13
|
-
|
13
|
+
# We need to convert Symbols to strings a lot at present -- ActiveRecord does too, so
|
14
|
+
# not too suprrising.
|
15
|
+
#
|
16
|
+
# In Rails 3.0 and above, we can use Symbol#name to get a frozen string back
|
17
|
+
# and avoid extra allocations. https://bugs.ruby-lang.org/issues/16150
|
18
|
+
#
|
19
|
+
# Ruby 2.7 doens't yet have it though. As long as we are supporting ruby 2.7,
|
20
|
+
# we'll just check at runtime to keep this lean
|
21
|
+
if RUBY_VERSION.split('.').first.to_i >= 3
|
22
|
+
def self.efficient_to_s(obj)
|
23
|
+
if obj.kind_of?(Symbol)
|
24
|
+
obj.name
|
25
|
+
else
|
26
|
+
obj.to_s
|
27
|
+
end
|
28
|
+
end
|
29
|
+
else
|
30
|
+
def self.efficient_to_s(obj)
|
31
|
+
obj.to_s
|
32
|
+
end
|
33
|
+
end
|
14
34
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: attr_json
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonathan Rochkind
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-12-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -207,7 +207,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
207
207
|
- !ruby/object:Gem::Version
|
208
208
|
version: '0'
|
209
209
|
requirements: []
|
210
|
-
rubygems_version: 3.4.
|
210
|
+
rubygems_version: 3.4.21
|
211
211
|
signing_key:
|
212
212
|
specification_version: 4
|
213
213
|
summary: ActiveRecord attributes stored serialized in a json column, super smooth.
|