如何通过在连接表中包含附加属性的关系查询has_many?

时间:2014-04-11 16:17:38

标签: sql ruby-on-rails

情况

在我的应用程序中,用户可以创建计划。创建计划后,用户可以定义计划的利益相关者/团队成员。每个团队成员都成为一名负责人。有许多计划,用户可以成为多个计划的利益相关者,并且在每个计划中他们都有不同的责任。

示例

管理员创建计划并将10个用户指定为利益相关者。 1是问责,2是负责,7只需要通知

到目前为止我做了什么

我在两个模型之间建立了一个has_many关系:

class User < ActiveRecord::Base
  has_many :assignments
  has_many :plans, through: :assignments
end

class Plan < ActiveRecord::Base
  has_many :assignments
  has_many :users, through: :assignments
end

分配表如下所示:

    create_table :assignments do |t|
        t.belongs_to :user
        t.belongs_to :plan
        t.string :responsibility
    end

    add_index :assignments, [:user_id, :plan_id]

责任包含4个不同值中的一个(负责,负责,知情,咨询。)

我在找什么

我知道如何查询已分配给该计划的所有用户(@plan.users.to_a),但我不知道如何能够额外补充用户信息以及他们在此计划中的责任。

我需要的查询是:

通过查看分配表来选择属于计划X的用户。不要只使用赋值表来标识用户,还要从赋值表中的责任列中获取值并返回包含的数组:

  • user.first_name
  • user.last_name
  • user.responsibility(针对此特定计划)

2 个答案:

答案 0 :(得分:1)

我们有这个确切的要求,并以两种方式解决它:


<强> Use an SQL Alias Column

第一种方法是使用SQL Alias Column&amp;将其附加到您的has_many关联中,如下所示:

Class User < ActiveRecord::Base
  has_many :assignments
  has_many :plans, -> { select("#{User.table_name}.*, #{Plan.table_name}.responsibility AS responsibility") }, through: :assignments, dependent: :destroy
end

这将允许您拨打@user.plans.first.responsibility,如果没有记录,将会正常失败


使用ActiveRecord Association Extensions

这是最好但更复杂的方式,因为它在内存中使用proxy_association对象(而不是执行另一个DB请求)。这个剧本花了我们两个星期的时间来创作,所以我们为此感到非常自豪!未经Rails 4.1测试:

#app/models/user.rb
Class User < ActiveRecord::Base
    has_many :assignments
    has_many :plans, through: :assignments, extend: Responsibility
end

#app/models/plan.rb
Class Plan < ActiveRecord::Base
    attr_accessor :responsibility
end

#app/models/concerns/Responsibility.rb
module Responsibility

    #Load
    def load
        captions.each do |caption|
            proxy_association.target << responsibility
        end
    end

    #Private
    private

    #Captions
    def captions
        return_array = []
        through_collection.each_with_index do |through,i|
            associate = through.send(reflection_name)
            associate.assign_attributes({responsibility: items[i]}) if items[i].present?
            return_array.concat Array.new(1).fill( associate )
        end
        return_array
    end

    #######################
    #      Variables      #
    #######################

    #Association
    def reflection_name
        proxy_association.source_reflection.name
    end

    #Foreign Key
    def through_source_key
        proxy_association.reflection.source_reflection.foreign_key
    end

    #Primary Key
    def through_primary_key
        proxy_association.reflection.through_reflection.active_record_primary_key
    end

    #Through Name
    def through_name
        proxy_association.reflection.through_reflection.name
    end

    #Through
    def through_collection
        proxy_association.owner.send through_name
    end

    #Responsibilities
    def items
        through_collection.map(&:responsibility)
    end

    #Target
    def target_collection
        #load_target
        proxy_association.target
    end

end

答案 1 :(得分:0)

查询约会表,直接筛选当前计划中的所有用户:

Appointement.select(:id).where(user_id: Plan.find(params[:id]).users.pluck(:user_id), plan_id: params[:id]).group(:id).having('count(*) = ?', Plan.find(params[:id]).users.count)