random_user_0891
random_user_0891

Reputation: 2071

Rails - pass argument to chained scopes

I'm trying to figure out how to chain multiple scopes together. What I'm trying to do is have a search box that will pass params[:user_search] to the controller which calls the by_keyword scope in the user model. the by_keyword scope is working as i have it now, but i would like to make it also search all of the other scopes i have as well. So essentially the by_keyword scope should query all scopes for whatever keyword a user entered.

in my users_controller index action

  if params[:user_search].present? 
    @users = @users.by_keyword(params[:user_search])
  end    

in my user model i have

  scope :by_keyword, -> (keyword) { where('experience LIKE ? OR current_job_title LIKE ?', "%#{keyword}%", "%#{keyword}%" ).order(updated_at: :desc) if keyword.present? }

i would like to find a way to chain all of these to the by_keyword scope

  # these scopes call child classes of User such as skills, languages, patents, etc... 

  scope :by_skill, -> (sk) { joins(:skills).distinct.where( 'skills.name LIKE ?', "%#{sk}%" ).order(updated_at: :desc) if sk.present? }  
  scope :by_language, -> (lang) { joins(:languages).distinct.where( 'languages.language LIKE ?', "%#{lang}%" ).order(updated_at: :desc) if lang.present? }  
  scope :by_certification_or_cert_authority, -> (cert) { joins(:certifications).distinct.where( 'certifications.certification_name LIKE ? OR certifications.certification_authority LIKE ?', "%#{cert}%", "%#{cert}%" ).order(updated_at: :desc) if cert.present? }  
  scope :by_education_level, -> (ed) { joins(:qualifications).distinct.where( 'qualifications.education LIKE ?', "%#{ed}%" ).order(updated_at: :desc) if ed.present? }  
  scope :by_university_major, -> (maj) { joins(:qualifications).distinct.where( 'qualifications.university_major LIKE ?', "%#{maj}%" ).order(updated_at: :desc) if maj.present? }  

i read over http://guides.rubyonrails.org/active_record_querying.html#scopes

and the example they give is this, but I'm not sure how to do this with more than just 2 scopes chained together.

class Article < ApplicationRecord
  scope :published,               -> { where(published: true) }
  scope :published_and_commented, -> { published.where("comments_count > 0") }
end 

Upvotes: 1

Views: 1103

Answers (3)

random_user_0891
random_user_0891

Reputation: 2071

I was able to pass an argument to the scopes like this

  scope :by_keyword, -> (k) { by_skill(k) | by_language(k) | by_certification_or_cert_authority(k) | by_education_level(k) | by_university_major(k)}

I'm not sure if this is really considered "chaining" them though. I'm guessing there is probably a better way to do this, if there is please let me know.

it's making a ton of queries so I'm not sure if this is advisable performance wise to even have this many scopes being called the way they are. This is the result when searching on the term "CCNA"

  User Load (0.5ms)  SELECT DISTINCT "users".* FROM "users" INNER JOIN "skills" ON "skills"."user_id" = "users"."id" WHERE (skills.name LIKE '%CCNA%') ORDER BY "users"."updated_at" DESC
  User Load (0.5ms)  SELECT DISTINCT "users".* FROM "users" INNER JOIN "languages" ON "languages"."user_id" = "users"."id" WHERE (languages.language LIKE '%CCNA%') ORDER BY "users"."updated_at" DESC
  User Load (0.6ms)  SELECT DISTINCT "users".* FROM "users" INNER JOIN "certifications" ON "certifications"."user_id" = "users"."id" WHERE (certifications.certification_name LIKE '%CCNA%' OR certifications.certification_authority LIKE '%CCNA%') ORDER BY "users"."updated_at" DESC
  User Load (0.5ms)  SELECT DISTINCT "users".* FROM "users" INNER JOIN "qualifications" ON "qualifications"."user_id" = "users"."id" WHERE (qualifications.education LIKE '%CCNA%') ORDER BY "users"."updated_at" DESC
  User Load (0.5ms)  SELECT DISTINCT "users".* FROM "users" INNER JOIN "qualifications" ON "qualifications"."user_id" = "users"."id" WHERE (qualifications.university_major LIKE '%CCNA%') ORDER BY "users"."updated_at" DESC

Also from a security standpoint I'm not sure if it's all that good to allow the user to enter one query that touches so many tables.

Upvotes: 0

Ritesh Ranjan
Ritesh Ranjan

Reputation: 1012

Try this:

scope :all_clild, -> (val) { |val| by_skill(val).or.by_language(val).or.by_certification_or_cert_authority(val).........# all other scopes}

merged_scope = by_keyword.merge(all_clild)

Upvotes: 0

Amro Abdalla
Amro Abdalla

Reputation: 584

You can do it by make a function in user controller which call send and give it an array like here

In user model

def self.send_chain(methods)
  methods.inject(self, :send)
end

Then call it like

User.send_chain(["by_skill", "by_language"])

If you have to send params you can do it like this:

scopes = ["by_skill", "by_language"]
parameters = ["clever", "English"]
result = []
scopes.each_with_index do |scope, index|
  result = result + User.send(scope, parameters[index])
end

Hope this helps.

Upvotes: 1

Related Questions