Skip to content

Commit

Permalink
Allow proc defaults with the Attributes API
Browse files Browse the repository at this point in the history
This is a variant implementation of the changes proposed in #19914.
Unlike that PR, the change in behavior is isolated in its own class.
This is to prevent wonky behavior if a Proc is assigned outside of the
default, and it is a natural place to place the behavior required by #19921
as well.

Close #19914.

[Sean Griffin & Kir Shatrov]
  • Loading branch information
sgrif committed May 28, 2015
1 parent 73aab03 commit a6e3cda
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 1 deletion.
19 changes: 19 additions & 0 deletions activerecord/lib/active_record/attribute/user_provided_default.rb
@@ -0,0 +1,19 @@
require 'active_record/attribute'

module ActiveRecord
class Attribute # :nodoc:
class UserProvidedDefault < FromUser
def initialize(name, value, type)
super(name, value, type)
end

def type_cast(value)
if value.is_a?(Proc)
super(value.call)
else
super
end
end
end
end
end
8 changes: 7 additions & 1 deletion activerecord/lib/active_record/attributes.rb
@@ -1,3 +1,5 @@
require 'active_record/attribute/user_provided_default'

module ActiveRecord
# See ActiveRecord::Attributes::ClassMethods for documentation
module Attributes
Expand Down Expand Up @@ -236,7 +238,11 @@ def define_default_attribute(name, value, type, from_user:)
if value == NO_DEFAULT_PROVIDED
default_attribute = _default_attributes[name].with_type(type)
elsif from_user
default_attribute = Attribute.from_user(name, value, type)
default_attribute = Attribute::UserProvidedDefault.new(
name,
value,
type,
)
else
default_attribute = Attribute.from_database(name, value, type)
end
Expand Down
10 changes: 10 additions & 0 deletions activerecord/test/cases/attributes_test.rb
Expand Up @@ -125,6 +125,16 @@ def deserialize(*)
assert_equal "from user", model.wibble
end

test "procs for default values" do
klass = Class.new(OverloadedType) do
@@counter = 0
attribute :counter, :integer, default: -> { @@counter += 1 }
end

assert_equal 1, klass.new.counter
assert_equal 2, klass.new.counter
end

if current_adapter?(:PostgreSQLAdapter)
test "arrays types can be specified" do
klass = Class.new(OverloadedType) do
Expand Down

3 comments on commit a6e3cda

@PikachuEXE
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the proc call instance method without receiver?
It's not clear with current test

@sgrif
Copy link
Contributor Author

@sgrif sgrif commented on a6e3cda May 30, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the proc doesn't get instance_eval ed, as it's meant to be a class level default. You should use after_initialize if you need more complex behavior.

@azyzio
Copy link

@azyzio azyzio commented on a6e3cda Jan 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @sgrif , I know it's an old commit but thanks to it I just realized we can't use instance method in the proc call :) Maybe it would be worth putting it in the documentation

Please sign in to comment.