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?
Class Methods are the methods that are defined inside the class, public class methods can be accessed with the help of objects. The method is marked as private by default, when a method is defined outside of the class definition.
Active Model is a library containing various modules used in developing classes that need some features present on Active Record.
Scopes are just class methods. Internally Active Record converts a scope into a class method. "There is no difference between them" or “it is a matter of taste”.
There are two standard approaches for defining class method in Ruby. The first one is the “def self. method” (let's call it Style #1), and the second one is the “class << self” (let's call it Style #2). Both of them have pros and cons.
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?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With