Modifying Conditions for has_many at runtime in controller?

I am attempting to bring Rails into my company, and so far I have been
able to impress the senior managers with the flexibility /
configurability of Rails on ActiveScaffold. I have hit a point now
where I am stumped, and would really appreciate the help of the
community.

I have a Person ActiveRecord model that has_many Orders.

class Person < ActiveRecord::Base
has_many :orders
end

I am building out a simple reporting tool. The tool allows the user
to set criteria for which People they want to see. For example, “show
only the People/Orders where the Order’s value is greater than $100”

When the user sets this criteria on a web form, I now need to modify
my has_many association. I now need the association to be:

class Person < ActiveRecord::Base
has_many :orders, :conditions => “ORDER_VALUE > 100”
end

This way, when I do a person.orders.count, I should only get the count
of orders where the Order Value is greater than $100.

Is there any way I can do what I’m describing here? I’m open to any/
all possibilities here.

Thanks for your help! I’m very close to selling my company on using
Rails for some bigger projects!

Robert H. Goretsky
Hoboken, NJ

Just do person.orders.find(:all, :conditions => [‘order_value > ?’,
100]) and leave the conditions off of your relation declaration.
You may want to wrap that in a method or even look for a slicker way
of doing the same thing as I believe there are more “rails-fu” ways
of doing that sort of find now

-Michael
http://javathehutt.blogspot.com

Michael,
Thanks for the response! The issue I have with just using

person.orders.find(:all, :conditions => [‘order_value > ?’, 100])

is:
a) It does not remove all of the Person records that do not have an
order_value > 100 from the collection of People. So, my report would
end up showing a lot of parent Person records that have no children.

b) It does not update the person.orders count correctly to only count
the children with order_value > 100.

c) (I know this is somewhat outside the scope of this discussion, but
figure I list it to be fair) – I’m using ActiveScaffold, which just
wraps associated parent/children models into beautiful nested view
code. I’d rather not put tons of ugly customizations in if all I
really need is this one silly condition added to the has_many
association.

Any ideas on how to accomplish what I’m going after here?

Thanks again,
Rob

Robert Goretsky wrote:

b) It does not update the person.orders count correctly to only count
the children with order_value > 100.

http://groups.google.com/group/rubyonrails-talk/browse_thread/thread/5199cea1d6267c5c/06a1f5da4203a11d
http://groups.google.com/group/rubyonrails-talk/browse_thread/thread/7f7f715d917b9faa/426c768dd282929f


We develop, watch us RoR, in numbers too big to ignore.

Michael,
Thanks for the response! The issue I have with just using

person.orders.find(:all, :conditions => [‘order_value > ?’, 100])

is:
a) It does not remove all of the Person records that do not have an
order_value > 100 from the collection of People. So, my report would
end up showing a lot of parent Person records that have no children.

b) It does not update the person.orders count correctly to only count
the children with order_value > 100.

c) (I know this is somewhat outside the scope of this discussion, but
figure I list it to be fair) – I’m using ActiveScaffold, which just
wraps associated parent/children models into beautiful nested view
code. I’d rather not put tons of ugly customizations in if all I
really need is this one silly condition added to the has_many
association.

Any ideas on how to accomplish what I’m going after here?

Thanks again,
Rob

Robert Goretsky wrote:

Michael,
Thanks for the response! The issue I have with just using

person.orders.find(:all, :conditions => [‘order_value > ?’, 100])

is:
a) It does not remove all of the Person records that do not have an
order_value > 100 from the collection of People. So, my report would
end up showing a lot of parent Person records that have no children.

b) It does not update the person.orders count correctly to only count
the children with order_value > 100.

c) (I know this is somewhat outside the scope of this discussion, but
figure I list it to be fair) – I’m using ActiveScaffold, which just
wraps associated parent/children models into beautiful nested view
code. I’d rather not put tons of ugly customizations in if all I
really need is this one silly condition added to the has_many
association.

Any ideas on how to accomplish what I’m going after here?

Thanks again,
Rob

Your not finding orders then, your finding people. So you don’t want to
directly mess with the association like that. Try this:

Person.find(:all,
:include => :orders,
:conditions => [‘order.value >= ?’, 100])

You can include the association in the SQL query and reference it in the
conditions. But since your calling find on Person, it will return
people, not orders.

Mark,
Thanks, your link to the discussion from last year was exactly what I
needed!

I was looking for a way to dynamically modify has_many conditions, and
the answer you linked to, using the reflect_on_association class
method of ActiveRecord, was the way to go.

Person.reflect_on_association(:conditional_orders).options[:conditions]
= …(new dynamic conditions)…

Thanks again for your help!

-Rob

Yes, if you change the association, it will change for all users that
hit that Webrick instance, or that dispatch.fcgi, and so forth.

I would very much recommend you look into an alternative implementation
of what you are trying to do, associations are not meant to be used in
this way. You should either implement a new method on the Person class,
for example:

class Person < ActiveRecord::Base
has_many :orders

def orders_greater_than(value)
  orders.find(:all, :conditions => ['ORDER_VALUE > ?', value])
end

end

or, something using an association proxy extension, which is documented
here:
ActiveRecord::Associations::ClassMethods.
You could potentially then do something like:

person.orders.with_value_higher_than(params[:value])

There are also other ways to implement what you seek, but I think the
current path you’re going down is a problematic one.

  • Gabriel

Robert Goretsky wrote:

Mark,
I was just thinking about this some more and I think I will hit a
severe issue when deploying this application to multiple users using
your suggestion.

I’m assuming that using the reflect_on_association method is giving me
access to a class variable of the Person class that holds the
conditions.

I have read that class variables are SHARED across user instances in a
production Mongrel/Webrick environment. So, if I have > 1 concurrent
web users, when one web user sets a condition, now all web users will
have this condition set!

Am I misunderstanding ActiveRecord’s implementation of associations as
class varibles here, or am I correct?

Thanks again…

-Rob

Mark,
I was just thinking about this some more and I think I will hit a
severe issue when deploying this application to multiple users using
your suggestion.

I’m assuming that using the reflect_on_association method is giving me
access to a class variable of the Person class that holds the
conditions.

I have read that class variables are SHARED across user instances in a
production Mongrel/Webrick environment. So, if I have > 1 concurrent
web users, when one web user sets a condition, now all web users will
have this condition set!

Am I misunderstanding ActiveRecord’s implementation of associations as
class varibles here, or am I correct?

Thanks again…

-Rob

dear sender,
i´m out of the office until may 29th.
your email will not be forwarded.
for urgent stuff please contact [email protected]
kind regards,
alexander

Robert Goretsky wrote:

production Mongrel/Webrick environment. So, if I have > 1 concurrent
web users, when one web user sets a condition, now all web users will
have this condition set!

Am I misunderstanding ActiveRecord’s implementation of associations as
class varibles here, or am I correct?

Rob, yes, the condition will change for all requests on that Rails
process, but requests are currently handled serially on each of these
processes, so you can set the condition, do the find, then set it back,
perhaps using an automatic wrapper around the finds through a custom
find method, or by putting finds in a block that is yielded-to by a
condition-setting method. If and when Rails becomes multi-threaded,
some locks can be added to the condition-setting method.

And you only need to reset the condition if the find sometimes needs
to be done without condition. If that’s the case, an alternative to
condition reset would be to define the association twice, one fixed
and condition-less, and one with a name suffix that indicates use of
a dynamic condition.


We develop, watch us RoR, in numbers too big to ignore.