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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 933681c08828de5d892b6d4a200a6376b0de7b700c55bc824124f1b0114501fc
4
- data.tar.gz: 1e1fa1a8bb6999ca7da4bf1d91d9ed1163e4770904c0f8eeb8c63b3f6c207575
3
+ metadata.gz: 974063fe5e304e09553d556f1eae415a7908f16bc026e713e811cb8180bc7510
4
+ data.tar.gz: 5754483e8d63baace26522a6e7885c750e0010a21ee87ce795421a917e0823d6
5
5
  SHA512:
6
- metadata.gz: be6eb8de9809e1398ea1e4e13256a2118acc319a306ec144c9b89723c4ed2c677a83c98898844f0a899c4144f9b85de8e9a9d9ea0110d5b70c5e366862db82e1
7
- data.tar.gz: 38dc638bc04ae0bcb3317e3886f22aacb2c784e9909a316e2d734795f4da40d7faaee6e931451daa42ac40975350eb5e27e6c9c192104238e20872a270da6972
6
+ metadata.gz: 13628f9ab3a4fbe96db3a2accfd2be3d00ac711994ece3694c3e3e3d15cf46c94def5e6f53270bd1b9242eb6bae40381b545c70ca68f62bc6d032415107d3918
7
+ data.tar.gz: 30833906903fb6291aad2157615631e76880a3df3d6342a057fc9993b021caa4d49173c13910aec5349b98d0af4647087fc4e18b26a7c7caba4a34b236cb9c81
data/CHANGELOG.md CHANGED
@@ -12,7 +12,9 @@ This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html
12
12
 
13
13
  -
14
14
 
15
+ ## 1.0.0 - 2024-02-14
15
16
 
17
+ - This gem has been in productive use for 12 years now, time for a 1.0 release! 🥳
16
18
 
17
19
  ## 0.18.1 - 2023-09-06
18
20
 
data/Gemfile.5.0.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- assignable_values (0.18.1)
4
+ assignable_values (1.0.0)
5
5
  activerecord (>= 2.3)
6
6
 
7
7
  GEM
data/Gemfile.5.1.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- assignable_values (0.18.1)
4
+ assignable_values (1.0.0)
5
5
  activerecord (>= 2.3)
6
6
 
7
7
  GEM
data/Gemfile.5.1.pg.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- assignable_values (0.18.1)
4
+ assignable_values (1.0.0)
5
5
  activerecord (>= 2.3)
6
6
 
7
7
  GEM
data/Gemfile.6.1.pg.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- assignable_values (0.18.1)
4
+ assignable_values (1.0.0)
5
5
  activerecord (>= 2.3)
6
6
 
7
7
  GEM
data/Gemfile.7.0.pg.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- assignable_values (0.18.1)
4
+ assignable_values (1.0.0)
5
5
  activerecord (>= 2.3)
6
6
 
7
7
  GEM
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
- class Song < ActiveRecord::Base
17
- assignable_values_for :genre do
18
- ['pop', 'rock', 'electronic']
19
- end
20
- end
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
- Song.new(genre: 'rock').valid? # => true
25
- Song.new(genre: 'elephant').valid? # => false
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
- song.assignable_genres # => ['pop', 'rock', 'electronic']
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
- form.select :genre, form.object.assignable_genres
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
- en:
49
- assignable_values:
50
- song:
51
- genre:
52
- pop: 'Pop music'
53
- rock: 'Rock music'
54
- electronic: 'Electronic music'
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
- song = Song.new(:genre => 'pop')
59
- song.humanized_genre # => 'Pop music'
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
- song.humanized_genre('rock') # => 'Rock music'
64
- Song.humanized_genre('rock') # => 'Rock music'
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
- song.humanized_assignable_genres.size # => 3
69
- song.humanized_assignable_genres.first.value # => "pop"
70
- song.humanized_assignable_genres.first.humanized # => "Pop music"
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
- form.collection_select :genre, form.object.humanized_assignable_genres, :value, :humanized
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
- class FunnySong < Song
81
- ...
82
- end
96
+ ```rb
97
+ class FunnySong < Song
98
+ ...
99
+ end
100
+ ```
83
101
 
84
- en:
85
- assignable_values:
86
- funny_song:
87
- genre:
88
- pop: 'The stuff you hear on mainstream radio all day long'
89
- rock: 'A lot of electric guitars and drums'
90
- electronic: 'Whatever David Guetta does'
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
- class Song < ActiveRecord::Base
100
- assignable_values_for :genre, default: 'rock' do
101
- ['pop', 'rock', 'electronic']
102
- end
103
- end
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
- Song.new.genre # => 'rock'
128
+ ```rb
129
+ Song.new.genre # => 'rock'
130
+ ```
108
131
 
109
132
  Defaults can be procs:
110
133
 
111
- class Song < ActiveRecord::Base
112
- assignable_values_for :year, default: proc { Date.today.year } do
113
- 1980 .. 2011
114
- end
115
- end
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
- class Song < ActiveRecord::Base
122
- assignable_values_for :year, default: 1999, secondary_default: proc { Date.today.year } do
123
- (Date.today.year - 2) .. Date.today.year
124
- end
125
- end
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
- Song.new.year # => 2013
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
- class Song < ActiveRecord::Base
140
- assignable_values_for :genre, default: 'rock', allow_blank: true do
141
- ['pop', 'rock', 'electronic']
142
- end
143
- end
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
- class Song < ActiveRecord::Base
155
- assignable_values_for :year do
156
- (Date.today.year - 2) .. Date.today.year
157
- end
158
- end
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
- Song.update_all(year: 1985) # update all records with a value that is no longer valid
163
- song = Song.last
164
- song.year # => 1985
165
- song.valid? # => true
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
- song.assignable_years # => [2010, 2011, 2012, 1985]
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
- song.assignable_years(include_old_value: false) # => [2010, 2011, 2012]
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
- song.year = '2010'
178
- song.save!
179
- song.assignable_years # => [2010, 2011, 2012]
180
- song.year = 1985
181
- song.valid? # => false
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
- class Song < ActiveRecord::Base
255
+ ```rb
256
+ class Song < ActiveRecord::Base
217
257
 
218
- belongs_to :artist
258
+ belongs_to :artist
219
259
 
220
- assignable_values_for :artist do
221
- Artist.where(signed: true)
222
- end
260
+ assignable_values_for :artist do
261
+ Artist.where(signed: true)
262
+ end
223
263
 
224
- end
264
+ end
265
+ ```
225
266
 
226
267
  Listing and validating also works the same:
227
268
 
228
- chicane = Artist.create!(name: 'Chicane', signed: true)
229
- lt2 = Artist.create!(name: 'LT2', signed: false)
269
+ ```rb
270
+ chicane = Artist.create!(name: 'Chicane', signed: true)
271
+ lt2 = Artist.create!(name: 'LT2', signed: false)
230
272
 
231
- song = Song.new
273
+ song = Song.new
232
274
 
233
- song.assignable_artists # => [#<Artist id: 1, name: "Chicane">]
275
+ song.assignable_artists # => [#<Artist id: 1, name: "Chicane">]
234
276
 
235
- song.artist = chicane
236
- song.valid? # => true
277
+ song.artist = chicane
278
+ song.valid? # => true
237
279
 
238
- song.artist = lt2
239
- song.valid? # => false
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
- class Song < ActiveRecord::Base
295
+ ```rb
296
+ class Song < ActiveRecord::Base
253
297
 
254
- validates_numericality_of :year
298
+ validates_numericality_of :year
255
299
 
256
- assignable_values_for :genre do
257
- genres = []
258
- genres << 'jazz' if year > 1900
259
- genres << 'rock' if year > 1960
260
- genres
261
- end
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
- end
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
- class Story < ActiveRecord::Base
274
- assignable_values_for :state, through: proc { Power.current }
275
- end
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
- class Power
326
+ ```rb
327
+ class Power
280
328
 
281
- cattr_accessor :current
329
+ cattr_accessor :current
282
330
 
283
- def initialize(role)
284
- @role = role
285
- end
331
+ def initialize(role)
332
+ @role = role
333
+ end
286
334
 
287
- def assignable_story_states(story)
288
- states = ['draft', 'pending']
289
- states << 'accepted' if @role == :admin
290
- states
291
- end
335
+ def assignable_story_states(story)
336
+ states = ['draft', 'pending']
337
+ states << 'accepted' if @role == :admin
338
+ states
339
+ end
292
340
 
293
- end
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
- story = Story.new(state: 'accepted')
298
-
299
- Power.current = Power.new(:guest)
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
- Power.current = Power.new(:admin)
304
- story.assignable_states # => ['draft', 'pending', 'accepted']
305
- story.valid? # => true
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
- story = Story.new(state: 'foo')
310
- Power.current = nil
311
- story.valid? # => true
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
- class Story < ActiveRecord::Base
318
- attr_accessor :power
319
- assignable_values_for :state, through: :power
320
- end
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
- class ApplicationController < ActionController::Base
330
- include Consul::Controller
382
+ ```rb
383
+ class ApplicationController < ActionController::Base
384
+ include Consul::Controller
331
385
 
332
- current_power do
333
- Power.new(current_user)
334
- end
386
+ current_power do
387
+ Power.new(current_user)
388
+ end
335
389
 
336
- end
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
- class Story < ActiveRecord::Base
343
- authorize_values_for :state
344
- end
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
- class Story < ActiveRecord::Base
349
- assignable_values_for :state, through: proc { Power.current }
350
- end
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
- gem 'assignable_values'
418
+ ```rb
419
+ gem 'assignable_values'
420
+ ```
361
421
 
362
422
  Now run `bundle install` and restart your server. Done.
363
423
 
@@ -1,3 +1,3 @@
1
1
  module AssignableValues
2
- VERSION = '0.18.1'
2
+ VERSION = '1.0.0'
3
3
  end
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.18.1
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: 2023-09-06 00:00:00.000000000 Z
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.14
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