In a model class, can the target of the has_many :conditions option
be a
method? For example:
class etc.
has_many abcs :conditions => :local_conditions
Not quite like that. But those conditions are interpolated in the
context of the model, so for example you can say
:conditions => 'start_date > #{@start_date}'
When the association is fetched that will be interpolated in the
context of the model. You probably could write
:conditions => '#{local_conditions}'
as long as local conditions returned a string of sql (ie not a hash as
shown below)
Not quite like that. But those conditions are interpolated in the
context of the model, so for example you can say
:conditions => 'start_date > #{@start_date}'
When the association is fetched that will be interpolated in the
context of the model. You probably could write
:conditions => '#{local_conditions}'
as long as local conditions returned a string of sql (ie not a hash as
shown below)
Fred
Well, yes. I had that working in that exact fashion. The situation that
I am now trying to address is I have a large number of associations in
serveral models that will share identical conditional SQL fragments and
I would like to store these in a single module and call them wherever
required.
Then this assigns an array to the :conditions key, correct?
Is there no way to pass a variable that represents an array with the
exact same content to :conditions such that the array contents are
assigned to the key?
Is there no way to pass a variable that represents an array with the
exact same content to :conditions such that the array contents are
assigned to the key?
Is there no way to pass a variable that represents an array with the
exact same content to :conditions such that the array contents are
assigned to the key?
Keep in mind, though, that that will evaluate DateTime.now when it
loads the model file, and (depending on what environment you're in,
etc.) may not do it again.
Is there a better/cleaner idiom that works?
Good question. I can't think of one that doesn't stringify the
argument, which makes an array useless. That's not to say there isn't
a way... but I haven't come up with it.
Keep in mind, though, that that will evaluate DateTime.now when it
loads the model file, and (depending on what environment you're in,
etc.) may not do it again.
Is this a consequence of using a class variable rather than an instance
variable in this specific case or is this a general trait deriving from
the manner in which Rails caches db calls?
Keep in mind, though, that that will evaluate DateTime.now when it
loads the model file, and (depending on what environment you're in,
etc.) may not do it again.
Is this a consequence of using a class variable rather than an instance
variable in this specific case or is this a general trait deriving from
the manner in which Rails caches db calls?
It's just a matter of how Ruby parses your file. What you've got is:
When the file is read in and executed, a value will be assigned to the
class variable @@active_row. Then, the method has_many will be
executed, with the arguments :abc and { :conditions => ["start <=
:start_date", { :start_date <= "2008-05-05 21:43:47" } ] } (assuming
that's the date and time at that moment). That string won't change
(unless there's a reload). So every time you do:
m = Model.find(x)
abcs = m.abcs
you'll be constraining the abcs collection as being <= 2008-05-05
21:43:47.
variable in this specific case or is this a general trait deriving from
the manner in which Rails caches db calls?
It's just a matter of how Ruby parses your file. What you've got is:
I infer from this that finders containing dynamic selection elements
have to go into the controllers, or does the same problem arise there as
well?
You can have a finder method that does this (or some variation on this
-- it's just an example):
def find_earlier_things
self.class.find(:all, :conditions => [...])
end
where the ... includes DateTime stuff, and then when you do
thing.find_earlier_things, it will go find them and, since the method
is being executed, it will evaluated the conditions array on the spot.
The problem with the association situation is that the association
(has_many) is itself a method, and it only gets called once -- at
which point it has to have its arguments in place. The only way to
have the arguments update themselves later would be to have one of
them be an executable object (Proc or method), and I don't think
there's a way to insinuate one into the conditions position (though if
I'm wrong, or if it's been added recently and I haven't noticed, I'd
be glad to be corrected).
The only way I'm aware of is the interpolation trick (:conditions => '#{something to evaluate later}'). If you use sanitize_sql you can probably keep on using hash conditions & stuff.
You can have a finder method that does this (or some variation on this
-- it's just an example):
def find_earlier_things
self.class.find(:all, :conditions => [...])
end
where the ... includes DateTime stuff, and then when you do
thing.find_earlier_things, it will go find them and, since the method
is being executed, it will evaluated the conditions array on the spot.
The problem with the association situation is that the association
(has_many) is itself a method, and it only gets called once -- at
which point it has to have its arguments in place. The only way to
have the arguments update themselves later would be to have one of
them be an executable object (Proc or method), and I don't think
there's a way to insinuate one into the conditions position (though if
I'm wrong, or if it's been added recently and I haven't noticed, I'd
be glad to be corrected).
The only way I'm aware of is the interpolation trick (:conditions =>
'#{something to evaluate later}'). If you use sanitize_sql you can
probably keep on using hash conditions & stuff.
I had given up on '#{}' because of the problem of having it mush
arrays and hashes together for string representation -- but you're
quite right that there's an escape clause....
So here's what I've got in my little testbed:
def self.sanitize_me(array)
sanitize_sql(array)
end
The extra method is because sanitize_sql is protected. Next cup of
coffee may or may not produce a way to avoid that rather inelegant
workaround (There's 'send', of course.)
NoMethodError: undefined method `sanitize_me' for Class:Class
from
/home/byrnejb/Software/Development/Projects/proforma/app/models/entity.rb:47
from
/home/byrnejb/Software/Development/Projects/proforma/vendor/rails/activerecord/lib/../../activesupport/lib/active_support/dependencies.rb:203:in
`load_without_new_constant_marking'
The crucial difference is that you are using double quotes and not
single quotes. That is why your conditions are evaluated when the
class is loaded and not later on.
Forgot to say, that's also why david's trick wasn't working (because that assumes that it is being evaluated in the context of an instance of the class, but by swapping the quotes you make it evaluate at load time and thus in the context of the class)
The crucial difference is that you are using double quotes and not
single quotes. That is why your conditions are evaluated when the
class is loaded and not later on.
Fred
Well, I would never have realized that problem on my own. However, when
I change the outer " to ' then I always throw and undefined method
error.
...
def self.sql_sanitize_here(array)
sanitize_sql(array)
end
...
has_one :active_client, :class_name => 'Client',
:conditions => '#{self.class.sanitize_sql_here([
" effective_from <= :date_today
AND ( superseded_after > :date_today OR
superseded_after IS null ) ",
{ :date_today => DateTime.now } ])}'
@entity.active_client
NoMethodError: undefined method `sanitize_sql_here' for
#<Class:0xb7558edc>
from
/home/byrnejb/Software/Development/Projects/proforma/vendor/rails/activerecord/lib/active_record/base.rb:2550:in
`interpolate_sql'
from (eval):1:in `interpolate_sql'
from
/home/byrnejb/Software/Development/Projects/proforma/vendor/rails/activerecord/lib/active_record/associations/association_proxy.rb:139:in
`send'