Rails Model: includes method on self - ruby-on-rails

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.

Related

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?

Define method on activerecord relation

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.

Wrapping an object with methods from another class

Let's say I have a model called Article:
class Article < ActiveRecord::Base
end
And then I have a class that is intended to add behavior to an article object (a decorator):
class ArticleDecorator
def format_title
end
end
If I wanted to extend behavior of an article object, I could make ArticleDecorator a module and then call article.extend(ArticleDecorator), but I'd prefer something like this:
article = ArticleDecorator.decorate(Article.top_articles.first) # for single object
or
articles = ArticleDecorator.decorate(Article.all) # for collection of objects
How would I go about implementing this decorate method?
What exactly do you want from decorate method? Should it simply add some new methods to passed objects or it should automatically wrap methods of these objects with corresponding format methods? And why do you want ArticleDecorator to be a class and not just a module?
Updated:
Seems like solution from nathanvda is what you need, but I'd suggest a bit cleaner version:
module ArticleDecorator
def format_title
"#{title} [decorated]"
end
def self.decorate(object_or_objects_to_decorate)
object_or_objects_to_decorate.tap do |objects|
Array(objects).each { |obj| obj.extend ArticleDecorator }
end
end
end
It does the same thing, but:
Avoids checking type of the arguments relying on Kernel#Array method.
Calls Object#extend directly (it's a public method so there's no need in invoking it through send).
Object#extend includes only instance methods so we can put them right in ArticleDecorator without wrapping them with another module.
May I propose a solution which is not using Module mixins and thereby granting you more flexibility. For example, using a solution a bit more like the traditional GoF decorator, you can unwrap your Article (you can't remove a mixin if it is applied once) and it even allows you to exchange the wrapped Article for another one in runtime.
Here is my code:
class ArticleDecorator < BasicObject
def self.[](instance_or_array)
if instance_or_array.respond_to?(:to_a)
instance_or_array.map {|instance| new(instance) }
else
new(instance_or_array)
end
end
attr_accessor :wrapped_article
def initialize(wrapped_article)
#wrapped_article = wrapped_article
end
def format_title
#wrapped_article.title.upcase
end
protected
def method_missing(method, *arguments)
#wrapped_article.method(method).call(*arguments)
end
end
You can now extend a single Article by calling
extended_article = ArticleDecorator[article]
or multiple articles by calling
articles = [article_a, article_b]
extended_articles = ArticleDecorator[articles]
You can regain the original Article by calling
extended_article.wrapped_article
Or you can exchange the wrapped Article inside like this
extended_article = ArticleDecorator[article_a]
extended_article.format_title
# => "FIRST"
extended_article.wrapped_article = article_b
extended_article.format_title
# => "SECOND"
Because the ArticleDecorator extends the BasicObject class, which has almost no methods already defined, even things like #class and #object_id stay the same for the wrapped item:
article.object_id
# => 123
extended_article = ArticleDecorator[article]
extended_article.object_id
# => 123
Notice though that BasicObject exists only in Ruby 1.9 and above.
You'd extend the article class instance, call alias_method, and point it at whatever method you want (although it sounds like a module, not a class, at least right now). The new version gets the return value and processes it like normal.
In your case, sounds like you want to match up things like "format_.*" to their respective property getters.
Which part is tripping you up?
module ArticleDecorator
def format_title
"Title: #{title}"
end
end
article = Article.top_articles.first.extend(ArticleDecorator) # for single object
Should work fine.
articles = Article.all.extend(ArticleDecorator)
May also work depending on ActiveRecord support for extending a set of objects.
You may also consider using ActiveSupport::Concern.

Ruby / Rails: create a class method that operates on instances of its children?

In my app, Photo has_and_belong_to_many :land_uses
I have this helper method in the Photo model:
def land_use_list
land_uses.map(&:name).join(', ')
end
This strikes me as a code smell (demeter), but I haven't been able to figure out how to move it to the LandUse model. What I'd like to do is something like:
class LandUse < ActiveRecord::Base
...
def self.list
self.map(&:name).join(', ')
end
...
end
So that instead of calling photo.land_use_list I could call photo.land_uses.list.
But that doesn't work, because it gets called against the class instead of being called against the scoped instances belonging to a particular photo.
Is there a way to do what I'm thinking of? And, more generally, how do you approach issues like this in your app? Is moving the list code to the LandUse model the right approach, or would you recommend something different?
First, I don't think this is violating the Law of Demeter per se. You have a method on an object that calls one method on an attribute to create a temporary variable, and then act on the temporary variable.
It would be a violation of the Law of Demeter, if you were doing this from a different class entirely. eg
class User
def names_of_lands_ive_known
photos.map(:land_uses).map(:name).join ', '
end
end
As it is, it's just good information hiding. But, if you wanted to be able to write photo.land_uses.names, you could add an extension to the association to do what you want.
class Photo
has_and_belong_to_many :land_uses do
def names_as_list_string
all.map(:name).join ', '
end
end
end
For more information on association extensions, check out the docs.
The best way to conform to the law of demeter is to do more or less what you are doing though, because by adding your method on Photo, it means that the methods that interact with Photo, don't also need to know about the LandUse class, just that photo has a method that returns a string of the names of land uses.
I am not in front of a rails app but I believe
photo.land_uses
with return an Array of LandUse objects
So you just need to move your map down to that array like:
photo.land_uses.map(&:name).join(', ')
which is what you had originally - just in your other model. I think you may be right and it means that Photo knows too much about LandUse therefore I would move it out.
You can use :
class LandUse
def self.list_for_photo(id)
LandUse.find_by_photo_id(id).join(', ')
end
def to_s
self.name
end
end
Hope it helps !

Class methods in Ruby on Rails 3 — I'm totally lost!

Background here.
In the above link, the following example is given:
class << self
def by_author(author)
where(:author_id => author.id)
end
end
Aside from that syntax being foreign to a beginner like me — I had always thought class methods were defined with def self.my_class_method — where can I find documentation about class methods in Ruby on Rails?
As far as I know, class methods are always called on the class itself (MyClass.my_class_method), but if class methods in Rails are chainable, it seems as though something else must be going on here!
Edit:
I suppose I sort of cheated by making that comment about the syntax for class methods. I'm really asking how Rails makes a class method chainable — I understand how method chaining works, but not how Rails can allow you to chain class methods without actually returning the class object itself after each "link" in the chain.
Class methods in Ruby are really just members of the singleton class, and doing class << self involves opening the singleton class directly and adding to it, removing the need to declare it in each method definition.
This article on Ruby singletons does a good job explaining.
As far as class methods being chainable, that isn't something specific to class methods, the second method call is simply called on the object returned from the first. For example:
bar = foo.do_something.do_more
is equivalent to:
tmp = foo.do_something
bar = tmp.do_more
In Rails, this chainability is most often used for building SQL queries (e.g., with where or order, etc.). This is achieved because each of these methods returns an ActiveRecord Relation.
The reason
foo.scoped.my_foo_class_method
works is because of ActiveRecord::Relation#method_missing doing the following:
elsif #klass.respond_to?(method)
scoping { #klass.send(method, *args, &block) }
Which checks if the ActiveRecord class responds to the method called, and if so, calls that.
Having class << self is also another way to define your methods so that you do not have to call "def self.my_method" or "def MyClass.my_method" for every single method that you are defining as a class method. Instead of calling
def self.my_method1
end
def self.my_method2
end
class << self
def my_method1
end
def my_method2
end
end
Cheers!
The following two bits of code are equivalent.
Using self.method:
class Hello
def self.world
puts "Hello, World!"
end
end
Using class << self:
class Hello
class << self
def world
puts "Hello, World!"
end
end
end
The only difference is readability, as well as the ease in refactoring.
The class << self technique is often used when metaprogramming.
There is another thread that explains this. class << self vs self.method with Ruby: what's better?

Resources