How to access named_scope arguments from named scope extension? - ruby-on-rails

following example:
named_scope :search, lambda {|my_args| {...}} do
def access_my_args
p "#{my_args}"
end
end
# Call:
Model.search(args).access_my_args
As you can see I want to access the arguments from the lambda in the named_scope extension. Is there a way to do this?
A more specific example:
class User < ActiveRecord::Base
named_scope :by_name, lambda {|name_from_scope| {:conditions => {:name => name_from_scope}}} do
def change_name
each { |i| i.update_attribute(:name, "#{name_from_scope}xyz") }
end
end
end
(I know that there is a find_by_name scope and so on...). I want to use the name_from_scope argument, that is passed in the scope in the scope extension.

named_scope :test_scope, lambda {|id| {:conditions => {:id => id}} } do
def test_scope_method
each {|i| puts #proxy_options.to_yaml}
end
end
I don't believe you can get to the arguments directly without extending activerecord.
#proxy_options will give you the compiled options in the block. So, in your example, you won't have access to name_from_scope but you will have access to #proxy_options[:conditions][:name].

Is this what you're trying to do?
named_scope :search, lambda {|*my_args|
OtherClass.announce_search_for_model(my_args, self.class)
{ :conditions => ['created_at < ?', my_args[:created_at]], :limit => my_args[:limit] }
}
args = {:created_at > 'NOW()', :limit => 5}
Model.search(args)
If you're wanting to observe what's passed onto the named_scope then I would do that in the lambda.
Named_scope results will always be a result as if you'd used Model.find. This is a functionality of rails so you need to override rails functionality with a Module if you want something different. I wouldn't recommend doing that because named_scope extensions are there for simplifying finders, not observing parameters.

Related

How to chain class methods together in Ruby on Rails?

I've got this in my Rails 5 model:
def self.payable
open.where.not(:delivery_status => "draft")
end
def self.draft
where(:delivery_status => "draft")
end
def self.open
where(:payment_status => "open")
end
Is there a more elegant way to write the first method?
It would be great to chain the open and draft methods together like this:
def self.payable
open.not(:draft)
end
Unfortunately, this doesn't work.
To chain negated queries you can use this trick:
def self.payable
open.where.not(id: draft)
end
Another alternative if you don't care if an ActiveRecord::Relation object is returned is using -, which returns an Array:
def self.payable
open - draft
end
I would personally use scopes instead of class methods for queries: https://guides.rubyonrails.org/active_record_querying.html#scopes. So:
scope :draft, -> { where(:delivery_status => "draft") }
scope :open, -> { where(:payment_status => "open") }
scope :payable, -> { open.where.not(id: draft) }
Maybe you can use scopes?
scope :payable, -> { open.where.not(:delivery_status => "draft") }
You can use this like that
YouModel.payable

using scope in Rails custom validations

I want to apply scope limiter in my custom validation
I have this Product Model
which has make,model,serial_number, vin as a attributes
Now I have a custom validation to check against vin if vin is not present to check for combination of make+model+serial_number uniqueness in database something like this
validate :combination_vin,:if => "vin.nil?"
def combination_vin
if Product.exists?(:make => make,:model => model,:serial_number => serial_number)
errors.add(:base,"The Combination of 'make+model+serial_number' already present")
end
end
I want to introduce a scope in this validator against user_id
Now I know I could easily write this to achieve same using
def combination_vin
if Product.exists?(:make => make,:model => model,:serial_number => serial_number,:user_id => user_id)
errors.add(:base,"The Combination of 'make+model+serial_number' already present")
end
end
But out of curiosity I was thinking is there a scope validator (something like {:scope => :user_id}) on custom validation
so that I dont have to pass that extra user_id in the exists? hash
Thanks
Try :
validate :combination_vin , :uniqueness => { :scope => :user_id } , :if => "vin.nil?"

Paperclip and json, "stack level too deep"

I use Paperclip in one of my model :
class Event < ActiveRecord::Base
belongs_to :continent
belongs_to :event_type
scope :continent, lambda { |continent|
self.scoped.where('continent_id IN ( ? )', continent) unless continent.blank?
}
scope :event_type, lambda { |eventType|
self.scoped.where('event_type_id IN ( ? )', eventType) unless eventType.blank?
}
scope :in_date, lambda { |date|
self.scoped.where('(MONTH(`date_start`) BETWEEN ? AND ?) OR (MONTH(`date_end`) BETWEEN ? AND ?)', date[0],date[1],date[0],date[1]) unless date.blank?
}
has_attached_file :map, :styles => { :medium => "238x238>",
:thumb => "100x100>"
}
end
I make a Ajax request on this action :
def filter
#events = Event.scoped
#events = #events.continent(params[:continents]) unless params[:continents].blank?
#events = #events.event_type(params[:event_type]) unless params[:event_type].blank?
#events = #events.in_date(params[:months]) unless params[:months].blank?
respond_with( #events )
end
I call this url to get the json answer. When i did, i get the error : "stack level too deep"
Anyone can help me?
My trace is here :
http://paste.bradleygill.com/index.php?paste_id=316663
Stack depth too deep indicates that you ended up in an infinite loop. Your continent scope is the problem, since your method and argument have the same name, when you call the argument within the continent scope, you end up with an infinite loop.
Also, why not just write your scopes as a series of class methods? I'm not a huge fan of using lambdas to pass in arguments in scopes, as it makes it somewhat harder to read. Here is an example of having the scopes as class methods
class Event < ActiveRecord::Base
belongs_to :continent
belongs_to :event_type
class << self
def continent(cont)
where('continent_id IN ( ? )', cont) unless cont.blank?
end
def event_type(eventType)
where('event_type_id IN ( ? )', event_type_id) unless event_type_id.blank?
end
def in_date(date)
where('(MONTH(`date_start`) BETWEEN ? AND ?) OR (MONTH(`date_end`) BETWEEN ? AND ?)', date[0],date[1],date[0],date[1]) unless date.blank?
end
end
has_attached_file :map, :styles => { :medium => "238x238>",
:thumb => "100x100>"
}
end

Dynamic find conditions in active record

I have an index action in rails that can handle quite a few params eg:
params[:first_name] # can be nil or first_name
params[:age] # can be nil or age
params[:country] # can be nil or country
When finding users I would like to AND all the conditions that are not nil. This gives me 8 permutations of the find conditions.
How can I can I keep my code DRY and flexible and not end up with a bunch of if statements just to build the conditions for the find. Keep in mind that if no conditions are specified I just want to return User.all
How about something like:
conditions = params.only(:first_name, :age, :country)
conditions = conditions.delete_if {|key, value| value.blank?}
if conditions.empty?
User.all
else
User.all(:conditions => conditions)
end
I would normally use named scopes for something like this:
class User < ActiveRecord::Base
named_scope :name_like, lambda {|name| {:conditions => ["first_name LIKE ?", "#{name}%"]}}
named_scope :age, lambda {|age| {:conditions => {:age => age}}}
named_scope :in_country, lambda {|country| {:conditions => {:country => country}}}
end
class UsersController < ActionController
def index
root = User
root = root.name_like(params[:first_name]) unless params[:first_name].blank?
root = root.age(params[:age]) unless params[:age].blank?
root = root.country(params[:country]) unless params[:age].blank?
#users = root.paginate(params[:page], :order => "first_name")
end
end
That's what I normally do.
This seems to work quite nicely:
conditions = params.slice(:first_name, :age, :country)
hash = conditions.empty? ? {} : {:conditions => conditions}
#users = User.all hash
Using James Healy answer, I modify the code to be used in Rails 3.2 (in case anyone out there need this).
conditions = params.slice(:first_name, :age, :country)
conditions = conditions.delete_if {|key, value| value.blank?}
#users = User.where(conditions)
You could try Ambition, or a number of other ActiveRecord extensions.
This works for me too
conditions = params[:search] ? params[:search].keep_if{|key, value| !value.blank?} : {}
User.all(:conditions => conditions)
If you happen to be on an ancient project (Rails 2.x) and very messy, you could do something like the following for adding new fields to the original query.
Original code:
User.find(:all,
:conditions => ['first_name LIKE ? AND age=? AND country=?',
"#{name}%", age, country]
Adding a new dynamic condition on zip_code field:
zip_code = params[:zip_code] # Can be blank
zip_query = "AND zip_code = ?" unless zip_code.blank?
User.find(:all,
:conditions => ['first_name LIKE ? AND age=? AND country=? #{zip_query}',
"#{name}%", age, country, zip_code].reject(&:blank?)
Adding a reject(&:blank?) to the conditions arrays will filter the nil value.
Note: The other answers are much better if you are coding from zero, or refactoring.

Rails: named_scope, lambda and blocks

I thought the following two were equivalent:
named_scope :admin, lambda { |company_id| {:conditions => ['company_id = ?', company_id]} }
named_scope :admin, lambda do |company_id|
{:conditions => ['company_id = ?', company_id]}
end
but Ruby is complaining:
ArgumentError: tried to create Proc object without a block
Any ideas?
it's a parser problem. try this
named_scope :admin, (lambda do |company_id|
{:conditions => ['company_id = ?', company_id]}
end)
I think the problem may be related to the difference in precedence between {...} and do...end
There's some SO discussion here
I think assigning a lambda to a variable (which would be a Proc) could be done with a do
... end:
my_proc = lambda do
puts "did it"
end
my_proc.call #=> did it
If you're on ruby 1.9 or later 1, you can use the lambda literal (arrow syntax), which has high enough precedence to prevent the method call from "stealing" the block from the lambda.
named_scope :admin, ->(company_id) do
{:conditions => ['company_id = ?', company_id]}
end
1 The first stable Ruby 1.9.1 release was 2009-01-30.
It's something related to precedence as I can tell
1.upto 3 do # No parentheses, block delimited with do/end
|x| puts x
end
1.upto 3 {|x| puts x } # Syntax Error: trying to pass a block to 3!

Resources