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.
Files changed (3) hide show
  1. data/README.md +192 -38
  2. data/lib/assignable_values/version.rb +1 -1
  3. metadata +3 -3
data/README.md CHANGED
@@ -1,10 +1,13 @@
1
- # assignable_values - Enums on vitamins
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
- ## Restricting attributes
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
- ### How assignable values are generated
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
- You can ask a record for a list of values that can be assigned to attribute:
177
+ Obtaining assignable values from a delegate
178
+ -------------------------------------------
44
179
 
45
- song.assignable_genres # => ['pop', 'rock', 'electronic']
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
- This is useful for populating &lt;select&gt; tags in web forms:
182
+ class Song < ActiveRecord::Base
183
+ assignable_values_for :state, :through => lambda { Power.current }
184
+ end
48
185
 
49
- form.select :genre, form.object.assignable_genres
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
- ### Humanized labels
188
+ class Power
52
189
 
53
- You will often want to present internal values in a humanized form. E.g. `"pop"` should be presented as `"Pop music"`.
190
+ cattr_accessor :current
54
191
 
55
- You can define human labels in your I18n dictionary:
192
+ def initialize(role)
193
+ @role = role
194
+ end
56
195
 
57
- en:
58
- assignable_values:
59
- song:
60
- genre:
61
- pop: 'Pop music'
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
- song.assignable_genres.first # => 'pop'
68
- song.assignable_genres.first.humanized # => 'Pop music'
202
+ end
69
203
 
70
- You can populate a &lt;select&gt; tag with pairs of internal values and human labels like this:
204
+ Listing and validating works the same with delegation:
71
205
 
72
- form.collection_select :genre, form.object.assignable_genres, :to_s, :humanized
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
- ## Restricting belongs_to associations
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
- ## Defining default values
230
+ Installation
231
+ ------------
79
232
 
80
- ## Obtaining assignable values from a delegate
233
+ Put this into your `Gemfile`:
81
234
 
82
- Text here.
235
+ gem 'assignable_values'
83
236
 
84
- ## Previously saved values
237
+ Now run `bundle install` and restart your server. Done.
85
238
 
86
- - Always valid
87
- - Are listed
88
239
 
89
- ## Installation
240
+ Development
241
+ -----------
90
242
 
91
- Text here.
243
+ - Fork the repository.
244
+ - Push your changes with specs.
245
+ - Send me a pull request.
92
246
 
93
- ## Development
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
- ## Credits
250
+ Credits
251
+ -------
98
252
 
99
- Text here.
253
+ [Henning Koch](henning.koch@makandra.de) from [makandra](http://makandra.com/).
@@ -1,3 +1,3 @@
1
1
  module AssignableValues
2
- VERSION = '0.1.1'
2
+ VERSION = '0.1.2'
3
3
  end
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: 25
4
+ hash: 31
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 1
10
- version: 0.1.1
9
+ - 2
10
+ version: 0.1.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Henning Koch