dolzenko-default_value_for 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.
@@ -0,0 +1,436 @@
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 duplicated
167
+
168
+ The given default values are duplicated when they are filled in, so if
169
+ you mutate a value that was filled in with a default value, then it will
170
+ not affect 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 does not mutate the default value:
186
+ book1.author.name = "John"
187
+
188
+ book2 = Book.new
189
+ book2.author.name # => nil
190
+
191
+ However the duplication is shallow. If you modify any objects that are
192
+ referenced by the default value then it will affect subsequent default values:
193
+
194
+ class Author < ActiveRecord::Base
195
+ attr_accessor :useless_hash
196
+ default_value_for :useless_hash, { :foo => [] }
197
+ end
198
+
199
+ author1 = Author.new
200
+ author1.useless_hash # => { :foo => [] }
201
+ # This mutates the referred array:
202
+ author1.useless_hash[:foo] << 1
203
+
204
+ author2 = Author.new
205
+ author2.useless_hash # => { :foo => [1] }
206
+
207
+ You can prevent this from happening by passing a block to +default_value_for+,
208
+ which returns a new object instance with fresh references every time:
209
+
210
+ class Author < ActiveRecord::Base
211
+ attr_accessor :useless_hash
212
+ default_value_for :useless_hash do
213
+ { :foo => [] }
214
+ end
215
+ end
216
+
217
+ author1 = Author.new
218
+ author1.useless_hash # => { :foo => [] }
219
+ author1.useless_hash[:foo] << 1
220
+
221
+ author2 = Author.new
222
+ author2.useless_hash # => { :foo => [] }
223
+
224
+ === Caveats
225
+
226
+ A conflict can occur if your model class overrides the 'initialize' method,
227
+ because this plugin overrides 'initialize' as well to do its job.
228
+
229
+ class User < ActiveRecord::Base
230
+ def initialize # <-- this constructor causes problems
231
+ super(:name => 'Name cannot be changed in constructor')
232
+ end
233
+ end
234
+
235
+ We recommend you to alias chain your initialize method in models where you use
236
+ +default_value_for+:
237
+
238
+ class User < ActiveRecord::Base
239
+ default_value_for :age, 20
240
+
241
+ def initialize_with_my_app
242
+ initialize_without_my_app(:name => 'Name cannot be changed in constructor')
243
+ end
244
+
245
+ alias_method_chain :initialize, :my_app
246
+ end
247
+
248
+ Also, stick with the following rules:
249
+ - There is no need to +alias_method_chain+ your initialize method in models that
250
+ don't use +default_value_for+.
251
+ - Make sure that +alias_method_chain+ is called *after* the last
252
+ +default_value_for+ occurance.
253
+
254
+
255
+ == When (not) to use default_value_for?
256
+
257
+ You can also specify default values in the database schema. For example, you
258
+ can specify a default value in a migration as follows:
259
+
260
+ create_table :users do |t|
261
+ t.string :username, :null => false, :default => 'default username'
262
+ t.integer :age, :null => false, :default => 20
263
+ end
264
+
265
+ This has similar effects as passing the default value as the second argument to
266
+ +default_value_for+:
267
+
268
+ default_value_for(:username, 'default_username')
269
+ default_value_for(:age, 20)
270
+
271
+ Default values are filled in whether you use the schema defaults or the
272
+ default_value_for defaults:
273
+
274
+ user = User.new
275
+ user.username # => 'default username'
276
+ user.age # => 20
277
+
278
+ It's recommended that you use this over +default_value_for+ whenever possible.
279
+
280
+ However, it's not possible to specify a schema default for serialized columns.
281
+ With +default_value_for+, you can:
282
+
283
+ class User < ActiveRecord::Base
284
+ serialize :color
285
+ default_value_for :color, [255, 0, 0]
286
+ end
287
+
288
+ And if schema defaults don't provide the flexibility that you need, then
289
+ +default_value_for+ is the perfect choice. For example, with +default_value_for+
290
+ you could specify a per-environment default:
291
+
292
+ class User < ActiveRecord::Base
293
+ if RAILS_ENV == "development"
294
+ default_value_for :is_admin, true
295
+ end
296
+ end
297
+
298
+ Or, as you've seen in an earlier example, you can use +default_value_for+ to
299
+ generate a default random UUID:
300
+
301
+ class User < ActiveRecord::Base
302
+ default_value_for :uuid do
303
+ UuidGenerator.new.generate_uuid
304
+ end
305
+ end
306
+
307
+ Or you could use it to generate a timestamp that's relative to the time at which
308
+ the record is instantiated:
309
+
310
+ class User < ActiveRecord::Base
311
+ default_value_for :account_expires_at do
312
+ 3.years.from_now
313
+ end
314
+ end
315
+
316
+ User.new.account_expires_at # => Mon Sep 22 18:43:42 +0200 2008
317
+ sleep(2)
318
+ User.new.account_expires_at # => Mon Sep 22 18:43:44 +0200 2008
319
+
320
+ Finally, it's also possible to specify a default via an association:
321
+
322
+ # Has columns: 'name' and 'default_price'
323
+ class SuperMarket < ActiveRecord::Base
324
+ has_many :products
325
+ end
326
+
327
+ # Has columns: 'name' and 'price'
328
+ class Product < ActiveRecord::Base
329
+ belongs_to :super_market
330
+
331
+ default_value_for :price do |product|
332
+ product.super_market.default_price
333
+ end
334
+ end
335
+
336
+ super_market = SuperMarket.create(:name => 'Albert Zwijn', :default_price => 100)
337
+ soap = super_market.products.create(:name => 'Soap')
338
+ soap.price # => 100
339
+
340
+ === What about before_validate/before_save?
341
+
342
+ True, +before_validate+ and +before_save+ does what we want if we're only
343
+ interested in filling in a default before saving. However, if one wants to be
344
+ able to access the default value even before saving, then be prepared to write
345
+ a lot of code. Suppose that we want to be able to access a new record's UUID,
346
+ even before it's saved. We could end up with the following code:
347
+
348
+ # In the controller
349
+ def create
350
+ @user = User.new(params[:user])
351
+ @user.generate_uuid
352
+ email_report_to_admin("#{@user.username} with UUID #{@user.uuid} created.")
353
+ @user.save!
354
+ end
355
+
356
+ # Model
357
+ class User < ActiveRecord::Base
358
+ before_save :generate_uuid_if_necessary
359
+
360
+ def generate_uuid
361
+ self.uuid = ...
362
+ end
363
+
364
+ private
365
+ def generate_uuid_if_necessary
366
+ if uuid.blank?
367
+ generate_uuid
368
+ end
369
+ end
370
+ end
371
+
372
+ The need to manually call +generate_uuid+ here is ugly, and one can easily forget
373
+ to do that. Can we do better? Let's see:
374
+
375
+ # Controller
376
+ def create
377
+ @user = User.new(params[:user])
378
+ email_report_to_admin("#{@user.username} with UUID #{@user.uuid} created.")
379
+ @user.save!
380
+ end
381
+
382
+ # Model
383
+ class User < ActiveRecord::Base
384
+ before_save :generate_uuid_if_necessary
385
+
386
+ def uuid
387
+ value = read_attribute('uuid')
388
+ if !value
389
+ value = generate_uuid
390
+ write_attribute('uuid', value)
391
+ end
392
+ value
393
+ end
394
+
395
+ # We need to override this too, otherwise User.new.attributes won't return
396
+ # a default UUID value. I've never tested with User.create() so maybe we
397
+ # need to override even more things.
398
+ def attributes
399
+ uuid
400
+ super
401
+ end
402
+
403
+ private
404
+ def generate_uuid_if_necessary
405
+ uuid # Reader method automatically generates UUID if it doesn't exist
406
+ end
407
+ end
408
+
409
+ That's an awful lot of code. Using +default_value_for+ is easier, don't you think?
410
+
411
+ === What about other plugins?
412
+
413
+ I've only been able to find 2 similar plugins:
414
+
415
+ - Default Value: http://agilewebdevelopment.com/plugins/default_value
416
+ - ActiveRecord Defaults: http://agilewebdevelopment.com/plugins/activerecord_defaults
417
+
418
+ 'Default Value' appears to be unmaintained; its SVN link is broken. This leaves
419
+ only 'ActiveRecord Defaults'. However, it is semantically dubious, which leaves
420
+ it wide open for corner cases. For example, it is not clearly specified what
421
+ ActiveRecord Defaults will do when attributes are protected by +attr_protected+
422
+ or +attr_accessible+. It is also not clearly specified what one is supposed to
423
+ do if one needs a custom +initialize+ method in the model.
424
+
425
+ I've taken my time to thoroughly document default_value_for's behavior.
426
+
427
+
428
+ == Credits
429
+
430
+ I've wanted such functionality for a while now and it baffled me that ActiveRecord
431
+ doesn't provide a clean way for me to specify default values. After reading
432
+ http://groups.google.com/group/rubyonrails-core/browse_thread/thread/b509a2fe2b62ac5/3e8243fa1954a935,
433
+ it became clear that someone needs to write a plugin. This is the result.
434
+
435
+ Thanks to Pratik Naik for providing the initial code snippet on which this plugin
436
+ is based on: http://m.onkey.org/2007/7/24/how-to-set-default-values-in-your-model
@@ -0,0 +1,6 @@
1
+ task :default => :test
2
+
3
+ desc "Run unit tests."
4
+ task :test do
5
+ ruby "test.rb"
6
+ end
@@ -0,0 +1,10 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = %q{dolzenko-default_value_for}
3
+ s.version = "1.0.1"
4
+ s.summary = %q{Provides a way to specify default values for ActiveRecord models}
5
+ s.description = %q{The default_value_for plugin allows one to define default values for ActiveRecord models in a declarative manner}
6
+ s.email = %q{info@phusion.nl}
7
+ s.homepage = %q{http://github.com/FooBarWidget/default_value_for}
8
+ s.authors = ["Hongli Lai"]
9
+ s.files = ['default_value_for.gemspec', 'init.rb', 'lib/default_value_for.rb', 'lib/default_value_for/railtie.rb', 'LICENSE.TXT', 'Rakefile', 'README.rdoc', 'test.rb']
10
+ end
data/init.rb ADDED
@@ -0,0 +1,23 @@
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.
20
+
21
+ # Rails 2 initialization
22
+ require "default_value_for"
23
+ ActiveRecord::Base.extend(DefaultValueForPlugin::ClassMethods)
@@ -0,0 +1,79 @@
1
+ require "default_value_for/railtie"
2
+
3
+ module DefaultValueForPlugin
4
+ class NormalValueContainer
5
+ def initialize(value)
6
+ @value = value
7
+ end
8
+
9
+ def evaluate(instance)
10
+ if @value.duplicable?
11
+ return @value.dup
12
+ else
13
+ return @value
14
+ end
15
+ end
16
+ end
17
+
18
+ class BlockValueContainer
19
+ def initialize(block)
20
+ @block = block
21
+ end
22
+
23
+ def evaluate(instance)
24
+ return @block.call(instance)
25
+ end
26
+ end
27
+
28
+ module ClassMethods
29
+ def default_value_for(attribute, value = nil, &block)
30
+ if !method_defined?(:initialize_with_defaults)
31
+ include(InstanceMethods)
32
+ alias_method_chain :initialize, :defaults
33
+ class_inheritable_accessor :_default_attribute_values
34
+ self._default_attribute_values = ActiveSupport::OrderedHash.new
35
+ end
36
+ if block_given?
37
+ container = BlockValueContainer.new(block)
38
+ else
39
+ container = NormalValueContainer.new(value)
40
+ end
41
+ _default_attribute_values[attribute.to_s] = container
42
+ end
43
+
44
+ def default_values(values)
45
+ values.each_pair do |key, value|
46
+ if value.kind_of? Proc
47
+ default_value_for(key, &value)
48
+ else
49
+ default_value_for(key, value)
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ module InstanceMethods
56
+ def initialize_with_defaults(attrs = nil)
57
+ initialize_without_defaults(attrs) do
58
+ if attrs
59
+ stringified_attrs = attrs.stringify_keys
60
+ safe_attrs = if respond_to? :sanitize_for_mass_assignment
61
+ sanitize_for_mass_assignment(stringified_attrs)
62
+ else
63
+ remove_attributes_protected_from_mass_assignment(stringified_attrs)
64
+ end
65
+ safe_attribute_names = safe_attrs.keys.map do |x|
66
+ x.to_s
67
+ end
68
+ end
69
+ self.class._default_attribute_values.each do |attribute, container|
70
+ if safe_attribute_names.nil? || !safe_attribute_names.any? { |attr_name| attr_name =~ /^#{attribute}($|\()/ }
71
+ __send__("#{attribute}=", container.evaluate(self))
72
+ changed_attributes.delete(attribute)
73
+ end
74
+ end
75
+ yield(self) if block_given?
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,13 @@
1
+ # Rails 3 initialization
2
+ module DefaultValueForPlugin
3
+ if defined? Rails::Railtie
4
+ require 'rails'
5
+ class Railtie < Rails::Railtie
6
+ initializer 'default_value_for.insert_into_active_record' do
7
+ ActiveSupport.on_load :active_record do
8
+ ActiveRecord::Base.extend(DefaultValueForPlugin::ClassMethods)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
data/test.rb ADDED
@@ -0,0 +1,293 @@
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 'active_support/core_ext/logger'
25
+ $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
26
+ require File.dirname(__FILE__) + '/init'
27
+ Dir.chdir(File.dirname(__FILE__))
28
+
29
+ if RUBY_PLATFORM == "java"
30
+ database_adapter = "jdbcsqlite3"
31
+ else
32
+ database_adapter = "sqlite3"
33
+ end
34
+
35
+ File.unlink('test.sqlite3') rescue nil
36
+ ActiveRecord::Base.logger = Logger.new(STDERR)
37
+ ActiveRecord::Base.logger.level = Logger::WARN
38
+ ActiveRecord::Base.establish_connection(
39
+ :adapter => database_adapter,
40
+ :database => 'test.sqlite3'
41
+ )
42
+ ActiveRecord::Base.connection.create_table(:users, :force => true) do |t|
43
+ t.string :username
44
+ t.integer :default_number
45
+ end
46
+ ActiveRecord::Base.connection.create_table(:numbers, :force => true) do |t|
47
+ t.string :type
48
+ t.integer :number
49
+ t.integer :count, :null => false, :default => 1
50
+ t.integer :user_id
51
+ t.timestamp :timestamp
52
+ end
53
+
54
+ class User < ActiveRecord::Base
55
+ has_many :numbers, :class_name => 'TestClass'
56
+ end
57
+
58
+ class Number < ActiveRecord::Base
59
+ end
60
+
61
+ class DefaultValuePluginTest < Test::Unit::TestCase
62
+ def setup
63
+ Number.create(:number => 9876)
64
+ end
65
+
66
+ def teardown
67
+ Number.delete_all
68
+ end
69
+
70
+ def define_model_class(name = "TestClass", parent_class_name = "ActiveRecord::Base", &block)
71
+ Object.send(:remove_const, name) rescue nil
72
+ eval("class #{name} < #{parent_class_name}; end", TOPLEVEL_BINDING)
73
+ klass = eval(name, TOPLEVEL_BINDING)
74
+ klass.class_eval do
75
+ set_table_name 'numbers'
76
+ end
77
+ klass.class_eval(&block) if block_given?
78
+ end
79
+
80
+ def test_default_value_can_be_passed_as_argument
81
+ define_model_class do
82
+ default_value_for(:number, 1234)
83
+ end
84
+ object = TestClass.new
85
+ assert_equal 1234, object.number
86
+ end
87
+
88
+ def test_default_value_can_be_passed_as_block
89
+ define_model_class do
90
+ default_value_for(:number) { 1234 }
91
+ end
92
+ object = TestClass.new
93
+ assert_equal 1234, object.number
94
+ end
95
+
96
+ def test_works_with_create
97
+ define_model_class do
98
+ default_value_for :number, 1234
99
+ end
100
+ TestClass.create
101
+ assert_not_nil TestClass.find_by_number(1234)
102
+ end
103
+
104
+ def test_overwrites_db_default
105
+ define_model_class do
106
+ default_value_for :count, 1234
107
+ end
108
+ object = TestClass.new
109
+ assert_equal 1234, object.count
110
+ end
111
+
112
+ def test_doesnt_overwrite_values_provided_by_mass_assignment
113
+ define_model_class do
114
+ default_value_for :number, 1234
115
+ end
116
+ object = TestClass.new(:number => 1, :count => 2)
117
+ assert_equal 1, object.number
118
+ end
119
+
120
+ def test_doesnt_overwrite_values_provided_by_multiparameter_assignment
121
+ define_model_class do
122
+ default_value_for :timestamp, Time.mktime(2000, 1, 1)
123
+ end
124
+ timestamp = Time.mktime(2009, 1, 1)
125
+ object = TestClass.new('timestamp(1i)' => '2009', 'timestamp(2i)' => '1', 'timestamp(3i)' => '1')
126
+ assert_equal timestamp, object.timestamp
127
+ end
128
+
129
+ def test_doesnt_overwrite_values_provided_by_constructor_block
130
+ define_model_class do
131
+ default_value_for :number, 1234
132
+ end
133
+ object = TestClass.new do |x|
134
+ x.number = 1
135
+ x.count = 2
136
+ end
137
+ assert_equal 1, object.number
138
+ end
139
+
140
+ def test_doesnt_overwrite_explicitly_provided_nil_values_in_mass_assignment
141
+ define_model_class do
142
+ default_value_for :number, 1234
143
+ end
144
+ object = TestClass.new(:number => nil)
145
+ assert_nil object.number
146
+ end
147
+
148
+ def test_default_values_are_inherited
149
+ define_model_class("TestSuperClass") do
150
+ default_value_for :number, 1234
151
+ end
152
+ define_model_class("TestClass", "TestSuperClass")
153
+ object = TestClass.new
154
+ assert_equal 1234, object.number
155
+ end
156
+
157
+ def test_doesnt_set_default_on_saved_records
158
+ define_model_class do
159
+ default_value_for :number, 1234
160
+ end
161
+ assert_equal 9876, TestClass.find(:first).number
162
+ end
163
+
164
+ def test_also_works_on_attributes_that_arent_database_columns
165
+ define_model_class do
166
+ default_value_for :hello, "hi"
167
+ attr_accessor :hello
168
+ end
169
+ object = TestClass.new
170
+ assert_equal 'hi', object.hello
171
+ end
172
+
173
+ def test_constructor_ignores_forbidden_mass_assignment_attributes
174
+ define_model_class do
175
+ default_value_for :number, 1234
176
+ attr_protected :number
177
+ end
178
+ object = TestClass.new(:number => 5678, :count => 987)
179
+ assert_equal 1234, object.number
180
+ assert_equal 987, object.count
181
+ end
182
+
183
+ def test_doesnt_conflict_with_overrided_initialize_method_in_model_class
184
+ define_model_class do
185
+ def initialize(attrs = {})
186
+ @initialized = true
187
+ super(:count => 5678)
188
+ end
189
+
190
+ default_value_for :number, 1234
191
+ end
192
+ object = TestClass.new
193
+ assert_equal 1234, object.number
194
+ assert_equal 5678, object.count
195
+ assert object.instance_variable_get('@initialized')
196
+ end
197
+
198
+ def test_model_instance_is_passed_to_the_given_block
199
+ $instance = nil
200
+ define_model_class do
201
+ default_value_for :number do |n|
202
+ $instance = n
203
+ end
204
+ end
205
+ object = TestClass.new
206
+ assert_same object, $instance
207
+ end
208
+
209
+ def test_can_specify_default_value_via_association
210
+ user = User.create(:username => 'Kanako', :default_number => 123)
211
+ define_model_class do
212
+ belongs_to :user
213
+
214
+ default_value_for :number do |n|
215
+ n.user.default_number
216
+ end
217
+ end
218
+ object = user.numbers.create
219
+ assert_equal 123, object.number
220
+ end
221
+
222
+ def test_default_values
223
+ define_model_class do
224
+ default_values :type => "normal",
225
+ :number => lambda { 10 + 5 }
226
+ end
227
+
228
+ object = TestClass.new
229
+ assert_equal("normal", object.type)
230
+ assert_equal(15, object.number)
231
+ end
232
+
233
+ def test_default_value_order
234
+ define_model_class do
235
+ default_value_for :count, 5
236
+ default_value_for :number do |this|
237
+ this.count * 2
238
+ end
239
+ end
240
+ object = TestClass.new
241
+ assert_equal(5, object.count)
242
+ assert_equal(10, object.number)
243
+ end
244
+
245
+ def test_attributes_with_default_values_are_not_marked_as_changed
246
+ define_model_class do
247
+ default_value_for :count, 5
248
+ default_value_for :number, 2
249
+ end
250
+
251
+ object = TestClass.new
252
+ assert(!object.changed?)
253
+ assert_equal([], object.changed)
254
+
255
+ object.type = "foo"
256
+ assert(object.changed?)
257
+ assert_equal(["type"], object.changed)
258
+ end
259
+
260
+ def test_default_values_are_duplicated
261
+ define_model_class do
262
+ set_table_name "users"
263
+ default_value_for :username, "hello"
264
+ end
265
+ user1 = TestClass.new
266
+ user1.username << " world"
267
+ user2 = TestClass.new
268
+ assert_equal("hello", user2.username)
269
+ end
270
+
271
+ def test_default_values_are_shallow_copied
272
+ define_model_class do
273
+ set_table_name "users"
274
+ attr_accessor :hash
275
+ default_value_for :hash, { 1 => [] }
276
+ end
277
+ user1 = TestClass.new
278
+ user1.hash[1] << 1
279
+ user2 = TestClass.new
280
+ assert_equal([1], user2.hash[1])
281
+ end
282
+
283
+ def test_constructor_does_not_affect_the_hash_passed_to_it
284
+ define_model_class do
285
+ default_value_for :count, 5
286
+ end
287
+
288
+ options = { :count => 5, :user_id => 1 }
289
+ options_dup = options.dup
290
+ object = TestClass.new(options)
291
+ assert_equal(options_dup, options)
292
+ end
293
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dolzenko-default_value_for
3
+ version: !ruby/object:Gem::Version
4
+ hash: 21
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 1
10
+ version: 1.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Hongli Lai
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-09-15 00:00:00 -07:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: The default_value_for plugin allows one to define default values for ActiveRecord models in a declarative manner
23
+ email: info@phusion.nl
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files: []
29
+
30
+ files:
31
+ - default_value_for.gemspec
32
+ - init.rb
33
+ - lib/default_value_for.rb
34
+ - lib/default_value_for/railtie.rb
35
+ - LICENSE.TXT
36
+ - Rakefile
37
+ - README.rdoc
38
+ - test.rb
39
+ has_rdoc: true
40
+ homepage: http://github.com/FooBarWidget/default_value_for
41
+ licenses: []
42
+
43
+ post_install_message:
44
+ rdoc_options: []
45
+
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ hash: 3
54
+ segments:
55
+ - 0
56
+ version: "0"
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ hash: 3
63
+ segments:
64
+ - 0
65
+ version: "0"
66
+ requirements: []
67
+
68
+ rubyforge_project:
69
+ rubygems_version: 1.3.7
70
+ signing_key:
71
+ specification_version: 3
72
+ summary: Provides a way to specify default values for ActiveRecord models
73
+ test_files: []
74
+