dolzenko-default_value_for 1.0.1

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.
@@ -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
+