如何在Rails中使用值初始化ActiveRecord?

30

在普通的Java中,我会使用:

public User(String name, String email) {
  this.name = name;
  this.email = f(email);
  this.admin = false;
}

然而,在Rails(3.2.3)中,我找不到一个简单标准的方法来使用ActiveRecords。
1. 重写initialize函数
def initialize(attributes = {}, options = {})
  @name  = attributes[:name]
  @email = f(attributes[:email])
  @admin = false
end

当从数据库创建记录时,可能会被忽略。

2. 使用after_initialize回调函数

通过覆盖它:

def after_initialize(attributes = {}, options = {})
  ...
end

或者使用宏:
after_initialize : my_own_little_init
def my_own_little_init(attributes = {}, options = {})
  ...
end

但可能会存在一些废弃问题

在SO中还有其他链接,但它们可能过时了


所以,使用正确/标准的方法是什么?

4
您无需编写任何自定义代码即可完成此操作: User.new(:name => 'Bon', :email => 'bob@example.com')。您是想以不同的方式使用它吗? - Dylan Markow
你说得对。我想我在问默认值,而不是在创建时传递的初始值。 - Asaf
1
仅返回翻译后的文本:或在构建时对给定输入进行一些操作 - Asaf
5个回答

22

当默认值适用于所有记录时,您应该在模式中定义它们。

def change
  creates_table :posts do |t|
    t.boolean :published, default: false
    t.string :title
    t.text :content
    t.references :author
    t.timestamps
  end
end

在这里,每个新的帖子都将被设置为未发布。如果您希望在对象级别上使用默认值,最好使用工厂模式实现:
User.build_admin(params)

def self.build_admin(params)
  user = User.new(params)
  user.admin = true
  user
end

所以基本上,如果我想在所有用户实例(创建时)中拥有规范化的电子邮件,我应该使用一个 def self.build_normalized(params) 工厂方法?这将把找到正确的创建方法的工作传递给我的代码客户端。但它并不能涵盖所有执行路径。此外,在Rails中是否有可用的工厂实用程序或标准模式?谢谢。 - Asaf
如果我们在模式定义中放置默认值,并将任何其他初始化逻辑放置在对象本身中,那么这不是将一个关注点分散到代码的不同位置(也许是抽象级别)吗? - Asaf
@Asaf -- 我会使用db init values或工厂模式,而不是两者都用。 - Jesse Wolgamott

16
根据Rails指南,最好的方法是使用after_initialize来完成这个操作。因为在初始化中我们必须声明super,所以最好使用回调函数。

2
链接已失效,after_create 是新的 after_initialize 吗? - James McMahon
@JamesMcMahon 不,我们仍然有 after_initialize。我更新了文档链接。 - Mauro George
1
近年来,我几乎完全避免使用AR回调函数。对我而言,它们使测试更加困难,也让我的模型变得更加复杂。我希望我的模型非常简单。 - bennick
after_initialize 在创建之前运行,并且在实例化记录时也会运行,例如,在使用 Thing.find(1) 查找记录后。如果您希望它仅对新记录运行,可以像这样操作:after_initialize :do_something, if: :new_record? - Joshua Pinter

5

我喜欢的一种解决方案是通过scopes:

class User ...
   scope :admins, where(admin: true)

那么你可以做到两全其美: 通过 User.admins.new(...) 在管理员状态下创建新用户(即 admin==true),以及通过相同方式 User.admins 获取所有管理员。

你可以创建几个查询范围并使用其中一些作为创建/搜索的模板。同时,你也可以使用 default_scope 达到相同的效果,但它没有名称并且默认应用。


使用default_scope会有什么问题吗?构造函数中的其他参数操作呢? - Asaf
1
如果你正在处理评论模型并希望为FBI保存已删除的评论,那么这将非常有用 :) 只需设置default_scope为where(deleted: false),除非你直接通过Comment.where(deleted: true)Comment.unscoped方法覆盖作用域,否则你永远不会遇到已删除的答案。还有其他方法,但我喜欢这个 :) - jdoe
我对作用域有一些了解,但只是为了查找,而不是创建。然而,我正在寻找一种正确初始化我的对象的方法,而不是将它们“分区”到作用域中... - Asaf
default_scopes 对于指定必须与当前模型一起加载的相关记录也非常有用。如果您不熟悉,它被称为贪婪加载。如果您发现自己一遍又一遍地加载相关模型并循环遍历它们,那么您可以通过这种方式显着减少 DB-hits:default_scope include: :tags。但那是另外一个故事。 :) - jdoe
10x... 我已经与Hibernate的注解战斗了很久,现在我立刻去寻找等效的方法 :) - Asaf

4

这将适用于Rails 4。

def initialize(params)
    super
    params[:name] = params[:name] + "xyz" 
    write_attribute(:name, params[:name]) 
    write_attribute(:some_other_field, "stuff")
    write_attribute(:email, params[:email])
    write_attribute(:admin, false)
end

4
我今天早上在寻找类似的东西。虽然在数据库中设置默认值显然可以工作,但它似乎违反了Rails的约定,即由应用程序处理数据完整性(因此也包括默认值)。
我偶然发现了这个post。由于您可能不想立即将记录保存到数据库中,我认为最好的方法是通过调用write_attribute()来覆盖初始化方法。
def initialize
  super
  write_attribute(name, "John Doe")
  write_attribute(email,  f(email))
  write_attribute(admin, false)
end

2
由于某些原因,如果您尝试执行类似Model.new(param1: arg1, param2: arg2)的操作,将会出现"ArgumentError: wrong number of arguments (1 for 0)"错误。看起来Mauro的答案更好,如果您想要动态编写属性(例如随机键)。 - concept47
我同意。你可能应该使用after_initialize。 - clekstro
其实,对于我所需的功能,after_create 的效果更好。当我使用 after_initialize 时,每次在具有随机属性的模型上执行 .find 操作时,值都会发生变化,而这并不是我想要的。 - concept47
它不起作用是因为方法签名错误,只需要声明一个可选参数即可。 - Eduardo
我在下一个答案建议中添加了可选参数的示例。 - Harry Fairbanks

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接