【问题标题】:In Rails, how can I create group of users as another association, such as "members"?在 Rails 中,如何将用户组创建为另一个关联,例如“成员”?
【发布时间】:2012-08-02 14:11:30
【问题描述】:

我正在尝试在两个现有模型UserDwelling 之间创建特殊关系。 Dwelling 在创建时只有一个所有者(Dwelling belongs_to :userUser has_one :dwelling)。但是其他用户可以添加到这个DwellingRoomies(现在没有为此创建模型,Roomie 是一个概念关系)。

我认为我不需要单独的模型,而是需要与现有模型建立特殊关系,但我可能错了。我认为需要使用Users 表中的user_id 进行引用。我不确定从哪里开始。感谢您的所有帮助!

例如:

Dwelling1 
  user_id: 1 
  roomies: [1, 2, 3, 4]

其中 1、2、3、4 是 user_id。

更新模型

Dwelling Model

# dwelling.rb
class Dwelling < ActiveRecord::Base
    attr_accessible :street_address, :city, :state, :zip, :nickname

    belongs_to :owner, :class_name => "User", :foreign_key => "owner_id"
    has_many :roomies, :class_name => "User"

    validates :street_address, presence: true
    validates :city, presence: true
    validates :state, presence: true
    validates :zip, presence: true

end

User Model

# user.rb
class User < ActiveRecord::Base
  attr_accessible :email, :first_name, :last_name, :password, :password_confirmation, :zip
  has_secure_password

  before_save { |user| user.email = email.downcase }
  before_save :create_remember_token

  belongs_to :dwelling
  has_many :properties, :class_name => "Dwelling", :foreign_key => "owner_id"

  validates :first_name, presence: true, length: { maximum: 50 }
  ...

Updated Dwelling Create Action

#dwellings_controller.rb
...
def create
@dwelling = current_user.properties.build(params[:dwelling])

  if @dwelling.save
    current_user.dwelling = @dwelling
    if current_user.save
      flash[:success] = "Woohoo! Your dwelling has been created. Welcome home!"
      redirect_to current_user
    else
      render 'new'
    end
  end
end
...

【问题讨论】:

  • 非常感谢@MurifoX 清理我的问题格式!

标签: ruby-on-rails associations


【解决方案1】:

我的回答假设您只希望 user 在一个 dwelling 中成为 roomie。如果您希望user 成为多个dwellingroomie,我认为@ari 的答案很好,尽管我可能会选择has_and_belongs_to_many 而不是has_many :through

现在我的答案是:

我会将其设置为 belongs_to 为业主和has_many 室友(可能包括业主,但不一定)。

您可以将User 模型用于ownersroomies。您不需要任何额外的表或模型,您只需要使用:class_name:foreign_key 选项设置正确的关系。

在您的Dwelling 模型中:

# dwelling.rb
belongs_to :owner, :class_name => "User", :foreign_key => "owner_id"
has_many :roomies, :class_name => "User"

在您的User 模型中:

# user.rb
belongs_to :dwelling # This is where the user lives
has_many :properties, :class_name => "Dwelling", :foreign_key => "owner_id"  # This is the dwellings the user owns

在您的dwellings 表中,您需要一个owner_id 列来存储所有者的user_id

在您的users 表中,您需要一个dwelling_id 来存储用户居住的住宅的dwelling_id

在 cmets 中回答您关于控制器的问题:

如果您想将current_user 设置为新住宅的所有者,请执行以下操作:

@dwelling = current_user.properties.build(params[:dwelling])
....

如果您想将current_user 设置为新住宅的所有者和室友,请执行以下操作:

@dwelling = current_user.properties.build(params[:dwelling]

if @dwelling.save
  current_user.dwelling = @dwelling
  if current_user.save
     # flash and redirect go here        
  else
     # It's not clear why this wouldn't save, but you'll to determine
     # What to do in such a case.
  end
else
   ...
end

上面最棘手的部分是处理住宅有效并保存的情况,但由于某些不相关的原因,current_user 无法保存。根据您的应用程序,您可能希望住宅无论如何保存,即使您不能将current_user 分配为室友。或者,您可能希望不保存住宅 --- 如果是这样,您需要使用模型交易,这超出了本问题的范围。

您的控制器代码不起作用,因为保存住宅实际上并没有更新current_user 记录以存储dwelling_id。您的代码将等同于以下内容:

@dwelling = Dwelling.new(params[:dwelling])
current_user.dwelling = @dwelling
if @dwelling.save
   ...

注意current_user 永远不会被保存,所以current_user.dwelling = @dwelling 行没有用。

这可能看起来违反直觉,但最重要的是build_dwelling 实际上并没有像您预期的那样在内存中设置东西。如果您保存正在构建的模型来自而不是您正在构建的模型,您将获得更直观的结果:

@dwelling = current_user.build_dwelling(params[:dwelling])
if current_user.save # This will save the dwelling (if it is valid)

但是,除非您为关联打开:autosave,否则此(默认情况下)不会保存住所,如果它有验证错误,这也有点超出了这个问题的范围。我真的不推荐这种方法。

更新:

这里有更详细的代码sn -p:**

# dwellings_controller.rb

def create
  @dwelling = current_user.properties.build(params[:dwelling])

  if @dwelling.save
    # The current user is now the owner, but we also want to try to assign
    # his as a roomie:
    current_user.dwelling = @dwelling
    if current_user.save
      flash[:notice] = "You have successfully created a dwelling"
    else
       # For some reason, current_user couldn't be assigned as a roomie at the 
       # dwelling.  This could be for several reasons such as validations on the
       # user model that prevent the current_user from being saved.
       flash[:notice] = "You have successfully created a dwelling, but we could not assign you to it as a roomie"
    end
    redirect_to current_user
  else
    # Dwelling could not be saved, so re-display the creation form:
    render :new
  end
end

当一个住宅成功保存时,当前用户将成为所有者(数据库中的owner_id)。但是,如果current_user 没有保存,您需要决定您的应用程序应该如何响应。在上面的示例中,我允许保存住宅(即我不回滚它的创建),但我通知用户他不能被分配为室友。发生这种情况时,很可能是您的应用程序中的其他代码导致了问题。您可以检查current_user 的错误以了解原因。或者,您可以暂时使用current_user.save! 而不是current_user.save 进行故障排除。

另一种方法是在Dwelling 模型中使用after_create 回调。在许多方面,这将是一种更清洁、更简单的方法。但是,捕获无法保存current_user 的情况可能比上述方法更难看,具体取决于您要如何处理。

我相信底线是current_user.save 代码导致了一些问题。您需要诊断原因,然后确定您的应用程序在这种情况下应该做什么。有几种方法可以处理这个问题,至少包括以下这些

  1. 将所有内容放入事务块中,并使用current_use.save! 而不是current_user.save,这样会引发异常并且不会保存用户或住所。
  2. 保存住所,但通知用户他不是室友(如上)
  3. 不要保存 current_user,而是使用update_column(这样可以避免回调、验证等)。

我相信您当前遇到的问题与最初的问题基本无关。如果您需要进一步的帮助,最好将其作为一个单独的问题分开。

【讨论】:

  • 我已经更新了上面的问题,以反映UserDwelling 模型的更新。我还在users 中添加了dwelling_id 列,在dwellings 中添加了owner_id 列。除非我遗漏了其他东西,否则看起来解决方案可能不完整。我可以通过网络界面成功创建Dwelling。但是,创建后,这些关系都不存在。 user.dwellingdwelling.ownernildwelling.roomies 返回一个空数组。这两种关系都应该是belongs_to吗?我已经为 Dwellings 添加了我的 create 操作。
  • @justinraczak:关联是正确的。当您使用build_dwelling@dwelling.save 时,就像创建一个新住宅,然后将该住宅分配给current_user 作为他的家。这是他在内存中的家,但您的控制器永远不会将该事实保存到数据库中。我会用更好的方法来更新我的答案来设置控制器操作。
  • @Nathan 我想我可能没有正确实施解决方案。我已经更新了上面的Dwelling 创建操作。但不可否认,我对修改后的答案中的各种 sn-ps 有点困惑,所以如果我没有完全正确,我深表歉意。现在,在创建之后,页面在Dwelling 表单上重新加载,但似乎处于更新状态而不是创建状态。当我在控制台中查询dwelling 时,dwelling 确实有owner_id,但user 没有dwelling_id。我是否混淆了解决方案?
  • @justinraczak:听起来Dwelling 正在被保存,但 current_user 没有被保存,因此您的控制器正在为保存的Dwelling 对象呈现new 视图,这取决于关于您的视图是如何编码的,可能与Dwelling 的更新表单实际上相同。如果是这种情况,您会想弄清楚为什么 current_user 没有被保存。在任何情况下,如果用户不保存,您可能不想为Dwelling 呈现new 视图。我会用更多建议更新我的答案。
  • @Nathan:感谢您到目前为止提供的所有帮助。通过您的实现,我可以肯定地看到问题实际上发生在保存用户的某个地方,因为正在触发 create 方法的 else 分支。我打开了一个新问题,希望能在这里正确表达问题:[链接]stackoverflow.com/questions/11812834/…。再次感谢您的所有帮助,非常感谢。
【解决方案2】:

您可以通过将 Roomie id 作为列存储在 Dwelling 中来做到这一点

进行迁移:

class AddRoomiesToDwelling < ActiveRecord::Migration
  def self.up
    add_column :dwelling, :roomies, :text
  end

  def self.down
    remove_column :dwelling, :roomies
  end
end

在您的住宅模型中:

class Dwelling < ActiveRecord::Base
  serialize :roomies
end

然后您可以使用以下命令设置房间 ID:

roomie_ids = [1, 2, 3, 4]  
@dwelling.roomies = {:ids => roomie_ids}
@dwelling.save!

取自this在文本列中保存数组、哈希和其他不可映射的对象部分

【讨论】:

  • 如果您以这种方式进行设置,您将无法轻松确定特定室友住在哪个住宅。您将必须加载所有住宅并取消序列化每个住宅的房间列表。
  • 虽然这可能是一种可行的方法,但它似乎忽略了首先拥有关系数据库的价值。无意冒犯@Sabar,老实说,我认为这是一个糟糕的解决方案。
  • 没有采取,我只是个人不喜欢为这种关系创建另一个模型
  • 嘿@Nathan,这正是我对这种方法的关注。在高层次上,此应用程序管理室友的生活状况 - 账单、分摊费用/购买、财产等。因此,所有实体/模型之间的关系需要明确且支持 Rails。 @Sabar,非常感谢您的建议,如果只是松散地跟踪关系,那绝对是有道理的。但我需要启用Dwelling.roomiesRoomie.dwelling 等功能。
【解决方案3】:

您有两种可能的选择。 根据您的计划,住宅拥有_one 所有者而不是所有者拥有一个住宅可能更清楚。那么住宅也将能够拥有用户。您可以在 User 中添加一个名为dwelling_id 的列,然后您可以做住宅has_many 用户。

另一种选择是使用"has_many through" association。这意味着您需要创建一个新模型来跟踪此关联,例如“Relationship.rb”,该模型将同时属于 User 和 Dwelling(并为它们都有列)。然后你就可以写出这样的代码了:

//in Dwelling.rb
has_many :roomies, through: :relationships, source: :user

//in User.rb
has_many :dwellings, through: :relationships

这将使用户还可以加入多个住宅。

【讨论】:

  • 嗨,阿里,非常感谢您的建议。我想我将尝试走第二条路线,通过室友创建第二张桌子来容纳两者之间的关系。在这种情况下,Roomie_Relationship.rb 模型会是一个空模型吗?
  • 另外,我是否需要向我的User 模型添加任何内容才能启用此功能?上面的实现出现以下错误:ActiveRecord::HasManyThroughAssociationNotFoundError: Could not find the association :dwelling_roomie_relationships in model Dwelling 我已添加到我的 Dwelling 模型中:belongs_to :user has_many :roomies, through: :dwelling_roomie_relationships, source: :userdwelling_roomie_relationships.rb 是一个空模型。
  • 关系将需要为每个列。您还需要向 User 模型添加类似的代码,请参阅我的编辑。阅读我的链接以了解有关该主题的更多信息。
  • +1 如果您打算让给定的user 成为多个roomieroomie,这是一个很好的答案。我提供的答案只有在任何给定的user 是一个roomie 时才有效。当然,一个简单的 HABTM 关系与一个简单的连接表会更简单和更直接,除非您需要该关系的附加属性。当has_and_belongs_to_many 足够时使用has_many :through 是有争议的。
猜你喜欢
  • 1970-01-01
  • 2013-02-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多