Define method on activerecord relation - ruby-on-rails

I want to define a custom method on an activerecord relation, eg:
Transaction.all.summed_values
A simple example would be where summed_values should evaluate sum(:value) on the relation.
Where should I define the method summed_values? Looks like it should be on ActiveRecord::Relation. If it should be directly there, which file should I put it in?
Also, if this new method only has meaning for Transactions, is there any way to tell rails to only define this method for ActiveRecord::Relations that consist of Transactions?

Create a self.summed_values method directly in the transaction model.

You should use extending
Transaction.all.extending do
def summed_values
sum(:what_you_want)
end
end
For more info: ActiveRecord::QueryMethods

Is there any specific reason why you want to create this method as extension to ActiveRecord::Relation? I would propose a class method like so:
class Transaction ...
def self.summed_values(transactions=nil)
if transactions.nil?
all.sum(...)...
else
where(id => transactions).sum(...)...
end
end
end
This also has the advantage that it is only available for transactions.

Related

Rails ActiveRecord: Where to define method?

I have a question about ActiveRecord of Rails.
For example I have a Service model, and Service has name as a column.
This is app/model/service.rb
class Service < ActiveRecord::Base
def self.class_method
puts 'This is class method'
end
def instance_method
puts 'This is instance method'
end
end
Then I can do,
Service.class_method #=> 'This is class method'
Service.find(1).instance_method #=> 'This is instance method'
This is easy. But when I get ActiveRecord Instance in Array, for example
Service.where(id: [1,2,3])
and I need method like,
Service.where(id: [1,2,3]).sample_method
def sample_method
self.length
end
but how and where to define method for Active Record Array? I want to handle this object just like other Service class or instances.
Thanks.
First of all, where returns an ActiveRecord::Relation object, not an array. It behaves similar to an array, but inherits a load more methods to process data/ construct SQL for querying a database.
If you want to add additional functionality to the ActiveRecord::Relation class, you can do something like this:
class ActiveRecord::Relation
def your_method
# do something
end
end
This will need to reside somewhere meaningful, such as the lib directory or config/initializers.
This should allow you to do something like
Service.where(id: [1,2,3]).your_method
You can do something similar for any Ruby class, like the Hash, or Array class as well.
However, there's almost ALWAYS a better solution than extending/ overriding Rails/ Ruby source classes...
Your methods like get_count and get_name are a bit pointless... Why not just do:
Service.count
Service.find(1).name
Class methods like count, and instance methods like name (i.e. database column names) are all public - so you don't need to define your own getter methods.
As for your second example, you could just write the following:
Service.where(id: [1,2,3]).map{ |s| s.name }
Or equivalently:
Service.where(id: [1,2,3]).map(&:name)
But the following is actually more efficient, since it is performing the calculation in SQL rather than in ruby. (If you're confused what I mean by that, try running both versions and compare what SQL is generated, in the log):
Service.where(id: [1,2,3]).pluck(:name)

How to call a method when creating new ActiveRecord object

I'm trying to integrate an algorithm I wrote in pure Ruby into a Rails app. The main class of my Ruby project could be a resource.
When it was initialized in Ruby I immediately called a function in the initialize method:
def initialize(keyword)
#keyword = keyword
#sources = get_titles_of_sources
end
In Rails, when I create new objects I usually don't have or at least see a initialize method.
#user = User.new(attribute1: value1, attribute2: value2)
But this style doesn't allow me to automatically call a method when creating a new object.
Given what you said in comments, I feel like you should avoid the callbacks (btw you should always avoid callbacks if you can...).
Anyway, I suggest you to create a build method like:
class User
def self.build(props = {})
new(props).tap do |user|
#your code on initialize
end
end
end
I usually tend to move build methods into builders. But it makes you create an additional class
Use after_initialize callback.
Lastly an after_find and after_initialize callback is triggered for
each object that is found and instantiated by a finder, with
after_initialize being triggered after new objects are instantiated as
well.
Active Record Callbacks
Sounds like a use for ActiveRecord Callbacks (http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html).

Rails model class method for collection of objects

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?

Rails Model: includes method on self

I'm trying to optimise database queries so have been adding Model.includes(:related_model) where appropriate.
What is the appropriate way use this within methods inside my model? For example if I have a method in my model like:
def some_method
self.child_models.each do |child_model|
total_score += child_model.attribute
end
end
How do I use includes in instances like this? It seems natural to do it like this but it doesn't work:
def some_method
self.includes(:child_model).child_models.each do |child_model|
total_score += child_model.attribute
end
end
Most times when I produce an n+1 query it seems I'm referencing the model self but I can't seem to find any examples of this.
Thanks!
You are using self in an instance method so self is the instance of your class but includes is a class method. You need to use your original sample code to use includes Model.includes(:related_model). I think what you really want is:
def some_method
self.child_models.sum('attribute')
end
I would use includes when I am building conditions in a relation not looking at the children of an instance.

How to query Rails / ActiveRecord model based on custom model method?

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

Resources