Reputation: 11896
In a screen cast on Exporting CSV from a rails app, Ryan Bates presented the following simple code.
I'm trying to figure out how the class method Product::to_csv actually gets invoked on line 5 of ProductController.rb, as it doesn't seem to follow the normal Ruby rules.
product.rb
1 class Product < ActiveRecord::Base
2 def self.to_csv(options = {})
3 ...
4 end
5 end
products_controller.rb
1 class ProductsController < ApplicationController
2 def index
3 @products = Product.order(:name)
4 respond_to do |format|
5 format.csv { send_data @products.to_csv }
6 ...
7 end
8 end
9 end
Since to_csv is a class method, I'd expect the invocation to look like Product::to_csv().
According to the documentation, @products is an instance of ActiveRecord::Relation. Why do messages sent to an instance of ActiveRecord::Relation cause methods on the Product class object to get invoked? To make it even stranger, renaming to_csv to some arbitrary name in both the the sender and receiver leads to NoMethodError, so maybe there is some magic afoot based on names that begin with to_?
Am I missing something obvious? Any clarification would be greatly appreciated.
Upvotes: 3
Views: 323
Reputation: 51707
This is just one of the things Rails does. Any class methods automatically become available as "collection" methods, meaning they are available to the relation objects. Scopes and class methods are interchangeable that way.
Upvotes: 3
Reputation: 12455
I can answer the following question for now:
Why do messages sent to an instance of ActiveRecord::Relation cause methods on the Product class object to get invoked?
ActiveRecord::Relation
class is used to chain several methods without actually trigger multiple SQL queries. This way you can write something like Product.where('price <= ?', 100).order(:price).limit(30)
and Rails will execute just one query.
The magic works because you have an ActiveRecord::Relation
instance until you try to access the data (e.g. because a first
or all
call), at that time the query will be run and you'll have ActiveRecord::Base
or one of his descendants.
Long story short, if you check the class with @products.class
you'll see is an ActiveRecord::Relation
but later you have Product
instances, and then you can call the to_csv
method.
Upvotes: 1