anchormodel 0.1.5 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/Gemfile.lock +1 -1
- data/README.md +78 -2
- data/VERSION +1 -1
- data/anchormodel.gemspec +4 -4
- data/doc/Anchormodel/ActiveModelTypeValueSingle.html +113 -31
- data/doc/Anchormodel/Attribute.html +109 -9
- data/doc/Anchormodel/ModelMixin.html +75 -3
- data/doc/Anchormodel/SimpleFormInputs/Helpers/AnchormodelInputsCommon.html +269 -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/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 +62 -6
- 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 +8 -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 +11 -2
data/doc/method_list.html
CHANGED
@@ -77,6 +77,14 @@
|
|
77
77
|
|
78
78
|
|
79
79
|
<li class="odd ">
|
80
|
+
<div class="item">
|
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
|
+
</div>
|
84
|
+
</li>
|
85
|
+
|
86
|
+
|
87
|
+
<li class="even ">
|
80
88
|
<div class="item">
|
81
89
|
<span class='object_link'><a href="Anchormodel/Attribute.html#attribute_name-instance_method" title="Anchormodel::Attribute#attribute_name (method)">#attribute_name</a></span>
|
82
90
|
<small>Anchormodel::Attribute</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/ModelMixin.html#belongs_to_anchormodel-class_method" title="Anchormodel::ModelMixin.belongs_to_anchormodel (method)">belongs_to_anchormodel</a></span>
|
90
98
|
<small>Anchormodel::ModelMixin</small>
|
@@ -92,7 +100,23 @@
|
|
92
100
|
</li>
|
93
101
|
|
94
102
|
|
103
|
+
<li class="even ">
|
104
|
+
<div class="item">
|
105
|
+
<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>
|
106
|
+
<small>Anchormodel::ModelMixin</small>
|
107
|
+
</div>
|
108
|
+
</li>
|
109
|
+
|
110
|
+
|
95
111
|
<li class="odd ">
|
112
|
+
<div class="item">
|
113
|
+
<span class='object_link'><a href="Anchormodel/ActiveModelTypeValueMulti.html#cast-instance_method" title="Anchormodel::ActiveModelTypeValueMulti#cast (method)">#cast</a></span>
|
114
|
+
<small>Anchormodel::ActiveModelTypeValueMulti</small>
|
115
|
+
</div>
|
116
|
+
</li>
|
117
|
+
|
118
|
+
|
119
|
+
<li class="even ">
|
96
120
|
<div class="item">
|
97
121
|
<span class='object_link'><a href="Anchormodel/ActiveModelTypeValueSingle.html#cast-instance_method" title="Anchormodel::ActiveModelTypeValueSingle#cast (method)">#cast</a></span>
|
98
122
|
<small>Anchormodel::ActiveModelTypeValueSingle</small>
|
@@ -100,7 +124,7 @@
|
|
100
124
|
</li>
|
101
125
|
|
102
126
|
|
103
|
-
<li class="
|
127
|
+
<li class="odd ">
|
104
128
|
<div class="item">
|
105
129
|
<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
130
|
<small>Anchormodel::ActiveModelTypeValueSingle</small>
|
@@ -108,7 +132,7 @@
|
|
108
132
|
</li>
|
109
133
|
|
110
134
|
|
111
|
-
<li class="
|
135
|
+
<li class="even ">
|
112
136
|
<div class="item">
|
113
137
|
<span class='object_link'><a href="Anchormodel.html#find-class_method" title="Anchormodel.find (method)">find</a></span>
|
114
138
|
<small>Anchormodel</small>
|
@@ -116,6 +140,14 @@
|
|
116
140
|
</li>
|
117
141
|
|
118
142
|
|
143
|
+
<li class="odd ">
|
144
|
+
<div class="item">
|
145
|
+
<span class='object_link'><a href="Anchormodel.html#form_collection-class_method" title="Anchormodel.form_collection (method)">form_collection</a></span>
|
146
|
+
<small>Anchormodel</small>
|
147
|
+
</div>
|
148
|
+
</li>
|
149
|
+
|
150
|
+
|
119
151
|
<li class="even ">
|
120
152
|
<div class="item">
|
121
153
|
<span class='object_link'><a href="Anchormodel.html#index-instance_method" title="Anchormodel#index (method)">#index</a></span>
|
@@ -189,6 +221,14 @@
|
|
189
221
|
|
190
222
|
|
191
223
|
<li class="odd ">
|
224
|
+
<div class="item">
|
225
|
+
<span class='object_link'><a href="Anchormodel/Attribute.html#multiple%3F-instance_method" title="Anchormodel::Attribute#multiple? (method)">#multiple?</a></span>
|
226
|
+
<small>Anchormodel::Attribute</small>
|
227
|
+
</div>
|
228
|
+
</li>
|
229
|
+
|
230
|
+
|
231
|
+
<li class="even ">
|
192
232
|
<div class="item">
|
193
233
|
<span class='object_link'><a href="Anchormodel/Attribute.html#optional-instance_method" title="Anchormodel::Attribute#optional (method)">#optional</a></span>
|
194
234
|
<small>Anchormodel::Attribute</small>
|
@@ -196,6 +236,14 @@
|
|
196
236
|
</li>
|
197
237
|
|
198
238
|
|
239
|
+
<li class="odd ">
|
240
|
+
<div class="item">
|
241
|
+
<span class='object_link'><a href="Anchormodel/ActiveModelTypeValueMulti.html#serializable%3F-instance_method" title="Anchormodel::ActiveModelTypeValueMulti#serializable? (method)">#serializable?</a></span>
|
242
|
+
<small>Anchormodel::ActiveModelTypeValueMulti</small>
|
243
|
+
</div>
|
244
|
+
</li>
|
245
|
+
|
246
|
+
|
199
247
|
<li class="even ">
|
200
248
|
<div class="item">
|
201
249
|
<span class='object_link'><a href="Anchormodel/ActiveModelTypeValueSingle.html#serializable%3F-instance_method" title="Anchormodel::ActiveModelTypeValueSingle#serializable? (method)">#serializable?</a></span>
|
@@ -205,6 +253,14 @@
|
|
205
253
|
|
206
254
|
|
207
255
|
<li class="odd ">
|
256
|
+
<div class="item">
|
257
|
+
<span class='object_link'><a href="Anchormodel/ActiveModelTypeValueMulti.html#serialize-instance_method" title="Anchormodel::ActiveModelTypeValueMulti#serialize (method)">#serialize</a></span>
|
258
|
+
<small>Anchormodel::ActiveModelTypeValueMulti</small>
|
259
|
+
</div>
|
260
|
+
</li>
|
261
|
+
|
262
|
+
|
263
|
+
<li class="even ">
|
208
264
|
<div class="item">
|
209
265
|
<span class='object_link'><a href="Anchormodel/ActiveModelTypeValueSingle.html#serialize-instance_method" title="Anchormodel::ActiveModelTypeValueSingle#serialize (method)">#serialize</a></span>
|
210
266
|
<small>Anchormodel::ActiveModelTypeValueSingle</small>
|
@@ -212,7 +268,7 @@
|
|
212
268
|
</li>
|
213
269
|
|
214
270
|
|
215
|
-
<li class="
|
271
|
+
<li class="odd ">
|
216
272
|
<div class="item">
|
217
273
|
<span class='object_link'><a href="Anchormodel.html#setup!-class_method" title="Anchormodel.setup! (method)">setup!</a></span>
|
218
274
|
<small>Anchormodel</small>
|
@@ -220,7 +276,7 @@
|
|
220
276
|
</li>
|
221
277
|
|
222
278
|
|
223
|
-
<li class="
|
279
|
+
<li class="even ">
|
224
280
|
<div class="item">
|
225
281
|
<span class='object_link'><a href="Anchormodel.html#to_s-instance_method" title="Anchormodel#to_s (method)">#to_s</a></span>
|
226
282
|
<small>Anchormodel</small>
|
@@ -228,7 +284,7 @@
|
|
228
284
|
</li>
|
229
285
|
|
230
286
|
|
231
|
-
<li class="
|
287
|
+
<li class="odd ">
|
232
288
|
<div class="item">
|
233
289
|
<span class='object_link'><a href="Anchormodel/ActiveModelTypeValueSingle.html#type-instance_method" title="Anchormodel::ActiveModelTypeValueSingle#type (method)">#type</a></span>
|
234
290
|
<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
|
103
|
+
Generated on Sat Apr 27 10:18:33 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,25 @@ 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,
|
35
|
+
include_blank: !am_attr.multiple? && am_attr.optional
|
31
36
|
)
|
32
37
|
|
33
38
|
@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'
|
@@ -21,10 +21,6 @@ class UserTest < Minitest::Test
|
|
21
21
|
)
|
22
22
|
end
|
23
23
|
|
24
|
-
def test_missing_key
|
25
|
-
assert_raises { Role.find(:does_not_exist) }
|
26
|
-
end
|
27
|
-
|
28
24
|
def test_basic_setters_and_getters
|
29
25
|
u = User.create!(role: 'guest', locale: 'de') # String assignment
|
30
26
|
assert_equal Role.find(:guest), u.role
|
@@ -57,11 +53,6 @@ class UserTest < Minitest::Test
|
|
57
53
|
assert Role.find(:moderator) < Role.find(:admin)
|
58
54
|
end
|
59
55
|
|
60
|
-
def test_presence_validation
|
61
|
-
valentine = User.new
|
62
|
-
assert_raises(ActiveRecord::RecordInvalid) { valentine.save! }
|
63
|
-
end
|
64
|
-
|
65
56
|
def test_alternative_column_name
|
66
57
|
ben = User.create!(
|
67
58
|
role: Role.find(:moderator),
|
@@ -119,6 +110,19 @@ class UserTest < Minitest::Test
|
|
119
110
|
assert_nil u.secondary_role
|
120
111
|
end
|
121
112
|
|
113
|
+
###---
|
114
|
+
# Testing failures
|
115
|
+
###---
|
116
|
+
|
117
|
+
def test_presence_validation
|
118
|
+
valentine = User.new
|
119
|
+
assert_raises(ActiveRecord::RecordInvalid) { valentine.save! }
|
120
|
+
end
|
121
|
+
|
122
|
+
def test_missing_key
|
123
|
+
assert_raises { Role.find(:does_not_exist) }
|
124
|
+
end
|
125
|
+
|
122
126
|
# Attempting to create a model with an invalid constant name should fail
|
123
127
|
def test_invalid_key_update
|
124
128
|
assert_raises(RuntimeError) { User.create!(role: :admin, locale: :de, preferred_locale: :invalid) }
|
@@ -137,4 +141,64 @@ class UserTest < Minitest::Test
|
|
137
141
|
ActiveRecord::Base.connection.execute(sql)
|
138
142
|
assert_raises(RuntimeError) { User.first.role }
|
139
143
|
end
|
144
|
+
|
145
|
+
###---
|
146
|
+
# Testing multiple anchormodel associations
|
147
|
+
###---
|
148
|
+
|
149
|
+
def test_multi_basics
|
150
|
+
u = User.create!(role: 'guest', locale: 'de')
|
151
|
+
assert_equal(Set.new, u.animals)
|
152
|
+
# Adding
|
153
|
+
u.animals << 'cat'
|
154
|
+
assert_equal(Set.new([Animal.find(:cat)]), u.animals)
|
155
|
+
u.animals.add :dog
|
156
|
+
assert_equal(Set.new([Animal.find(:cat), Animal.find(:dog)]), u.animals)
|
157
|
+
u.animals.add Animal.find(:horse)
|
158
|
+
assert_equal(Set.new([Animal.find(:cat), Animal.find(:dog), Animal.find(:horse)]), u.animals)
|
159
|
+
# Removing
|
160
|
+
u.animals.delete 'cat'
|
161
|
+
assert_equal(Set.new([Animal.find(:dog), Animal.find(:horse)]), u.animals)
|
162
|
+
u.animals.delete :dog
|
163
|
+
assert_equal(Set.new([Animal.find(:horse)]), u.animals)
|
164
|
+
u.animals.delete Animal.find(:horse)
|
165
|
+
assert_equal(false, u.animals.any?)
|
166
|
+
# Setting
|
167
|
+
u.animals = %i[cat dog]
|
168
|
+
assert_equal(Set.new([Animal.find(:cat), Animal.find(:dog)]), u.animals)
|
169
|
+
# Clearing
|
170
|
+
u.animals.clear
|
171
|
+
assert_equal(false, u.animals.any?)
|
172
|
+
end
|
173
|
+
|
174
|
+
def test_multi_save_load
|
175
|
+
u = User.create!(role: 'guest', locale: 'de')
|
176
|
+
u.animals = %i[cat dog]
|
177
|
+
u.save!
|
178
|
+
freshly_loaded_u = User.first
|
179
|
+
assert_equal(Set.new([Animal.find(:cat), Animal.find(:dog)]), freshly_loaded_u.animals)
|
180
|
+
end
|
181
|
+
|
182
|
+
def test_multi_model_readers_and_writers
|
183
|
+
u = User.create!(role: 'guest', locale: 'de')
|
184
|
+
u.cat!
|
185
|
+
u.cat!
|
186
|
+
assert_equal(Set.new([Animal.find(:cat)]), u.animals) # tolerate no duplicate cat
|
187
|
+
assert_equal(true, u.cat?)
|
188
|
+
u.dog!
|
189
|
+
assert_equal(true, u.cat?)
|
190
|
+
assert_equal(true, u.dog?)
|
191
|
+
assert_equal(false, u.horse?)
|
192
|
+
end
|
193
|
+
|
194
|
+
def test_multi_model_scopes
|
195
|
+
u = User.create!(role: 'guest', locale: 'fr', animals: %w[dog cat])
|
196
|
+
v = User.create!(role: 'guest', locale: 'it', animals: %w[dog horse])
|
197
|
+
assert_equal(0, User.rat.count)
|
198
|
+
assert_equal(1, User.cat.count)
|
199
|
+
assert_equal(1, User.horse.count)
|
200
|
+
assert_equal(2, User.dog.count)
|
201
|
+
assert_equal(u.id, User.cat.first.id)
|
202
|
+
assert_equal(v.id, User.horse.first.id)
|
203
|
+
end
|
140
204
|
end
|
@@ -3,4 +3,5 @@ class User < ApplicationRecord
|
|
3
3
|
belongs_to_anchormodel :secondary_role, Role, optional: true, model_readers: false, model_writers: false, model_scopes: false
|
4
4
|
belongs_to_anchormodel :locale, model_methods: false
|
5
5
|
belongs_to_anchormodel :preferred_locale, Locale
|
6
|
+
belongs_to_anchormodels :animals
|
6
7
|
end
|
data/test/dummy/db/schema.rb
CHANGED
@@ -10,7 +10,7 @@
|
|
10
10
|
#
|
11
11
|
# It's strongly recommended that you check this file into your version control system.
|
12
12
|
|
13
|
-
ActiveRecord::Schema[7.0].define(version:
|
13
|
+
ActiveRecord::Schema[7.0].define(version: 20_240_425_182_000) do
|
14
14
|
create_table 'users', force: :cascade do |t|
|
15
15
|
t.string 'role'
|
16
16
|
t.string 'locale'
|
@@ -18,5 +18,6 @@ ActiveRecord::Schema[7.0].define(version: 20_230_124_084_241) do
|
|
18
18
|
t.datetime 'updated_at', null: false
|
19
19
|
t.string 'secondary_role'
|
20
20
|
t.string 'preferred_locale', default: 'en', null: false
|
21
|
+
t.string 'animals', default: '', null: false
|
21
22
|
end
|
22
23
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: anchormodel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sandro Kalbermatter
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-04-
|
11
|
+
date: 2024-04-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -173,9 +173,14 @@ files:
|
|
173
173
|
- doc/Anchormodel/ActiveModelTypeValueSingle.html
|
174
174
|
- doc/Anchormodel/Attribute.html
|
175
175
|
- doc/Anchormodel/ModelMixin.html
|
176
|
+
- doc/Anchormodel/SimpleFormInputs.html
|
177
|
+
- doc/Anchormodel/SimpleFormInputs/Helpers.html
|
178
|
+
- doc/Anchormodel/SimpleFormInputs/Helpers/AnchormodelInputsCommon.html
|
176
179
|
- doc/Anchormodel/Util.html
|
177
180
|
- doc/Anchormodel/Version.html
|
178
181
|
- doc/AnchormodelGenerator.html
|
182
|
+
- doc/AnchormodelInput.html
|
183
|
+
- doc/AnchormodelRadioButtonsInput.html
|
179
184
|
- doc/_index.html
|
180
185
|
- doc/class_list.html
|
181
186
|
- doc/css/common.css
|
@@ -191,9 +196,11 @@ files:
|
|
191
196
|
- doc/method_list.html
|
192
197
|
- doc/top-level-namespace.html
|
193
198
|
- lib/anchormodel.rb
|
199
|
+
- lib/anchormodel/active_model_type_value_multi.rb
|
194
200
|
- lib/anchormodel/active_model_type_value_single.rb
|
195
201
|
- lib/anchormodel/attribute.rb
|
196
202
|
- lib/anchormodel/model_mixin.rb
|
203
|
+
- lib/anchormodel/simple_form_inputs/anchormodel_check_boxes_input.rb
|
197
204
|
- lib/anchormodel/simple_form_inputs/anchormodel_input.rb
|
198
205
|
- lib/anchormodel/simple_form_inputs/anchormodel_radio_buttons_input.rb
|
199
206
|
- lib/anchormodel/simple_form_inputs/helpers/anchormodel_inputs_common.rb
|
@@ -206,6 +213,7 @@ files:
|
|
206
213
|
- test/active_record_model/user_test.rb
|
207
214
|
- test/dummy/.gitignore
|
208
215
|
- test/dummy/Rakefile
|
216
|
+
- test/dummy/app/anchormodels/animal.rb
|
209
217
|
- test/dummy/app/anchormodels/locale.rb
|
210
218
|
- test/dummy/app/anchormodels/role.rb
|
211
219
|
- test/dummy/app/helpers/application_helper.rb
|
@@ -230,6 +238,7 @@ files:
|
|
230
238
|
- test/dummy/config/puma.rb
|
231
239
|
- test/dummy/config/routes.rb
|
232
240
|
- test/dummy/db/migrate/20230107173151_create_users.rb
|
241
|
+
- test/dummy/db/migrate/20240425182000_add_animals_to_users.rb
|
233
242
|
- test/dummy/db/schema.rb
|
234
243
|
- test/dummy/db/seeds.rb
|
235
244
|
- test/dummy/lib/tasks/.keep
|