如何将属性映射到 Rails 中的对象列表?

How to map an attribute into a list of objects in Rails?

我有四个模型。 EventInvitationProfileUser.

它们有如下关系:

我的问题是,当我以用户身份登录时,我可以在前端访问其 id,基于此,我想查询 [=15] 的列表=] 这样我就知道用户(他们的个人资料)是否已经被邀请或者他们仍然可以请求邀请。

我对我想要的内容的伪描述如下:获取所有事件,如果事件有邀请,请检查其中一个是否属于我用户的个人资料。如果是,请向该特定事件添加一个 applied = true 字段。 Return 所有事件,无论应用是真还是假

我想这是一个很好的 wherejoin 语句链,但我想我在这里用我的 Ruby 知识碰壁了。

感谢您的指导!

您想知道某个活动是否有邀请,其中任何一个都属于给定用户的个人资料,因此在 Event 上定义一个方法,它可以准确地告诉您:

# app/models/event.rb
class Event < ApplicationRecord
  def applied(user_id)
    invitations.any? do |invitation|
      invitation.profile.user_id == user_id
    end
  end
end

# app/controllers/events_controller.rb
class EventsController < ApplicationController
  def index
    @events = Event.all
    @current_user = current_user
  end
end

那么我想你想在类似这样的视图中显示列表:

# app/views/events/index.erb
<%= render @events %>

# app/views/events/_event.erb
<tr>
  <td> <%= event.name %> </td>
  <td> <%= event.applied(@current_user.id) %>
</tr>

现在有一些方法可以提高效率,例如预先加载,但这是基本的建议方法。这符合您的需求吗?

我会写出代码,这样设置就清楚了。

class Invitation < ApplicationRecord
  belongs_to :event
  belongs_to :profile
end

class Event < ApplicationRecord
  has_many :invitations
end

class Profile < ApplicationRecord
  has_many :invitations
  belongs_to :user
end

class User < ApplicationRecord
  has_many :profiles
end

如果您想了解某个活动的用户,请使用 has_many :through 通过另一个关联访问一个关联。首先,我们设置个人资料和活动,通过他们的邀请相互了解。

class Event < ApplicationRecord
  has_many :invitations
  has_many :profiles, through: :invitations
end

class Profile < ApplicationRecord
  belongs_to :user
  has_many :invitations
  has_many :events, through: :invitations
end

现在我们可以设置用户通过个人资料获得邀请,通过邀请获得活动。

class User < ApplicationRecord
  has_many :profiles
  has_many :invitations, through: :profiles
  has_many :events, through: :invitations
end

我们也为 Event 做同样的事情,通过个人资料找到它的用户。

class Event < ApplicationRecord
  has_many :invitations
  has_many :profiles, through: :invitations
  has_many :users, through: :profiles
end

现在活动可以找到他们的用户,用户也可以找到他们的活动。

users = event.users
events = users.events

Rails 现在知道如何从事件加入用户,使您的任务和许多其他任务变得更加轻松。

您要避免的是必须将每个事件加载到内存中。您还希望避免在查询事件列表时使用 N+1 查询,然后查询每个事件是否与您的用户匹配。那会变得很慢。我们想要获取所有事件并检查它们是否已在单个查询中应用。

首先,我们将向名为 applied 的事件添加一个伪造的列属性。这让我们可以存储一些来自查询的额外信息。

class Event < ApplicationRecord
  has_many :invitations
  has_many :profiles, through: :invitations
  has_many :users, through: :profiles

  attribute :applied, :boolean
end

然后编写填充它的查询。我们可以利用刚才建立的Event -> User关系,直接和User一起加入Event。

# We can't use bind parameters in select, quoting will have to do.
user_id = ActiveRecord::Base.connection.quote(user_id)

@events = Event
  # A left outer join ensures we get all events, even ones without users.
  .left_outer_joins(:users)
  .select(
    # Grab all of event's columns plus...
    "events.*",

    # Populate applied.
    "(users.id = #{user.id}) as applied"
  )

现在您可以在单个查询中高效地遍历事件,而无需将它们全部加载到内存中。

@events.find_each { |event|
  do_something if event.applied
}

画龙点睛的是将其打包为scope以便于使用。

class Event < ApplicationRecord
  has_many :invitations
  has_many :profiles, through: :invitations
  has_many :users, through: :profiles

  attribute :applied, :boolean

  scope :applied_to_user, ->(user_id) {
    # We can't use bind parameters in select, quoting will have to do.
    user_id = ActiveRecord::Base.connection.quote(user_id)

    left_outer_joins(:users)
      .select(
        # Grab all of event's columns plus...
        "events.*",
    
        # Populate applied.
        "(users.id = #{user.id}) as applied"
      )
  }
end

Event.applied_to_user(user_id).find_each { |event|
  ...
}