Can't figure this one out.
In rails model, I want to call a method within the same model to manipulate data returned by a find method. This 'filter' method will be called from many custom find method within this model, so I want it to be separate. (and I cannot filter from the SQL it is too complicated)
Here is an example:
#controller
#data = Model.find_current
#model
class Model
def self.find_current
#rows = find(:all)
filter_my_rows
return #rows
end
def filter_my_rows
#do stuff here on #rows
for row in #rows
#basically I remove rows that do not meet certain conditions
end
end
end
The result of this is: undefined method `filter_my_rows'
Thank you for any help!
Part of the problem is you're defining a class method called find_current and an instance method called filter_my_rows. Generally you define them both within the same scope for them to work together.
Another thing is you can do a lot of the filtering you need with a simple Array#reject call. For example:
#models = all.reject do |m|
# This block is used to remove entries that do not qualify
# by having this evaluate to true.
!m.current
end
You can modularize this somewhat by plugging in functions as required, too, but that can get wildly complicated to manage if you're not careful.
# Define reusable blocks that are organized into a Hash
CONDITION_FILTERS = {
:current => lambda { |m| m.current }
}
# Array#select is the inverse of Array#reject
#models = all.select(CONDITION_FILTERS[:current])
While you stated in your question that this was only required because of concerns about not being able to determine the relevance of a particular record before all the records are loaded from the database, this is generally bad form since you will probably be rejecting a large amount of data that you've gone through the trouble of retrieving and instantiating as models only to immediately discard them.
If possible, you should at least cache the retrieved rows for the duration of the request so you don't have to keep fetching them over and over.
function of class and function of instance is your problem.
You can't call a instance function in your class function that way.
Use self.filter_my_rows to define your function (note the self) and everything will go right.
use a named_scope instead
named_scope :current, :conditions => {:active => true} # this is normal find criteria
then in your controller
#date = Model.current
you can also make the named_scopes lambda functions
What's wrong with your solutions? What are you looking for exactly?
If I understood your point of view, the main problem of your implementation is that
This 'filter' method will be called
from many custom find method within
this model, so I want it to be
separate.
... that you can't use named_scopes or with_scope, the first solution that comes to my mind is to create a custom wrapper to act as a filter.
class Model
def self.find_current
filtered do
all
end
end
def self.other_method
filtered do
all :conditions => { :foo => "bar" }
end
end
def self.filtered(&block)
records = yield
# do something with records
records
end
end
Related
Okay, this is kind of a followup to this question: Is overriding an ActiveRecord relation's count() method okay? Basically I have a relation I want to paginate on, and counting it is slow, so I'm overriding count() with a cached counter attribute.
I have:
class CountDelegator < SimpleDelegator
def initialize(obj, total_count)
super(obj)
#total_count = total_count
end
def count
#total_count
end
end
class Parent
has_many :kids do
def chatty_with_singleton
resultset = where(:chatty => true)
def resultset.count
proxy_association.owner.chatty_kids_count
end
resultset
end
def chatty_with_delegation
resultset = where(:chatty => true)
CountDelegator.new(resultset, proxy_association.owner.chatty_kids_count)
end
end
end
p = Parent.first
Now, when I do either p.kids.chatty_with_singleton.count or p.kids.chatty_with_delegation.count, I use the cached count. Great! However, the following behave differently:
# Uses the cached count
p.kids.chatty_with_singleton(:order => "id desc").count
# Does not use the cached count
p.kids.chatty_with_delegation(:order => "id desc").count
I'm totally confused — I don't know why these two cases would behave differently in practice. (Yes, I'm aware that p.kids.chatty_with_singleton(:id => 0).count returns the wrong value and I am okay with that.)
Why does defining the method on the singleton resultset cause that definition to dominate, while the delegator doesn't?
You're re-implementing the built-in counter_cache option provided by belongs_to. You simply specify the column of your cache as well, if you're not using the rails default column.
Using the counter_cache will automatically read the cached value on table instead of executing a COUNT.
I'm having trouble writing class methods to use on collections of ActiveRecord objects. I've run into this issue twice in the last couple of hours, and it seems like a simple problem, so I know I'm missing something, but I haven't been able to find answers elsewhere.
Example:
class Order < ActiveRecord::Base
belongs_to :customer
scope :month, -> { where('order_date > ?', DateTime.now.beginning_of_month.utc) }
def self.first_order_count
map(&:first_for_customer?).count(true)
end
def first_for_customer?
self == customer.orders.first
# this self == bit seems awkward, but that's a separate question...
end
end
If I call Order.month.first_order_count, I get
NoMethodError: undefined method 'map' for #<Class:...
As far as I know, that's because map can't be called directly on Order, but needs an Enumerable object instead. If I call Order.year.map(&:first_for_customer?).count(true), I get the desired result.
What's the right way to write methods to use on a collection of ActiveRecord objects, but not on the class directly?
In your case, you can use a trick in this case.
def self.first_order_count
all.map(&:first_for_customer?).count(true)
end
Will do the trick, without any other problems, this way if you concatenate this method on where clause you still get results from that where, this way you get what you need if you call this method directly on Order.
ActiveRecord collections are usually manipulated using scopes, with the benefits of being able to chain them and let the database do the heavy lifting. If you must manage it in Ruby, you can start with all.
def self.first_order_count
all.map(&:first_for_customer?).count(true)
end
What are you trying to achieve with your code though?
Disclaimer: I'm relatively new to rails.
I have a custom method in my model that I'd like to query on. The method, called 'active?', returns a boolean. What I'd really like to do is create an ActiveRecord query of the following form:
Users.where(:active => true)
Naturally, I get a "column does not exist" when I run the above as-is, so my question is as follows:
How do I do the equivalent of the above, but for a custom method on the model rather than an actual DB column?
Instead of using the active? method, you would have a scope to help find items that match.
Something like this...
def self.active
joins(:parent_table).where(:archived => false).where("? BETWEEN parent_table.start_date AND parent_table.end_date ", Time.now)
end
And, you should be able to do this
def active?
User.active.exists?(self)
end
If you would like to reuse this scope for the instance test.
An easy way to do this would be by using the select method with your exiting model method.
Users.select{|u| u.active}
This will return an array so you won't be able to use Active Record Query methods on it. To return the results as an ActiveRecord_Relation object, you can use the where function to query instances that have matching ids:
class User < ActiveRecord::Base
def self.active
active_array = self.select{|r| r.active?}
active_relation = self.where(id: active_array.map(&:id))
return active_relation
end
end
I want to extend the Array returned from a query to add new methods.
Lets assume I have a model Query and it has a few calculated attributes size_x, size_y ...
In my controller, I retrieve a few queries
#queries = Query.limit(10).all
Now #queries becomes an Array. I would like to extend the Array with 2 methods
#queries.average_size_x
And
#queries.average_size_y
I am looking for a neat solution that will enhance the Array returned with those 2 methods without adding it to the global Array class. Is there any way to do that ?
Here are some things I tried:
Redefining the self.find method
This only works if I change my query to the old syntax
Query.find(:all, :limit => 5)
extending the relation
Query.limit(10).extending(QueryAggregator)
This works if I don't call the all method as the object is an ActiveRecord relation but onceall is called, the methods average_size_x is not available.
The only option I see is to extend Array class and add the methods there, but it might be irrelevant for other Models...
Any suggestions ?
This should works. You have a proxy instance and extending by module.
query = Query.limit(10).all
module SomeModule
def average_size_x
end
def average_size_y
end
end
query.extend(SomeModule)
query.average_size_y
query.average_size_y
I am using Ruby on Rails 3.2.2 and I would like to know if it is a correct / not dangerous / common approach to pass an ActiveRecord::Relation object as a method parameter.
At this time I am planning to use this approach in a scope method of a my model this way:
class Article < ActiveRecord::Base
def self.with_active_associations(associations, active = nil)
# associations.class
# => ActiveRecord::Relation
case active
when nil
scoped
when 'active'
with_ids(associations.pluck(:associated_id))
when 'not_active'
...
else
...
end
end
end
Note I: I would like to use this approach for performance reasons since the ActiveRecord::Relation is lazy loaded (in my case, if the active parameter value is not active the database is not hit at all).
Note II: the usage of the pluck method may generate an error if I pass as association parameter value an Array instead of an ActiveRecord::Relation.
1) In my opinion it's a sound tradeoff, you lose the ability to send an array as argument but you gain some perfomance. It's not that strange; for example, every time you define a scope you are doing exactly that, a filter than works only on relations and not on arrays.
2) You can always add Enumerable#pluck so the method works transparently with arrays. Of course it won't work if you use more features of relations.
module Enumerable
def pluck(method, *args)
map { |x| x.send(method, *args) }
end
end