anchormodel 0.1.5 → 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 +4 -4
- data/CHANGELOG.md +9 -0
- data/Gemfile.lock +6 -6
- data/README.md +78 -2
- data/VERSION +1 -1
- data/anchormodel.gemspec +4 -4
- data/doc/Anchormodel/ActiveModelTypeValueMulti.html +410 -0
- data/doc/Anchormodel/ActiveModelTypeValueSingle.html +113 -31
- data/doc/Anchormodel/Attribute.html +109 -9
- data/doc/Anchormodel/ModelMixin.html +3 -92
- data/doc/Anchormodel/SimpleFormInputs/Helpers/AnchormodelInputsCommon.html +267 -0
- data/doc/Anchormodel/SimpleFormInputs/Helpers.html +124 -0
- data/doc/Anchormodel/SimpleFormInputs.html +124 -0
- data/doc/Anchormodel/Util.html +131 -15
- data/doc/Anchormodel/Version.html +3 -3
- data/doc/Anchormodel.html +97 -32
- data/doc/AnchormodelCheckBoxesInput.html +140 -0
- data/doc/AnchormodelGenerator.html +3 -3
- data/doc/AnchormodelInput.html +140 -0
- data/doc/AnchormodelRadioButtonsInput.html +140 -0
- data/doc/_index.html +16 -4
- data/doc/class_list.html +1 -1
- data/doc/file.README.html +77 -5
- data/doc/frames.html +1 -1
- data/doc/index.html +77 -5
- data/doc/method_list.html +49 -9
- data/doc/top-level-namespace.html +4 -4
- data/lib/anchormodel/active_model_type_value_multi.rb +31 -0
- data/lib/anchormodel/active_model_type_value_single.rb +4 -2
- data/lib/anchormodel/attribute.rb +7 -1
- data/lib/anchormodel/model_mixin.rb +7 -0
- data/lib/anchormodel/simple_form_inputs/anchormodel_check_boxes_input.rb +23 -0
- data/lib/anchormodel/simple_form_inputs/helpers/anchormodel_inputs_common.rb +7 -3
- data/lib/anchormodel/util.rb +57 -7
- data/lib/anchormodel.rb +7 -0
- data/test/active_record_model/user_test.rb +73 -9
- data/test/dummy/app/anchormodels/animal.rb +6 -0
- data/test/dummy/app/models/user.rb +1 -0
- data/test/dummy/db/migrate/20240425182000_add_animals_to_users.rb +5 -0
- data/test/dummy/db/schema.rb +2 -1
- metadata +13 -2
data/doc/index.html
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
<title>
|
7
7
|
File: README
|
8
8
|
|
9
|
-
— Documentation by YARD 0.9.
|
9
|
+
— Documentation by YARD 0.9.34
|
10
10
|
|
11
11
|
</title>
|
12
12
|
|
@@ -261,15 +261,87 @@
|
|
261
261
|
<p>If you want to have multiple attributes in the same model pointing to the same Anchormodel, you need to disable <code>model_methods</code> for at least one of them (otherwise the model methods will clash in your model class):</p>
|
262
262
|
|
263
263
|
<pre class="code ruby"><code class="ruby"><span class='comment'># app/models/user.rb
|
264
|
-
</span
|
265
|
-
|
264
|
+
</span><span class='id identifier rubyid_belongs_to_anchormodel'>belongs_to_anchormodel</span> <span class='symbol'>:role</span>
|
265
|
+
<span class='id identifier rubyid_belongs_to_anchormodel'>belongs_to_anchormodel</span> <span class='symbol'>:secondary_role</span><span class='comma'>,</span> <span class='const'>Role</span><span class='comma'>,</span> <span class='label'>model_methods:</span> <span class='kw'>false</span>
|
266
|
+
</code></pre>
|
267
|
+
|
268
|
+
<h1 id="label-Attaching+multiple+Anchormodels+to+an+attribute+-28similar+to+a+has_many+collection-29">Attaching multiple Anchormodels to an attribute (similar to a has_many collection)</h1>
|
269
|
+
|
270
|
+
<p>Collections of Anchormodels are supported. Assuming that your <code>User</code> can have multiple <code>Role</code> anchormodels, your code can look as follows:</p>
|
271
|
+
|
272
|
+
<pre class="code ruby"><code class="ruby"><span class='comment'># app/models/user.rb
|
273
|
+
</span><span class='id identifier rubyid_belongs_to_anchormodels'>belongs_to_anchormodels</span> <span class='symbol'>:roles</span>
|
274
|
+
</code></pre>
|
275
|
+
|
276
|
+
<p>The method is deliberately called <code>belongs_to...</code> and not <code>has_many...</code> in order to indicate that the key is stored in a column of the model in which you are calling it. The rule of thumb for using a collection of Anchormodels is:</p>
|
277
|
+
<ul><li>
|
278
|
+
<p>Your column should be of type <code>string</code>, just like with the singular <code>belongs_to_anchormodel</code>.</p>
|
279
|
+
</li><li>
|
280
|
+
<p>Anchormodels will be stored in string form, separated by the <code>,</code> character</p>
|
281
|
+
</li><li>
|
282
|
+
<p>When reading the attribute, you will get a <code>Set</code>, thus duplicates are avoided.</p>
|
283
|
+
</li></ul>
|
284
|
+
|
285
|
+
<p>Example usage for a User model with multiple roles as shown above:</p>
|
286
|
+
|
287
|
+
<pre class="code ruby"><code class="ruby"><span class='id identifier rubyid_u'>u</span> <span class='op'>=</span> <span class='const'>User</span><span class='period'>.</span><span class='id identifier rubyid_first'>first</span>
|
288
|
+
<span class='id identifier rubyid_u'>u</span><span class='period'>.</span><span class='id identifier rubyid_roles'>roles</span> <span class='op'>=</span> <span class='qsymbols_beg'>%i[</span><span class='tstring_content'>moderator</span><span class='words_sep'> </span><span class='tstring_content'>admin</span><span class='tstring_end'>]</span></span> <span class='comment'># this will replace the user's roles by the two specified
|
289
|
+
</span><span class='id identifier rubyid_u'>u</span><span class='period'>.</span><span class='id identifier rubyid_roles'>roles</span> <span class='comment'># this will return a set of two Role Anchormodel instances, moderator and role
|
290
|
+
</span><span class='id identifier rubyid_u'>u</span><span class='period'>.</span><span class='id identifier rubyid_guest!'>guest!</span> <span class='comment'># this will add the role `guest` to the user's roles
|
291
|
+
</span><span class='id identifier rubyid_u'>u</span><span class='period'>.</span><span class='id identifier rubyid_guest?'>guest?</span> <span class='comment'># this will query whether the role `guest` is part of the user's roles
|
292
|
+
</span><span class='const'>User</span><span class='period'>.</span><span class='id identifier rubyid_moderator'>moderator</span> <span class='comment'># This will return all users that have the moderator role as part of their roles
|
293
|
+
</span></code></pre>
|
294
|
+
|
295
|
+
<p>For modifying a collection of Anchormodels, the following methods are implemented, the first three accepting a String, Symbol or Anchormodel:</p>
|
296
|
+
|
297
|
+
<pre class="code ruby"><code class="ruby"><span class='id identifier rubyid_u'>u</span><span class='period'>.</span><span class='id identifier rubyid_roles'>roles</span><span class='period'>.</span><span class='id identifier rubyid_add'>add</span><span class='lparen'>(</span><span class='symbol'>:moderator</span><span class='rparen'>)</span> <span class='comment'># same as u.moderator!
|
298
|
+
</span><span class='id identifier rubyid_u'>u</span><span class='period'>.</span><span class='id identifier rubyid_roles'>roles</span> <span class='op'><<</span> <span class='symbol'>:moderator</span> <span class='comment'># alias of add
|
299
|
+
</span><span class='id identifier rubyid_u'>u</span><span class='period'>.</span><span class='id identifier rubyid_roles'>roles</span><span class='period'>.</span><span class='id identifier rubyid_delete'>delete</span><span class='lparen'>(</span><span class='symbol'>:moderator</span><span class='rparen'>)</span>
|
300
|
+
<span class='id identifier rubyid_u'>u</span><span class='period'>.</span><span class='id identifier rubyid_roles'>roles</span><span class='period'>.</span><span class='id identifier rubyid_clear'>clear</span>
|
301
|
+
</code></pre>
|
302
|
+
|
303
|
+
<p>Note that no other methods of Set are overwritten at this point - if you use any other methods mutating the underlying Set, your changes will not be applied.</p>
|
304
|
+
|
305
|
+
<h2 id="label-Basic+rails+form+for+a+collection+of+Anchormodels">Basic rails form for a collection of Anchormodels</h2>
|
306
|
+
|
307
|
+
<pre class="code ruby"><code class="ruby"><%= form_with(model: user) do |form| %>
|
308
|
+
<%# ... %>
|
309
|
+
<%= form.collection_select :role, Role.all, :key, :label, multiple: true %>
|
310
|
+
<%# ... %>
|
311
|
+
<% end %>
|
312
|
+
</code></pre>
|
313
|
+
|
314
|
+
<p>If you get an error due to unpermitted params, make sure, you are allowing array-style parameters: <code>params.require(:user).permit(roles: [])</code></p>
|
315
|
+
|
316
|
+
<h2 id="label-SimpleForm+for+a+collection+of+Anchormodels">SimpleForm for a collection of Anchormodels</h2>
|
317
|
+
|
318
|
+
<p>Anchormodel’s <a href="https://github.com/heartcombo/simple_form">simple_form</a> support also includes collections of Anchormodels.</p>
|
319
|
+
|
320
|
+
<p>Just like in the single Anchormodel implementation, a select input can be provided with:</p>
|
321
|
+
|
322
|
+
<pre class="code ruby"><code class="ruby"><%= simple_form_for user do |f| %>
|
323
|
+
<%# ... %>
|
324
|
+
<%= f.input :role %>
|
325
|
+
<%# ... %>
|
326
|
+
<% end %>
|
327
|
+
</code></pre>
|
328
|
+
|
329
|
+
<p>The input figures out automatically that it is operating on a collection, so the form code is the same as for a single Anchormodel.</p>
|
330
|
+
|
331
|
+
<p>However, radio buttons are unsuitable for collections, so use check boxes instead:</p>
|
332
|
+
|
333
|
+
<pre class="code ruby"><code class="ruby"><%= simple_form_for user do |f| %>
|
334
|
+
<%# ... %>
|
335
|
+
<%= f.input :role, as: :anchormodel_check_boxes %>
|
336
|
+
<%# ... %>
|
337
|
+
<% end %>
|
266
338
|
</code></pre>
|
267
339
|
</div></div>
|
268
340
|
|
269
341
|
<div id="footer">
|
270
|
-
Generated on Wed
|
342
|
+
Generated on Wed May 22 11:24:43 2024 by
|
271
343
|
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
|
272
|
-
0.9.
|
344
|
+
0.9.34 (ruby-3.2.2).
|
273
345
|
</div>
|
274
346
|
|
275
347
|
</div>
|
data/doc/method_list.html
CHANGED
@@ -78,21 +78,29 @@
|
|
78
78
|
|
79
79
|
<li class="odd ">
|
80
80
|
<div class="item">
|
81
|
-
<span class='object_link'><a href="Anchormodel/
|
82
|
-
<small>Anchormodel::
|
81
|
+
<span class='object_link'><a href="Anchormodel/ActiveModelTypeValueSingle.html#attribute-instance_method" title="Anchormodel::ActiveModelTypeValueSingle#attribute (method)">#attribute</a></span>
|
82
|
+
<small>Anchormodel::ActiveModelTypeValueSingle</small>
|
83
83
|
</div>
|
84
84
|
</li>
|
85
85
|
|
86
86
|
|
87
87
|
<li class="even ">
|
88
88
|
<div class="item">
|
89
|
-
<span class='object_link'><a href="Anchormodel/
|
90
|
-
<small>Anchormodel::
|
89
|
+
<span class='object_link'><a href="Anchormodel/Attribute.html#attribute_name-instance_method" title="Anchormodel::Attribute#attribute_name (method)">#attribute_name</a></span>
|
90
|
+
<small>Anchormodel::Attribute</small>
|
91
91
|
</div>
|
92
92
|
</li>
|
93
93
|
|
94
94
|
|
95
95
|
<li class="odd ">
|
96
|
+
<div class="item">
|
97
|
+
<span class='object_link'><a href="Anchormodel/ActiveModelTypeValueMulti.html#cast-instance_method" title="Anchormodel::ActiveModelTypeValueMulti#cast (method)">#cast</a></span>
|
98
|
+
<small>Anchormodel::ActiveModelTypeValueMulti</small>
|
99
|
+
</div>
|
100
|
+
</li>
|
101
|
+
|
102
|
+
|
103
|
+
<li class="even ">
|
96
104
|
<div class="item">
|
97
105
|
<span class='object_link'><a href="Anchormodel/ActiveModelTypeValueSingle.html#cast-instance_method" title="Anchormodel::ActiveModelTypeValueSingle#cast (method)">#cast</a></span>
|
98
106
|
<small>Anchormodel::ActiveModelTypeValueSingle</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/ActiveModelTypeValueSingle.html#changed_in_place%3F-instance_method" title="Anchormodel::ActiveModelTypeValueSingle#changed_in_place? (method)">#changed_in_place?</a></span>
|
106
114
|
<small>Anchormodel::ActiveModelTypeValueSingle</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.html#find-class_method" title="Anchormodel.find (method)">find</a></span>
|
114
122
|
<small>Anchormodel</small>
|
@@ -116,6 +124,14 @@
|
|
116
124
|
</li>
|
117
125
|
|
118
126
|
|
127
|
+
<li class="odd ">
|
128
|
+
<div class="item">
|
129
|
+
<span class='object_link'><a href="Anchormodel.html#form_collection-class_method" title="Anchormodel.form_collection (method)">form_collection</a></span>
|
130
|
+
<small>Anchormodel</small>
|
131
|
+
</div>
|
132
|
+
</li>
|
133
|
+
|
134
|
+
|
119
135
|
<li class="even ">
|
120
136
|
<div class="item">
|
121
137
|
<span class='object_link'><a href="Anchormodel.html#index-instance_method" title="Anchormodel#index (method)">#index</a></span>
|
@@ -189,6 +205,14 @@
|
|
189
205
|
|
190
206
|
|
191
207
|
<li class="odd ">
|
208
|
+
<div class="item">
|
209
|
+
<span class='object_link'><a href="Anchormodel/Attribute.html#multiple%3F-instance_method" title="Anchormodel::Attribute#multiple? (method)">#multiple?</a></span>
|
210
|
+
<small>Anchormodel::Attribute</small>
|
211
|
+
</div>
|
212
|
+
</li>
|
213
|
+
|
214
|
+
|
215
|
+
<li class="even ">
|
192
216
|
<div class="item">
|
193
217
|
<span class='object_link'><a href="Anchormodel/Attribute.html#optional-instance_method" title="Anchormodel::Attribute#optional (method)">#optional</a></span>
|
194
218
|
<small>Anchormodel::Attribute</small>
|
@@ -196,6 +220,14 @@
|
|
196
220
|
</li>
|
197
221
|
|
198
222
|
|
223
|
+
<li class="odd ">
|
224
|
+
<div class="item">
|
225
|
+
<span class='object_link'><a href="Anchormodel/ActiveModelTypeValueMulti.html#serializable%3F-instance_method" title="Anchormodel::ActiveModelTypeValueMulti#serializable? (method)">#serializable?</a></span>
|
226
|
+
<small>Anchormodel::ActiveModelTypeValueMulti</small>
|
227
|
+
</div>
|
228
|
+
</li>
|
229
|
+
|
230
|
+
|
199
231
|
<li class="even ">
|
200
232
|
<div class="item">
|
201
233
|
<span class='object_link'><a href="Anchormodel/ActiveModelTypeValueSingle.html#serializable%3F-instance_method" title="Anchormodel::ActiveModelTypeValueSingle#serializable? (method)">#serializable?</a></span>
|
@@ -205,6 +237,14 @@
|
|
205
237
|
|
206
238
|
|
207
239
|
<li class="odd ">
|
240
|
+
<div class="item">
|
241
|
+
<span class='object_link'><a href="Anchormodel/ActiveModelTypeValueMulti.html#serialize-instance_method" title="Anchormodel::ActiveModelTypeValueMulti#serialize (method)">#serialize</a></span>
|
242
|
+
<small>Anchormodel::ActiveModelTypeValueMulti</small>
|
243
|
+
</div>
|
244
|
+
</li>
|
245
|
+
|
246
|
+
|
247
|
+
<li class="even ">
|
208
248
|
<div class="item">
|
209
249
|
<span class='object_link'><a href="Anchormodel/ActiveModelTypeValueSingle.html#serialize-instance_method" title="Anchormodel::ActiveModelTypeValueSingle#serialize (method)">#serialize</a></span>
|
210
250
|
<small>Anchormodel::ActiveModelTypeValueSingle</small>
|
@@ -212,7 +252,7 @@
|
|
212
252
|
</li>
|
213
253
|
|
214
254
|
|
215
|
-
<li class="
|
255
|
+
<li class="odd ">
|
216
256
|
<div class="item">
|
217
257
|
<span class='object_link'><a href="Anchormodel.html#setup!-class_method" title="Anchormodel.setup! (method)">setup!</a></span>
|
218
258
|
<small>Anchormodel</small>
|
@@ -220,7 +260,7 @@
|
|
220
260
|
</li>
|
221
261
|
|
222
262
|
|
223
|
-
<li class="
|
263
|
+
<li class="even ">
|
224
264
|
<div class="item">
|
225
265
|
<span class='object_link'><a href="Anchormodel.html#to_s-instance_method" title="Anchormodel#to_s (method)">#to_s</a></span>
|
226
266
|
<small>Anchormodel</small>
|
@@ -228,7 +268,7 @@
|
|
228
268
|
</li>
|
229
269
|
|
230
270
|
|
231
|
-
<li class="
|
271
|
+
<li class="odd ">
|
232
272
|
<div class="item">
|
233
273
|
<span class='object_link'><a href="Anchormodel/ActiveModelTypeValueSingle.html#type-instance_method" title="Anchormodel::ActiveModelTypeValueSingle#type (method)">#type</a></span>
|
234
274
|
<small>Anchormodel::ActiveModelTypeValueSingle</small>
|
@@ -6,7 +6,7 @@
|
|
6
6
|
<title>
|
7
7
|
Top Level Namespace
|
8
8
|
|
9
|
-
— Documentation by YARD 0.9.
|
9
|
+
— Documentation by YARD 0.9.34
|
10
10
|
|
11
11
|
</title>
|
12
12
|
|
@@ -84,7 +84,7 @@
|
|
84
84
|
|
85
85
|
|
86
86
|
|
87
|
-
<strong class="classes">Classes:</strong> <span class='object_link'><a href="Anchormodel.html" title="Anchormodel (class)">Anchormodel</a></span>, <span class='object_link'><a href="AnchormodelGenerator.html" title="AnchormodelGenerator (class)">AnchormodelGenerator</a></span>, <span class='object_link'><a href="AnchormodelInput.html" title="AnchormodelInput (class)">AnchormodelInput</a></span>, <span class='object_link'><a href="AnchormodelRadioButtonsInput.html" title="AnchormodelRadioButtonsInput (class)">AnchormodelRadioButtonsInput</a></span>
|
87
|
+
<strong class="classes">Classes:</strong> <span class='object_link'><a href="Anchormodel.html" title="Anchormodel (class)">Anchormodel</a></span>, <span class='object_link'><a href="AnchormodelCheckBoxesInput.html" title="AnchormodelCheckBoxesInput (class)">AnchormodelCheckBoxesInput</a></span>, <span class='object_link'><a href="AnchormodelGenerator.html" title="AnchormodelGenerator (class)">AnchormodelGenerator</a></span>, <span class='object_link'><a href="AnchormodelInput.html" title="AnchormodelInput (class)">AnchormodelInput</a></span>, <span class='object_link'><a href="AnchormodelRadioButtonsInput.html" title="AnchormodelRadioButtonsInput (class)">AnchormodelRadioButtonsInput</a></span>
|
88
88
|
|
89
89
|
|
90
90
|
</p>
|
@@ -100,9 +100,9 @@
|
|
100
100
|
</div>
|
101
101
|
|
102
102
|
<div id="footer">
|
103
|
-
Generated on Wed
|
103
|
+
Generated on Wed May 22 11:24:43 2024 by
|
104
104
|
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
|
105
|
-
0.9.
|
105
|
+
0.9.34 (ruby-3.2.2).
|
106
106
|
</div>
|
107
107
|
|
108
108
|
</div>
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Anchormodel::ActiveModelTypeValueMulti < Anchormodel::ActiveModelTypeValueSingle
|
2
|
+
# This converts DB or input to an Anchormodel instance
|
3
|
+
def cast(values)
|
4
|
+
return values.split(',').map { |value| super(value) }.compact.to_set
|
5
|
+
end
|
6
|
+
|
7
|
+
# This converts an Anchormodel instance to string for DB
|
8
|
+
def serialize(values)
|
9
|
+
return case values
|
10
|
+
when Enumerable
|
11
|
+
values.map { |value| super(value) }.compact.join(',')
|
12
|
+
when String
|
13
|
+
values
|
14
|
+
when nil
|
15
|
+
''
|
16
|
+
else
|
17
|
+
fail "Attempt to set #{@attribute.attribute_name} to unsupported type #{values.class}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def serializable?(values)
|
22
|
+
return case values
|
23
|
+
when Enumerable
|
24
|
+
values.map { |value| super(value) }.compact.join(',')
|
25
|
+
when String
|
26
|
+
values.split(',').map { |value| super(value) }.compact
|
27
|
+
else
|
28
|
+
false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# @see https://www.rubydoc.info/docs/rails/ActiveModel/Type/Value
|
2
2
|
class Anchormodel::ActiveModelTypeValueSingle < ActiveModel::Type::Value
|
3
|
+
attr_reader :attribute
|
4
|
+
|
3
5
|
def initialize(attribute)
|
4
6
|
super()
|
5
7
|
@attribute = attribute
|
@@ -9,14 +11,14 @@ class Anchormodel::ActiveModelTypeValueSingle < ActiveModel::Type::Value
|
|
9
11
|
:anchormodel
|
10
12
|
end
|
11
13
|
|
12
|
-
# This converts
|
14
|
+
# This converts DB or input to an Anchormodel instance
|
13
15
|
def cast(value)
|
14
16
|
value = value.presence
|
15
17
|
return value if value.is_a?(@attribute.anchormodel_class)
|
16
18
|
return @attribute.anchormodel_class.find(value)
|
17
19
|
end
|
18
20
|
|
19
|
-
# This converts
|
21
|
+
# This converts an Anchormodel instance to string for DB
|
20
22
|
def serialize(value)
|
21
23
|
value = value.presence
|
22
24
|
return case value
|
@@ -10,10 +10,16 @@ class Anchormodel::Attribute
|
|
10
10
|
# @param attribute_name [String,Symbol] The name and database column of the attribute
|
11
11
|
# @param anchormodel_class [Class] Class of the Anchormodel (omit if attribute `:foo_bar` holds an `FooBar`)
|
12
12
|
# @param optional [Boolean] If true, a presence validation is added to the model.
|
13
|
-
|
13
|
+
# @param multiple [Boolean] If true, this attribute holds multiple anchormodels.
|
14
|
+
def initialize(model_class, attribute_name, anchormodel_class = nil, optional = false, multiple = false)
|
14
15
|
@model_class = model_class
|
15
16
|
@attribute_name = attribute_name.to_sym
|
16
17
|
@anchormodel_class = anchormodel_class
|
17
18
|
@optional = optional
|
19
|
+
@multiple = multiple
|
20
|
+
end
|
21
|
+
|
22
|
+
def multiple?
|
23
|
+
@multiple
|
18
24
|
end
|
19
25
|
end
|
@@ -14,5 +14,12 @@ module Anchormodel::ModelMixin
|
|
14
14
|
def belongs_to_anchormodel(*args, **kwargs)
|
15
15
|
Anchormodel::Util.install_methods_in_model(self, *args, **kwargs)
|
16
16
|
end
|
17
|
+
|
18
|
+
# Creates an attribute linking to an array of Anchormodels. The attribute should be
|
19
|
+
# present in the DB and the column should be named the singular of `attribute_name`.
|
20
|
+
# @see Anchormodel::Util#install_methods_in_model Parameters
|
21
|
+
def belongs_to_anchormodels(*args, **kwargs)
|
22
|
+
Anchormodel::Util.install_methods_in_model(self, *args, **kwargs, multiple: true)
|
23
|
+
end
|
17
24
|
end
|
18
25
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
unless defined? SimpleForm
|
2
|
+
begin
|
3
|
+
require 'simple_form'
|
4
|
+
rescue LoadError
|
5
|
+
nil
|
6
|
+
end
|
7
|
+
|
8
|
+
end
|
9
|
+
if defined? SimpleForm
|
10
|
+
class AnchormodelCheckBoxesInput < SimpleForm::Inputs::CollectionCheckBoxesInput
|
11
|
+
include Anchormodel::SimpleFormInputs::Helpers::AnchormodelInputsCommon
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def sf_selection_key
|
16
|
+
:checked
|
17
|
+
end
|
18
|
+
|
19
|
+
def before_render_input
|
20
|
+
@input_type = :check_boxes
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -14,20 +14,24 @@ did you `include Anchormodel::ModelMixin` in your `application_record.rb`? Affec
|
|
14
14
|
Affected object: #{object.inspect}")
|
15
15
|
end
|
16
16
|
am_class = am_attr.anchormodel_class
|
17
|
+
options[:multiple] = true if am_attr.multiple? && options[:multiple] != false # allow overriding
|
17
18
|
|
18
19
|
# Attempt to read selected key from html input options "value", as the caller might not know that this is a select.
|
19
20
|
selected_key = input_options[:value]
|
20
21
|
if selected_key.blank? && object
|
21
22
|
# No selected key override present and a model is present, use the model to find out what to select.
|
22
23
|
selected_am = object.send(@attribute_name)
|
23
|
-
|
24
|
+
if am_attr.multiple?
|
25
|
+
selected_key = selected_am&.map(&:key)&.map(&:to_s) || []
|
26
|
+
else
|
27
|
+
selected_key = (selected_am&.key || am_class.all.first).to_s
|
28
|
+
end
|
24
29
|
end
|
25
30
|
|
26
31
|
options.deep_merge!(
|
27
32
|
label_method: :first,
|
28
33
|
value_method: :second,
|
29
|
-
sf_selection_key => selected_key
|
30
|
-
include_blank: am_attr.optional
|
34
|
+
sf_selection_key => selected_key
|
31
35
|
)
|
32
36
|
|
33
37
|
@collection = collect(am_class.all)
|
data/lib/anchormodel/util.rb
CHANGED
@@ -5,21 +5,24 @@ module Anchormodel::Util
|
|
5
5
|
# @param model_class [ActiveRecord::Base] Internal only. The model class that the attribute should be installed to.
|
6
6
|
# @param attribute_name [String,Symbol] The name and database column of the attribute
|
7
7
|
# @param anchormodel_class [Class] Class of the Anchormodel (omit if attribute `:foo_bar` holds a `FooBar`)
|
8
|
-
# @param optional [Boolean] If false, a presence validation is added to the model.
|
8
|
+
# @param optional [Boolean] If false, a presence validation is added to the model. Forced to true if multiple is true.
|
9
|
+
# @param multiple [Boolean] Internal only. Distinguishes between `belongs_to_anchormodel` and `belongs_to_anchormodels`.
|
9
10
|
# @param model_readers [Boolean] If true, the model is given an ActiveRecord::Enum style method `my_model.my_key?` reader for each key in the anchormodel
|
10
11
|
# @param model_writers [Boolean] If true, the model is given an ActiveRecord::Enum style method `my_model.my_key!` writer for each key in the anchormodel
|
11
12
|
# @param model_scopes [Boolean] If true, the model is given an ActiveRecord::Enum style scope `MyModel.mykey` for each key in the anchormodel
|
12
13
|
# @param model_methods [Boolean, NilClass] If non-nil, this mass-assigns and overrides `model_readers`, `model_writers` and `model_scopes`
|
13
14
|
def self.install_methods_in_model(model_class, attribute_name, anchormodel_class = nil,
|
14
15
|
optional: false,
|
16
|
+
multiple: false,
|
15
17
|
model_readers: true,
|
16
18
|
model_writers: true,
|
17
19
|
model_scopes: true,
|
18
20
|
model_methods: nil)
|
19
21
|
|
22
|
+
optional = true if multiple
|
20
23
|
anchormodel_class ||= attribute_name.to_s.classify.constantize
|
21
24
|
attribute_name = attribute_name.to_sym
|
22
|
-
attribute = Anchormodel::Attribute.new(
|
25
|
+
attribute = Anchormodel::Attribute.new(model_class, attribute_name, anchormodel_class, optional, multiple)
|
23
26
|
|
24
27
|
# Mass configurations if model_methods was specfied
|
25
28
|
unless model_methods.nil?
|
@@ -38,11 +41,43 @@ module Anchormodel::Util
|
|
38
41
|
|
39
42
|
# Make casting work
|
40
43
|
# Define serializer/deserializer
|
41
|
-
active_model_type_value = Anchormodel::ActiveModelTypeValueSingle.new(attribute)
|
44
|
+
active_model_type_value = (multiple ? Anchormodel::ActiveModelTypeValueMulti : Anchormodel::ActiveModelTypeValueSingle).new(attribute)
|
42
45
|
|
43
46
|
# Overwrite reader to force building anchors at every retrieval
|
44
47
|
model_class.define_method(attribute_name.to_s) do
|
45
|
-
active_model_type_value.deserialize(read_attribute_before_type_cast(attribute_name))
|
48
|
+
result = active_model_type_value.deserialize(read_attribute_before_type_cast(attribute_name))
|
49
|
+
|
50
|
+
# If this attribute holds multiple anchormodels (`belongs_to_anchormodels`), patch the Array before returning in order to implement collection modifiers:
|
51
|
+
if multiple
|
52
|
+
model = self # fetching the model in order to pass it to the implementation of the following via reflection to make it available for storage
|
53
|
+
single_tv = Anchormodel::ActiveModelTypeValueSingle.new(attribute) # Used for casting inputs to properly compare them to the set.
|
54
|
+
|
55
|
+
# Adding
|
56
|
+
result.define_singleton_method('add') do |value_to_add|
|
57
|
+
super(single_tv.cast(value_to_add))
|
58
|
+
model.write_attribute(attribute_name, active_model_type_value.serialize(self))
|
59
|
+
next self
|
60
|
+
end
|
61
|
+
result.define_singleton_method('<<') { |value_to_add| add(single_tv.cast(value_to_add)) }
|
62
|
+
|
63
|
+
# Deleting
|
64
|
+
result.define_singleton_method('delete') do |value_to_delete|
|
65
|
+
super(single_tv.cast(value_to_delete))
|
66
|
+
model.write_attribute(attribute_name, active_model_type_value.serialize(self))
|
67
|
+
next self
|
68
|
+
end
|
69
|
+
|
70
|
+
# Clearing
|
71
|
+
result.define_singleton_method('clear') do
|
72
|
+
super()
|
73
|
+
model.write_attribute(attribute_name, active_model_type_value.serialize(self))
|
74
|
+
next self
|
75
|
+
end
|
76
|
+
|
77
|
+
# In the future, further methods could be supported. e.g. delete_if, subtract etc.
|
78
|
+
end
|
79
|
+
|
80
|
+
return result
|
46
81
|
end
|
47
82
|
|
48
83
|
# Override writer to fail early when an invalid target value is specified
|
@@ -61,7 +96,11 @@ module Anchormodel::Util
|
|
61
96
|
fail("Anchormodel reader #{entry.key}? already defined for #{self}, add `model_readers: false` to `belongs_to_anchormodel :#{attribute_name}`.")
|
62
97
|
end
|
63
98
|
model_class.define_method(:"#{entry.key}?") do
|
64
|
-
|
99
|
+
if multiple
|
100
|
+
public_send(attribute_name.to_s).include?(entry)
|
101
|
+
else
|
102
|
+
public_send(attribute_name.to_s) == entry
|
103
|
+
end
|
65
104
|
end
|
66
105
|
end
|
67
106
|
end
|
@@ -74,7 +113,11 @@ module Anchormodel::Util
|
|
74
113
|
fail("Anchormodel writer #{entry.key}! already defined for #{self}, add `model_writers: false` to `belongs_to_anchormodel :#{attribute_name}`.")
|
75
114
|
end
|
76
115
|
model_class.define_method(:"#{entry.key}!") do
|
77
|
-
|
116
|
+
if multiple
|
117
|
+
public_send(attribute_name.to_s) << entry
|
118
|
+
else
|
119
|
+
public_send(:"#{attribute_name}=", entry)
|
120
|
+
end
|
78
121
|
end
|
79
122
|
end
|
80
123
|
end
|
@@ -86,7 +129,14 @@ module Anchormodel::Util
|
|
86
129
|
if model_class.respond_to?(entry.key)
|
87
130
|
fail("Anchormodel scope #{entry.key} already defined for #{self}, add `model_scopes: false` to `belongs_to_anchormodel :#{attribute_name}`.")
|
88
131
|
end
|
89
|
-
|
132
|
+
if multiple
|
133
|
+
model_class.scope(entry.key, lambda {
|
134
|
+
where("#{attribute_name} LIKE ? OR #{attribute_name} LIKE ? OR #{attribute_name} LIKE ? OR #{attribute_name} LIKE ?",
|
135
|
+
"%#{entry.key},%", "%#{entry.key}", "#{entry.key},%", entry.key.to_s)
|
136
|
+
})
|
137
|
+
else
|
138
|
+
model_class.scope(entry.key, -> { where(attribute_name => entry.key) })
|
139
|
+
end
|
90
140
|
end
|
91
141
|
end
|
92
142
|
end
|
data/lib/anchormodel.rb
CHANGED
@@ -25,6 +25,11 @@ class Anchormodel
|
|
25
25
|
entries_list
|
26
26
|
end
|
27
27
|
|
28
|
+
# Returns an array of tuples [label, key] suitable for passing as a collection to some form input helpers
|
29
|
+
def self.form_collection
|
30
|
+
entries_list.map { |el| [el.label, el.key.to_s] }
|
31
|
+
end
|
32
|
+
|
28
33
|
# Retrieves a particular value given the key. Fails if not found.
|
29
34
|
# @param key [String,Symbol] The key of the value that should be retrieved.
|
30
35
|
def self.find(key)
|
@@ -79,9 +84,11 @@ end
|
|
79
84
|
|
80
85
|
require 'anchormodel/util'
|
81
86
|
require 'anchormodel/active_model_type_value_single'
|
87
|
+
require 'anchormodel/active_model_type_value_multi'
|
82
88
|
require 'anchormodel/attribute'
|
83
89
|
require 'anchormodel/model_mixin'
|
84
90
|
require 'anchormodel/version'
|
85
91
|
require 'anchormodel/simple_form_inputs/helpers/anchormodel_inputs_common'
|
86
92
|
require 'anchormodel/simple_form_inputs/anchormodel_input'
|
87
93
|
require 'anchormodel/simple_form_inputs/anchormodel_radio_buttons_input'
|
94
|
+
require 'anchormodel/simple_form_inputs/anchormodel_check_boxes_input'
|