anchormodel 0.1.5 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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'
|