How can I use scopes with Ransack in Rails 3? - ruby-on-rails

In my Widget model I have the following:
scope :accessible_to, lambda { |user|
if user.has_role?('admin')
self.all
else
roles = user.roles
role_ids = []
roles.each { |r| role_ids << r.id }
self.joins(:widget_assignments).where('widget_assignments.role_id' => role_ids)
end
}
Ideally, I would like to use this scope as a filter for Ransack's search results, so in my controller I have:
def index
#q = Widget.accessible_to(current_user).search(params[:q])
#widgets = #q.result.order('created_at DESC')
end
Doing this generates the following error:
undefined method `search' for Array:0x007ff9b87a0300
I'm guessing that Ransack is looking for an ActiveRecord relation object and not an array. Is there anyway that I can use my scope as a filter for Ransack?

Change the self.all for self.scoped. all returns an array.
Update for Rails 4: all will now return a scope.

Related

Chaining ActiveRecord_Relation in PORO

In a Rails 5.1 app, I have a query object (PORO) named CoolProducts.
class CoolProducts
def self.call(relation = Product.all)
...
# return an instance of Product::ActiveRecord_Relation
end
end
Now I need to limit the found Products based on the fact the name matches a string.
The following works
CoolProducts.call.where("name ILIKE ?", "%#{string}%")
However, I'd like to encapsulate the matching login within the CoolProducts class allowing to do something like
CoolProducts.call.including_in_name(string)
But I'm not sure where to start from.
Any ideas?
It will be difficult if you want any of your methods to be chainable or return ActiveRecord::Relation.
If you consider explicitly fetching the records when you're done chaining being ok, this should work:
class CoolProducts
def initialize(relation)
#relation = relation
end
def self.call(relation = Product.all)
new(relation).apply_scopes
end
attr_reader :relation
alias_method :fetch, :relation
def including_in_name(string)
tap { #relation = relation.where("name ILIKE ?", string) }
end
def apply_scopes
tap { #relation = relation.where(price: 123) }
end
end
Usage:
CoolProducts.call.including_in_name(string).fetch

Rails Searchkick with scopes in controller

I'm making a search page where I have a couple of filters on the side and I'm trying to integrate them with Searchkick to query products.
These are my scopes I'm using for the products
models/product.rb
scope :in_price_range, ->(range) { where("price <= ?", range.first) }
scope :in_ratings_range, -> (range) { where("average_rating >= ?", range.first) }
def self.with_all_categories(category_ids)
select(:id).distinct.
joins(:categories).
where("categories.id" => category_ids)
end
This is where I'm actually calling the scopes
controllers/search_controller.rb
#results = Product.search(#query)
#results = #results.with_all_categories(params[:category_ids]) if params[:category_ids].present?
#results = #results.in_price_range(params[:price]) if params[:price].present?
#results = #results.in_ratings_range(params[:rating]) if params[:rating].present?
After running it, I get an error saying the searchkick model doesn't have any methods with the name of my scope.
undefined method `with_all_categories' for #Searchkick::Results:0x00007f4521074c30>
How do I use scopes with my search query?
You can apply scopes to Searchkick results with:
Product.search "milk", scope_results: ->(r) { in_price_range(params[:price]) }
See "Run additional scopes on results" in the readme.
However, if you apply ActiveRecord where filters, it will throw off pagination. For pagination to work correctly, you need to use Searchkick's where option:
Product.search(query, where: {price_range: 10..20})
The error (unknown to me at the time of writing this answer) might be because you defined with_all_categories as a class method on Product, but in your controller you call it on #results which must be an ActiveRecord::Relation.
Turning it into a scope should fix the issue:
Change this:
def self.with_all_categories(category_ids)
select(:id).distinct.
joins(:categories).
where("categories.id" => category_ids)
end
to:
scope :with_all_categories, -> (category_ids) { select(:id).distinct.joins(:categories).where("categories.id" => category_ids) }

Passing multiple scopes to Concern Method - Ruby on Rails

In the process of drying my Rails app code, I have created the following concern that is used to generate the contents of an index method.
define_method(:generate_index) do |string, scope|
instance_variable_set( "##{string}", string.camelize.constantize.public_send(scope))
end
I use this code to generate something like the following:
def index
generate_index("foo", "all")
# #foo = Foo.all
end
What I'd like to do is to have the define method accept a number of scopes. I tried passing in an array of scopes but that results in an error.
Any ideas?
Thanks
You could use the splash * operator:
define_method(:generate_index) do |klass, *scopes|
scope = klass.to_s.camelize.constantize
scopes.each { |s| scope = scope.send(s) }
instance_variable_set("##{string}", scope)
end
def index
generate_index(:foo, :all, :where_not_test)
# #foo = Foo.all.where_not_test
end

Will_Paginate gem error undefined method "total_pages"

The will_paginate gem isn't working after I changed a query to get followers/followed_users
How can I use will_paginate with this??
#users = #user.reverse_relationships.order("created_at DESC").collect { |r| User.find(r.follower) }
I've tried several options like:
#users = #user.reverse_relationships.order("created_at DESC").collect { |r| User.find(r.follower) }
#users = #users.paginate(:page => params[:page])
#users = #user.reverse_relationships.paginate(:page => params[:page]).order("created_at DESC").collect { |r| User.find(r.follower) }
Each time I get an error like undefined method "total_pages" or undefined method "paginate"
You should re-order your query so that you can call paginate and total_pages on an ActiveRecord::Relation instance, as will_paginate requires.
This would remove the collect which effectively turns your relation into an array.
This could be done with something like:
#relationships = #user.reverse_relationships.includes(:follower).order("created_at DESC")
And then just access the follower of each relationship in your view or whatnot.
This will also be more efficient - you won't be issuing a separate query for each follower, as your original code is doing.

Chaining Active Record methods error

I have the following code that I use in my search forms. I want to be able to chain the scoped method with the by_title, but I fail to see how. I want to have the by_title as a method instead of just doing:
# Arel helpers
class << self
def by_city(city)
where(['city_id = ?', city])
end
def by_title(title)
where('title LIKE ?', "%#{title}%")
end
end
def self.search(search_params)
experiences = scoped
experiences.self.by_title(search_params[:title]) if search_params[:title]
end
Why don't you play with scopes this way:
scope :by_title, lambda { |title| where('title LIKE ?', "%#{title}%") }
scope :by_city, lambda { |city| where('city_id = ?', city) }
By just removing self it should work I think:
experiences = scoped
experiences.by_title(search_params[:title]) if search_params[:title]
The scoped method returns an anonymous scope which can be chained with other scopes/class-methods.

Resources