attr_json 2.2.0 → 2.3.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: dadc04fb081fee0b326eaff03f569f15d745948119959e2a8eb5d91b690dec38
4
- data.tar.gz: fb6a28e048e03822432e63a7205d0242d218ddace88dd0566624517c3a5454b5
3
+ metadata.gz: eb4f8078039ec81cc25e7a95318ff5e42cb5bf85a955b40326f60f9022af16a2
4
+ data.tar.gz: 635f08ce5fa34ce45dfdd5256ae0e81fdcc2d455f4525bad5f71f5b0c4391c6b
5
5
  SHA512:
6
- metadata.gz: 6048863f54826dc166200400a93cdfdb91055ddba960d6d2b8587077eafda5e3058e49d030f60c3eb05f74ba2ea99512c087c403f8eb0a18e4dd45c4f7929bee
7
- data.tar.gz: afafb7cf57637cce5f99c3afacd38bc802aca8c5e60b9c0cd1adebc08a482b064ce5a7398c44507b612f94691fd8362bba9cb00d13f8d14603524e235e93b617
6
+ metadata.gz: 22c35f674c7b5f3a4e91f348d20439df3e8fe175ca5d3c8938720a5e71739df2474cdddd5fdab5f0527f8751ed0fb6dee11ba7f75889c143daca92bd84afc86b
7
+ data.tar.gz: 636628f0083dd61c869de5b9eb65a0c11988560e26d5dcd8e822759788bdac4bdb3c6f0359e2257867ac91a61721fda32df54a36f51a001450ef883289be433a
@@ -18,7 +18,10 @@ jobs:
18
18
  tests:
19
19
  services:
20
20
  db:
21
- image: postgres:9.4
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.2.0...HEAD)
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
- definitions.each { |d| store_key_index!(d) }
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.to_s] &&
50
- @store_key_to_definition[container_attribute.to_s][store_key.to_s]
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
- @name_to_definition.values
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(&:to_s)
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.to_s] ||= {})
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.to_s)
90
- existing = container_hash[definition.store_key.to_s]
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.to_s] = definition
128
+ container_hash[AttrJson.efficient_to_s(definition.store_key)] = definition
95
129
  end
96
130
  end
97
131
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'attr_json/type/array'
2
4
 
3
5
  module AttrJson
@@ -62,7 +64,7 @@
62
64
  end
63
65
 
64
66
  def store_key
65
- (@store_key || name).to_s
67
+ AttrJson.efficient_to_s(@store_key || name)
66
68
  end
67
69
 
68
70
  def has_default?
@@ -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.to_s)
140
- key = attribute_def.name.to_s
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.to_s, attr_json_registry.type_for_attribute(name)]}.to_h
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.to_s, value)
230
+ _attr_json_write(AttrJson.efficient_to_s(name), value)
229
231
  end
230
232
 
231
233
  define_method("#{name}") do
232
- attributes[name.to_s]
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.to_s)
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.to_s] = attribute_def.cast(value)
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.to_s] = value
443
+ attributes[AttrJson.efficient_to_s(key)] = value
442
444
  end
443
445
  end
444
446
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AttrJson
2
4
  module NestedAttributes
3
5
  # Implementation of `build_` methods, called by the `build_` methods
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AttrJson
2
4
  module NestedAttributes
3
5
  # Rails has a weird "multiparameter attribute" thing, that is used for simple_form's
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'attr_json/nested_attributes/multiparameter_attribute_writer'
2
4
 
3
5
  module AttrJson
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'attr_json/nested_attributes/builder'
2
4
  require 'attr_json/nested_attributes/writer'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AttrJson
2
4
  module Record
3
5
  # Implementation class called by #jsonb_contains scope method. Ordinarily
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'attr_json/record/query_builder'
2
4
 
3
5
  module AttrJson
@@ -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
- # Want to lazily add an attribute cover to the json container attribute,
167
- # only if it hasn't already been done. WARNING we are using internal
168
- # Rails API here, but only way to do this lazily, which I thought was
169
- # worth it. On the other hand, I think .attribute is idempotent, maybe we don't need it...
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(
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AttrJson
2
4
 
3
5
  # A little wrapper to provide an object that provides #dump and #load method for use
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AttrJson
2
4
  module Type
3
5
  # You can wrap any ActiveModel::Type in one of these, and it's magically
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AttrJson
2
4
  module Type
3
5
  # A type that gets applied to the AR container/store jsonb attribute,
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AttrJson
2
4
  module Type
3
5
  # An ActiveModel::Type representing a particular AttrJson::Model
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AttrJson
2
4
  module Type
3
5
  # AttrJson::Type::PolymorphicModel can be used to create attr_json attributes
@@ -1,3 +1,3 @@
1
1
  module AttrJson
2
- VERSION = "2.2.0"
2
+ VERSION = "2.3.0"
3
3
  end
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.2.0
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-10-05 00:00:00.000000000 Z
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.10
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.