store_model 2.0.1 → 2.1.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 +4 -4
- data/README.md +43 -1
- data/lib/store_model/configuration.rb +4 -0
- data/lib/store_model/model.rb +39 -15
- data/lib/store_model/nested_attributes.rb +50 -12
- data/lib/store_model/types/one.rb +1 -3
- data/lib/store_model/types/raw_json.rb +20 -0
- data/lib/store_model/types.rb +2 -0
- data/lib/store_model/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a3b12509ebafeb20d7a2bcb2ae1094dcd756f1b86f31dd8066b0abd61d8f998a
|
4
|
+
data.tar.gz: d3a06944b46ebf4094de55736e892f15ae27514e0604047260e74a033bbc7cda
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7d8ec95caf59aea39e3cedc20a471db9289f4c1eda9d083a1b2fd321f732e7d056b67ac58a7f4f437cb04e932c1f14f18e56dfb9b04c884e5d64025cc7d7989b
|
7
|
+
data.tar.gz: e526dbc89d1c4186b1fe94581acb2b1d80e207bcf08d69809dffc393d43e3d8d2cd08c905d3d3d4f842c9bf7db9ad1581611368fabe8206eb0445c16a2c615eb
|
data/README.md
CHANGED
@@ -76,7 +76,49 @@ product.save
|
|
76
76
|
_Usage note: Rails and assigning Arrays/Hashes to records_
|
77
77
|
|
78
78
|
- Assigned attributes must be a String, Hash, Array of Hashes, or StoreModel. For example, if the attributes are coming from a controller, be sure to convert any ActionController::Parameters as needed.
|
79
|
-
- Any changes made to a StoreModel instance requires the attribute be
|
79
|
+
- Any changes made to a StoreModel instance requires the attribute be flagged as dirty, either by reassignment (`self.my_stored_models = my_stored_models.map(&:as_json)`) or by `will_change!` (`self.my_stored_models_will_change!`)
|
80
|
+
- Mixing `StoreModel::NestedAttributes` into your model will allow you to use `accepts_nested_attributes_for` in the same way as ActiveRecord.
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
class Supplier < ActiveRecord::Base
|
84
|
+
include StoreModel::NestedAttributes
|
85
|
+
|
86
|
+
has_many :bicycles, dependent: :destroy
|
87
|
+
|
88
|
+
attribute :products, Product.to_array_type
|
89
|
+
|
90
|
+
accepts_nested_attributes_for :bicycles, :products, allow_destroy: true
|
91
|
+
end
|
92
|
+
```
|
93
|
+
|
94
|
+
This will allow the form builders to work their magic:
|
95
|
+
|
96
|
+
```erb
|
97
|
+
<%= form_with model: @supplier do |form| %>
|
98
|
+
<%= form.fields_for :products do |product_fields| %>
|
99
|
+
<%= product_fields.text_field :name %>
|
100
|
+
<% end %>
|
101
|
+
<% end %>
|
102
|
+
```
|
103
|
+
|
104
|
+
Resulting in:
|
105
|
+
```html
|
106
|
+
<input type="text" name="supplier[products_attributes][0][name]" id="supplier_products_attributes_0_name">
|
107
|
+
```
|
108
|
+
|
109
|
+
In the controller:
|
110
|
+
```ruby
|
111
|
+
def create
|
112
|
+
@supplier = Supplier.new(supplier_params)
|
113
|
+
@supplier.save
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def supplier_params
|
119
|
+
params.require(:supplier).permit(products_attributes: [:name])
|
120
|
+
end
|
121
|
+
```
|
80
122
|
|
81
123
|
## Documentation
|
82
124
|
|
@@ -15,6 +15,10 @@ module StoreModel
|
|
15
15
|
# @return [Boolean]
|
16
16
|
attr_accessor :serialize_unknown_attributes
|
17
17
|
|
18
|
+
# Controls if the result of `as_json` will serialize enum fiels using `as_json`
|
19
|
+
# @return [Boolean]
|
20
|
+
attr_accessor :serialize_enums_using_as_json
|
21
|
+
|
18
22
|
def initialize
|
19
23
|
@serialize_unknown_attributes = true
|
20
24
|
end
|
data/lib/store_model/model.rb
CHANGED
@@ -7,7 +7,7 @@ require "store_model/nested_attributes"
|
|
7
7
|
|
8
8
|
module StoreModel
|
9
9
|
# When included into class configures it to handle JSON column
|
10
|
-
module Model
|
10
|
+
module Model # rubocop:disable Metrics/ModuleLength
|
11
11
|
def self.included(base) # :nodoc:
|
12
12
|
base.include ActiveModel::Model
|
13
13
|
base.include ActiveModel::Attributes
|
@@ -31,16 +31,27 @@ module StoreModel
|
|
31
31
|
# @param options [Hash]
|
32
32
|
#
|
33
33
|
# @return [Hash]
|
34
|
-
def as_json(options = {})
|
34
|
+
def as_json(options = {}) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
35
35
|
serialize_unknown_attributes = if options.key?(:serialize_unknown_attributes)
|
36
36
|
options[:serialize_unknown_attributes]
|
37
37
|
else
|
38
38
|
StoreModel.config.serialize_unknown_attributes
|
39
39
|
end
|
40
40
|
|
41
|
-
|
41
|
+
serialize_enums_using_as_json = if options.key?(:serialize_enums_using_as_json)
|
42
|
+
options[:serialize_enums_using_as_json]
|
43
|
+
else
|
44
|
+
StoreModel.config.serialize_enums_using_as_json
|
45
|
+
end
|
46
|
+
|
47
|
+
result = @attributes.keys.each_with_object({}) do |key, values|
|
48
|
+
values[key] = serialized_attribute(key)
|
49
|
+
end.with_indifferent_access
|
50
|
+
|
42
51
|
result.merge!(unknown_attributes) if serialize_unknown_attributes
|
43
|
-
result.as_json(options)
|
52
|
+
result.as_json(options).tap do |json|
|
53
|
+
serialize_enums!(json) if serialize_enums_using_as_json
|
54
|
+
end
|
44
55
|
end
|
45
56
|
|
46
57
|
# Returns an Object, similar to Hash#fetch, raises
|
@@ -167,17 +178,6 @@ module StoreModel
|
|
167
178
|
@unknown_attributes ||= {}
|
168
179
|
end
|
169
180
|
|
170
|
-
# Serialized values for storing in db
|
171
|
-
#
|
172
|
-
# @return [Hash]
|
173
|
-
def values_for_database
|
174
|
-
result = @attributes.keys.each_with_object({}) do |key, values|
|
175
|
-
values[key] = @attributes.fetch(key).value_for_database
|
176
|
-
end
|
177
|
-
result.merge!(unknown_attributes)
|
178
|
-
result
|
179
|
-
end
|
180
|
-
|
181
181
|
private
|
182
182
|
|
183
183
|
def attribute?(attribute)
|
@@ -186,5 +186,29 @@ module StoreModel
|
|
186
186
|
else value.present?
|
187
187
|
end
|
188
188
|
end
|
189
|
+
|
190
|
+
def serialize_enums!(json)
|
191
|
+
enum_mappings =
|
192
|
+
self.class
|
193
|
+
.attribute_types
|
194
|
+
.select { |_, type| type.is_a?(StoreModel::Types::EnumType) }
|
195
|
+
|
196
|
+
enum_mappings.each do |name, _|
|
197
|
+
next unless json.key?(name)
|
198
|
+
|
199
|
+
json[name] = public_send(name).as_json unless json[name].nil?
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def serialized_attribute(attr_name)
|
204
|
+
attr = @attributes.fetch(attr_name)
|
205
|
+
if attr.value.is_a? StoreModel::Model
|
206
|
+
Types::RawJSONEncoder.new(attr.value_for_database)
|
207
|
+
elsif attr.value.is_a? Array
|
208
|
+
attr.value.as_json
|
209
|
+
else
|
210
|
+
attr.value_for_database
|
211
|
+
end
|
212
|
+
end
|
189
213
|
end
|
190
214
|
end
|
@@ -13,27 +13,54 @@ module StoreModel
|
|
13
13
|
# @param associations [Array] list of associations and options to define attributes, for example:
|
14
14
|
# accepts_nested_attributes_for [:suppliers, allow_destroy: true]
|
15
15
|
#
|
16
|
+
# Alternatively, use the standard Rails syntax:
|
17
|
+
#
|
18
|
+
# @param associations [Array] list of associations and attributes to define getters and setters.
|
19
|
+
#
|
20
|
+
# @param options [Hash] options not supported by StoreModel will still be passed to ActiveRecord.
|
21
|
+
#
|
16
22
|
# Supported options:
|
17
23
|
# [:allow_destroy]
|
18
24
|
# If true, destroys any members from the attributes hash with a
|
19
25
|
# <tt>_destroy</tt> key and a value that evaluates to +true+
|
20
26
|
# (e.g. 1, '1', true, or 'true'). This option is off by default.
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
#
|
28
|
+
# [:reject_if]
|
29
|
+
# Allows you to specify a Proc or a Symbol pointing to a method that
|
30
|
+
# checks whether a record should be built for a certain attribute hash.
|
31
|
+
# The hash is passed to the supplied Proc or the method and it should
|
32
|
+
# return either true or false. Passing <tt>:all_blank</tt> instead of a Proc
|
33
|
+
# will create a proc that will reject a record where all the attributes
|
34
|
+
# are blank excluding any value for <tt>_destroy</tt>.
|
35
|
+
#
|
36
|
+
# See https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html#method-i-accepts_nested_attributes_for
|
37
|
+
def accepts_nested_attributes_for(*attributes)
|
38
|
+
global_options = attributes.extract_options!
|
30
39
|
|
31
|
-
|
40
|
+
attributes.each do |attribute, options|
|
41
|
+
case attribute_types[attribute.to_s]
|
42
|
+
when Types::OneBase, Types::ManyBase
|
43
|
+
define_store_model_attr_accessors(attribute, options || global_options)
|
44
|
+
else
|
45
|
+
super(attribute, options || global_options)
|
46
|
+
end
|
32
47
|
end
|
33
48
|
end
|
34
49
|
|
35
50
|
private
|
36
51
|
|
52
|
+
def define_store_model_attr_accessors(attribute, options)
|
53
|
+
case attribute_types[attribute.to_s]
|
54
|
+
when Types::OneBase
|
55
|
+
define_association_setter_for_single(attribute, options)
|
56
|
+
alias_method "#{attribute}_attributes=", "#{attribute}="
|
57
|
+
when Types::ManyBase
|
58
|
+
define_association_setter_for_many(attribute, options)
|
59
|
+
end
|
60
|
+
|
61
|
+
define_attr_accessor_for_destroy(attribute, options)
|
62
|
+
end
|
63
|
+
|
37
64
|
def define_attr_accessor_for_destroy(association, options)
|
38
65
|
return unless options&.dig(:allow_destroy)
|
39
66
|
|
@@ -61,8 +88,6 @@ module StoreModel
|
|
61
88
|
end
|
62
89
|
end
|
63
90
|
|
64
|
-
private
|
65
|
-
|
66
91
|
def assign_nested_attributes_for_collection_association(association, attributes, options)
|
67
92
|
attributes = attributes.values if attributes.is_a?(Hash)
|
68
93
|
|
@@ -72,7 +97,20 @@ module StoreModel
|
|
72
97
|
end
|
73
98
|
end
|
74
99
|
|
100
|
+
attributes.reject! { |attribute| call_reject_if(attribute, options[:reject_if]) } if options&.dig(:reject_if)
|
101
|
+
|
75
102
|
send("#{association}=", attributes)
|
76
103
|
end
|
104
|
+
|
105
|
+
def call_reject_if(attributes, callback)
|
106
|
+
callback = ActiveRecord::NestedAttributes::ClassMethods::REJECT_ALL_BLANK_PROC if callback == :all_blank
|
107
|
+
|
108
|
+
case callback
|
109
|
+
when Symbol
|
110
|
+
method(callback).arity.zero? ? send(callback) : send(callback, attributes)
|
111
|
+
when Proc
|
112
|
+
callback.call(attributes)
|
113
|
+
end
|
114
|
+
end
|
77
115
|
end
|
78
116
|
end
|
@@ -46,9 +46,7 @@ module StoreModel
|
|
46
46
|
# @return [String] serialized value
|
47
47
|
def serialize(value)
|
48
48
|
case value
|
49
|
-
when @model_klass
|
50
|
-
ActiveSupport::JSON.encode(value.values_for_database)
|
51
|
-
when Hash
|
49
|
+
when @model_klass, Hash
|
52
50
|
ActiveSupport::JSON.encode(value)
|
53
51
|
else
|
54
52
|
super
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module StoreModel
|
4
|
+
module Types
|
5
|
+
# Implements #encode_json and #as_json methods.
|
6
|
+
# By wrapping serialized objects in this type, it prevents duplicate
|
7
|
+
# JSON serialization passes on nested object. It is named as Encoder
|
8
|
+
# as it will not work to inflate typed attributes and is intended
|
9
|
+
# to be used internally.
|
10
|
+
class RawJSONEncoder < String
|
11
|
+
def encode_json(_encoder)
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def as_json(_options = nil)
|
16
|
+
JSON.parse(self)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/store_model/types.rb
CHANGED
data/lib/store_model/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: store_model
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- DmitryTsepelev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-08-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -115,6 +115,7 @@ files:
|
|
115
115
|
- lib/store_model/types/one_of.rb
|
116
116
|
- lib/store_model/types/one_polymorphic.rb
|
117
117
|
- lib/store_model/types/polymorphic_helper.rb
|
118
|
+
- lib/store_model/types/raw_json.rb
|
118
119
|
- lib/store_model/version.rb
|
119
120
|
homepage: https://github.com/DmitryTsepelev/store_model
|
120
121
|
licenses:
|