default_value_for 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (8) hide show
  1. data/README.rdoc +39 -24
  2. data/Rakefile +0 -21
  3. data/default_value_for.gemspec +10 -0
  4. data/init.rb +99 -1
  5. data/test.rb +14 -2
  6. metadata +24 -14
  7. data/.gitignore +0 -1
  8. data/VERSION +0 -1
data/README.rdoc CHANGED
@@ -163,11 +163,11 @@ It works with anything for which there's an assignment method:
163
163
  user.age # => 20
164
164
  user.instance_variable_get('@registering') # => true
165
165
 
166
- === Default values are *not* duplicated
166
+ === Default values are duplicated
167
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:
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
171
 
172
172
  class Author < ActiveRecord::Base
173
173
  # This model only has a 'name' attribute.
@@ -182,33 +182,44 @@ all subsequent default values:
182
182
 
183
183
  book1 = Book.new
184
184
  book1.author.name # => nil
185
- # This mutates the default value:
185
+ # This does not mutate the default value:
186
186
  book1.author.name = "John"
187
187
 
188
188
  book2 = Book.new
189
- book2.author.name # => "John"
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] }
190
206
 
191
207
  You can prevent this from happening by passing a block to +default_value_for+,
192
- which returns a new object instance every time:
208
+ which returns a new object instance with fresh references every time:
193
209
 
194
- class Book < ActiveRecord::Base
195
- belongs_to :author
196
-
197
- default_value_for :author do
198
- Author.new
210
+ class Author < ActiveRecord::Base
211
+ attr_accessor :useless_hash
212
+ default_value_for :useless_hash do
213
+ { :foo => [] }
199
214
  end
200
215
  end
201
216
 
202
- book1 = Book.new
203
- book1.author.name # => nil
204
- book1.author.name = "John"
217
+ author1 = Author.new
218
+ author1.useless_hash # => { :foo => [] }
219
+ author1.useless_hash[:foo] << 1
205
220
 
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.
221
+ author2 = Author.new
222
+ author2.useless_hash # => { :foo => [] }
212
223
 
213
224
  === Caveats
214
225
 
@@ -249,16 +260,20 @@ can specify a default value in a migration as follows:
249
260
  create_table :users do |t|
250
261
  t.string :username, :null => false, :default => 'default username'
251
262
  t.integer :age, :null => false, :default => 20
252
- t.timestamp :last_seen, :null => false, :default => Time.now
253
263
  end
254
264
 
255
- This has the same effect as passing the default value as the second argument to
265
+ This has similar effects as passing the default value as the second argument to
256
266
  +default_value_for+:
257
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
+
258
274
  user = User.new
259
275
  user.username # => 'default username'
260
276
  user.age # => 20
261
- user.timestamp # => Mon Sep 22 18:31:47 +0200 2008
262
277
 
263
278
  It's recommended that you use this over +default_value_for+ whenever possible.
264
279
 
data/Rakefile CHANGED
@@ -1,24 +1,3 @@
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
1
  task :default => :test
23
2
 
24
3
  desc "Run unit tests."
@@ -0,0 +1,10 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = %q{default_value_for}
3
+ s.version = "1.0.0"
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', 'LICENSE.TXT', 'Rakefile', 'README.rdoc', 'test.rb']
10
+ end
data/init.rb CHANGED
@@ -1 +1,99 @@
1
- require 'lib/default_value_for.rb'
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
+ module DefaultValueForPlugin
22
+ class NormalValueContainer
23
+ def initialize(value)
24
+ @value = value
25
+ end
26
+
27
+ def evaluate(instance)
28
+ if @value.duplicable?
29
+ return @value.dup
30
+ else
31
+ return @value
32
+ end
33
+ end
34
+ end
35
+
36
+ class BlockValueContainer
37
+ def initialize(block)
38
+ @block = block
39
+ end
40
+
41
+ def evaluate(instance)
42
+ return @block.call(instance)
43
+ end
44
+ end
45
+
46
+ module ClassMethods
47
+ def default_value_for(attribute, value = nil, &block)
48
+ if !method_defined?(:initialize_with_defaults)
49
+ include(InstanceMethods)
50
+ alias_method_chain :initialize, :defaults
51
+ class_inheritable_accessor :_default_attribute_values
52
+ self._default_attribute_values = ActiveSupport::OrderedHash.new
53
+ end
54
+ if block_given?
55
+ container = BlockValueContainer.new(block)
56
+ else
57
+ container = NormalValueContainer.new(value)
58
+ end
59
+ _default_attribute_values[attribute.to_s] = container
60
+ end
61
+
62
+ def default_values(values)
63
+ values.each_pair do |key, value|
64
+ if value.kind_of? Proc
65
+ default_value_for(key, &value)
66
+ else
67
+ default_value_for(key, value)
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ module InstanceMethods
74
+ def initialize_with_defaults(attrs = nil)
75
+ initialize_without_defaults(attrs) do
76
+ if attrs
77
+ stringified_attrs = attrs.stringify_keys
78
+ safe_attrs = if respond_to? :sanitize_for_mass_assignment
79
+ sanitize_for_mass_assignment(stringified_attrs)
80
+ else
81
+ remove_attributes_protected_from_mass_assignment(stringified_attrs)
82
+ end
83
+ safe_attribute_names = safe_attrs.keys.map do |x|
84
+ x.to_s
85
+ end
86
+ end
87
+ self.class._default_attribute_values.each do |attribute, container|
88
+ if safe_attribute_names.nil? || !safe_attribute_names.any? { |attr_name| attr_name =~ /^#{attribute}($|\()/ }
89
+ __send__("#{attribute}=", container.evaluate(self))
90
+ changed_attributes.delete(attribute)
91
+ end
92
+ end
93
+ yield(self) if block_given?
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ ActiveRecord::Base.extend(DefaultValueForPlugin::ClassMethods)
data/test.rb CHANGED
@@ -255,7 +255,7 @@ class DefaultValuePluginTest < Test::Unit::TestCase
255
255
  assert_equal(["type"], object.changed)
256
256
  end
257
257
 
258
- def test_default_values_are_not_duplicated
258
+ def test_default_values_are_duplicated
259
259
  define_model_class do
260
260
  set_table_name "users"
261
261
  default_value_for :username, "hello"
@@ -263,7 +263,19 @@ class DefaultValuePluginTest < Test::Unit::TestCase
263
263
  user1 = TestClass.new
264
264
  user1.username << " world"
265
265
  user2 = TestClass.new
266
- assert_equal("hello world", user2.username)
266
+ assert_equal("hello", user2.username)
267
+ end
268
+
269
+ def test_default_values_are_shallow_copied
270
+ define_model_class do
271
+ set_table_name "users"
272
+ attr_accessor :hash
273
+ default_value_for :hash, { 1 => [] }
274
+ end
275
+ user1 = TestClass.new
276
+ user1.hash[1] << 1
277
+ user2 = TestClass.new
278
+ assert_equal([1], user2.hash[1])
267
279
  end
268
280
 
269
281
  def test_constructor_does_not_affect_the_hash_passed_to_it
metadata CHANGED
@@ -1,7 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: default_value_for
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ hash: 23
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
5
11
  platform: ruby
6
12
  authors:
7
13
  - Hongli Lai
@@ -9,7 +15,7 @@ autorequire:
9
15
  bindir: bin
10
16
  cert_chain: []
11
17
 
12
- date: 2009-11-20 00:00:00 +01:00
18
+ date: 2010-08-29 00:00:00 +02:00
13
19
  default_executable:
14
20
  dependencies: []
15
21
 
@@ -19,42 +25,46 @@ executables: []
19
25
 
20
26
  extensions: []
21
27
 
22
- extra_rdoc_files:
23
- - LICENSE.TXT
24
- - README.rdoc
28
+ extra_rdoc_files: []
29
+
25
30
  files:
26
- - .gitignore
31
+ - default_value_for.gemspec
32
+ - init.rb
27
33
  - LICENSE.TXT
28
- - README.rdoc
29
34
  - Rakefile
30
- - VERSION
31
- - init.rb
35
+ - README.rdoc
32
36
  - test.rb
33
37
  has_rdoc: true
34
38
  homepage: http://github.com/FooBarWidget/default_value_for
35
39
  licenses: []
36
40
 
37
41
  post_install_message:
38
- rdoc_options:
39
- - --charset=UTF-8
42
+ rdoc_options: []
43
+
40
44
  require_paths:
41
45
  - lib
42
46
  required_ruby_version: !ruby/object:Gem::Requirement
47
+ none: false
43
48
  requirements:
44
49
  - - ">="
45
50
  - !ruby/object:Gem::Version
51
+ hash: 3
52
+ segments:
53
+ - 0
46
54
  version: "0"
47
- version:
48
55
  required_rubygems_version: !ruby/object:Gem::Requirement
56
+ none: false
49
57
  requirements:
50
58
  - - ">="
51
59
  - !ruby/object:Gem::Version
60
+ hash: 3
61
+ segments:
62
+ - 0
52
63
  version: "0"
53
- version:
54
64
  requirements: []
55
65
 
56
66
  rubyforge_project:
57
- rubygems_version: 1.3.5
67
+ rubygems_version: 1.3.7
58
68
  signing_key:
59
69
  specification_version: 3
60
70
  summary: Provides a way to specify default values for ActiveRecord models
data/.gitignore DELETED
@@ -1 +0,0 @@
1
- test.sqlite3
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.1.0