【问题标题】:ActiveRecord finding record by relations requiring all attributesActiveRecord 通过需要所有属性的关系查找记录
【发布时间】:2015-11-25 06:57:54
【问题描述】:

好的,所以我是 ruby​​ 和 rails 的新手,我的问题可能会显示出来。

我正在编写一个有趣的应用程序,并且有一个关于在 HABTM 关系中查找记录的特别奇怪的问题。底层数据库是Postgresql

我的模型如下所示

class Family < ActiveRecord::Base
  has_and_belongs_to_many :persons
end

class Person < ActiveRecord::Base
  has_and_belongs_to_many :families
end

假设我有 4 个人“乔”、“简”、“玛丽”、“本”。他们都可以属于多个家庭。

Family 1 << 'Joe', 'Jane', 'Ben'

Family 2 << 'Jane', 'Ben'

Family 3 << 'Joe', 'Jane', 'Ben', 'Mary'

我希望能够通过搜索他们的名字找到家人

这就是我的查询当前的样子

Family.joins(:persons).where(persons: {name:['Joe','Jane','Ben']})

这非常适合查找包含 Joe、Jane 或 Ben(所有家庭)的所有记录,但不适用于返回只有 Joe、Jane 和 Ben 的记录。

在我的示例中,我只查找家庭 1,而不是其他 2。

我如何确保只找到包含所有名称的记录,不多也不少。

是否有更好的查询要编写或者我应该重新考虑我的数据库结构?

【问题讨论】:

    标签: ruby-on-rails ruby postgresql activerecord


    【解决方案1】:

    我会这样设置模型:

    class Family < ActiveRecord::Base
      has_many :persons
    end
    
    class Person < ActiveRecord::Base
      belongs_to :family
    end
    

    然后将family_id添加到family表中。

    现在您可以拨打family.first.persons

    或者

    Family.where(id: 1).first.persons
    

    另外,如果您觉得这种方法不合适,那么我建议您使用has_many :through 而不是has_many_and_belongs_to

    这里有更多信息: http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association

    【讨论】:

      【解决方案2】:

      所以在对#rubyonrails irc 进行了一些挖掘和一些帮助之后,我想出了这个解决方案。

      class Family < ActiveRecord::Base
      
        def self.find_by_person_names(persons = [])
          family = nil
          families = Family.joins(:persons).where(persons: {name:persons})
          families.each do |f|
            if f.persons.names.sort == persons.sort
              family = f
            end
          end
          return family
        end
      end
      

      【讨论】:

        【解决方案3】:

        我也认为 has_many through 关系更适合:has_and_belongs_to_many vs has_many through

        这是一个不需要在 ruby​​ 中过滤结果的选项:

        1. 选择其中包含每个所需人员的所有 FamilyPerson 对象
        2. 选择所有 FamilyPerson 对象中的人不是所需的人
        3. 在第一个查询结果而不是第二个查询结果中选择所有具有 ID 的 Family 对象 - 换句话说,那些拥有所有必要人员但没有更多人的家庭

        实施

        app/models/family.rb

        class Family < ActiveRecord::Base
          has_many :family_people
          has_many :people, through: :family_people
        
          def self.all_with_members members
            member_ids = members.collect {|m| m.id }
        
            # FamilyPersons that have each of the members in it
            enough = FamilyPerson.where(person_id: member_ids).group("family_id").having("count(person_id) >= ?", member_ids.count).collect { |fp| fp.family_id }
        
            # FamilyPersons that have no additional members
            too_many = FamilyPerson.where("person_id NOT IN(?)", member_ids).group("family_id").having("count(person_id) > 0").collect { |fp| fp.family_id }
        
            Family.find enough - too_many
          end
        end
        

        app/models/person.rb

        class Person < ActiveRecord::Base
          has_many :family_people
          has_many :families, through: :family_people
        end  
        

        app/models/family_person.rb

        class FamilyPerson < ActiveRecord::Base
          belongs_to :family
          belongs_to :person
        end
        

        摘自 db/schema.rb

        create_table "families", force: true do |t|
          t.string "name"
        end
        
        create_table "family_people", force: true do |t|
          t.integer "person_id"
          t.integer "family_id"
        end
        
        create_table "people", force: true do |t|
          t.string "name"
        end
        

        测试

        spec/models/family_spec.rb

        使用 RSpec 进行测试

        require 'rails_helper'
        
        describe Family do
          before :each do
            Rails.application.load_seed
          end
        
          describe "self.all_with_members" do
            it "returns all families that have each person in it" do
              people = [Person.find_by_name("Joe"), Person.find_by_name("Jane"), Person.find_by_name("Ben")]
              expect(Family.all_with_members(people).to_a).to eq Family.where(name: "Family 1").to_a
            end
          end
        end
        

        db/seeds.rb

        种子数据

        Family.destroy_all
        Person.destroy_all
        FamilyPerson.destroy_all
        
        joe = Person.create name: "Joe"
        jane = Person.create name: "Jane"
        ben = Person.create name: "Ben"
        mary = Person.create name: "Mary"
        
        family_one = Family.create name: "Family 1"
        family_two = Family.create name: "Family 2"
        family_three = Family.create name: "Family 3"
        
        family_one.people << joe
        family_one.people << jane
        family_one.people << ben
        family_one.save!
        
        family_two.people << jane
        family_two.people << ben
        family_two.people << mary
        family_two.save!
        
        family_three.people << joe
        family_three.people << jane
        family_three.people << ben
        family_three.people << mary
        family_three.save!
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-03-18
          • 1970-01-01
          • 1970-01-01
          • 2013-01-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多