Search code examples
ruby-on-railssearchnested-attributesransack

search nested_attributes in rails


class Subject
  has_many :subject_attribute_types
  has_many :subject_attributes

  accepts_nested_attributes_for :subject_attributes
end

class SubjectAttributeType
  belongs_to :subject
  has_many :subject_attributes

  attr_accessible :type_name
end

class SubjectAttribute
  belongs_to :subject
  belongs_to :subject_attribute_type

  attr_accessible :value
end

For example:

s1 = Subject.create()
s2 = Subject.create()

sat1 = SubjectAttributeType.create(subject: s1, name: 'Age')
sat2 = SubjectAttributeType.create(subject: s1, name: 'Sex')

sat3 = SubjectAttributeType.create(subject: s2, type_name: 'Age')
sat5 = SubjectAttributeType.create(subject: s2, type_name: 'Username')

SubjectAttribute.create(subject: s1, subject_attribute_type: sat1, value: 20)
SubjectAttribute.create(subject: s1, subject_attribute_type: sat2, value: "male")
SubjectAttribute.create(subject: s2, subject_attribute_type: sat3, value: 21)
SubjectAttribute.create(subject: s2, subject_attribute_type: sat1, value: "user1")

Problem:
What's the best practice to make a search on exact subject_attributes.
If i want to find all Subjects with age >= 18 and nickname like %user%

currently i am using ransack gem, but i can't think out how to make a search on nested_attributes


Solution

  • I see there is a problem in business logic of your app. Why would you need your AttributeType to know about any of subject?

    class Subject < ActiveRecord::Base
      has_many :subject_attributes
      has_many :attribute_types, through: :subject_attributes
    end
    
    class SubjectAttribute < ActiveRecord::Base
      belongs_to :attribute_type
      belongs_to :subject
      attr_accessible :attribute_type_id, :subject_id, :value
    end
    
    class AttributeType < ActiveRecord::Base
      attr_accessible :type_name
    end
    

    After that if you insert some data:

    s1 = Subject.create
    s2 = Subject.create
    
    sat1 = AttributeType.create(type_name: "Age")
    sat2 = AttributeType.create(type_name: "Sex")
    sat3 = AttributeType.create(type_name: "Username")
    
    SubjectAttribute.create(subject:s1, attribute_type:sat1, value: 20)
    SubjectAttribute.create(subject:s1, attribute_type:sat2, value:"male")
    SubjectAttribute.create(subject:s2, attribute_type:sat1, value:21)
    SubjectAttribute.create(subject:s2, attribute_type:sat3, value:"user1")
    

    you will be able to make selects. In your example you use several attributes, so you have to make several requests:

    that way you'll find subject with value name:

    names = Subject.joins(:attribute_types).where("attribute_types.type_name = 'Username'   
                                                   and value like '%user%'")
    => [#<Subject id: 2, created_at: "2013-05-29 11:11:51", updated_at: "2013-05-29 11:11:51">]
    

    that way you'll find subject with value age

    ages = Subject.joins(:attribute_types).where("attribute_types.type_name = 'Age' 
                                                  and value >= 18")
    => [#<Subject id: 1, created_at: "2013-05-29 11:11:42", updated_at: "2013-05-29 11:11:42">, 
       #<Subject id: 2, created_at: "2013-05-29 11:11:51", updated_at: "2013-05-29 11:11:51">]
    

    That way you'll find intersected subjects

    subjects = (names&ages)
    => [#<Subject id: 2, created_at: "2013-05-29 11:11:51", updated_at: "2013-05-29 11:11:51">] 
    

    Using dynamic attribute_types makes select really hard. so if you ok with making separate request for each type-value params, use it. Otherwise maybe its really just columns of Subjects?