standard_procedure_has_attributes 0.1.0 → 0.2.1

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: 1fd9d76cb1b8d6eca0e2be02212fb237a1f49c1c014010dc71ddca9678ea247a
4
- data.tar.gz: f66ffafac490c8ad1d0915b20bcd56caa2be7eb0a622134785f00c0c4d6f779b
3
+ metadata.gz: 778498af3e84a9695e11e78dea536d76dca1be4d86919b81694cd5d0cb39be19
4
+ data.tar.gz: 6351d9e54b44182b9c076bc3b861293cbcd328368e34a7c11f18432984d4e406
5
5
  SHA512:
6
- metadata.gz: bf4fe8a2fd19fadd3a8f6838e1ff2df9798a4880049cf36f46be7a39402abe5bc9fd0d7f7c940deb13d22aa6661c902dea5c38dd97bb4826de8812883787c40a
7
- data.tar.gz: 7527db1e32925575be31abd9f887231245782d31f86df335d543cc62924d304724d1c84552ae22131c193c751257da228f3080cfc96ccafceed6e36165534bef
6
+ metadata.gz: b110c7d8308842ef2e7d00809d797ac4808105db8f494df2e2a22388c6923f28ddb8b8361b9b91a728976ee5fbf9aaca4b55ec198334e902d406fbc06ab0eb2c
7
+ data.tar.gz: e45bd96f526d0a04c82b1d6911bded7f37e54831b05e7b1a42a3c83b8847fbef132f484149924a2844ed36df31f0ec1f5cdc70512909b258e95a5dc8064eb570
data/README.md CHANGED
@@ -71,10 +71,16 @@ Apart from storage, the attribute behaves just like any other attribute on your
71
71
 
72
72
  ### Defining models
73
73
 
74
- You can also store references to other models using the `has_model` declaration. This stores a [GlobalID](https://github.com/rails/globalid) inside the data field, converting the reference back to a real model when needed. The actual GlobalID is mangled slightly before it is stored, to provide compatibility with the [GlobalIdSerialiser](https://github.com/standard-procedure/global_id_serialiser), which also uses GlobalIDs to store references to models. The difference with `has_model` is `has_model` reloads the model on-demand, whereas `GlobalIdSerialiser` loads all models from the data field when the record is loaded. GlobalIdSerialiser also allows you to store your models within arrays or nested inside hashes - but you need to be careful about the performance implications if lots of models are stored.
74
+ You can also store references to other models using the `has_model` declaration. This stores a [GlobalID](https://github.com/rails/globalid) inside the data field, converting the reference back to a real model when needed. `has_model` reloads the model on-demand so there is no database request until you actually try to access the stored model. However, because of the way this works, you cannot eager-load stored models, so be careful when trying to display large numbers of records, as you will end up with N+1 queries.
75
75
 
76
76
  When using `has_model` you can optionally specify a class name (as a string). If given, the model will be tested to make sure it is that class (or a subclass of it) and, if not the record is marked as invalid.
77
77
 
78
+ ### Defining arrays of models
79
+
80
+ Similarly, you can store arrays of models using the `has_models` declaration. Internally this stores a comma separated list of GlobalIDs inside the data field, using `GlobalID::Locator.locate_many` to convert the array back to models as efficiently as possible. Again, eager loading is not possible so watch out for N+1 queries.
81
+
82
+ Like `has_model` you can optionally specify a class name (as a string), which then validates the provided models to make sure they are valid classes or subclasses.
83
+
78
84
  ## Development
79
85
 
80
86
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -0,0 +1 @@
1
+ a81ee1c0a59142295d6bbfddefd39697884c3de3b911836c49d8f51ffa75b73c21ffe2efa1a7ac90235b640ce5aa07d378d09f4f4b76371a416885704d1a184a
@@ -0,0 +1 @@
1
+ 63c96059dcaa5affe06c9dd6d785385b00b8908dd7f6301cc2e7f09cf442f88c68d36e1b3448a5bab589cc2a91fb7363fd038ed6300ffa87f57548edd517c56f
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HasAttributes
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.1"
5
5
  end
@@ -3,7 +3,7 @@
3
3
  require_relative "has_attributes/version"
4
4
  require "active_support/concern"
5
5
  require "active_support/core_ext/string/inflections"
6
- require "global_id_serialiser"
6
+ require "global_id"
7
7
 
8
8
  module HasAttributes
9
9
  extend ActiveSupport::Concern
@@ -12,22 +12,23 @@ module HasAttributes
12
12
  class_methods do
13
13
  def has_attribute name, cast_type = :string, field_name: :data, **options
14
14
  field_name = field_name.to_sym
15
- name = name.to_sym
15
+ name = name.to_s
16
16
  typecaster = cast_type.nil? ? nil : ActiveRecord::Type.lookup(cast_type)
17
17
  typecast_value = ->(value) { typecaster.nil? ? value : typecaster.cast(value) }
18
+ field_name_value = ->(instance) { instance.send(field_name.to_sym) || {} }
18
19
  define_attribute_method name
19
20
  if cast_type != :boolean
20
- define_method(name.to_sym) { typecast_value.call(send(field_name.to_sym)[name.to_s]) || options[:default] }
21
+ define_method(name.to_sym) { typecast_value.call(field_name_value.call(self)[name.to_s]) || options[:default] }
21
22
  else
22
23
  define_method(name.to_sym) do
23
- value = typecast_value.call(send(field_name.to_sym)[name.to_s])
24
+ value = typecast_value.call(field_name_value.call(self)[name.to_s])
24
25
  [true, false].include?(value) ? value : options[:default]
25
26
  end
26
27
  alias_method :"#{name}?", name.to_sym
27
28
  end
28
29
  define_method(:"#{name}=") do |value|
29
30
  attribute_will_change! name
30
- send(field_name.to_sym)[name.to_s] = typecast_value.call(value)
31
+ send :"#{field_name}=", field_name_value.call(self).merge(name.to_s => typecast_value.call(value))
31
32
  end
32
33
  end
33
34
 
@@ -38,7 +39,7 @@ module HasAttributes
38
39
  validate :"#{name}_class_name", if: -> { send(name).present? } if class_name.present?
39
40
 
40
41
  define_method(name.to_sym) do
41
- model_id = send id_attribute
42
+ model_id = send id_attribute.to_sym
42
43
  model_id.nil? ? nil : GlobalID::Locator.locate(model_id.sub("modelid-", ""))
43
44
  rescue ActiveRecord::RecordNotFound
44
45
  nil
@@ -53,5 +54,32 @@ module HasAttributes
53
54
  errors.add name, :invalid unless send(name).is_a?(class_name.constantize)
54
55
  end
55
56
  end
57
+
58
+ def has_models name, class_name = nil, field_name: :data, **options
59
+ name = name.to_sym
60
+ array_attribute = "#{name}_array"
61
+ has_attribute array_attribute, field_name:, **options
62
+ validate :"#{name}_class_names", if: -> { send(name).any? } if class_name.present?
63
+
64
+ define_method(name.to_sym) do
65
+ model_ids = send(array_attribute).to_s.split(",").map do |model_id|
66
+ model_id.to_s.sub("modelid-", "")
67
+ end
68
+ GlobalID::Locator.locate_many(model_ids)
69
+ rescue ActiveRecord::RecordNotFound
70
+ []
71
+ end
72
+
73
+ define_method(:"#{name}=") do |models|
74
+ model_ids = Array.wrap(models).map do |model|
75
+ model.nil? ? nil : "modelid-#{model.to_global_id}"
76
+ end.compact
77
+ send :"#{array_attribute}=", model_ids.join(",")
78
+ end
79
+
80
+ define_method :"#{name}_class_names" do
81
+ errors.add name, :invalid if send(name).any? { |model| !model.is_a?(class_name.constantize) }
82
+ end
83
+ end
56
84
  end
57
85
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: standard_procedure_has_attributes
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rahoul Baruah
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-06-13 00:00:00.000000000 Z
10
+ date: 2025-07-03 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activesupport
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '7.1'
26
+ - !ruby/object:Gem::Dependency
27
+ name: globalid
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.2'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.2'
26
40
  description: Store Rails attributes in a serialized JSON field, with support for GlobalIDs
27
41
  and indexes
28
42
  email:
@@ -38,6 +52,8 @@ files:
38
52
  - README.md
39
53
  - Rakefile
40
54
  - checksums/standard_procedure_has_attributes-0.1.0.gem.sha512
55
+ - checksums/standard_procedure_has_attributes-0.2.0.gem.sha512
56
+ - checksums/standard_procedure_has_attributes-0.2.1.gem.sha512
41
57
  - lib/has_attributes.rb
42
58
  - lib/has_attributes/version.rb
43
59
  - sig/has_attributes.rbs