I have a named_scope in my model and want a different condition, based on the locale. On the development this code works perfect, after switching to production the named_scope doesn't work and I get every time the first condition ("de"). I think it's a caching problem?
I'm working with 2.3.5. and env settings out of the box.
named_scope :public,
:conditions => I18n.locale == "de" || I18n.locale == :de ? ["published=? AND trash=?", true, false] : ["published_#{I18n.locale} =? AND trash=?", true, false]
The value for the conditions option is evaluated during class loading. In the development mode class is loaded for every request. Hence your code works in the development mode.
Create the named scope using a parametrized lambda:
named_scope :published, lambda { |*args| {
name = "_de" if (args.first||"").to_s == 'de'
:conditions => ["published#{name} =? AND trash=?", true, false]
}
}
You can the lambda as follows:
Post.published # no locale passed
Post.published(locale) # with locale passed
Yes, it's a "problem" with the class caching of rails. In production the class is loaded and then cached, since the condition is evaluated in class context this should be the value of I18n.locale when the class was loaded.
To solve the problem you could simply turn it off by setting
config.cache_classes = false
in your 'config/enviroments/production.yml', but this slows down response time.
A better solution would be to use a lambda and pass in the locale
named_scope :public, lambda {|l|
if l == :de or l == 'de'
{:conditions => ["published=? AND trash=?", true, false]}
else
{:conditions => ["published_#{l} =? AND trash=?", true, false]}
end
}
Then you can use it like this:
Article.public(I18n.locale)
Related
I want to hide past events if they are defined and get all other. How to show all documents even if :once_at is nil and if :once_at is defined then hide these ones which are expired?
My recent approach, shows only events with defined :once_at, (I tryed with :once_at => nil, but without results):
default_scope where(:once_at.gte => Date.today)
or (also not working)
default_scope excludes(:once_at.lte => Date.today)
When do you think Date.today is evaluated? If you say this:
default_scope where(:once_at.gte => Date.today)
Date.today will be evaluated when the class is being loaded. This is almost never what you want to happen, you usually want Date.today to be evaluated when the default scope is used and the usual way to make that happen is to use a proc or lambda for the scope:
default_scope -> { where(:once_at.gte => Date.today) }
The next problem is what to do about documents that don't have a :once_at or those with an explicit nil in :once_at. nil won't be greater than today so you'd best check your conditions separately with an :$or query:
default_scope -> do
where(
:$or => [
{ :once_at => nil },
{ :once_at.gte => Date.today }
]
)
end
The date_validator in its examples has a comment:
Using Proc.new prevents production cache issues
Does it mean, that everywhere in my code, where I use current time related methods (Time.now, 1.day.since(Time.zone.now), etc.) I should surround them with Proc.new { }?
I don't completely understand this, since replacing
time_now = Time.now.utc
with
time_now = Proc.new { Time.now.utc }
just doesn't make sense to me (new type of object is returned).
So, the question is, when and how should I use Proc.new with time related methods? And does that still apply to the latest versions of Ruby (1.92) and Rails (3.1)?
No, it only references the given example:
validates :expiration_date,
:date => {:after => Proc.new { Time.now },
:before => Proc.new { Time.now + 1.year } }
If instead you'd written
validates :expiration_date,
:date => {:after => Time.now,
:before => Time.now + 1.year }
Time.now would be interpreted when the class is parsed and it would be validating against that value.
Using Proc.new in that validation means Time.new will be evaluated when the validation is actually run - not when it's initially being interpreted.
What Proc.new (and lambda) does is, save all your statements in their original form (in an anonymous function), and doesn't evaluate them.
Date Validator gem must have some kind of test to check if a Proc was passed, and it evaluates it when it's actually validating the stuff.
Edit: It does this here - https://github.com/codegram/date_validator/blob/master/lib/active_model/validations/date_validator.rb#L47
option_value = option_value.call(record) if option_value.is_a?(Proc)
A quick example :
pry(main)> time_now = Time.now
=> 2011-06-19 21:07:07 +0530
pry(main)> time_proc = Proc.new { Time.now }
=> #<Proc:0x9710cc4#(pry):1>
pry(main)> time_proc.call
=> 2011-06-19 21:07:28 +0530
pry(main)> time_proc.call
=> 2011-06-19 21:07:31 +0530
pry(main)>
Note that this will only work with libraries that do implement this kind of check, and not every function accepting a Time.
I have a few places in a model that does stuff like
def ServerInfo.starttime(param)
find(:all, :conditions => "name ='#{param}_started'", :select => "date").first.date.to_datetime
end
Now, for reasons not relevant to the question, it can happen that this particular row is not in the database at all and the code above fails with NoMethodError (undefined method `date' for nil:NilClass):. My current fix is
res = find(:all, :conditions => "name ='#{param}_started'", :select => "date")
check_time = res.first.nil? ? 0 : res.first.date.to_datetime
This works find, but I feel it's not right to sprinkle that code all over the place. Is there some more ruby-ish / rail-ish way to prevent dereferencing nil?
In order to avoid the NoMethodError for nil, you should define a begin rescue block,
def ServerInfo.starttime(param)
begin
find(:all, :conditions => "foo").first.date.to_datetime
rescue
0
end
end
I also like the Rails try method:
find(:all, :conditions => "foo").first.try(:date).try(:to_datetime) || 0
maybe this is cleaner:
check_time = res.first.date.to_datetime if res.first
btw, don't use:
:conditions => "name ='#{param}_started'" # SQL injection vulnerability.
use this one instead:
:conditions => ["name = ?", "#{param}_started"] # This is safer. Pure clean Ruby
it's safer
You may also define a scope. For instance in a Rails3 app you should try:
In your ServerInfo.rb model:
scope :starttime, lambda{|param|
if self.has_attribute?(param+'_started')
where("name = ?", param+'_started' ).select('date')
else
false
end
}
// Remember to never put your params directly in your sql query, that is bad practice since you risk some sql injection //
Then in a controller:
res = ServerInfo.starttime('a_param')
check_time = res.first.date.to_datetime if res
I didn't try that code, then you may need to adapt it to your need (or to your Rails2 app)
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!
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.