Reputation: 11706
Considering I have the following classes and some records in the DB for each one:
class User < ActiveRecord::Base
has_many :user_roles
has_many :roles, :through => :user_roles
end
class Role < ActiveRecord::Base
has_many :user_roles
has_many :users, :through => :user_roles
def self.method1
"boo!"
end
end
Why does User.first.roles.method1
work successfully? I thought User.first.roles returns an Array
, whose class doesn't have method1 defined.
> User.first.roles.method1
=> "boo!"
Of course, Role.all.method1
throws an undefined method error as I expected.
My stack: Ruby 1.9.3p484, Rails 3.2.13
Upvotes: 1
Views: 1574
Reputation: 32933
Ok - i understand what you're asking now - i think a lot of people including me have been confused by the question, sorry. I think the confusion came about because you reworded the question, and because "How am i able to do this" is a very vague and broad reaching question.
I think that what you're asking, specifically, could be rephrased as "If I call a Role class-level method ("method1") on an array of Role objects, returned from User.first.roles
then it returns the same result as calling Role.method1
. Shouldn't it fail, because Array doesn't have that method?"
The answer is that when rails returns the results of an association, it mixes in a module (or more likely a bunch of modules) which add extra methods. One of those methods is method_missing, which tries to call the method on the class which has been collected. So, it's still an Array, but it's an array with extra methods.
If you want to see where it happens, put raise "BANG!"
in your method1 method, then call your code again: in the stack trace you'll be able to see where the magic happens.
Upvotes: 2
Reputation: 787
Actually it is not an Array. it is ActiveRecord::Associations::CollectionProxy You can check definition here activerecord/lib/active_record/associations/collection_proxy.rb
It has changed method_missing which sends all requests to parent class.
Upvotes: 3
Reputation: 32933
In Ruby, you can call the same method (or, more generally, run the same block of code) on an array of objects with the "map" method (aka "collect" - i prefer "collect" myself because in Java a "map" is what we call a "hash" in ruby).
eg
User.first.roles.map{|role| role.method1}
=> #an array of the values of method1
I'm not sure if this is what you want to do though, your question isn't very clear.
EDIT: it just occurred to me that you want to call method1 on a single role, instead of the array. In that case, you could say
User.first.roles.first.method1
assuming that you actually want the first role object.
Upvotes: 0
Reputation: 3323
User.first.roles will return you array, so you can not call your method on an array
You can call method like this
User.first.roles.collect{|role| role.method1}
Upvotes: 1