【问题标题】:Building in model callback returning nil for value建立模型回调返回 nil 的值
【发布时间】:2013-05-25 13:56:19
【问题描述】:

首先,感谢您花时间阅读。我是 Rails 的新手,并且已经在这个问题上停留了好几个小时。

在我的 Rails 3.2 应用程序中,我有三个模型:用户、组织和成员资格(最后一个是用户和组织之间的连接模型)。

当用户创建组织时,他/她应该在创建时成为成员。因此,在我的组织模型中,我包含了一个用于构建成员资格的 before_create 回调。 问题在于,虽然在创建新组织时会构建 Membership,但 Membership 对象上的 user_id 设置为“nil”。因此当前用户不是成员。

回调中 user_id 属性中的硬编码实际上确实正确构建了成员资格,即(:user_id => "1"),但通常要求组织模型了解当前用户状态似乎是不好的 MVC 做法。

在新会员资格上设置当前用户 ID 的正确方法是什么?看来我的协会应该处理这个问题,但我可能错了。

这是我的模型——为了便于阅读,我省略了一些验证行。提前非常感谢。

user.rb

class User < ActiveRecord::Base
    has_many :memberships
    has_many :organizations, :through => :memberships
end

membership.rb

class Membership < ActiveRecord::Base
    belongs_to :user
    belongs_to :organization
end

organization.rb

class Organization < ActiveRecord::Base
    has_many :memberships
    has_many :users, :through => :memberships
    accepts_nested_attributes_for :memberships, :allow_destroy => true
    ...
    before_create :add_membership

    protected
    def add_membership
        self.memberships.build
    end
end

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-3 activerecord rails-activerecord


    【解决方案1】:

    你说得对,让你的模型神奇地了解当前用户是不好的 MVC 做法。所以你必须在创建过程中以某种方式传递当前的用户 ID。您可以通过多种方式做到这一点;例如在控制器中:

    def create 
      @organization = Organization.new( params[:organization] ) do |org|
        org.memberships.build( user_id: current_user.id )
      end
      # save, etc.
    end
    

    在控制器中执行此操作很好,但如果您的业务逻辑能够反映创建组织的用户应该自动属于该组织这一事实,那就更好了。您可以在Organization 上覆盖new 和/或create(或者如果您害怕覆盖,请创建自己的方法):

    def new( params = {}, options = {} )
      creator = options.delete( :creator )
      super( params, options ) do |org|
        org.memberships.build( user_id: creator.id ) if creator
        yield org if block_given?
      end
    end
    

    现在通过用户很容易:

    def create
      @organization = Organization.new(params[:organization], creator: current_user)
    end
    

    如果你不喜欢这种方式,或者你不想覆盖new或者创建特定的工厂方法,你也可以做类似nested_attributes的东西:

    attr_accessible :creator_id
    
    def creator_id=( user_id )
      memberships.build user_id: user_id
    end
    

    那么在你看来:

    f.hidden_field :creator_id, current_user.id
    

    可选

    使用第一种方法,为了更加清晰/易于使用,您还可以在 User 上创建一个方法:

    def new_organization( params = {}, options = {}, &block )
      Organization.new( params, options.merge(creator: self), &block )
    end
    

    ...好的,Organization 在这里是硬编码的(不好!)但是你的工作流程现在很容易理解了:

    def create
      # we know at first glance that the user is responsible for the organization
      # creation, and that there must be specific logic associated to this
      @organization = current_user.new_organization( params[:organization] )
      # etc
    end
    

    稍加思考,应该可以避免将Organization 硬编码为User(例如使用关联扩展)

    编辑

    为了能够对会员的组织存在进行验证,您需要这样做:

    class Organization < ActiveRecord::Base
      has_many :memberships, inverse_of: :organization
    end
    
    class Membership < ActiveRecord::Base
      belongs_to :organization, inverse_of: :memberships
    
      validates :organization, presence: true
    end
    

    让我们解释一下:

    • inverse_of 将您的关联设置为双向。默认情况下,关联是单向的,这意味着当您执行organization.memberships.first.organization 时,rails 会尝试再次加载组织,因为它不知道如何“爬回”关联。使用 inverse_of 时,rails 知道它不必重新加载组织。
    • validates 必须设置在 organization 而不是 organization_id。这样验证器就知道我们正在“爬回”关联,它知道organization 是一个“父”记录并且它正在被保存 - 所以它不会抱怨。

    【讨论】:

    • 非常感谢!这很有帮助,我正在学习很多。第一个解决方案没有通过验证,因为Membershiporganization_id 是空白的。我猜是因为组织还没有创建?我仍在研究其他解决方案。再次感谢。
    • 没关系,我能够删除该验证,并且仍然可以正确创建它。
    • 这是 nested_attributes 的常见问题:您想验证父记录的存在,但父记录尚不存在...有一种解决方法,我将编辑我的答案以向您展示如何做
    • 超级有趣。我从来不知道inverse_of。再次感谢!
    猜你喜欢
    • 1970-01-01
    • 2016-04-23
    • 2015-06-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-22
    • 2018-05-02
    相关资源
    最近更新 更多