anchormodel 0.3.1 → 0.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 +4 -4
- data/.ruby-version +1 -1
- data/CHANGELOG.md +14 -0
- data/EXAMPLES.md +408 -0
- data/Gemfile.lock +1 -1
- data/README.md +43 -1
- data/VERSION +1 -1
- data/anchormodel.gemspec +3 -3
- data/bin/test +9 -0
- data/doc/Anchormodel/ActiveModelTypeValueMulti.html +204 -42
- data/doc/Anchormodel/ActiveModelTypeValueSingle.html +243 -54
- data/doc/Anchormodel/Attribute.html +60 -37
- data/doc/Anchormodel/ModelMixin.html +166 -16
- data/doc/Anchormodel/SimpleFormInputs/Helpers/AnchormodelInputsCommon.html +82 -21
- data/doc/Anchormodel/SimpleFormInputs/Helpers.html +2 -11
- data/doc/Anchormodel/SimpleFormInputs.html +2 -11
- data/doc/Anchormodel/Util.html +497 -38
- data/doc/Anchormodel/Version.html +2 -20
- data/doc/Anchormodel.html +536 -129
- data/doc/AnchormodelCheckBoxesInput.html +22 -1
- data/doc/AnchormodelGenerator.html +64 -13
- data/doc/AnchormodelInput.html +24 -1
- data/doc/AnchormodelRadioButtonsInput.html +22 -1
- data/doc/_index.html +16 -1
- data/doc/class_list.html +1 -1
- data/doc/file.README.html +40 -2
- data/doc/index.html +40 -2
- data/doc/method_list.html +57 -17
- data/doc/top-level-namespace.html +1 -1
- data/lib/anchormodel/active_model_type_value_multi.rb +31 -5
- data/lib/anchormodel/active_model_type_value_single.rb +34 -6
- data/lib/anchormodel/attribute.rb +20 -8
- data/lib/anchormodel/model_mixin.rb +43 -8
- data/lib/anchormodel/simple_form_inputs/anchormodel_check_boxes_input.rb +7 -0
- data/lib/anchormodel/simple_form_inputs/anchormodel_input.rb +9 -0
- data/lib/anchormodel/simple_form_inputs/anchormodel_radio_buttons_input.rb +7 -0
- data/lib/anchormodel/simple_form_inputs/helpers/anchormodel_inputs_common.rb +14 -0
- data/lib/anchormodel/util.rb +102 -17
- data/lib/anchormodel.rb +109 -15
- data/lib/generators/anchormodel/anchormodel_generator.rb +8 -0
- data/test/active_record_model/user_test.rb +221 -10
- data/test/dummy/app/anchormodels/animal.rb +1 -0
- metadata +3 -1
data/doc/method_list.html
CHANGED
|
@@ -69,6 +69,14 @@
|
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
<li class="even ">
|
|
72
|
+
<div class="item">
|
|
73
|
+
<span class='object_link'><a href="Anchormodel/ModelMixin.html#anchormodel_attributes-instance_method" title="Anchormodel::ModelMixin#anchormodel_attributes (method)">#anchormodel_attributes</a></span>
|
|
74
|
+
<small>Anchormodel::ModelMixin</small>
|
|
75
|
+
</div>
|
|
76
|
+
</li>
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
<li class="odd ">
|
|
72
80
|
<div class="item">
|
|
73
81
|
<span class='object_link'><a href="Anchormodel/Attribute.html#anchormodel_class-instance_method" title="Anchormodel::Attribute#anchormodel_class (method)">#anchormodel_class</a></span>
|
|
74
82
|
<small>Anchormodel::Attribute</small>
|
|
@@ -76,7 +84,7 @@
|
|
|
76
84
|
</li>
|
|
77
85
|
|
|
78
86
|
|
|
79
|
-
<li class="
|
|
87
|
+
<li class="even ">
|
|
80
88
|
<div class="item">
|
|
81
89
|
<span class='object_link'><a href="Anchormodel.html#as_json-instance_method" title="Anchormodel#as_json (method)">#as_json</a></span>
|
|
82
90
|
<small>Anchormodel</small>
|
|
@@ -84,7 +92,7 @@
|
|
|
84
92
|
</li>
|
|
85
93
|
|
|
86
94
|
|
|
87
|
-
<li class="
|
|
95
|
+
<li class="odd ">
|
|
88
96
|
<div class="item">
|
|
89
97
|
<span class='object_link'><a href="Anchormodel/ActiveModelTypeValueSingle.html#attribute-instance_method" title="Anchormodel::ActiveModelTypeValueSingle#attribute (method)">#attribute</a></span>
|
|
90
98
|
<small>Anchormodel::ActiveModelTypeValueSingle</small>
|
|
@@ -92,7 +100,7 @@
|
|
|
92
100
|
</li>
|
|
93
101
|
|
|
94
102
|
|
|
95
|
-
<li class="
|
|
103
|
+
<li class="even ">
|
|
96
104
|
<div class="item">
|
|
97
105
|
<span class='object_link'><a href="Anchormodel/Attribute.html#attribute_name-instance_method" title="Anchormodel::Attribute#attribute_name (method)">#attribute_name</a></span>
|
|
98
106
|
<small>Anchormodel::Attribute</small>
|
|
@@ -100,7 +108,7 @@
|
|
|
100
108
|
</li>
|
|
101
109
|
|
|
102
110
|
|
|
103
|
-
<li class="
|
|
111
|
+
<li class="odd ">
|
|
104
112
|
<div class="item">
|
|
105
113
|
<span class='object_link'><a href="Anchormodel/ModelMixin.html#belongs_to_anchormodel-class_method" title="Anchormodel::ModelMixin.belongs_to_anchormodel (method)">belongs_to_anchormodel</a></span>
|
|
106
114
|
<small>Anchormodel::ModelMixin</small>
|
|
@@ -108,7 +116,7 @@
|
|
|
108
116
|
</li>
|
|
109
117
|
|
|
110
118
|
|
|
111
|
-
<li class="
|
|
119
|
+
<li class="even ">
|
|
112
120
|
<div class="item">
|
|
113
121
|
<span class='object_link'><a href="Anchormodel/ModelMixin.html#belongs_to_anchormodels-class_method" title="Anchormodel::ModelMixin.belongs_to_anchormodels (method)">belongs_to_anchormodels</a></span>
|
|
114
122
|
<small>Anchormodel::ModelMixin</small>
|
|
@@ -116,7 +124,7 @@
|
|
|
116
124
|
</li>
|
|
117
125
|
|
|
118
126
|
|
|
119
|
-
<li class="
|
|
127
|
+
<li class="odd ">
|
|
120
128
|
<div class="item">
|
|
121
129
|
<span class='object_link'><a href="Anchormodel/ActiveModelTypeValueMulti.html#cast-instance_method" title="Anchormodel::ActiveModelTypeValueMulti#cast (method)">#cast</a></span>
|
|
122
130
|
<small>Anchormodel::ActiveModelTypeValueMulti</small>
|
|
@@ -124,7 +132,7 @@
|
|
|
124
132
|
</li>
|
|
125
133
|
|
|
126
134
|
|
|
127
|
-
<li class="
|
|
135
|
+
<li class="even ">
|
|
128
136
|
<div class="item">
|
|
129
137
|
<span class='object_link'><a href="Anchormodel/ActiveModelTypeValueSingle.html#cast-instance_method" title="Anchormodel::ActiveModelTypeValueSingle#cast (method)">#cast</a></span>
|
|
130
138
|
<small>Anchormodel::ActiveModelTypeValueSingle</small>
|
|
@@ -132,7 +140,7 @@
|
|
|
132
140
|
</li>
|
|
133
141
|
|
|
134
142
|
|
|
135
|
-
<li class="
|
|
143
|
+
<li class="odd ">
|
|
136
144
|
<div class="item">
|
|
137
145
|
<span class='object_link'><a href="Anchormodel/ActiveModelTypeValueSingle.html#changed_in_place%3F-instance_method" title="Anchormodel::ActiveModelTypeValueSingle#changed_in_place? (method)">#changed_in_place?</a></span>
|
|
138
146
|
<small>Anchormodel::ActiveModelTypeValueSingle</small>
|
|
@@ -140,7 +148,23 @@
|
|
|
140
148
|
</li>
|
|
141
149
|
|
|
142
150
|
|
|
151
|
+
<li class="even ">
|
|
152
|
+
<div class="item">
|
|
153
|
+
<span class='object_link'><a href="Anchormodel/Util.html#csv_contains_like-class_method" title="Anchormodel::Util.csv_contains_like (method)">csv_contains_like</a></span>
|
|
154
|
+
<small>Anchormodel::Util</small>
|
|
155
|
+
</div>
|
|
156
|
+
</li>
|
|
157
|
+
|
|
158
|
+
|
|
143
159
|
<li class="odd ">
|
|
160
|
+
<div class="item">
|
|
161
|
+
<span class='object_link'><a href="Anchormodel/Util.html#escape_like-class_method" title="Anchormodel::Util.escape_like (method)">escape_like</a></span>
|
|
162
|
+
<small>Anchormodel::Util</small>
|
|
163
|
+
</div>
|
|
164
|
+
</li>
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
<li class="even ">
|
|
144
168
|
<div class="item">
|
|
145
169
|
<span class='object_link'><a href="Anchormodel.html#find-class_method" title="Anchormodel.find (method)">find</a></span>
|
|
146
170
|
<small>Anchormodel</small>
|
|
@@ -148,7 +172,7 @@
|
|
|
148
172
|
</li>
|
|
149
173
|
|
|
150
174
|
|
|
151
|
-
<li class="
|
|
175
|
+
<li class="odd ">
|
|
152
176
|
<div class="item">
|
|
153
177
|
<span class='object_link'><a href="Anchormodel.html#first-class_method" title="Anchormodel.first (method)">first</a></span>
|
|
154
178
|
<small>Anchormodel</small>
|
|
@@ -156,7 +180,7 @@
|
|
|
156
180
|
</li>
|
|
157
181
|
|
|
158
182
|
|
|
159
|
-
<li class="
|
|
183
|
+
<li class="even ">
|
|
160
184
|
<div class="item">
|
|
161
185
|
<span class='object_link'><a href="Anchormodel.html#form_collection-class_method" title="Anchormodel.form_collection (method)">form_collection</a></span>
|
|
162
186
|
<small>Anchormodel</small>
|
|
@@ -164,6 +188,14 @@
|
|
|
164
188
|
</li>
|
|
165
189
|
|
|
166
190
|
|
|
191
|
+
<li class="odd ">
|
|
192
|
+
<div class="item">
|
|
193
|
+
<span class='object_link'><a href="Anchormodel.html#hash-instance_method" title="Anchormodel#hash (method)">#hash</a></span>
|
|
194
|
+
<small>Anchormodel</small>
|
|
195
|
+
</div>
|
|
196
|
+
</li>
|
|
197
|
+
|
|
198
|
+
|
|
167
199
|
<li class="even ">
|
|
168
200
|
<div class="item">
|
|
169
201
|
<span class='object_link'><a href="Anchormodel.html#index-instance_method" title="Anchormodel#index (method)">#index</a></span>
|
|
@@ -245,6 +277,14 @@
|
|
|
245
277
|
|
|
246
278
|
|
|
247
279
|
<li class="even ">
|
|
280
|
+
<div class="item">
|
|
281
|
+
<span class='object_link'><a href="Anchormodel/Util.html#normalize_anchormodel_keys-class_method" title="Anchormodel::Util.normalize_anchormodel_keys (method)">normalize_anchormodel_keys</a></span>
|
|
282
|
+
<small>Anchormodel::Util</small>
|
|
283
|
+
</div>
|
|
284
|
+
</li>
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
<li class="odd ">
|
|
248
288
|
<div class="item">
|
|
249
289
|
<span class='object_link'><a href="Anchormodel/Attribute.html#optional-instance_method" title="Anchormodel::Attribute#optional (method)">#optional</a></span>
|
|
250
290
|
<small>Anchormodel::Attribute</small>
|
|
@@ -252,7 +292,7 @@
|
|
|
252
292
|
</li>
|
|
253
293
|
|
|
254
294
|
|
|
255
|
-
<li class="
|
|
295
|
+
<li class="even ">
|
|
256
296
|
<div class="item">
|
|
257
297
|
<span class='object_link'><a href="Anchormodel/ActiveModelTypeValueMulti.html#serializable%3F-instance_method" title="Anchormodel::ActiveModelTypeValueMulti#serializable? (method)">#serializable?</a></span>
|
|
258
298
|
<small>Anchormodel::ActiveModelTypeValueMulti</small>
|
|
@@ -260,7 +300,7 @@
|
|
|
260
300
|
</li>
|
|
261
301
|
|
|
262
302
|
|
|
263
|
-
<li class="
|
|
303
|
+
<li class="odd ">
|
|
264
304
|
<div class="item">
|
|
265
305
|
<span class='object_link'><a href="Anchormodel/ActiveModelTypeValueSingle.html#serializable%3F-instance_method" title="Anchormodel::ActiveModelTypeValueSingle#serializable? (method)">#serializable?</a></span>
|
|
266
306
|
<small>Anchormodel::ActiveModelTypeValueSingle</small>
|
|
@@ -268,7 +308,7 @@
|
|
|
268
308
|
</li>
|
|
269
309
|
|
|
270
310
|
|
|
271
|
-
<li class="
|
|
311
|
+
<li class="even ">
|
|
272
312
|
<div class="item">
|
|
273
313
|
<span class='object_link'><a href="Anchormodel/ActiveModelTypeValueMulti.html#serialize-instance_method" title="Anchormodel::ActiveModelTypeValueMulti#serialize (method)">#serialize</a></span>
|
|
274
314
|
<small>Anchormodel::ActiveModelTypeValueMulti</small>
|
|
@@ -276,7 +316,7 @@
|
|
|
276
316
|
</li>
|
|
277
317
|
|
|
278
318
|
|
|
279
|
-
<li class="
|
|
319
|
+
<li class="odd ">
|
|
280
320
|
<div class="item">
|
|
281
321
|
<span class='object_link'><a href="Anchormodel/ActiveModelTypeValueSingle.html#serialize-instance_method" title="Anchormodel::ActiveModelTypeValueSingle#serialize (method)">#serialize</a></span>
|
|
282
322
|
<small>Anchormodel::ActiveModelTypeValueSingle</small>
|
|
@@ -284,7 +324,7 @@
|
|
|
284
324
|
</li>
|
|
285
325
|
|
|
286
326
|
|
|
287
|
-
<li class="
|
|
327
|
+
<li class="even ">
|
|
288
328
|
<div class="item">
|
|
289
329
|
<span class='object_link'><a href="Anchormodel.html#setup!-class_method" title="Anchormodel.setup! (method)">setup!</a></span>
|
|
290
330
|
<small>Anchormodel</small>
|
|
@@ -292,7 +332,7 @@
|
|
|
292
332
|
</li>
|
|
293
333
|
|
|
294
334
|
|
|
295
|
-
<li class="
|
|
335
|
+
<li class="odd ">
|
|
296
336
|
<div class="item">
|
|
297
337
|
<span class='object_link'><a href="Anchormodel.html#to_s-instance_method" title="Anchormodel#to_s (method)">#to_s</a></span>
|
|
298
338
|
<small>Anchormodel</small>
|
|
@@ -300,7 +340,7 @@
|
|
|
300
340
|
</li>
|
|
301
341
|
|
|
302
342
|
|
|
303
|
-
<li class="
|
|
343
|
+
<li class="even ">
|
|
304
344
|
<div class="item">
|
|
305
345
|
<span class='object_link'><a href="Anchormodel/ActiveModelTypeValueSingle.html#type-instance_method" title="Anchormodel::ActiveModelTypeValueSingle#type (method)">#type</a></span>
|
|
306
346
|
<small>Anchormodel::ActiveModelTypeValueSingle</small>
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
</div>
|
|
101
101
|
|
|
102
102
|
<div id="footer">
|
|
103
|
-
Generated on Wed May 13
|
|
103
|
+
Generated on Wed May 13 15:48:24 2026 by
|
|
104
104
|
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
|
|
105
105
|
0.9.28 (ruby-3.3.5).
|
|
106
106
|
</div>
|
|
@@ -1,16 +1,36 @@
|
|
|
1
|
+
# ActiveModel type adapter for collection-valued anchormodel attributes (`belongs_to_anchormodels`).
|
|
2
|
+
#
|
|
3
|
+
# Translates between an in-memory `Set<Anchormodel>` and a CSV `String` stored in a single
|
|
4
|
+
# DB column. Inherits scalar handling from {Anchormodel::ActiveModelTypeValueSingle} and
|
|
5
|
+
# overrides the collection-aware methods.
|
|
6
|
+
#
|
|
7
|
+
# @see https://www.rubydoc.info/docs/rails/ActiveModel/Type/Value Rails type interface
|
|
1
8
|
class Anchormodel::ActiveModelTypeValueMulti < Anchormodel::ActiveModelTypeValueSingle
|
|
2
|
-
#
|
|
9
|
+
# Splits the stored CSV and casts each entry into an Anchormodel instance.
|
|
10
|
+
#
|
|
11
|
+
# @param values [String,nil] CSV string from the DB, or `nil` (NULL).
|
|
12
|
+
# @return [Set<Anchormodel>] Set of Anchormodel instances. Empty when `values` is nil or `""`.
|
|
13
|
+
# @raise [Anchormodel::InvalidKey] if any CSV entry is not a registered key.
|
|
3
14
|
def cast(values)
|
|
15
|
+
return Set.new if values.nil?
|
|
4
16
|
return values.split(',').map { |value| super(value) }.compact.to_set
|
|
5
17
|
end
|
|
6
18
|
|
|
7
|
-
#
|
|
19
|
+
# Serializes a Set/Array of anchormodel-shaped values into a CSV `String` for the DB.
|
|
20
|
+
# Validates every entry and raises immediately on invalid keys (rather than deferring
|
|
21
|
+
# the error to the next read).
|
|
22
|
+
#
|
|
23
|
+
# @param values [Enumerable,String,nil] Collection of anchormodel-shaped values, a
|
|
24
|
+
# pre-formed CSV `String`, or `nil`.
|
|
25
|
+
# @return [String] CSV of validated keys, or `""` for nil / empty collection.
|
|
26
|
+
# @raise [Anchormodel::InvalidKey] if any element is an unknown key.
|
|
27
|
+
# @raise [RuntimeError] if `values` is not an Enumerable, String, or `nil`.
|
|
8
28
|
def serialize(values)
|
|
9
29
|
return case values
|
|
10
30
|
when Enumerable
|
|
11
31
|
values.map { |value| super(value) }.compact.join(',')
|
|
12
32
|
when String
|
|
13
|
-
values
|
|
33
|
+
values.split(',').map { |value| super(value) }.compact.join(',')
|
|
14
34
|
when nil
|
|
15
35
|
''
|
|
16
36
|
else
|
|
@@ -18,12 +38,18 @@ class Anchormodel::ActiveModelTypeValueMulti < Anchormodel::ActiveModelTypeValue
|
|
|
18
38
|
end
|
|
19
39
|
end
|
|
20
40
|
|
|
41
|
+
# Reports whether {#serialize} would accept `values`. Returns strict Boolean.
|
|
42
|
+
#
|
|
43
|
+
# @param values [Object]
|
|
44
|
+
# @return [Boolean]
|
|
21
45
|
def serializable?(values)
|
|
22
46
|
return case values
|
|
23
47
|
when Enumerable
|
|
24
|
-
values.
|
|
48
|
+
values.all? { |value| super(value) }
|
|
25
49
|
when String
|
|
26
|
-
values.split(',').
|
|
50
|
+
values.split(',').all? { |value| super(value) }
|
|
51
|
+
when nil
|
|
52
|
+
true
|
|
27
53
|
else
|
|
28
54
|
false
|
|
29
55
|
end
|
|
@@ -1,34 +1,62 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ActiveModel type adapter for single-value anchormodel attributes.
|
|
2
|
+
#
|
|
3
|
+
# Translates between the in-memory Ruby form (an `Anchormodel` instance or `nil`) and
|
|
4
|
+
# the on-disk form (a String key in the DB column). Registered with AR via
|
|
5
|
+
# `model_class.attribute(name, ActiveModelTypeValueSingle.new(attribute))` by
|
|
6
|
+
# {Anchormodel::Util.install_methods_in_model}.
|
|
7
|
+
#
|
|
8
|
+
# @see https://www.rubydoc.info/docs/rails/ActiveModel/Type/Value Rails type interface
|
|
2
9
|
class Anchormodel::ActiveModelTypeValueSingle < ActiveModel::Type::Value
|
|
10
|
+
# @!attribute [r] attribute
|
|
11
|
+
# @return [Anchormodel::Attribute] Metadata about the column this type is bound to.
|
|
3
12
|
attr_reader :attribute
|
|
4
13
|
|
|
14
|
+
# @param attribute [Anchormodel::Attribute]
|
|
5
15
|
def initialize(attribute)
|
|
6
16
|
super()
|
|
7
17
|
@attribute = attribute
|
|
8
18
|
end
|
|
9
19
|
|
|
20
|
+
# @return [Symbol] `:anchormodel` — the type identifier.
|
|
10
21
|
def type
|
|
11
22
|
:anchormodel
|
|
12
23
|
end
|
|
13
24
|
|
|
14
|
-
#
|
|
25
|
+
# Coerces an input value into an Anchormodel instance (or nil). Used by Rails when
|
|
26
|
+
# reading from the DB, when assigning via mass-assignment, and for dirty-tracking.
|
|
27
|
+
#
|
|
28
|
+
# @param value [String,Symbol,Anchormodel,nil] DB value, user input, or already-cast instance.
|
|
29
|
+
# @return [Anchormodel,nil] The matching Anchormodel instance, or `nil` for blank input.
|
|
30
|
+
# @raise [Anchormodel::InvalidKey] if a String/Symbol is not a registered key.
|
|
15
31
|
def cast(value)
|
|
16
32
|
value = value.presence
|
|
17
33
|
return value if value.is_a?(@attribute.anchormodel_class)
|
|
18
34
|
return @attribute.anchormodel_class.find(value)
|
|
19
35
|
end
|
|
20
36
|
|
|
21
|
-
#
|
|
37
|
+
# Converts an Anchormodel-shaped value into the String key stored in the DB.
|
|
38
|
+
#
|
|
39
|
+
# @param value [String,Symbol,Anchormodel,nil]
|
|
40
|
+
# @return [String,nil] The key as a String, or `nil` for blank input.
|
|
41
|
+
# @raise [Anchormodel::InvalidKey] for unknown String/Symbol keys.
|
|
42
|
+
# @raise [RuntimeError] for any other input type (e.g. Array, Integer).
|
|
22
43
|
def serialize(value)
|
|
23
|
-
return value.map { |v| serialize_scalar(v) } if value.is_a?(Array)
|
|
24
44
|
serialize_scalar(value)
|
|
25
45
|
end
|
|
26
46
|
|
|
47
|
+
# Reports whether `value` is a shape that {#serialize} would accept. Used by AR's
|
|
48
|
+
# `Arel::Nodes::HomogeneousIn` to gate which array elements get bound into IN clauses.
|
|
49
|
+
# Returns true for any String/Symbol/`nil`/Anchormodel instance — actual key validation
|
|
50
|
+
# is deferred to {#serialize}.
|
|
51
|
+
#
|
|
52
|
+
# @param value [Object]
|
|
53
|
+
# @return [Boolean]
|
|
27
54
|
def serializable?(value)
|
|
28
|
-
return value.all? { |v| scalar_serializable?(v) } if value.is_a?(Array)
|
|
29
55
|
scalar_serializable?(value)
|
|
30
56
|
end
|
|
31
57
|
|
|
58
|
+
# Used by AR's dirty tracking to detect in-place mutation.
|
|
59
|
+
# @api private
|
|
32
60
|
def changed_in_place?(raw_old_value, value)
|
|
33
61
|
old_value = deserialize(raw_old_value)
|
|
34
62
|
old_value != value
|
|
@@ -41,7 +69,7 @@ class Anchormodel::ActiveModelTypeValueSingle < ActiveModel::Type::Value
|
|
|
41
69
|
return case value
|
|
42
70
|
when Symbol, String
|
|
43
71
|
unless @attribute.anchormodel_class.valid_keys.include?(value.to_sym)
|
|
44
|
-
|
|
72
|
+
raise(Anchormodel::InvalidKey, "Attempt to set #{@attribute.attribute_name} to unsupported key #{value.inspect}.")
|
|
45
73
|
end
|
|
46
74
|
value.to_s
|
|
47
75
|
when @attribute.anchormodel_class
|
|
@@ -1,16 +1,27 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
3
|
-
#
|
|
1
|
+
# Metadata about an anchormodel attribute installed on a Rails model.
|
|
2
|
+
#
|
|
3
|
+
# One instance is created per call to {Anchormodel::ModelMixin#belongs_to_anchormodel}
|
|
4
|
+
# or {Anchormodel::ModelMixin#belongs_to_anchormodels} and stored in the model class's
|
|
5
|
+
# `anchormodel_attributes` hash. Used internally by the AR type casters and by SimpleForm
|
|
6
|
+
# inputs to discover what an attribute points to.
|
|
4
7
|
class Anchormodel::Attribute
|
|
8
|
+
# @!attribute [r] attribute_name
|
|
9
|
+
# @return [Symbol] The model attribute / DB column name.
|
|
5
10
|
attr_reader :attribute_name
|
|
11
|
+
|
|
12
|
+
# @!attribute [r] anchormodel_class
|
|
13
|
+
# @return [Class] The Anchormodel subclass this attribute references.
|
|
6
14
|
attr_reader :anchormodel_class
|
|
15
|
+
|
|
16
|
+
# @!attribute [r] optional
|
|
17
|
+
# @return [Boolean] Whether the attribute may be `nil` (no presence validation added).
|
|
7
18
|
attr_reader :optional
|
|
8
19
|
|
|
9
|
-
# @param model_class [
|
|
10
|
-
# @param attribute_name [String,Symbol] The
|
|
11
|
-
# @param anchormodel_class [Class]
|
|
12
|
-
# @param optional [Boolean] If true,
|
|
13
|
-
# @param multiple [Boolean] If true, this attribute holds
|
|
20
|
+
# @param model_class [Class] The ActiveRecord model class on which {Anchormodel::ModelMixin#belongs_to_anchormodel} is called.
|
|
21
|
+
# @param attribute_name [String,Symbol] The model attribute / DB column name.
|
|
22
|
+
# @param anchormodel_class [Class,nil] The Anchormodel subclass. Omit if attribute `:foo_bar` references `FooBar`.
|
|
23
|
+
# @param optional [Boolean] If true, no presence validation is added.
|
|
24
|
+
# @param multiple [Boolean] If true, this attribute holds a Set of anchormodels (CSV in column).
|
|
14
25
|
def initialize(model_class, attribute_name, anchormodel_class = nil, optional = false, multiple = false)
|
|
15
26
|
@model_class = model_class
|
|
16
27
|
@attribute_name = attribute_name.to_sym
|
|
@@ -19,6 +30,7 @@ class Anchormodel::Attribute
|
|
|
19
30
|
@multiple = multiple
|
|
20
31
|
end
|
|
21
32
|
|
|
33
|
+
# @return [Boolean] true for `belongs_to_anchormodels` (collection), false for `belongs_to_anchormodel` (single).
|
|
22
34
|
def multiple?
|
|
23
35
|
@multiple
|
|
24
36
|
end
|
|
@@ -1,23 +1,58 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
1
|
+
# Include this mixin in every Rails model that uses anchormodel attributes. The common
|
|
2
|
+
# pattern is to include it once in `application_record.rb` so all descendant models pick
|
|
3
|
+
# it up automatically.
|
|
4
|
+
#
|
|
5
|
+
# Adds two class-level macros:
|
|
6
|
+
# - {ClassMethods#belongs_to_anchormodel} for single-value attributes
|
|
7
|
+
# - {ClassMethods#belongs_to_anchormodels} for collection-valued attributes
|
|
8
|
+
#
|
|
9
|
+
# @example Install in ApplicationRecord
|
|
10
|
+
# class ApplicationRecord < ActiveRecord::Base
|
|
11
|
+
# include Anchormodel::ModelMixin
|
|
12
|
+
# end
|
|
3
13
|
module Anchormodel::ModelMixin
|
|
4
14
|
extend ActiveSupport::Concern
|
|
5
15
|
|
|
6
16
|
included do
|
|
17
|
+
# @!attribute [rw] anchormodel_attributes
|
|
18
|
+
# @return [Hash{Symbol => Anchormodel::Attribute}] All anchormodel attributes
|
|
19
|
+
# declared on this model class, keyed by attribute name.
|
|
7
20
|
class_attribute :anchormodel_attributes, default: {}.freeze
|
|
8
21
|
end
|
|
9
22
|
|
|
10
23
|
class_methods do
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
24
|
+
# Declares a single-value anchormodel attribute.
|
|
25
|
+
#
|
|
26
|
+
# The DB table must have a String column with the same name as `attribute_name`.
|
|
27
|
+
# @see Anchormodel::Util.install_methods_in_model Full parameter documentation.
|
|
28
|
+
# @example Basic usage
|
|
29
|
+
# class User < ApplicationRecord
|
|
30
|
+
# belongs_to_anchormodel :role # `Role` inferred from attribute name
|
|
31
|
+
# belongs_to_anchormodel :role, optional: true
|
|
32
|
+
# belongs_to_anchormodel :favorite_color, Color # explicit anchormodel class
|
|
33
|
+
# end
|
|
14
34
|
def belongs_to_anchormodel(*args, **kwargs)
|
|
15
35
|
Anchormodel::Util.install_methods_in_model(self, *args, **kwargs)
|
|
16
36
|
end
|
|
17
37
|
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
38
|
+
# Declares a collection-valued anchormodel attribute.
|
|
39
|
+
#
|
|
40
|
+
# The DB table must have a String column with the same name as `attribute_name`.
|
|
41
|
+
# Multiple keys are stored as a comma-separated string. Reading the attribute returns
|
|
42
|
+
# a `Set` of Anchormodel instances. `optional: true` is forced (an empty collection
|
|
43
|
+
# is a valid value).
|
|
44
|
+
#
|
|
45
|
+
# @see Anchormodel::Util.install_methods_in_model Full parameter documentation.
|
|
46
|
+
# @example Basic usage
|
|
47
|
+
# class User < ApplicationRecord
|
|
48
|
+
# belongs_to_anchormodels :animals
|
|
49
|
+
# end
|
|
50
|
+
#
|
|
51
|
+
# user = User.create!(animals: %i[cat dog])
|
|
52
|
+
# user.animals # => #<Set: {#<Animal<cat>>, #<Animal<dog>>}>
|
|
53
|
+
# user.cat? # => true
|
|
54
|
+
# user.animals << :horse
|
|
55
|
+
# User.with_any_animals(:cat, :horse) # => relation matching the user above
|
|
21
56
|
def belongs_to_anchormodels(*args, **kwargs)
|
|
22
57
|
Anchormodel::Util.install_methods_in_model(self, *args, **kwargs, multiple: true)
|
|
23
58
|
end
|
|
@@ -7,6 +7,13 @@ unless defined? SimpleForm
|
|
|
7
7
|
|
|
8
8
|
end
|
|
9
9
|
if defined? SimpleForm
|
|
10
|
+
# SimpleForm input for a collection-valued anchormodel attribute (`belongs_to_anchormodels`),
|
|
11
|
+
# rendered as check boxes (one per anchormodel key).
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# <%= simple_form_for user do |f| %>
|
|
15
|
+
# <%= f.input :animals, as: :anchormodel_check_boxes %>
|
|
16
|
+
# <% end %>
|
|
10
17
|
class AnchormodelCheckBoxesInput < SimpleForm::Inputs::CollectionCheckBoxesInput
|
|
11
18
|
include Anchormodel::SimpleFormInputs::Helpers::AnchormodelInputsCommon
|
|
12
19
|
|
|
@@ -7,6 +7,15 @@ unless defined? SimpleForm
|
|
|
7
7
|
|
|
8
8
|
end
|
|
9
9
|
if defined? SimpleForm
|
|
10
|
+
# SimpleForm input for an anchormodel attribute. Renders a `<select>` collection
|
|
11
|
+
# whose options are the entries of the bound anchormodel.
|
|
12
|
+
#
|
|
13
|
+
# Auto-detected by SimpleForm because the attribute's AR type is `:anchormodel`.
|
|
14
|
+
#
|
|
15
|
+
# @example
|
|
16
|
+
# <%= simple_form_for user do |f| %>
|
|
17
|
+
# <%= f.input :role %>
|
|
18
|
+
# <% end %>
|
|
10
19
|
class AnchormodelInput < SimpleForm::Inputs::CollectionSelectInput
|
|
11
20
|
include Anchormodel::SimpleFormInputs::Helpers::AnchormodelInputsCommon
|
|
12
21
|
|
|
@@ -7,6 +7,13 @@ unless defined? SimpleForm
|
|
|
7
7
|
|
|
8
8
|
end
|
|
9
9
|
if defined? SimpleForm
|
|
10
|
+
# SimpleForm input for a single-value anchormodel attribute, rendered as radio buttons.
|
|
11
|
+
# Unsuitable for collection attributes — use {AnchormodelCheckBoxesInput} for those.
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# <%= simple_form_for user do |f| %>
|
|
15
|
+
# <%= f.input :role, as: :anchormodel_radio_buttons %>
|
|
16
|
+
# <% end %>
|
|
10
17
|
class AnchormodelRadioButtonsInput < SimpleForm::Inputs::CollectionRadioButtonsInput
|
|
11
18
|
include Anchormodel::SimpleFormInputs::Helpers::AnchormodelInputsCommon
|
|
12
19
|
|
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
class Anchormodel
|
|
2
2
|
module SimpleFormInputs
|
|
3
3
|
module Helpers
|
|
4
|
+
# Shared logic for all anchormodel SimpleForm inputs ({AnchormodelInput},
|
|
5
|
+
# {AnchormodelRadioButtonsInput}, {AnchormodelCheckBoxesInput}). Resolves the
|
|
6
|
+
# anchormodel class from the bound object, builds the collection of label/key
|
|
7
|
+
# tuples, and computes the currently-selected key(s).
|
|
8
|
+
#
|
|
9
|
+
# Callers can supply a custom `:collection` (either an array of anchormodels
|
|
10
|
+
# or an array of `[label, key]` tuples) to override the default of `am_class.all`.
|
|
11
|
+
# Callers without a bound `object` must pass `:anchormodel_attribute` explicitly.
|
|
4
12
|
module AnchormodelInputsCommon
|
|
13
|
+
# Resolves the anchormodel attribute, builds the collection, and delegates to
|
|
14
|
+
# the underlying SimpleForm input class via `super`.
|
|
15
|
+
# @param wrapper_options [Hash,nil]
|
|
16
|
+
# @return [String] Rendered HTML.
|
|
17
|
+
# @raise [RuntimeError] if the bound object does not include {Anchormodel::ModelMixin},
|
|
18
|
+
# or if the attribute does not look like an anchormodel attribute.
|
|
5
19
|
def input(wrapper_options = nil)
|
|
6
20
|
if object.present?
|
|
7
21
|
unless object.respond_to?(:anchormodel_attributes)
|