anchormodel 0.1.5 → 0.2.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/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
|