assignable_values 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +192 -38
- data/lib/assignable_values/version.rb +1 -1
- metadata +3 -3
data/README.md
CHANGED
@@ -1,10 +1,13 @@
|
|
1
|
-
|
1
|
+
assignable_values - Enums on vitamins
|
2
|
+
=====================================
|
2
3
|
|
3
4
|
`assignable_values` lets you restrict the values that can be assigned to attributes or associations of ActiveRecord models. You can think of it as enums where the list of allowed values is generated at runtime and the value is checked during validation.
|
4
5
|
|
5
6
|
We carefully enhanced the cure enum functionality with small tweaks that are useful for web forms, internationalized applications and common authorization patterns.
|
6
7
|
|
7
|
-
|
8
|
+
|
9
|
+
Restricting scalar attributes
|
10
|
+
-----------------------------
|
8
11
|
|
9
12
|
The basic usage to restrict the values assignable to strings, integers, etc. is this:
|
10
13
|
|
@@ -21,7 +24,139 @@ The assigned value is checked during validation:
|
|
21
24
|
|
22
25
|
The validation error message is the same as the one from `validates_inclusion_of` (`errors.messages.inclusion` in your I18n dictionary).
|
23
26
|
|
24
|
-
|
27
|
+
|
28
|
+
### Listing assignable values
|
29
|
+
|
30
|
+
You can ask a record for a list of values that can be assigned to an attribute:
|
31
|
+
|
32
|
+
song.assignable_genres # => ['pop', 'rock', 'electronic']
|
33
|
+
|
34
|
+
This is useful for populating `<select>` tags in web forms:
|
35
|
+
|
36
|
+
form.select :genre, form.object.assignable_genres
|
37
|
+
|
38
|
+
|
39
|
+
### Humanized labels
|
40
|
+
|
41
|
+
You will often want to present internal values in a humanized form. E.g. `"pop"` should be presented as `"Pop music"`.
|
42
|
+
|
43
|
+
You can define human labels in your I18n dictionary:
|
44
|
+
|
45
|
+
en:
|
46
|
+
assignable_values:
|
47
|
+
song:
|
48
|
+
genre:
|
49
|
+
pop: 'Pop music'
|
50
|
+
rock: 'Rock music'
|
51
|
+
electronic: 'Electronic music'
|
52
|
+
|
53
|
+
You can access the humanized version for the current value like this:
|
54
|
+
|
55
|
+
song = Song.new(:genre => 'pop')
|
56
|
+
song.humanized_genre # => 'Pop music'
|
57
|
+
|
58
|
+
When obtaining a list of assignable values, each value will have a method `#humanized` that returns the translation:
|
59
|
+
|
60
|
+
song.assignable_genres.first # => "pop"
|
61
|
+
song.assignable_genres.first.humanized # => "Pop music"
|
62
|
+
|
63
|
+
You can populate a `<select>` tag with pairs of internal values and human labels like this:
|
64
|
+
|
65
|
+
form.collection_select :genre, form.object.assignable_genres, :to_s, :humanized
|
66
|
+
|
67
|
+
|
68
|
+
### Defining default values
|
69
|
+
|
70
|
+
You can define a default value by using the `:default` option:
|
71
|
+
|
72
|
+
class Song < ActiveRecord::Base
|
73
|
+
assignable_values_for :genre, :default => 'rock' do
|
74
|
+
['pop', 'rock', 'electronic']
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
The default is applied to new records:
|
79
|
+
|
80
|
+
Song.new.genre # => 'rock'
|
81
|
+
|
82
|
+
Defaults can be lambdas:
|
83
|
+
|
84
|
+
class Song < ActiveRecord::Base
|
85
|
+
assignable_values_for :genre, :default => lambda { Date.today.year } do
|
86
|
+
1980 .. 2011
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
The lambda will be evaluated in the context of the record instance.
|
91
|
+
|
92
|
+
|
93
|
+
### Values are only validated when they change
|
94
|
+
|
95
|
+
Values are only validated when they change. This is useful when the list of assignable values can change during runtime:
|
96
|
+
|
97
|
+
class Song < ActiveRecord::Base
|
98
|
+
assignable_values_for :year do
|
99
|
+
(Date.today.year - 2) .. Date.today.year
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
If a value has been saved before, it will remain valid, even if it is no longer assignable:
|
104
|
+
|
105
|
+
Song.update_all(:year => 1985) # update all records with a value that is no longer valid
|
106
|
+
song = Song.last
|
107
|
+
song.year # => 1985
|
108
|
+
song.valid? # => true
|
109
|
+
|
110
|
+
It will also be returned when obtaining the list of assignable values:
|
111
|
+
|
112
|
+
song.assignable_genres # => [2010, 2011, 2012, 1985]
|
113
|
+
|
114
|
+
Once a changed value has been saved, the previous value disappears from the list of assignable values:
|
115
|
+
|
116
|
+
song.genre = 'pop'
|
117
|
+
song.save!
|
118
|
+
song.assignable_years # => [2010, 2011, 2012]
|
119
|
+
song.year = 1985
|
120
|
+
song.valid? # => false
|
121
|
+
|
122
|
+
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.
|
123
|
+
|
124
|
+
|
125
|
+
Restricting belongs_to associations
|
126
|
+
-----------------------------------
|
127
|
+
|
128
|
+
You can restrict `belongs_to` associations in the same manner as scalar attributes:
|
129
|
+
|
130
|
+
class Song
|
131
|
+
|
132
|
+
belongs_to :artist
|
133
|
+
|
134
|
+
assignable_values_for :artist do
|
135
|
+
Artist.where(:signed => true)
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
Listing and validating als works the same:
|
141
|
+
|
142
|
+
chicane = Artist.create!(:name => 'Chicane', :signed => true)
|
143
|
+
lt2 = Artist.create!(:name => 'LT2', :signed => false)
|
144
|
+
|
145
|
+
song = Song.new
|
146
|
+
|
147
|
+
song.assignable_artists # => [#<Artist id: 1, name: "Chicane">]
|
148
|
+
|
149
|
+
song.artist = chicane
|
150
|
+
song.valid? # => true
|
151
|
+
|
152
|
+
song.artist = lt2
|
153
|
+
song.valid? # => false
|
154
|
+
|
155
|
+
Similiar to scalar attributes, associations are only validated when the foreign key (`artist_id` in the example) changes. Previously saved values will remain assignable until another association has been saved.
|
156
|
+
|
157
|
+
|
158
|
+
How assignable values are evaluated
|
159
|
+
-----------------------------------
|
25
160
|
|
26
161
|
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:
|
27
162
|
|
@@ -38,62 +173,81 @@ The list of assignable values is generated at runtime. Since the given block is
|
|
38
173
|
|
39
174
|
end
|
40
175
|
|
41
|
-
### Obtaining lists
|
42
176
|
|
43
|
-
|
177
|
+
Obtaining assignable values from a delegate
|
178
|
+
-------------------------------------------
|
44
179
|
|
45
|
-
|
180
|
+
The list of assignable values can be provided by a delegate. This is useful for authorization scenarios like [Consul](https://github.com/makandra/consul) or [CanCan](https://github.com/ryanb/cancan), where permissions are defined in a single class.
|
46
181
|
|
47
|
-
|
182
|
+
class Song < ActiveRecord::Base
|
183
|
+
assignable_values_for :state, :through => lambda { Power.current }
|
184
|
+
end
|
48
185
|
|
49
|
-
|
186
|
+
`Power.current` must now respond to a method `assignable_song_states` or `assignable_song_states(song)` which returns an `Enumerable` of state strings:
|
50
187
|
|
51
|
-
|
188
|
+
class Power
|
52
189
|
|
53
|
-
|
190
|
+
cattr_accessor :current
|
54
191
|
|
55
|
-
|
192
|
+
def initialize(role)
|
193
|
+
@role = role
|
194
|
+
end
|
56
195
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
rock: 'Rock music'
|
63
|
-
electronic: 'Electronic music'
|
64
|
-
|
65
|
-
When obtaining a list of assignable values, each value will have a method `#human` that returns the translation:
|
196
|
+
def assignable_song_states(song)
|
197
|
+
states = ['draft', 'pending']
|
198
|
+
states << 'accepted' if @role == :admin
|
199
|
+
states
|
200
|
+
end
|
66
201
|
|
67
|
-
|
68
|
-
song.assignable_genres.first.humanized # => 'Pop music'
|
202
|
+
end
|
69
203
|
|
70
|
-
|
204
|
+
Listing and validating works the same with delegation:
|
71
205
|
|
72
|
-
|
206
|
+
song = Song.new(:state => 'accepted')
|
207
|
+
|
208
|
+
Power.current = Power.new(:guest)
|
209
|
+
song.assignable_states # => ['draft', 'pending']
|
210
|
+
song.valid? # => false
|
73
211
|
|
74
|
-
|
212
|
+
Power.current = Power.new(:admin)
|
213
|
+
song.assignable_states # => ['draft', 'pending', 'accepted']
|
214
|
+
song.valid? # => true
|
215
|
+
|
216
|
+
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:
|
217
|
+
|
218
|
+
song = Song.new(:state => 'foo')
|
219
|
+
Power.current = nil
|
220
|
+
song.valid? # => true
|
221
|
+
|
222
|
+
Instead of a lambda you can also use the `:through` option to name an instance method:
|
223
|
+
|
224
|
+
class Song < ActiveRecord::Base
|
225
|
+
attr_accessor :power
|
226
|
+
assignable_values_for :state, :through => :power
|
227
|
+
end
|
75
228
|
|
76
|
-
### No caching
|
77
229
|
|
78
|
-
|
230
|
+
Installation
|
231
|
+
------------
|
79
232
|
|
80
|
-
|
233
|
+
Put this into your `Gemfile`:
|
81
234
|
|
82
|
-
|
235
|
+
gem 'assignable_values'
|
83
236
|
|
84
|
-
|
237
|
+
Now run `bundle install` and restart your server. Done.
|
85
238
|
|
86
|
-
- Always valid
|
87
|
-
- Are listed
|
88
239
|
|
89
|
-
|
240
|
+
Development
|
241
|
+
-----------
|
90
242
|
|
91
|
-
|
243
|
+
- Fork the repository.
|
244
|
+
- Push your changes with specs.
|
245
|
+
- Send me a pull request.
|
92
246
|
|
93
|
-
|
247
|
+
I'm very eager to keep this gem leightweight and on topic. If you're unsure whether a change would make it into the gem, [talk to me beforehand](henning.koch@makandra.de).
|
94
248
|
|
95
|
-
Text here.
|
96
249
|
|
97
|
-
|
250
|
+
Credits
|
251
|
+
-------
|
98
252
|
|
99
|
-
|
253
|
+
[Henning Koch](henning.koch@makandra.de) from [makandra](http://makandra.com/).
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: assignable_values
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 31
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 2
|
10
|
+
version: 0.1.2
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Henning Koch
|