Reputation: 2071
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
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
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
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