Rails: Filter by comparing between two models

I confused about matching between two models. I have researched on stackoverflow and google until 2 days but no way... There is more questions and answer about matching and filtering but it is about one way searching, listing. May be I couldn’t understand I’m not sure. For example: I have contents and users in my app. I shared contents and I don’t want to make visible, likeable for all users. Users should see who;

My first idea was create profile for users and take information from them. Also create profile filter for Content model and fill the form like belov.

But problem is at final step; “How i compare them for only users can see and like who has true attributes same as Content filter attributes.

Models;

User and Content

Profile belongs_to user

Contentfilter belongs_to Content

Than; Show contens if current_user profile attributes same as Content filter.

Do you have any suggestion? Thanks.

Upvotes: 0

Views: 734

Answers (1)

Dom
Dom

Reputation: 3378

I'm going to assume you mean any matching content but I'm providing a solution for both ALL or ANY matching profile filters.

Here's one quick solution on how you can achieve what you want.

Given the following models:

# app/models/user.rb
class User < ApplicationRecord
  has_one :profile, as: :profilable

  def all_matching_content
    profile: Profile.where(
      "? >= age", profile.age).
      where(
        gender: profile.gender,
        city: profile.city,
        license: profile.license).
      where(profilable_type: "Content"))
  end

  def any_matching_content
    Content.where(
      profile: Profile.where("? >= age", profile.age).
        or(Profile.where(gender: profile.gender)).
        or(Profile.where(city: profile.city)).
        or(Profile.where(license: profile.license)).
      where(profilable_type: "Content"))
    end
  end
end

# app/models/profile.rb
class Profile < ApplicationRecord
  belongs_to :profilable, polymorphic: true, optional: true
end

# app/models/content.rb
class Content < ApplicationRecord
  has_one :profile, as: :profilable
end

And the following migrations:

# db/migrate/20181114004031_create_users.rb
class CreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      t.string :name, index: true
      t.timestamps
    end
  end
end

# db/migrate/20181114004134_create_profiles.rb
class CreateProfiles < ActiveRecord::Migration[5.2]
  def change
    create_table :profiles do |t|
      t.references :profilable, polymorphic: true, null: true
      t.boolean :license, index: true
      t.string :city, index: true
      t.string :gender, index: true
      t.integer :age, index: true
      t.timestamps
      t.index [:license, :city, :gender, :age], name: "profile_composite_index"
    end
  end
end

# db/migrate/20181114004307_create_contents.rb
class CreateContents < ActiveRecord::Migration[5.2]
  def change
    create_table :contents do |t|
      t.string :title, index: true
      t.timestamps
    end
  end
end

And the following seeds:

# db/seeds.rb
User.create!(
  name: "John",
  profile: Profile.create!(
    license: true,
    city: "London",
    gender: "Male",
    age: 19
  )
)

User.create!(
  name: "Jane",
  profile: Profile.create!(
    license: false,
    city: "London",
    gender: "Female",
    age: 17
  )
)

User.create!(
  name: "Rose",
  profile: Profile.create!(
    license: true,
    city: "Edinburgh",
    gender: "Female",
    age: 21
  )
)

User.create!(
  name: "Hans",
  profile: Profile.create!(
    license: true,
    city: "Berlin",
    gender: "Male",
    age: 24
  )
)

Content.create!(
  title: "London introduces new tax",
  profile: Profile.create!(
    city: "London"
  )
)

Content.create!(
  title: "New license requirements in Berlin",
  profile: Profile.create!(
    city: "Berlin",
    license: true
  )
)

Content.create!(
  title: "Women should get new immunization",
  profile: Profile.create!(
    gender: "Female"
  )
)

Content.create!(
  title: "Adults only expo opens Wednesday",
  profile: Profile.create!(
    age: 18
  )
)

You'll be able to do the following:

> john = User.find_by(name: "John")
> john.all_matching_content
=> #<ActiveRecord::Relation []>

> john.any_matching_content.map(&:title)
=> ["London introduces new tax", "New license requirements in Berlin", "Adults only expo opens Wednesday"]

> jane = User.find_by(name: "Jane")
> jane.any_matching_content.map(&:title)
=> ["London introduces new tax", "Women should get new immunization"]

> rose = User.find_by(name: "Rose")
> rose.any_matching_content.map(&:title)
=> ["New license requirements in Berlin", "Women should get new immunization", "Adults only expo opens Wednesday"]

> hans = User.find_by(name: "Hans")
> hans.any_matching_content.map(&:title)
=> ["New license requirements in Berlin", "Adults only expo opens Wednesday"]

The solution works by sharing a polymorphic Profile model with User and Content. It then matches Content with the User instance by passing the user's Profile attributes to a query for matching profiles that belong to Content types.

Note that the #all_matching_content method requires that all profile attributes match, basically it means all conditons in the query are "AND"ed. The #any_matching_content method uses "OR" conditions instead.

I have uploaded a quick project to GitHub with this example already coded:

https://github.com/JurgenJocubeit/so-53289847

Upvotes: 1

Related Questions