CollectionProxy vs AssociationRelation
ActiveRecord::Relation
are simple query objects before being turned into a query and executed, CollectionProxy
on the other hand are a little bit more sophisticated.
First of all you get association extentions, you probably saw something that looks like this, assume a book store model that has many books
class Store < ActiveRecord::Base
has_many :books do
def used
where(is_used: true)
end
end
end
This way you get to call used books in a store using a syntax that looks like this
Store.first.books.used
But this is the most basic uses, you could use the attributes that are exposed to you in the collection proxy, which are owner
, reflection
and target
Owner
The owner
provides a a reference to the parent object holding the association
Reflection
The reflection
object is an instance of ActiveRecord::Reflection::AssocciationReflection
and contains all the configuration options for the association.
Target
The target
is the association collection objects (or single object when has_one
and belongs_to
).
Using those methods you could do some conditons in your association extention, for example if we have a blog, we give access to all deleted posts to users who are admins (lame example I know)
Class Publisher < ActiveRecord::Base
has_many :posts do
def deleted
if owner.admin?
Post.where(deleted: true)
else
where(deleted: true)
end
end
end
end
You also get access to two more methods which are reset
and reload
, the first one (reset
) clears the cached association objects, the second one (reload
) is more common and is used to reset
and then loads the associated objects from the database.
I hope this explains how having a CollectionProxy
class would be so useful
Many to many through: Going from Associations::CollectionProxy to AssociationRelation
You can operate on ActiveRecord::Associations::CollectionProxy
as you would normally do on ActiveRecord_AssociationRelation
.
But if you really want to have the latter, just call, for example, all
:
user.favorites.all.class #=> Client::ActiveRecord_AssociationRelation
How do I get a relation with all the clients?
You have defined the association:
has_many :clients, through: 'favorites'
Thus getting a relation of associated clients
is as easy as:
user.clients
Rails: Model.association vs Model.association.all
CollectionProxy vs AssociationRelation
Here is an explanation from a previous SO question that might help you out.
ActiveRecord Class Methods / Relations self
Something along the lines of
class Blah < ActiveRecord::Base
def self.thing
MyProxyObject.new(all)
end
end
works for me (prior to rails 4, use scoped
rather than all):
Blah.where(:foo => 1).thing
results in the proxy object holding a relation with the appropriate conditions applied.
Is it possible to get the ActiveRecord::Relation object for an association
For a few minutes I used the where(nil)
hack, then I had a brainwave and tried something random:
User.first.posts.scoped
That's it! :D
Yeah, Rails + Arel is really poorly documented. Looking forward to it maturing to the point where I can actually look things up and get actual answers.
Use send() on a related collection
The problem here is that the name collides with Kernel#open
which is used to open IO streams.
irb(main):001:0> z = Zone.create
(0.2ms) BEGIN
Zone Create (0.8ms) INSERT INTO "zones" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "id" [["created_at", "2018-10-19 16:29:40.018339"], ["updated_at", "2018-10-19 16:29:40.018339"]]
(0.7ms) COMMIT
=> #<Zone id: 10, created_at: "2018-10-19 16:29:40", updated_at: "2018-10-19 16:29:40">
irb(main):002:0> z.orders.send(:open)
Creating scope :open. Overwriting existing method Order.open.
ArgumentError: wrong number of arguments (given 0, expected 1..3)
from (irb):2:in `initialize'
from (irb):2:in `open'
from (irb):2
irb(main):003:0> z.orders.method(:open)
=> #<Method: Order::ActiveRecord_Associations_CollectionProxy(Kernel)#open>
irb(main):004:0> z.orders.method(:open).call
ArgumentError: wrong number of arguments (given 0, expected 1..3)
from (irb):4:in `initialize'
from (irb):4:in `open'
from (irb):4:in `call'
from (irb):4
irb(main):005:0> z.orders
Order Load (0.8ms) SELECT "orders".* FROM "orders" INNER JOIN "customers" ON "orders"."customer_id" = "customers"."id" WHERE "customers"."zone_id" = $1 LIMIT $2 [["zone_id", 10], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy []>
irb(main):006:0> z.orders.open
Order Load (0.9ms) SELECT "orders".* FROM "orders" INNER JOIN "customers" ON "orders"."customer_id" = "customers"."id" WHERE "customers"."zone_id" = $1 AND "orders"."status" = $2 LIMIT $3 [["zone_id", 10], ["status", "open"], ["LIMIT", 11]]
=> #<ActiveRecord::AssociationRelation []>
As you can see the call goes to Kernel#open
first and then goes to the method defined by the scope after the collection is loaded. I'm guessing this is due to ActiveRecord::Associations::CollectionProxy
doing some sort of lazy proxing to the association target class. Since the CollectionProxy
already has an open
method (Kernel#open) it uses it instead.
A simple solution here instead of dynamic calling would be to use a scope that takes an argument:
class Order < ApplicationRecord
belongs_to :customer
scope :with_status, ->(status){ where(status: status.to_s) }
end
Or even better use ActiveRecord::Enum
which is smart enough to properly work with the collection proxy.
class Order < ApplicationRecord
belongs_to :customer
# you need to change the DB column to an integer type
enum status: [:open, :closed]
end
How can an ActiveRecord::Relation object call class methods
There's not much documentation on the application on class methods to ActiveRecord::Relation
objects, but we can understand this behavior by taking a look at how ActiveRecord scopes work.
First, a Rails model scope will return an ActiveRecord::Relation
object. From the docs:
Class methods on your model are automatically available on scopes. Assuming the following setup:
class Article < ActiveRecord::Base
scope :published, -> { where(published: true) }
scope :featured, -> { where(featured: true) }
def self.latest_article
order('published_at desc').first
end
def self.titles
pluck(:title)
end
end
First, invoking a scope returns an ActiveRecord::Relation
object:
Article.published.class
#=> ActiveRecord::Relation
Article.featured.class
#=> ActiveRecord::Relation
Then, you can operate on the ActiveRecord::Relation
object using the respective model's class methods:
Article.published.featured.latest_article
Article.featured.titles
It's a bit of a roundabout way of understanding the relationship between class methods and ActiveRecord::Relation
, but the gist is this:
- By definition, model scopes return
ActiveRecord::Relation
objects - By definition, scopes have access to class methods
- Therefore,
ActiveRecord::Relation
objects have access to class methods
Related Topics
Unable to Load Gem Cocoa Pods While Creating Repo
Verb-Agnostic Matching in Sinatra
What Happens When Modifying Gemfile.Lock Directly
How to Use Ajax Send Data to Controller in Ruby on Rails
Ruby: How to Generate CSV Files That Has Excel-Friendly Encoding
Deleting While Iterating in Ruby
How to Configure Mongomapper and Activerecord in Same Ruby Rails Project
Rails Previous Sunday in Relation to Any Datetime
Can't Run Jenkins Build - Bundle: "Command Not Found"
How to Prevent Pipe Character from Causing a Bad Uri Error in Rails 3/Ruby 1.9.2
Checking Whether the C Compiler Works... No
Carrierwave - Resizing Images to Fixed Width
Rails Can't Login to Postgresql - Pg::Error - Password - Correct Info
Stream Multiple Body Using Async Sinatra
Generating a Short Uuid String Using Uuidtools in Rails
Ruby Split String by Repeating Characters or a Space