Passing arguments to scope_procedure in searchlogic - ruby-on-rails

I'd like to use searchlogic's scope_procedure feature like so
class MyModelObject < ActiveRecord::Base
scope_procedure :my_scope_proc, lambda { |p1, p2| { :conditions => "p1 >= #{p1} AND p2 < #{p2}" }}
end
Then, I am doing the search:
scope = MyModelObject.search(:my_scope_proc => true)
scope.all
The above code obviously doesn't work because I didn't pass p1 and p2 parameters to my named scope.
I can't figure out how to pass parameters to the named scope.

A wild guess is (meaning, I didn't check!):
scope = MyModelObject.search(:my_scope_proc => [p1, p2])
scope.all
One suggestion for the scope_procedure:
class MyModelObject < ActiveRecord::Base
scope_procedure :my_scope_proc, lambda { |p1, p2| { :conditions => ["p1 >= ? AND p2 < ?", p1, p2] }}
end
This prevents SQL injection.
Or the searchlogic way:
class MyModelObject < ActiveRecord::Base
scope_procedure :my_scope_proc, lambda { |p1, p2| p1_gte(p1).p2_lt(p2) }
end

I was able to make it work with one parameter as follows:
class MyModelObject < ActiveRecord::Base
scope_procedure :my_scope_proc, lambda { |p| p1_gte(p[0]).p2_lt(p[1]) }
end
scope = MyModelObject.search(:my_scope_proc => [p1, p2])
scope.all

Related

How to define the scope?

I have:
price_plan.rb
class PricePlan < ActiveRecord::Base
has_many :users
scope :premium, lambda { where('price > ?', 0) }
scope :free, lambda { where('price == ?', 0) }
end
user.rb
class User < ActiveRecord::Base
belongs_to :price_plan
has_one :account
scope :free, lambda { joins(PricePlan.free) } #<--- no!
end
How to define scope for users, that use service free of charge?
This below should work, but I don't like it.
scope :free,-> where(priceplan_id: PricePlan.free.pluck(:id))
It will be
Solution 1: Use condition on relation
class User < ActiveRecord::Base
# Your current code
belongs_to :free_price_plan, -> { free }, class_name: 'PricePlan'
belongs_to :premium_price_plan, -> { premium }, class_name: 'PricePlan'
end
Solution 2: Define a scope
class User < ActiveRecord::Base
# Your current code
scope :free, -> {
joins(:price_plan).merge(PricePlan.free)
}
scope :premium, -> {
joins(:price_plan).merge(PricePlan.premium)
}
end

Scope with 2 different conditions

I would like to filter stories on my index based on 2 different conditions where one is for the Current Country and the other one is for All Countries. Is it possible to create a scope where it could fetch stories for both this condition ?
All Countries is boolean field where in my Story table. The logic is if the Story is created for all countries the field, all_countries = 1
Featured Item model, is where the stories could be featured on the index page if the writer would like to do so.
This is how my model looks like for now with the scopes
class Country < ActiveRecord::Base
has_many :stories
end
class Story < ActiveRecord::Base
belongs_to :countries
has_many :featured_items, dependent: :destroy
scope :by_country, lambda { |id| where(:country_id => id)}
scope :for_all_countries, where(:all_countries => true)
end
class FeaturedItem < ActiveRecord::Base
belongs_to :story
scope :by_country, -> (country) {joins(:story).where('`stories`.country_id = ?', Country.find(country) )}
scope :for_all_countries, -> { joins(:story).where('`stories`.all_countries = ?',true) }
end
p/s the scope for all countries on the featured Items also returns an error.
You can do this sort of thing:
scope :by_country, -> (country) { country == :all ? where(:all_countries => true) : where(:country_id => country) }
You may need to add a little more logic to handle bad params.
And for the join table, you can join and filter on the stories.
class FeaturedItem < ActiveRecord::Base
scope :by_country, -> (country) { (country == :all ? where( :stories => { :all_countries => true } ) : where( :stories => { :country_id => country } ) ).joins(:story) }
end
Your scope syntax is currently wrong, as is your pluralization of the belongs_to association.
You'll need to use the following (#swards answer is right, this is just an addition):
#app/models/story.rb
class Story < ActiveRecord::Base
belongs_to :country
scope :countries, ->(ids = :all) { ids == :all ? where(all_countries: true) : find(ids) }
end
This will allow you to call Story.countries to return all countries, and Story.countries(1,2,4,5) to return individual ones.
filter stories on my index based on 2 different conditions where one is for the Current Country and the other one is for All Countries.
Have you considered using the following in your Country model:
#stories = #country ? #country.stories : Country.stories
#app/models/country.rb
class Country < ActiveRecord::Base
has_many :stories
scope :stories, -> { joins(:stories).where(story: {all_countries: true}) }
end

Merge methods to one hash, through method chaining

I have a class which is generating queries for a db:
They should concatenated, so I do not repeat my code for a query (DRY).
I thought I could do something like this:
ruby
class Blah < ActiveRecord::Base
def no_scope
{scope: false}
end
def for_user(user_id)
{user_id: user_id}
end
end
Now my query
Blah.no_scope.for_user(1)
RESULT SHOULD BE A HASH:
{user_id: 1, scope: false}
Did you try to achieve this?
class Blah < ActiveRecord::Base
scope :no_scope, -> { where(scope: false) }
scope :for_user, -> (id) { where(user_id: id) }
end
Blah.no_scope.for_user(1) # where(scope: false, user_id: 1)
You just need to create scopes.
class Blah < ActiveRecord::Base
scope :no_scope, -> {all}
scope :for_user, -> (user_id) {where(user_id: user_id)}
end
Blah.no_scope.for_user(1) # where(user_id: 1)
See also scopes
To give the result as a real Hash instead of a Activerecord relation, you do it like this
class Blah < ActiveRecord::Base
scope :no_scope, ->{where(scope: false)}
scope :for_user, ->(id){where(user_id: id)} # no space in ->(id)
end
# give results as an array of hashes
Blah.no_scope.for_user(1).map { |e| {scope: e.scope, user_id: e.user_id} }
gives
[{"scope"=>false, "user_id"=>1}]

Trouble translating SQL query to Arel

I have a search page that narrows down the list of a specific class, and I want an OR condition that can grab two different conditions and add the together, for example, I have classes
model/party.rb
class Party < ActiveRecord::Base
has_many :invitations
end
mode/invitation.rb
class Invitation < ActiveRecord::Base
belongs_to :party
end
invitation has a status attribute, which will be "decline", "accept", or "unanswered"
What I want to do is grab all the parties that do not have any invitations, or any that have all of the invitations "unanswered".
I currently do
scope :not_confirmed, lambda { find_by_sql( "SELECT * FROM `parties` INNER JOIN `invitations` ON `invitations`.`party_id` = `parties`.`id` WHERE (invitations.status = 'unanswered') OR (parties.id NOT IN (SELECT DISTINCT(party_id) FROM invitations))" ) }
which works, but since it does not lazy load I can't add them in a facet like query.
I did something like
no_invitations.or(no_one_has_answered)
but it did not work.
I especially do not get the concept of using OR on AREL, could someone please help out?
edited:
For a very ugly yet functional work around until I get this down, here is what I have done
party.rb
scope :not_confirmed, lambda { joins(:invitations).where( "invitations.status NOT IN (?)", ["accepted", "declined" ] ) }
scope :with_no_invitations, lambda { includes(:invitaions).where( :invitations => { :party_id => nil } ) }
search_controller.rb
#parties = Party.all_the_shared_queries
#parties = ( #parties.not_confirmed + #parties.with_no_invitations).uniq
The query:
scope :not_confirmed, lambda { find_by_sql( "SELECT * FROM `parties` INNER JOIN `invitations` ON `invitations`.`party_id` = `parties`.`id` WHERE (invitations.status = 'unanswered') OR (parties.id NOT IN (SELECT DISTINCT(party_id) FROM invitations))" ) }
can be converted to arel with some transformation using boolean algebra too. But since it is only theoretical conversion, you have to verify it manually. So:
class Invitation < ActiveRecord::Base
belongs_to :party
scope :non_answered, -> { where(arel_table[:status].not_eq('unanswered')) }
end
class Party < ActiveRecord::Base
has_many :invitations
scope :not_confirmed, -> { not.join(:invitaions).merge(Invitation.non_answered)) }
end
Please test it and comment here.
Firstly, from the question tags, I have assumed that you are using Rails3 (had it been Rails4, there were more easy ways of doing things :))
For your requirement above (ie grab all the parties that do not have any invitations, or any that have all of the invitations "unanswered"), here is a way of doing it (using scope :unattended):
Party Model:
class Party < ActiveRecord::Base
has_many :invitations
scope :invitations_answered, -> { joins(:invitations).merge(Invitation.answered) }
scope :unattended, -> { where(arel_table[:id].not_in invitations_answered.pluck(:id)) }
end
Invitation Model:
class Invitation < ActiveRecord::Base
belongs_to :party
scope :answered, -> { where(status: ["decline", "accept"])}
end
In Rails 4, you can use where.not and simplify it further like this:
Party Model:
class Party < ActiveRecord::Base
has_many :invitations
scope :invitations_answered, -> { joins(:invitations).merge(Invitation.answered) }
scope :unattended, -> { where.not(id: invitations_answered.pluck(:id)) }
end
Invitation Model:
class Invitation < ActiveRecord::Base
belongs_to :party
scope :answered, -> { where.not(status: 'unanswered') }
end

How to return an unchanged AR relation from a class method

I have code like this:
class Item < ActiveRecord::Base
class << self
def for_category(c)
if c
return where(:category_id => c.id)
else
return self
end
end
end
end
I need to call it like this:
Item.where("created_at > ?", Time.now - 1.week).for_category(#category)
#category may or may not be null. In the case where category is null, I want the method to simply pass through and return the relation unchanged. Of course, return self simply returns the Item class.
What would be the correct way to do this?
Are you trying to return the Scope (as opposed to the Class itself) for further scope action? If so, then something like the following should work:
class Item < ActiveRecord::Base
class << self
def for_category(c)
if c
return where(:category_id => c.id)
else
return scoped
end
end
end
end
HTH
class Item < ActiveRecord::Base
def self.for_category(c)
conditions = {:category_id => c.id}
conditions.delete_if {|key,val| val.blank? }
self.where(conditions)
end
end
Your Item is associated with Category ? If yes then you can simply get all item categories by Item.where("created_at > ?", Time.now - 1.week).categroies not need for above code.
#scoped was deprecated in Rails 4. You can use #all to achieve the same effect:
class Item < ActiveRecord::Base
class << self
def for_category(c)
if c
return where(:category_id => c.id)
else
return all
end
end
end
end

Resources