新手问题 has_one 的疑问

joezhang · April 29, 2015 · Last by joezhang replied at April 30, 2015 · 2561 hits

之前一直没用过 has_one,查看了 Rails 文档也搞不清楚。想请问一下为什么 User role 更改了却保存不了?谢谢了!

class User < ActiveRecord::Base
  has_one :profile, dependent: :destroy
  after_initialize :default_value
  private
    def default_value
      @profile = self.build_profile
      @profile.role ||= "User"
      @profile.avatar ||= "" 
    end
end

class Profile < ActiveRecord::Base
  belongs_to :user
end
irb(main):001:0> User.first.profile
=> #<Profile id: nil, role: "User", avatar: "", user_id: 1, created_at: nil, updated_at: nil>
irb(main):002:0> User.first.profile.role = "Admin"
=> "Admin"
irb(main):003:0> User.first.profile.save
=> true
irb(main):004:0> User.first.profile
=> #<Profile id: nil, role: "User", avatar: "", user_id: 1, created_at: nil, updated_at: nil>

你没有建立 reference 呀,回头认真看下 rails 入门

顶楼代码查询了 4 次 User.first,创建了 4 个不同的 profile 对象。

user = User.first
user.profile.role = 'Admin'
user.profile.save

after_initialize 每次创建对象都会执行,覆盖掉已有的值,建议换其它 callback。

@jasontang168 谢谢你,以下是 db migration code, 应该已经加上 reference 了!

class CreateProfiles < ActiveRecord::Migration
  def change
    create_table :profiles do |t|
      t.string :role
      t.string :avatar
      t.references :user, index: true

      t.timestamps
    end
  end
end

@rei 谢谢,就是这个问题。每次都被覆盖掉。 题外话:我以前一直把 role, avatar...等等都加在 User 表中,可以直接定义缺省值,现在是想把这些字段分离到 Pofile 表中,这样的实现思路对不对呢?

#5 楼 @joezhang 就这两个字段,似乎没必要拆表。

由于每次执行的是 User.first.profile,所以每次都是在生成新的 User 对象,就也一直在 build 一个 Profile 对象 而一旦 build 新的 Profile 对象,由于 has_one 的原因,又会触发数据库的 update,把原有的 profile 的 user_id 置空

另外不知道下面的代码是不是你要的那种效果

class User < ActiveRecord::Base
  has_one :profile
  after_initialize :default_value

  private
    def default_value
      build_profile if profile.nil?
      profile.role ||= "User"
      profile.avatar ||= ""
    end
end

@rei 不止两个字段,这里是为了方便举例,主要是考虑到以后还会越来越多,而且使用 Device gem,所以不想把过多的 User Profile 字段加到 User 表。:-)

不是应该用 after_save 这个吗?

@wuwx 谢谢,这正是我想要的那种效果,已经加入我的代码中了。之前是没想到每次都会触发数据库的 update。也谢谢 Rei 之前的提醒。

@winnie 我以前一直把 role, avatar...等等都加在 User 表中,直接定义缺省值,现在是把这些字段分离到 Pofile 表中,如果用 after_save,就要更改程序中引用到 role,avatar...的地方,做一个预判断。使用 wuwx 提供的方案就不需要过多更改现有的程序。

after_create,看你的需求应该只是想初始化的时候创建 profile,你可以 google after_createafter_save的区别,after_create只在对象创建的时候只执行一次

class User < ActiveRecord::Base
  has_one :profile, dependent: :destroy
  after_create :build_profile
  private
    def build_profile
      Profile.create!(user_id: self.id, role: 'Admin', avatar: 'ooxx.png')
    end
end

class Profile < ActiveRecord::Base
  belongs_to :user
end

#12 楼 @flowerwrong has_one 已经添加了一个 build_profile 方法,最好不要覆盖引起歧义。

我喜欢 after_create :create_profile,字段默认值在数据库里指定(role),或者给 Profile 加 getter 方法(avatar)。

@rei 最终确定以下方案,谢谢大家!

class User < ActiveRecord::Base
  has_one :profile
  after_create :create_profile
end

class Profile < ActiveRecord::Base
  belongs_to :user
  befoe_create :default_values
  private
    def default_values
      self.role ||= "User"
    end
end

#14 楼 @joezhang 还有,after_create 再设置默认值已经晚了,不会被保存。用 before_create,另外要用 self.role,不然变成局部变量。

@rei 嗯,根据你的建议,已经更改了#14 楼的代码,很简洁明了,谢谢了!

You need to Sign in before reply, if you don't have an account, please Sign up first.