在模型回调中构建,返回值为nil

时间:2013-05-25 13:56:19

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

首先,感谢您花时间阅读。我是Rails的新手,并且已经坚持了几个小时。

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

当用户创建组织时,他/她应该在创建时成为成员。因此,在我的组织模型中,我已经包含了一个构建成员身份的before_create回调。 问题在于,在创建新组织时构建成员资格时,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

1 个答案:

答案 0 :(得分:0)

你是正确的,允许你的模型神奇地了解当前用户是不好的MVC实践。因此,您必须以某种方式在创建期间传递当前用户ID。你可以通过多种方式做到这一点;例如在控制器中:

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

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

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会再次尝试加载组织,因为它不知道如何“回归”#34;该协会。使用inverse_of时,rails知道它不必重新加载组织。
  • validates必须在organization而非organization_id上设置。通过这种方式,验证者知道我们已经回归&#34;该协会,它知道organization是&#34;父母&#34;记录并且它正在被保存 - 所以它不会抱怨。