default_value_for 0.1.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.
- data/.gitignore +1 -0
- data/LICENSE.TXT +19 -0
- data/README.rdoc +421 -0
- data/Rakefile +27 -0
- data/VERSION +1 -0
- data/init.rb +1 -0
- data/test.rb +279 -0
- metadata +62 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
test.sqlite3
|
data/LICENSE.TXT
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2008 Phusion
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,421 @@
|
|
1
|
+
= Introduction
|
2
|
+
|
3
|
+
The default_value_for plugin allows one to define default values for ActiveRecord
|
4
|
+
models in a declarative manner. For example:
|
5
|
+
|
6
|
+
class User < ActiveRecord::Base
|
7
|
+
default_value_for :name, "(no name)"
|
8
|
+
default_value_for :last_seen do
|
9
|
+
Time.now
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
u = User.new
|
14
|
+
u.name # => "(no name)"
|
15
|
+
u.last_seen # => Mon Sep 22 17:28:38 +0200 2008
|
16
|
+
|
17
|
+
*Note*: critics might be interested in the "When (not) to use default_value_for?"
|
18
|
+
section. Please read on.
|
19
|
+
|
20
|
+
|
21
|
+
== Installation
|
22
|
+
|
23
|
+
Install with:
|
24
|
+
|
25
|
+
./script/plugin install git://github.com/FooBarWidget/default_value_for.git
|
26
|
+
|
27
|
+
|
28
|
+
== The default_value_for method
|
29
|
+
|
30
|
+
The +default_value_for+ method is available in all ActiveRecord model classes.
|
31
|
+
|
32
|
+
The first argument is the name of the attribute for which a default value should
|
33
|
+
be set. This may either be a Symbol or a String.
|
34
|
+
|
35
|
+
The default value itself may either be passed as the second argument:
|
36
|
+
|
37
|
+
default_value_for :age, 20
|
38
|
+
|
39
|
+
...or it may be passed as the return value of a block:
|
40
|
+
|
41
|
+
default_value_for :age do
|
42
|
+
if today_is_sunday?
|
43
|
+
20
|
44
|
+
else
|
45
|
+
30
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
If you pass a value argument, then the default value is static and never
|
50
|
+
changes. However, if you pass a block, then the default value is retrieved by
|
51
|
+
calling the block. This block is called not once, but every time a new record is
|
52
|
+
instantiated and default values need to be filled in.
|
53
|
+
|
54
|
+
The latter form is especially useful if your model has a UUID column. One can
|
55
|
+
generate a new, random UUID for every newly instantiated record:
|
56
|
+
|
57
|
+
class User < ActiveRecord::Base
|
58
|
+
default_value_for :uuid do
|
59
|
+
UuidGenerator.new.generate_uuid
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
User.new.uuid # => "51d6d6846f1d1b5c9a...."
|
64
|
+
User.new.uuid # => "ede292289e3484cb88...."
|
65
|
+
|
66
|
+
Note that record is passed to the block as an argument, in case you need it for
|
67
|
+
whatever reason:
|
68
|
+
|
69
|
+
class User < ActiveRecord::Base
|
70
|
+
default_value_for :uuid do |x|
|
71
|
+
x # <--- a User object
|
72
|
+
UuidGenerator.new.generate_uuid
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
== The default_values method
|
77
|
+
|
78
|
+
As a shortcut, you can use +default_values+ to set multiple default values at once.
|
79
|
+
|
80
|
+
default_values :age => 20
|
81
|
+
:uuid => lambda { UuidGenerator.new.generate_uuid }
|
82
|
+
|
83
|
+
The difference is purely aesthetic. If you have lots of default values which are constants or constructed with one-line blocks, +default_values+ may look nicer. If you have default values constructed by longer blocks, +default_value_for+ suit you better. Feel free to mix and match.
|
84
|
+
|
85
|
+
As a side note, due to specifics of Ruby's parser, you cannot say,
|
86
|
+
|
87
|
+
default_value :uuid { UuidGenerator.new.generate_uuid }
|
88
|
+
|
89
|
+
because it will not parse. This is in part the inspiration for the +default_values+ syntax.
|
90
|
+
|
91
|
+
== Rules
|
92
|
+
|
93
|
+
=== Instantiation of new record
|
94
|
+
|
95
|
+
Upon instantiating a new record, the declared default values are filled into
|
96
|
+
the record. You've already seen this in the above examples.
|
97
|
+
|
98
|
+
=== Retrieval of existing record
|
99
|
+
|
100
|
+
Upon retrieving an existing record, the declared default values are _not_
|
101
|
+
filled into the record. Consider the example with the UUID:
|
102
|
+
|
103
|
+
user = User.create
|
104
|
+
user.uuid # => "529c91b8bbd3e..."
|
105
|
+
|
106
|
+
user = User.find(user.id)
|
107
|
+
# UUID remains unchanged because it's retrieved from the database!
|
108
|
+
user.uuid # => "529c91b8bbd3e..."
|
109
|
+
|
110
|
+
=== Mass-assignment
|
111
|
+
|
112
|
+
If a certain attribute is being assigned via the model constructor's
|
113
|
+
mass-assignment argument, that the default value for that attribute will _not_
|
114
|
+
be filled in:
|
115
|
+
|
116
|
+
user = User.new(:uuid => "hello")
|
117
|
+
user.uuid # => "hello"
|
118
|
+
|
119
|
+
However, if that attribute is protected by +attr_protected+ or +attr_accessible+,
|
120
|
+
then it will be filled in:
|
121
|
+
|
122
|
+
class User < ActiveRecord::Base
|
123
|
+
default_value_for :name, 'Joe'
|
124
|
+
attr_protected :name
|
125
|
+
end
|
126
|
+
|
127
|
+
user = User.new(:name => "Jane")
|
128
|
+
user.name # => "Joe"
|
129
|
+
|
130
|
+
=== Inheritance
|
131
|
+
|
132
|
+
Inheritance works as expected. All default values are inherited by the child
|
133
|
+
class:
|
134
|
+
|
135
|
+
class User < ActiveRecord::Base
|
136
|
+
default_value_for :name, 'Joe'
|
137
|
+
end
|
138
|
+
|
139
|
+
class SuperUser < User
|
140
|
+
end
|
141
|
+
|
142
|
+
SuperUser.new.name # => "Joe"
|
143
|
+
|
144
|
+
=== Attributes that aren't database columns
|
145
|
+
|
146
|
+
+default_value_for+ also works with attributes that aren't database columns.
|
147
|
+
It works with anything for which there's an assignment method:
|
148
|
+
|
149
|
+
# Suppose that your 'users' table only has a 'name' column.
|
150
|
+
class User < ActiveRecord::Base
|
151
|
+
default_value_for :name, 'Joe'
|
152
|
+
default_value_for :age, 20
|
153
|
+
default_value_for :registering, true
|
154
|
+
|
155
|
+
attr_accessor :age
|
156
|
+
|
157
|
+
def registering=(value)
|
158
|
+
@registering = true
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
user = User.new
|
163
|
+
user.age # => 20
|
164
|
+
user.instance_variable_get('@registering') # => true
|
165
|
+
|
166
|
+
=== Default values are *not* duplicated
|
167
|
+
|
168
|
+
The given default values are *not* duplicated when they are filled in, so if
|
169
|
+
you mutate a value that was filled in with a default value, then it will affect
|
170
|
+
all subsequent default values:
|
171
|
+
|
172
|
+
class Author < ActiveRecord::Base
|
173
|
+
# This model only has a 'name' attribute.
|
174
|
+
end
|
175
|
+
|
176
|
+
class Book < ActiveRecord::Base
|
177
|
+
belongs_to :author
|
178
|
+
|
179
|
+
# By default, a Book belongs to a new, unsaved author.
|
180
|
+
default_value_for :author, Author.new
|
181
|
+
end
|
182
|
+
|
183
|
+
book1 = Book.new
|
184
|
+
book1.author.name # => nil
|
185
|
+
# This mutates the default value:
|
186
|
+
book1.author.name = "John"
|
187
|
+
|
188
|
+
book2 = Book.new
|
189
|
+
book2.author.name # => "John"
|
190
|
+
|
191
|
+
You can prevent this from happening by passing a block to +default_value_for+,
|
192
|
+
which returns a new object instance every time:
|
193
|
+
|
194
|
+
class Book < ActiveRecord::Base
|
195
|
+
belongs_to :author
|
196
|
+
|
197
|
+
default_value_for :author do
|
198
|
+
Author.new
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
book1 = Book.new
|
203
|
+
book1.author.name # => nil
|
204
|
+
book1.author.name = "John"
|
205
|
+
|
206
|
+
book2 = Book.new
|
207
|
+
book2.author.name # => nil
|
208
|
+
|
209
|
+
The main reason why default values are not duplicated is because not all
|
210
|
+
objects can be duplicated. For example, +Fixnum+ responds to +dup+, but calling
|
211
|
+
+dup+ on a Fixnum will raise an exception.
|
212
|
+
|
213
|
+
=== Caveats
|
214
|
+
|
215
|
+
A conflict can occur if your model class overrides the 'initialize' method,
|
216
|
+
because this plugin overrides 'initialize' as well to do its job.
|
217
|
+
|
218
|
+
class User < ActiveRecord::Base
|
219
|
+
def initialize # <-- this constructor causes problems
|
220
|
+
super(:name => 'Name cannot be changed in constructor')
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
We recommend you to alias chain your initialize method in models where you use
|
225
|
+
+default_value_for+:
|
226
|
+
|
227
|
+
class User < ActiveRecord::Base
|
228
|
+
default_value_for :age, 20
|
229
|
+
|
230
|
+
def initialize_with_my_app
|
231
|
+
initialize_without_my_app(:name => 'Name cannot be changed in constructor')
|
232
|
+
end
|
233
|
+
|
234
|
+
alias_method_chain :initialize, :my_app
|
235
|
+
end
|
236
|
+
|
237
|
+
Also, stick with the following rules:
|
238
|
+
- There is no need to +alias_method_chain+ your initialize method in models that
|
239
|
+
don't use +default_value_for+.
|
240
|
+
- Make sure that +alias_method_chain+ is called *after* the last
|
241
|
+
+default_value_for+ occurance.
|
242
|
+
|
243
|
+
|
244
|
+
== When (not) to use default_value_for?
|
245
|
+
|
246
|
+
You can also specify default values in the database schema. For example, you
|
247
|
+
can specify a default value in a migration as follows:
|
248
|
+
|
249
|
+
create_table :users do |t|
|
250
|
+
t.string :username, :null => false, :default => 'default username'
|
251
|
+
t.integer :age, :null => false, :default => 20
|
252
|
+
t.timestamp :last_seen, :null => false, :default => Time.now
|
253
|
+
end
|
254
|
+
|
255
|
+
This has the same effect as passing the default value as the second argument to
|
256
|
+
+default_value_for+:
|
257
|
+
|
258
|
+
user = User.new
|
259
|
+
user.username # => 'default username'
|
260
|
+
user.age # => 20
|
261
|
+
user.timestamp # => Mon Sep 22 18:31:47 +0200 2008
|
262
|
+
|
263
|
+
It's recommended that you use this over +default_value_for+ whenever possible.
|
264
|
+
|
265
|
+
However, it's not possible to specify a schema default for serialized columns.
|
266
|
+
With +default_value_for+, you can:
|
267
|
+
|
268
|
+
class User < ActiveRecord::Base
|
269
|
+
serialize :color
|
270
|
+
default_value_for :color, [255, 0, 0]
|
271
|
+
end
|
272
|
+
|
273
|
+
And if schema defaults don't provide the flexibility that you need, then
|
274
|
+
+default_value_for+ is the perfect choice. For example, with +default_value_for+
|
275
|
+
you could specify a per-environment default:
|
276
|
+
|
277
|
+
class User < ActiveRecord::Base
|
278
|
+
if RAILS_ENV == "development"
|
279
|
+
default_value_for :is_admin, true
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
Or, as you've seen in an earlier example, you can use +default_value_for+ to
|
284
|
+
generate a default random UUID:
|
285
|
+
|
286
|
+
class User < ActiveRecord::Base
|
287
|
+
default_value_for :uuid do
|
288
|
+
UuidGenerator.new.generate_uuid
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
Or you could use it to generate a timestamp that's relative to the time at which
|
293
|
+
the record is instantiated:
|
294
|
+
|
295
|
+
class User < ActiveRecord::Base
|
296
|
+
default_value_for :account_expires_at do
|
297
|
+
3.years.from_now
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
User.new.account_expires_at # => Mon Sep 22 18:43:42 +0200 2008
|
302
|
+
sleep(2)
|
303
|
+
User.new.account_expires_at # => Mon Sep 22 18:43:44 +0200 2008
|
304
|
+
|
305
|
+
Finally, it's also possible to specify a default via an association:
|
306
|
+
|
307
|
+
# Has columns: 'name' and 'default_price'
|
308
|
+
class SuperMarket < ActiveRecord::Base
|
309
|
+
has_many :products
|
310
|
+
end
|
311
|
+
|
312
|
+
# Has columns: 'name' and 'price'
|
313
|
+
class Product < ActiveRecord::Base
|
314
|
+
belongs_to :super_market
|
315
|
+
|
316
|
+
default_value_for :price do |product|
|
317
|
+
product.super_market.default_price
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
super_market = SuperMarket.create(:name => 'Albert Zwijn', :default_price => 100)
|
322
|
+
soap = super_market.products.create(:name => 'Soap')
|
323
|
+
soap.price # => 100
|
324
|
+
|
325
|
+
=== What about before_validate/before_save?
|
326
|
+
|
327
|
+
True, +before_validate+ and +before_save+ does what we want if we're only
|
328
|
+
interested in filling in a default before saving. However, if one wants to be
|
329
|
+
able to access the default value even before saving, then be prepared to write
|
330
|
+
a lot of code. Suppose that we want to be able to access a new record's UUID,
|
331
|
+
even before it's saved. We could end up with the following code:
|
332
|
+
|
333
|
+
# In the controller
|
334
|
+
def create
|
335
|
+
@user = User.new(params[:user])
|
336
|
+
@user.generate_uuid
|
337
|
+
email_report_to_admin("#{@user.username} with UUID #{@user.uuid} created.")
|
338
|
+
@user.save!
|
339
|
+
end
|
340
|
+
|
341
|
+
# Model
|
342
|
+
class User < ActiveRecord::Base
|
343
|
+
before_save :generate_uuid_if_necessary
|
344
|
+
|
345
|
+
def generate_uuid
|
346
|
+
self.uuid = ...
|
347
|
+
end
|
348
|
+
|
349
|
+
private
|
350
|
+
def generate_uuid_if_necessary
|
351
|
+
if uuid.blank?
|
352
|
+
generate_uuid
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
The need to manually call +generate_uuid+ here is ugly, and one can easily forget
|
358
|
+
to do that. Can we do better? Let's see:
|
359
|
+
|
360
|
+
# Controller
|
361
|
+
def create
|
362
|
+
@user = User.new(params[:user])
|
363
|
+
email_report_to_admin("#{@user.username} with UUID #{@user.uuid} created.")
|
364
|
+
@user.save!
|
365
|
+
end
|
366
|
+
|
367
|
+
# Model
|
368
|
+
class User < ActiveRecord::Base
|
369
|
+
before_save :generate_uuid_if_necessary
|
370
|
+
|
371
|
+
def uuid
|
372
|
+
value = read_attribute('uuid')
|
373
|
+
if !value
|
374
|
+
value = generate_uuid
|
375
|
+
write_attribute('uuid', value)
|
376
|
+
end
|
377
|
+
value
|
378
|
+
end
|
379
|
+
|
380
|
+
# We need to override this too, otherwise User.new.attributes won't return
|
381
|
+
# a default UUID value. I've never tested with User.create() so maybe we
|
382
|
+
# need to override even more things.
|
383
|
+
def attributes
|
384
|
+
uuid
|
385
|
+
super
|
386
|
+
end
|
387
|
+
|
388
|
+
private
|
389
|
+
def generate_uuid_if_necessary
|
390
|
+
uuid # Reader method automatically generates UUID if it doesn't exist
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
That's an awful lot of code. Using +default_value_for+ is easier, don't you think?
|
395
|
+
|
396
|
+
=== What about other plugins?
|
397
|
+
|
398
|
+
I've only been able to find 2 similar plugins:
|
399
|
+
|
400
|
+
- Default Value: http://agilewebdevelopment.com/plugins/default_value
|
401
|
+
- ActiveRecord Defaults: http://agilewebdevelopment.com/plugins/activerecord_defaults
|
402
|
+
|
403
|
+
'Default Value' appears to be unmaintained; its SVN link is broken. This leaves
|
404
|
+
only 'ActiveRecord Defaults'. However, it is semantically dubious, which leaves
|
405
|
+
it wide open for corner cases. For example, it is not clearly specified what
|
406
|
+
ActiveRecord Defaults will do when attributes are protected by +attr_protected+
|
407
|
+
or +attr_accessible+. It is also not clearly specified what one is supposed to
|
408
|
+
do if one needs a custom +initialize+ method in the model.
|
409
|
+
|
410
|
+
I've taken my time to thoroughly document default_value_for's behavior.
|
411
|
+
|
412
|
+
|
413
|
+
== Credits
|
414
|
+
|
415
|
+
I've wanted such functionality for a while now and it baffled me that ActiveRecord
|
416
|
+
doesn't provide a clean way for me to specify default values. After reading
|
417
|
+
http://groups.google.com/group/rubyonrails-core/browse_thread/thread/b509a2fe2b62ac5/3e8243fa1954a935,
|
418
|
+
it became clear that someone needs to write a plugin. This is the result.
|
419
|
+
|
420
|
+
Thanks to Pratik Naik for providing the initial code snippet on which this plugin
|
421
|
+
is based on: http://m.onkey.org/2007/7/24/how-to-set-default-values-in-your-model
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
begin
|
2
|
+
require 'rake'
|
3
|
+
rescue LoadError
|
4
|
+
require 'rubygems'
|
5
|
+
require 'rake'
|
6
|
+
end
|
7
|
+
|
8
|
+
begin
|
9
|
+
require 'jeweler'
|
10
|
+
Jeweler::Tasks.new do |gemspec|
|
11
|
+
gemspec.name = "default_value_for"
|
12
|
+
gemspec.summary = "Provides a way to specify default values for ActiveRecord models"
|
13
|
+
gemspec.description = "The default_value_for plugin allows one to define default values for ActiveRecord models in a declarative manner"
|
14
|
+
gemspec.email = "info@phusion.nl"
|
15
|
+
gemspec.homepage = "http://github.com/FooBarWidget/default_value_for"
|
16
|
+
gemspec.authors = ["Hongli Lai"]
|
17
|
+
end
|
18
|
+
rescue LoadError
|
19
|
+
puts "default_value_for not available. Install it with: sudo gem install default_value_for"
|
20
|
+
end
|
21
|
+
|
22
|
+
task :default => :test
|
23
|
+
|
24
|
+
desc "Run unit tests."
|
25
|
+
task :test do
|
26
|
+
ruby "test.rb"
|
27
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'lib/default_value_for.rb'
|
data/test.rb
ADDED
@@ -0,0 +1,279 @@
|
|
1
|
+
# Copyright (c) 2008, 2009 Phusion
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require 'rubygems'
|
22
|
+
require 'active_record'
|
23
|
+
require 'test/unit'
|
24
|
+
require File.dirname(__FILE__) + '/init'
|
25
|
+
Dir.chdir(File.dirname(__FILE__))
|
26
|
+
|
27
|
+
if RUBY_PLATFORM == "java"
|
28
|
+
database_adapter = "jdbcsqlite3"
|
29
|
+
else
|
30
|
+
database_adapter = "sqlite3"
|
31
|
+
end
|
32
|
+
|
33
|
+
File.unlink('test.sqlite3') rescue nil
|
34
|
+
ActiveRecord::Base.logger = Logger.new(STDERR)
|
35
|
+
ActiveRecord::Base.logger.level = Logger::WARN
|
36
|
+
ActiveRecord::Base.establish_connection(
|
37
|
+
:adapter => database_adapter,
|
38
|
+
:database => 'test.sqlite3'
|
39
|
+
)
|
40
|
+
ActiveRecord::Base.connection.create_table(:users, :force => true) do |t|
|
41
|
+
t.string :username
|
42
|
+
t.integer :default_number
|
43
|
+
end
|
44
|
+
ActiveRecord::Base.connection.create_table(:numbers, :force => true) do |t|
|
45
|
+
t.string :type
|
46
|
+
t.integer :number
|
47
|
+
t.integer :count, :null => false, :default => 1
|
48
|
+
t.integer :user_id
|
49
|
+
t.timestamp :timestamp
|
50
|
+
end
|
51
|
+
|
52
|
+
class User < ActiveRecord::Base
|
53
|
+
has_many :numbers, :class_name => 'TestClass'
|
54
|
+
end
|
55
|
+
|
56
|
+
class Number < ActiveRecord::Base
|
57
|
+
end
|
58
|
+
|
59
|
+
class DefaultValuePluginTest < Test::Unit::TestCase
|
60
|
+
def setup
|
61
|
+
Number.create(:number => 9876)
|
62
|
+
end
|
63
|
+
|
64
|
+
def teardown
|
65
|
+
Number.delete_all
|
66
|
+
end
|
67
|
+
|
68
|
+
def define_model_class(name = "TestClass", parent_class_name = "ActiveRecord::Base", &block)
|
69
|
+
Object.send(:remove_const, name) rescue nil
|
70
|
+
eval("class #{name} < #{parent_class_name}; end", TOPLEVEL_BINDING)
|
71
|
+
klass = eval(name, TOPLEVEL_BINDING)
|
72
|
+
klass.class_eval do
|
73
|
+
set_table_name 'numbers'
|
74
|
+
end
|
75
|
+
klass.class_eval(&block) if block_given?
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_default_value_can_be_passed_as_argument
|
79
|
+
define_model_class do
|
80
|
+
default_value_for(:number, 1234)
|
81
|
+
end
|
82
|
+
object = TestClass.new
|
83
|
+
assert_equal 1234, object.number
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_default_value_can_be_passed_as_block
|
87
|
+
define_model_class do
|
88
|
+
default_value_for(:number) { 1234 }
|
89
|
+
end
|
90
|
+
object = TestClass.new
|
91
|
+
assert_equal 1234, object.number
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_works_with_create
|
95
|
+
define_model_class do
|
96
|
+
default_value_for :number, 1234
|
97
|
+
end
|
98
|
+
TestClass.create
|
99
|
+
assert_not_nil TestClass.find_by_number(1234)
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_overwrites_db_default
|
103
|
+
define_model_class do
|
104
|
+
default_value_for :count, 1234
|
105
|
+
end
|
106
|
+
object = TestClass.new
|
107
|
+
assert_equal 1234, object.count
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_doesnt_overwrite_values_provided_by_mass_assignment
|
111
|
+
define_model_class do
|
112
|
+
default_value_for :number, 1234
|
113
|
+
end
|
114
|
+
object = TestClass.new(:number => 1, :count => 2)
|
115
|
+
assert_equal 1, object.number
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_doesnt_overwrite_values_provided_by_multiparameter_assignment
|
119
|
+
define_model_class do
|
120
|
+
default_value_for :timestamp, Time.mktime(2000, 1, 1)
|
121
|
+
end
|
122
|
+
timestamp = Time.mktime(2009, 1, 1)
|
123
|
+
object = TestClass.new('timestamp(1i)' => '2009', 'timestamp(2i)' => '1', 'timestamp(3i)' => '1')
|
124
|
+
assert_equal timestamp, object.timestamp
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_doesnt_overwrite_values_provided_by_constructor_block
|
128
|
+
define_model_class do
|
129
|
+
default_value_for :number, 1234
|
130
|
+
end
|
131
|
+
object = TestClass.new do |x|
|
132
|
+
x.number = 1
|
133
|
+
x.count = 2
|
134
|
+
end
|
135
|
+
assert_equal 1, object.number
|
136
|
+
end
|
137
|
+
|
138
|
+
def test_doesnt_overwrite_explicitly_provided_nil_values_in_mass_assignment
|
139
|
+
define_model_class do
|
140
|
+
default_value_for :number, 1234
|
141
|
+
end
|
142
|
+
object = TestClass.new(:number => nil)
|
143
|
+
assert_nil object.number
|
144
|
+
end
|
145
|
+
|
146
|
+
def test_default_values_are_inherited
|
147
|
+
define_model_class("TestSuperClass") do
|
148
|
+
default_value_for :number, 1234
|
149
|
+
end
|
150
|
+
define_model_class("TestClass", "TestSuperClass")
|
151
|
+
object = TestClass.new
|
152
|
+
assert_equal 1234, object.number
|
153
|
+
end
|
154
|
+
|
155
|
+
def test_doesnt_set_default_on_saved_records
|
156
|
+
define_model_class do
|
157
|
+
default_value_for :number, 1234
|
158
|
+
end
|
159
|
+
assert_equal 9876, TestClass.find(:first).number
|
160
|
+
end
|
161
|
+
|
162
|
+
def test_also_works_on_attributes_that_arent_database_columns
|
163
|
+
define_model_class do
|
164
|
+
default_value_for :hello, "hi"
|
165
|
+
attr_accessor :hello
|
166
|
+
end
|
167
|
+
object = TestClass.new
|
168
|
+
assert_equal 'hi', object.hello
|
169
|
+
end
|
170
|
+
|
171
|
+
def test_constructor_ignores_forbidden_mass_assignment_attributes
|
172
|
+
define_model_class do
|
173
|
+
default_value_for :number, 1234
|
174
|
+
attr_protected :number
|
175
|
+
end
|
176
|
+
object = TestClass.new(:number => 5678, :count => 987)
|
177
|
+
assert_equal 1234, object.number
|
178
|
+
assert_equal 987, object.count
|
179
|
+
end
|
180
|
+
|
181
|
+
def test_doesnt_conflict_with_overrided_initialize_method_in_model_class
|
182
|
+
define_model_class do
|
183
|
+
def initialize(attrs = {})
|
184
|
+
@initialized = true
|
185
|
+
super(:count => 5678)
|
186
|
+
end
|
187
|
+
|
188
|
+
default_value_for :number, 1234
|
189
|
+
end
|
190
|
+
object = TestClass.new
|
191
|
+
assert_equal 1234, object.number
|
192
|
+
assert_equal 5678, object.count
|
193
|
+
assert object.instance_variable_get('@initialized')
|
194
|
+
end
|
195
|
+
|
196
|
+
def test_model_instance_is_passed_to_the_given_block
|
197
|
+
$instance = nil
|
198
|
+
define_model_class do
|
199
|
+
default_value_for :number do |n|
|
200
|
+
$instance = n
|
201
|
+
end
|
202
|
+
end
|
203
|
+
object = TestClass.new
|
204
|
+
assert_same object, $instance
|
205
|
+
end
|
206
|
+
|
207
|
+
def test_can_specify_default_value_via_association
|
208
|
+
user = User.create(:username => 'Kanako', :default_number => 123)
|
209
|
+
define_model_class do
|
210
|
+
belongs_to :user
|
211
|
+
|
212
|
+
default_value_for :number do |n|
|
213
|
+
n.user.default_number
|
214
|
+
end
|
215
|
+
end
|
216
|
+
object = user.numbers.create
|
217
|
+
assert_equal 123, object.number
|
218
|
+
end
|
219
|
+
|
220
|
+
def test_default_values
|
221
|
+
define_model_class do
|
222
|
+
default_values :type => "normal",
|
223
|
+
:number => lambda { 10 + 5 }
|
224
|
+
end
|
225
|
+
|
226
|
+
object = TestClass.new
|
227
|
+
assert_equal("normal", object.type)
|
228
|
+
assert_equal(15, object.number)
|
229
|
+
end
|
230
|
+
|
231
|
+
def test_default_value_order
|
232
|
+
define_model_class do
|
233
|
+
default_value_for :count, 5
|
234
|
+
default_value_for :number do |this|
|
235
|
+
this.count * 2
|
236
|
+
end
|
237
|
+
end
|
238
|
+
object = TestClass.new
|
239
|
+
assert_equal(5, object.count)
|
240
|
+
assert_equal(10, object.number)
|
241
|
+
end
|
242
|
+
|
243
|
+
def test_attributes_with_default_values_are_not_marked_as_changed
|
244
|
+
define_model_class do
|
245
|
+
default_value_for :count, 5
|
246
|
+
default_value_for :number, 2
|
247
|
+
end
|
248
|
+
|
249
|
+
object = TestClass.new
|
250
|
+
assert(!object.changed?)
|
251
|
+
assert_equal([], object.changed)
|
252
|
+
|
253
|
+
object.type = "foo"
|
254
|
+
assert(object.changed?)
|
255
|
+
assert_equal(["type"], object.changed)
|
256
|
+
end
|
257
|
+
|
258
|
+
def test_default_values_are_not_duplicated
|
259
|
+
define_model_class do
|
260
|
+
set_table_name "users"
|
261
|
+
default_value_for :username, "hello"
|
262
|
+
end
|
263
|
+
user1 = TestClass.new
|
264
|
+
user1.username << " world"
|
265
|
+
user2 = TestClass.new
|
266
|
+
assert_equal("hello world", user2.username)
|
267
|
+
end
|
268
|
+
|
269
|
+
def test_constructor_does_not_affect_the_hash_passed_to_it
|
270
|
+
define_model_class do
|
271
|
+
default_value_for :count, 5
|
272
|
+
end
|
273
|
+
|
274
|
+
options = { :count => 5, :user_id => 1 }
|
275
|
+
options_dup = options.dup
|
276
|
+
object = TestClass.new(options)
|
277
|
+
assert_equal(options_dup, options)
|
278
|
+
end
|
279
|
+
end
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: default_value_for
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Hongli Lai
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-11-20 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: The default_value_for plugin allows one to define default values for ActiveRecord models in a declarative manner
|
17
|
+
email: info@phusion.nl
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- LICENSE.TXT
|
24
|
+
- README.rdoc
|
25
|
+
files:
|
26
|
+
- .gitignore
|
27
|
+
- LICENSE.TXT
|
28
|
+
- README.rdoc
|
29
|
+
- Rakefile
|
30
|
+
- VERSION
|
31
|
+
- init.rb
|
32
|
+
- test.rb
|
33
|
+
has_rdoc: true
|
34
|
+
homepage: http://github.com/FooBarWidget/default_value_for
|
35
|
+
licenses: []
|
36
|
+
|
37
|
+
post_install_message:
|
38
|
+
rdoc_options:
|
39
|
+
- --charset=UTF-8
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: "0"
|
47
|
+
version:
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: "0"
|
53
|
+
version:
|
54
|
+
requirements: []
|
55
|
+
|
56
|
+
rubyforge_project:
|
57
|
+
rubygems_version: 1.3.5
|
58
|
+
signing_key:
|
59
|
+
specification_version: 3
|
60
|
+
summary: Provides a way to specify default values for ActiveRecord models
|
61
|
+
test_files: []
|
62
|
+
|