assignable_values 0.18.1 → 1.0.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 +2 -0
- data/Gemfile.5.0.lock +1 -1
- data/Gemfile.5.1.lock +1 -1
- data/Gemfile.5.1.pg.lock +1 -1
- data/Gemfile.6.1.pg.lock +1 -1
- data/Gemfile.7.0.pg.lock +1 -1
- data/README.md +206 -146
- data/lib/assignable_values/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 974063fe5e304e09553d556f1eae415a7908f16bc026e713e811cb8180bc7510
|
4
|
+
data.tar.gz: 5754483e8d63baace26522a6e7885c750e0010a21ee87ce795421a917e0823d6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 13628f9ab3a4fbe96db3a2accfd2be3d00ac711994ece3694c3e3e3d15cf46c94def5e6f53270bd1b9242eb6bae40381b545c70ca68f62bc6d032415107d3918
|
7
|
+
data.tar.gz: 30833906903fb6291aad2157615631e76880a3df3d6342a057fc9993b021caa4d49173c13910aec5349b98d0af4647087fc4e18b26a7c7caba4a34b236cb9c81
|
data/CHANGELOG.md
CHANGED
data/Gemfile.5.0.lock
CHANGED
data/Gemfile.5.1.lock
CHANGED
data/Gemfile.5.1.pg.lock
CHANGED
data/Gemfile.6.1.pg.lock
CHANGED
data/Gemfile.7.0.pg.lock
CHANGED
data/README.md
CHANGED
@@ -13,16 +13,20 @@ Restricting scalar attributes
|
|
13
13
|
|
14
14
|
The basic usage to restrict the values assignable to strings, integers, etc. is this:
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
```rb
|
17
|
+
class Song < ActiveRecord::Base
|
18
|
+
assignable_values_for :genre do
|
19
|
+
['pop', 'rock', 'electronic']
|
20
|
+
end
|
21
|
+
end
|
22
|
+
```
|
21
23
|
|
22
24
|
The assigned value is checked during validation:
|
23
25
|
|
24
|
-
|
25
|
-
|
26
|
+
```rb
|
27
|
+
Song.new(genre: 'rock').valid? # => true
|
28
|
+
Song.new(genre: 'elephant').valid? # => false
|
29
|
+
```
|
26
30
|
|
27
31
|
The validation error message is the same as the one from `validates_inclusion_of` (`errors.messages.inclusion` in your I18n dictionary).
|
28
32
|
You can also set a custom error message with the `:message` option.
|
@@ -31,13 +35,15 @@ You can also set a custom error message with the `:message` option.
|
|
31
35
|
### Listing assignable values
|
32
36
|
|
33
37
|
You can ask a record for a list of values that can be assigned to an attribute:
|
34
|
-
|
35
|
-
|
38
|
+
```rb
|
39
|
+
song.assignable_genres # => ['pop', 'rock', 'electronic']
|
40
|
+
```
|
36
41
|
|
37
42
|
This is useful for populating `<select>` tags in web forms:
|
38
43
|
|
39
|
-
|
40
|
-
|
44
|
+
```rb
|
45
|
+
form.select :genre, form.object.assignable_genres
|
46
|
+
```
|
41
47
|
|
42
48
|
### Humanized labels
|
43
49
|
|
@@ -45,49 +51,63 @@ You will often want to present internal values in a humanized form. E.g. `"pop"`
|
|
45
51
|
|
46
52
|
You can define human labels in your I18n dictionary:
|
47
53
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
54
|
+
```yaml
|
55
|
+
en:
|
56
|
+
assignable_values:
|
57
|
+
song:
|
58
|
+
genre:
|
59
|
+
pop: 'Pop music'
|
60
|
+
rock: 'Rock music'
|
61
|
+
electronic: 'Electronic music'
|
62
|
+
```
|
55
63
|
|
56
64
|
You can access the humanized version for the current value like this:
|
57
65
|
|
58
|
-
|
59
|
-
|
66
|
+
```rb
|
67
|
+
song = Song.new(:genre => 'pop')
|
68
|
+
song.humanized_genre # => 'Pop music'
|
69
|
+
```
|
60
70
|
|
61
71
|
Or you can retrieve the humanized version of any given value by passing it as an argument to either instance or class:
|
62
72
|
|
63
|
-
|
64
|
-
|
73
|
+
```rb
|
74
|
+
song.humanized_genre('rock') # => 'Rock music'
|
75
|
+
Song.humanized_genre('rock') # => 'Rock music'
|
76
|
+
```
|
65
77
|
|
66
78
|
You can obtain a list of all assignable values with their humanizations:
|
67
79
|
|
68
|
-
|
69
|
-
|
70
|
-
|
80
|
+
```rb
|
81
|
+
song.humanized_assignable_genres.size # => 3
|
82
|
+
song.humanized_assignable_genres.first.value # => "pop"
|
83
|
+
song.humanized_assignable_genres.first.humanized # => "Pop music"
|
84
|
+
```
|
71
85
|
|
72
86
|
A good way to populate a `<select>` tag with pairs of internal values and human labels is to use the `collection_select` helper from Rails:
|
73
87
|
|
74
|
-
|
88
|
+
```rb
|
89
|
+
form.collection_select :genre, form.object.humanized_assignable_genres, :value, :humanized
|
90
|
+
```
|
75
91
|
|
76
92
|
#### Humanized labels and inheritance
|
77
93
|
|
78
94
|
For models that inherit assignable values you can override the humanized labels:
|
79
95
|
|
80
|
-
|
81
|
-
|
82
|
-
|
96
|
+
```rb
|
97
|
+
class FunnySong < Song
|
98
|
+
...
|
99
|
+
end
|
100
|
+
```
|
83
101
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
102
|
+
```yaml
|
103
|
+
en:
|
104
|
+
assignable_values:
|
105
|
+
funny_song:
|
106
|
+
genre:
|
107
|
+
pop: 'The stuff you hear on mainstream radio all day long'
|
108
|
+
rock: 'A lot of electric guitars and drums'
|
109
|
+
electronic: 'Whatever David Guetta does'
|
110
|
+
```
|
91
111
|
|
92
112
|
If no humanization is provided for the child model (i.e. the `funny_song.genre` key) humanization will fall back to the
|
93
113
|
parent model (`song`).
|
@@ -96,38 +116,45 @@ parent model (`song`).
|
|
96
116
|
|
97
117
|
You can define a default value by using the `:default` option:
|
98
118
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
119
|
+
```rb
|
120
|
+
class Song < ActiveRecord::Base
|
121
|
+
assignable_values_for :genre, default: 'rock' do
|
122
|
+
['pop', 'rock', 'electronic']
|
123
|
+
end
|
124
|
+
end
|
125
|
+
```
|
104
126
|
|
105
127
|
The default is applied to new records:
|
106
|
-
|
107
|
-
|
128
|
+
```rb
|
129
|
+
Song.new.genre # => 'rock'
|
130
|
+
```
|
108
131
|
|
109
132
|
Defaults can be procs:
|
110
133
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
134
|
+
```rb
|
135
|
+
class Song < ActiveRecord::Base
|
136
|
+
assignable_values_for :year, default: proc { Date.today.year } do
|
137
|
+
1980 .. 2011
|
138
|
+
end
|
139
|
+
end
|
140
|
+
```
|
116
141
|
|
117
142
|
The proc will be evaluated in the context of the record instance.
|
118
143
|
|
119
144
|
You can also default a secondary default that is only set if the primary default value is not assignable:
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
145
|
+
```rb
|
146
|
+
class Song < ActiveRecord::Base
|
147
|
+
assignable_values_for :year, default: 1999, secondary_default: proc { Date.today.year } do
|
148
|
+
(Date.today.year - 2) .. Date.today.year
|
149
|
+
end
|
150
|
+
end
|
151
|
+
```
|
126
152
|
|
127
153
|
If called in 2013 the code above will fall back to:
|
128
154
|
|
129
|
-
|
130
|
-
|
155
|
+
```rb
|
156
|
+
Song.new.year # => 2013
|
157
|
+
```
|
131
158
|
|
132
159
|
### Allowing blank values
|
133
160
|
|
@@ -136,11 +163,13 @@ will get a validation error.
|
|
136
163
|
|
137
164
|
If you would like to change this behavior and allow blank values to be valid, use the `:allow_blank` option:
|
138
165
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
166
|
+
```rb
|
167
|
+
class Song < ActiveRecord::Base
|
168
|
+
assignable_values_for :genre, default: 'rock', allow_blank: true do
|
169
|
+
['pop', 'rock', 'electronic']
|
170
|
+
end
|
171
|
+
end
|
172
|
+
```
|
144
173
|
|
145
174
|
The `:allow_blank` option can be a symbol, in which case a method of that name will be called on the record.
|
146
175
|
|
@@ -151,34 +180,44 @@ The `:allow_blank` option can also be a proc, in which case the proc will be cal
|
|
151
180
|
|
152
181
|
Values are only validated when they change. This is useful when the list of assignable values can change during runtime:
|
153
182
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
183
|
+
```rb
|
184
|
+
class Song < ActiveRecord::Base
|
185
|
+
assignable_values_for :year do
|
186
|
+
(Date.today.year - 2) .. Date.today.year
|
187
|
+
end
|
188
|
+
end
|
189
|
+
```
|
159
190
|
|
160
191
|
If a value has been saved before, it will remain valid, even if it is no longer assignable:
|
161
192
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
193
|
+
```rb
|
194
|
+
Song.update_all(year: 1985) # update all records with a value that is no longer valid
|
195
|
+
song = Song.last
|
196
|
+
song.year # => 1985
|
197
|
+
song.valid? # => true
|
198
|
+
```
|
166
199
|
|
167
200
|
It will also be returned when obtaining the list of assignable values:
|
168
201
|
|
169
|
-
|
202
|
+
```rb
|
203
|
+
song.assignable_years # => [2010, 2011, 2012, 1985]
|
204
|
+
```
|
170
205
|
|
171
206
|
However, if you want only those values that are actually intended to be assignable, e.g. when updating a `<select>` via AJAX, pass an option:
|
172
207
|
|
173
|
-
|
208
|
+
```rb
|
209
|
+
song.assignable_years(include_old_value: false) # => [2010, 2011, 2012]
|
210
|
+
```
|
174
211
|
|
175
212
|
Once a changed value has been saved, the previous value disappears from the list of assignable values:
|
176
213
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
214
|
+
```rb
|
215
|
+
song.year = '2010'
|
216
|
+
song.save!
|
217
|
+
song.assignable_years # => [2010, 2011, 2012]
|
218
|
+
song.year = 1985
|
219
|
+
song.valid? # => false
|
220
|
+
```
|
182
221
|
|
183
222
|
This is to prevent records from becoming invalid as the list of assignable values evolves. This also prevents `<select>` menus with blank selections when opening an old record in a web form.
|
184
223
|
|
@@ -188,7 +227,7 @@ Assignable values can also be used for array values. This works when you use Rai
|
|
188
227
|
|
189
228
|
To validate array values, pass `multiple: true`:
|
190
229
|
|
191
|
-
```
|
230
|
+
```rb
|
192
231
|
class Song < ActiveRecord::Base
|
193
232
|
serialize :genres # skip this when you use PostgreSQL and an array type column
|
194
233
|
|
@@ -202,7 +241,7 @@ In this case, every *subset* of the given values is valid, for example `['pop',
|
|
202
241
|
|
203
242
|
For humanization, you can still use
|
204
243
|
|
205
|
-
```
|
244
|
+
```rb
|
206
245
|
song.humanized_genre('pop') # => "Pop music"
|
207
246
|
song.humanized_assignable_genres.last.humanized # => "Electronic music"
|
208
247
|
```
|
@@ -213,30 +252,34 @@ Restricting belongs_to associations
|
|
213
252
|
|
214
253
|
You can restrict `belongs_to` associations in the same manner as scalar attributes:
|
215
254
|
|
216
|
-
|
255
|
+
```rb
|
256
|
+
class Song < ActiveRecord::Base
|
217
257
|
|
218
|
-
|
258
|
+
belongs_to :artist
|
219
259
|
|
220
|
-
|
221
|
-
|
222
|
-
|
260
|
+
assignable_values_for :artist do
|
261
|
+
Artist.where(signed: true)
|
262
|
+
end
|
223
263
|
|
224
|
-
|
264
|
+
end
|
265
|
+
```
|
225
266
|
|
226
267
|
Listing and validating also works the same:
|
227
268
|
|
228
|
-
|
229
|
-
|
269
|
+
```rb
|
270
|
+
chicane = Artist.create!(name: 'Chicane', signed: true)
|
271
|
+
lt2 = Artist.create!(name: 'LT2', signed: false)
|
230
272
|
|
231
|
-
|
273
|
+
song = Song.new
|
232
274
|
|
233
|
-
|
275
|
+
song.assignable_artists # => [#<Artist id: 1, name: "Chicane">]
|
234
276
|
|
235
|
-
|
236
|
-
|
277
|
+
song.artist = chicane
|
278
|
+
song.valid? # => true
|
237
279
|
|
238
|
-
|
239
|
-
|
280
|
+
song.artist = lt2
|
281
|
+
song.valid? # => false
|
282
|
+
```
|
240
283
|
|
241
284
|
Similiar to scalar attributes, associations are only validated when the foreign key (`artist_id` in the example above) changes.
|
242
285
|
Values stored in the database will remain assignable until they are changed, and you can query actually assignable values with `song.assignable_artists(include_old_value: false)`.
|
@@ -249,18 +292,20 @@ How assignable values are evaluated
|
|
249
292
|
|
250
293
|
The list of assignable values is generated at runtime. Since the given block is evaluated on the record instance, so you can refer to other methods:
|
251
294
|
|
252
|
-
|
295
|
+
```rb
|
296
|
+
class Song < ActiveRecord::Base
|
253
297
|
|
254
|
-
|
298
|
+
validates_numericality_of :year
|
255
299
|
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
300
|
+
assignable_values_for :genre do
|
301
|
+
genres = []
|
302
|
+
genres << 'jazz' if year > 1900
|
303
|
+
genres << 'rock' if year > 1960
|
304
|
+
genres
|
305
|
+
end
|
262
306
|
|
263
|
-
|
307
|
+
end
|
308
|
+
```
|
264
309
|
|
265
310
|
|
266
311
|
Obtaining assignable values from another source
|
@@ -270,54 +315,62 @@ The list of assignable values can be provided by any object that is accessible f
|
|
270
315
|
|
271
316
|
You can define the source of assignable values by setting the `:through` option to a proc:
|
272
317
|
|
273
|
-
|
274
|
-
|
275
|
-
|
318
|
+
```rb
|
319
|
+
class Story < ActiveRecord::Base
|
320
|
+
assignable_values_for :state, through: proc { Power.current }
|
321
|
+
end
|
322
|
+
```
|
276
323
|
|
277
324
|
`Power.current` must now respond to a method `assignable_story_states` or `assignable_story_states(story)` which returns an `Enumerable` of state strings:
|
278
325
|
|
279
|
-
|
326
|
+
```rb
|
327
|
+
class Power
|
280
328
|
|
281
|
-
|
329
|
+
cattr_accessor :current
|
282
330
|
|
283
|
-
|
284
|
-
|
285
|
-
|
331
|
+
def initialize(role)
|
332
|
+
@role = role
|
333
|
+
end
|
286
334
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
335
|
+
def assignable_story_states(story)
|
336
|
+
states = ['draft', 'pending']
|
337
|
+
states << 'accepted' if @role == :admin
|
338
|
+
states
|
339
|
+
end
|
292
340
|
|
293
|
-
|
341
|
+
end
|
342
|
+
```
|
294
343
|
|
295
344
|
Listing and validating works the same with delegation:
|
345
|
+
```rb
|
346
|
+
story = Story.new(state: 'accepted')
|
296
347
|
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
story.assignable_states # => ['draft', 'pending']
|
301
|
-
story.valid? # => false
|
348
|
+
Power.current = Power.new(:guest)
|
349
|
+
story.assignable_states # => ['draft', 'pending']
|
350
|
+
story.valid? # => false
|
302
351
|
|
303
|
-
|
304
|
-
|
305
|
-
|
352
|
+
Power.current = Power.new(:admin)
|
353
|
+
story.assignable_states # => ['draft', 'pending', 'accepted']
|
354
|
+
story.valid? # => true
|
355
|
+
```
|
306
356
|
|
307
357
|
Note that delegated validation is skipped when the delegate is `nil`. This way your model remains usable when there is no authorization context, like in batch processes or the console:
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
358
|
+
```rb
|
359
|
+
story = Story.new(state: 'foo')
|
360
|
+
Power.current = nil
|
361
|
+
story.valid? # => true
|
362
|
+
```
|
312
363
|
|
313
364
|
Think of this as enabling an optional authorization layer on top of your model validations, which can be switched on or off depending on the current context.
|
314
365
|
|
315
366
|
Instead of a proc you can also use the `:through` option to name an instance method:
|
316
367
|
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
368
|
+
```rb
|
369
|
+
class Story < ActiveRecord::Base
|
370
|
+
attr_accessor :power
|
371
|
+
assignable_values_for :state, through: :power
|
372
|
+
end
|
373
|
+
```
|
321
374
|
|
322
375
|
|
323
376
|
### Obtaining assignable values from a Consul power
|
@@ -326,28 +379,34 @@ A common use case for the `:through` option is when there is some globally acces
|
|
326
379
|
|
327
380
|
If you are using [Consul](https://github.com/makandra/consul), you will get a lot of this plumbing for free. Consul gives you a macro `current_power` to instantiate a so called "power", which describes what the current user may access:
|
328
381
|
|
329
|
-
|
330
|
-
|
382
|
+
```rb
|
383
|
+
class ApplicationController < ActionController::Base
|
384
|
+
include Consul::Controller
|
331
385
|
|
332
|
-
|
333
|
-
|
334
|
-
|
386
|
+
current_power do
|
387
|
+
Power.new(current_user)
|
388
|
+
end
|
335
389
|
|
336
|
-
|
390
|
+
end
|
391
|
+
```
|
337
392
|
|
338
393
|
The code above will provide you with a helper method `current_power` for your controller and views. Everywhere else, you can simply access it from `Power.current`.
|
339
394
|
|
340
395
|
You can now delegate validation of assignable values to the current power by saying:
|
341
396
|
|
342
|
-
|
343
|
-
|
344
|
-
|
397
|
+
```rb
|
398
|
+
class Story < ActiveRecord::Base
|
399
|
+
authorize_values_for :state
|
400
|
+
end
|
401
|
+
```
|
345
402
|
|
346
403
|
This is a shortcut for saying:
|
347
404
|
|
348
|
-
|
349
|
-
|
350
|
-
|
405
|
+
```rb
|
406
|
+
class Story < ActiveRecord::Base
|
407
|
+
assignable_values_for :state, through: proc { Power.current }
|
408
|
+
end
|
409
|
+
```
|
351
410
|
|
352
411
|
Head over to the [Consul README](https://github.com/makandra/consul) for details.
|
353
412
|
|
@@ -356,8 +415,9 @@ Installation
|
|
356
415
|
------------
|
357
416
|
|
358
417
|
Put this into your `Gemfile`:
|
359
|
-
|
360
|
-
|
418
|
+
```rb
|
419
|
+
gem 'assignable_values'
|
420
|
+
```
|
361
421
|
|
362
422
|
Now run `bundle install` and restart your server. Done.
|
363
423
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: assignable_values
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Henning Koch
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-02-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -90,7 +90,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
90
90
|
- !ruby/object:Gem::Version
|
91
91
|
version: '0'
|
92
92
|
requirements: []
|
93
|
-
rubygems_version: 3.4.
|
93
|
+
rubygems_version: 3.4.13
|
94
94
|
signing_key:
|
95
95
|
specification_version: 4
|
96
96
|
summary: Restrict the values assignable to ActiveRecord attributes or associations
|