Jim Fisher
2006-Aug-18 17:35 UTC
[Rails] Rails is doing what I want - but I don''t understand how.
Hi guys, I have the strangest thing happening. The funny part is its doing exactly what I want to do, I just don''t understand how. Basically here is my model. class Role < ActiveRecord::Base has_and_belongs_to_many :users has_and_belongs_to_many :rights def self.names names = Array.new() for role in Role.find :all names << role.name end return names end end migration file for reference: class CreateRoles < ActiveRecord::Migration def self.up create_table :roles_users, :id => false do |t| t.column "role_id", :integer t.column "user_id", :integer end create_table :roles do |t| t.column "name", :string end end def self.down drop_table :roles_users drop_table :roles end end As you can see I wrote a self.names methods so that I can easily call all the names out of the roles table like so:>> role_names = Role.names=> ["trainers","admins"] So that makes perfect sense. So the weird but cool part, is that when I go to grab all the names of the roles that belong to a user like so:>> user = User.find :first=> #<User:0x25cd680 @attributes={"id"=>"1", "login"=>"trainer","email"=>"trainer@none.com"}>>> user.roles=> [#<Role:0x25c6998 @attributes={"name"=>"trainer", "role_id"=>"1", "id"=>"1", "user_id"=>"3"}>]>> user.roles.names=> ["trainer"] How does that work? I aspected it to error out, or even require some sort of special instance ''names'' definition at the very least. So, I''ve been digging and have released that ''roles'' just calls a special has_and_belongs_to_many find definition, but I''m still floored that this works the why I want it to - but just can''t grasp what is going on. I''m guessing that the Role.find methods I''m calling from within the self.names method is using a cached copy of all the roles found earlier by the user.roles methods??? Any suggestions or riddle solving would be greatly appreciated. Thanks, Jim -- Posted via http://www.ruby-forum.com/.
Chris Hall
2006-Aug-18 18:37 UTC
[Rails] Rails is doing what I want - but I don''t understand how.
you''re mostly right. here''s an example to see why it works try doing user.roles.ids you should get a NoMethodError: undefined method `ids'' for Role:Class Note the Role:Class...this is the key...see you DO have a names class method for Role so yes, it is a bit of rails ''magic'' going on. i''m not sure exactly how it works though as the query that is generated is NOT the find :all query ans a matter of fact, both the below calls generates the same query. user.roles user.roles.names my guess is that there is some sort of with_scope or association extension going on...as if you were to remove the names method from your Role class and add this association extension has_and_belongs_to_many :roles, :join_table => "roles_users" do def names find(:all).collect { |p| p.name } end end to your user class, you would see the same results only you don''t have the benefit of having a class method any more. that is quite strange though. perhaps someone else can provide more details on this behavior. Chris On 8/18/06, Jim Fisher <jim@paperfuse.com> wrote:> Hi guys, I have the strangest thing happening. The funny part is its > doing exactly what I want to do, I just don''t understand how. > > Basically here is my model. > > class Role < ActiveRecord::Base > has_and_belongs_to_many :users > has_and_belongs_to_many :rights > > def self.names > names = Array.new() > for role in Role.find :all > names << role.name > end > return names > end > end > > migration file for reference: > > class CreateRoles < ActiveRecord::Migration > > def self.up > create_table :roles_users, :id => false do |t| > t.column "role_id", :integer > t.column "user_id", :integer > end > > create_table :roles do |t| > t.column "name", :string > end > end > > def self.down > drop_table :roles_users > drop_table :roles > end > end > > As you can see I wrote a self.names methods so that I can easily call > all the names out of the roles table like so: > > >> role_names = Role.names > => ["trainers","admins"] > > So that makes perfect sense. > > So the weird but cool part, is that when I go to grab all the names of > the roles that belong to a user like so: > > >> user = User.find :first > => #<User:0x25cd680 @attributes={"id"=>"1", > "login"=>"trainer","email"=>"trainer@none.com"}> > >> user.roles > => [#<Role:0x25c6998 @attributes={"name"=>"trainer", "role_id"=>"1", > "id"=>"1", "user_id"=>"3"}>] > >> user.roles.names > => ["trainer"] > > How does that work? > I aspected it to error out, or even require some sort of special > instance ''names'' definition at the very least. > > So, I''ve been digging and have released that ''roles'' just calls a > special has_and_belongs_to_many find definition, but I''m still floored > that this works the why I want it to - but just can''t grasp what is > going on. I''m guessing that the Role.find methods I''m calling from > within the self.names method is using a cached copy of all the roles > found earlier by the user.roles methods??? > > Any suggestions or riddle solving would be greatly appreciated. > Thanks, > Jim > > > > -- > Posted via http://www.ruby-forum.com/. > _______________________________________________ > Rails mailing list > Rails@lists.rubyonrails.org > http://lists.rubyonrails.org/mailman/listinfo/rails >
Tom Mornini
2006-Aug-18 18:52 UTC
[Rails] Rails is doing what I want - but I don''t understand how.
On Aug 18, 2006, at 10:35 AM, Jim Fisher wrote:> Hi guys, I have the strangest thing happening. The funny part is its > doing exactly what I want to do, I just don''t understand how. > > Basically here is my model. > > class Role < ActiveRecord::Base > has_and_belongs_to_many :users > has_and_belongs_to_many :rights > > def self.names > names = Array.new() > for role in Role.find :all > names << role.name > end > return names > end > endI''m not sure about the rest of the email, but your names method could be simpler: def self.names find(:all).collect { |r| r.name } end :-) -- -- Tom Mornini
Justin Forder
2006-Aug-18 18:54 UTC
[Rails] Rails is doing what I want - but I don''t understand how.
Chris Hall wrote:> you''re mostly right. here''s an example to see why it works > > try doing > > user.roles.ids > > you should get a NoMethodError: undefined method `ids'' for Role:Class > > Note the Role:Class...this is the key...see you DO have a names class > method for Role > > so yes, it is a bit of rails ''magic'' going on. i''m not sure exactly > how it works though as the query that is generated is NOT the find > :all query > > ans a matter of fact, both the below calls generates the same query. > > user.roles > user.roles.namesGiven that you can do user.roles.find(...), and this adds the condition that the role found belongs to the user (and the find appears as a class method on Role), it looks as if the same magic is being applied to the database call in the names class method. You can check the query by looking in the development.log. regards Justin> > my guess is that there is some sort of with_scope or association > extension going on...as if you were to remove the names method from > your Role class and add this association extension > > has_and_belongs_to_many :roles, :join_table => "roles_users" do > def names > find(:all).collect { |p| p.name } > end > end > > to your user class, you would see the same results only you don''t have > the benefit of having a class method any more. > > that is quite strange though. perhaps someone else can provide more > details on this behavior. > > Chris > > On 8/18/06, Jim Fisher <jim@paperfuse.com> wrote: >> Hi guys, I have the strangest thing happening. The funny part is its >> doing exactly what I want to do, I just don''t understand how. >> >> Basically here is my model. >> >> class Role < ActiveRecord::Base >> has_and_belongs_to_many :users >> has_and_belongs_to_many :rights >> >> def self.names >> names = Array.new() >> for role in Role.find :all >> names << role.name >> end >> return names >> end >> end >> >> migration file for reference: >> >> class CreateRoles < ActiveRecord::Migration >> >> def self.up >> create_table :roles_users, :id => false do |t| >> t.column "role_id", :integer >> t.column "user_id", :integer >> end >> >> create_table :roles do |t| >> t.column "name", :string >> end >> end >> >> def self.down >> drop_table :roles_users >> drop_table :roles >> end >> end >> >> As you can see I wrote a self.names methods so that I can easily call >> all the names out of the roles table like so: >> >> >> role_names = Role.names >> => ["trainers","admins"] >> >> So that makes perfect sense. >> >> So the weird but cool part, is that when I go to grab all the names of >> the roles that belong to a user like so: >> >> >> user = User.find :first >> => #<User:0x25cd680 @attributes={"id"=>"1", >> "login"=>"trainer","email"=>"trainer@none.com"}> >> >> user.roles >> => [#<Role:0x25c6998 @attributes={"name"=>"trainer", "role_id"=>"1", >> "id"=>"1", "user_id"=>"3"}>] >> >> user.roles.names >> => ["trainer"] >> >> How does that work? >> I aspected it to error out, or even require some sort of special >> instance ''names'' definition at the very least. >> >> So, I''ve been digging and have released that ''roles'' just calls a >> special has_and_belongs_to_many find definition, but I''m still floored >> that this works the why I want it to - but just can''t grasp what is >> going on. I''m guessing that the Role.find methods I''m calling from >> within the self.names method is using a cached copy of all the roles >> found earlier by the user.roles methods??? >> >> Any suggestions or riddle solving would be greatly appreciated. >> Thanks, >> Jim >> >> >> >> -- >> Posted via http://www.ruby-forum.com/. >> _______________________________________________ >> Rails mailing list >> Rails@lists.rubyonrails.org >> http://lists.rubyonrails.org/mailman/listinfo/rails >> > _______________________________________________ > Rails mailing list > Rails@lists.rubyonrails.org > http://lists.rubyonrails.org/mailman/listinfo/rails > >
Tim McIntyre
2006-Aug-18 19:05 UTC
[Rails] Rails is doing what I want - but I don''t understand how.
Not sure I''m seeing straight or completely understanding the question but it seems to me that that will only work when the given user only has a single role, otherwise you''ll have to iterate over each roll to get the names? Am I missing the point here? I don''t see the confusion. Tim On Aug 18, 2006, at 11:54 AM, Justin Forder wrote:>>> >> user = User.find :first >>> => #<User:0x25cd680 @attributes={"id"=>"1", >>> "login"=>"trainer","email"=>"trainer@none.com"}> >>> >> user.roles >>> => [#<Role:0x25c6998 @attributes={"name"=>"trainer", "role_id"=>"1", >>> "id"=>"1", "user_id"=>"3"}>] >>> >> user.roles.names >>> => ["trainer"]-------------- next part -------------- An HTML attachment was scrubbed... URL: http://wrath.rubyonrails.org/pipermail/rails/attachments/20060818/14ecf18d/attachment-0001.html
Bosko Milekic
2006-Aug-18 19:07 UTC
[Rails] Rails is doing what I want - but I don''t understand how.
On 8/18/06, Jim Fisher <jim@paperfuse.com> wrote: ...> So the weird but cool part, is that when I go to grab all the names of > the roles that belong to a user like so: > > >> user = User.find :first > => #<User:0x25cd680 @attributes={"id"=>"1", > "login"=>"trainer","email"=>"trainer@none.com"}> > >> user.roles > => [#<Role:0x25c6998 @attributes={"name"=>"trainer", "role_id"=>"1", > "id"=>"1", "user_id"=>"3"}>] > >> user.roles.names > => ["trainer"] > > How does that work? > I aspected it to error out, or even require some sort of special > instance ''names'' definition at the very least. > > So, I''ve been digging and have released that ''roles'' just calls a > special has_and_belongs_to_many find definition, but I''m still floored > that this works the why I want it to - but just can''t grasp what is > going on. I''m guessing that the Role.find methods I''m calling from > within the self.names method is using a cached copy of all the roles > found earlier by the user.roles methods??? > > Any suggestions or riddle solving would be greatly appreciated. > Thanks, > JimHi Jim It''s not that complicated: first, there is a proxy class that is being used here called HasAndBelongsToManyAssociation. It captures the basic method calls/messages sent to the associated collection, so user.roles for example. In HasAndBelongsToManyAssociation (which is by the way defined in active_record/associations/has_and_belongs_to_many_association.rb) there is a method_missing. Take a look at its definition to understand. Cheers, -- Bosko Milekic <bosko.milekic@gmail.com> http://www.crowdedweb.com/
Jim Fisher
2006-Aug-18 19:25 UTC
[Rails] Re: Rails is doing what I want - but I don''t understand how.
Bosko Milekic wrote:> On 8/18/06, Jim Fisher <jim@paperfuse.com> wrote: > ... >> => ["trainer"] >> found earlier by the user.roles methods??? >> >> Any suggestions or riddle solving would be greatly appreciated. >> Thanks, >> Jim > > Hi Jim > > It''s not that complicated: first, there is a proxy class that is being > used here called HasAndBelongsToManyAssociation. It captures the > basic method calls/messages sent to the associated collection, so > user.roles for example. In HasAndBelongsToManyAssociation (which is > by the way defined in > active_record/associations/has_and_belongs_to_many_association.rb) > there is a method_missing. Take a look at its definition to > understand. > > Cheers,def method_missing(method, *args, &block) if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method)) super else @reflection.klass.with_scope(:find => { :conditions => @finder_sql, :joins => @join_sql, :readonly => false }) do @reflection.klass.send(method, *args, &block) end end end I''m guessing it''s the: @reflection.klass.with_scope(:find => {:conditions => @find_sql,...}) portion that is narrowing the scope of the find method in user.roles.find(:all)?? Also, I was aware of the proxy class prior to posting, I''m just not far enough along in my railing to dig through all that code to find where the magic was happening, and for that I appologize. Now with that said, did I just discover a safe way to implement future collect type definitions? My primary concern was that the "magic" was unintentional. As long as I know that this way is the best way to get what I want then I''ll be happy. Thanks guys, Jim -- Posted via http://www.ruby-forum.com/.
Bosko Milekic
2006-Aug-18 19:57 UTC
[Rails] Re: Rails is doing what I want - but I don''t understand how.
On 8/18/06, Jim Fisher <jim@paperfuse.com> wrote:> Bosko Milekic wrote: > > On 8/18/06, Jim Fisher <jim@paperfuse.com> wrote: > > ... > >> => ["trainer"] > >> found earlier by the user.roles methods??? > >> > >> Any suggestions or riddle solving would be greatly appreciated. > >> Thanks, > >> Jim > > > > Hi Jim > > > > It''s not that complicated: first, there is a proxy class that is being > > used here called HasAndBelongsToManyAssociation. It captures the > > basic method calls/messages sent to the associated collection, so > > user.roles for example. In HasAndBelongsToManyAssociation (which is > > by the way defined in > > active_record/associations/has_and_belongs_to_many_association.rb) > > there is a method_missing. Take a look at its definition to > > understand. > > > > Cheers, > > > def method_missing(method, *args, &block) > if @target.respond_to?(method) || > (!@reflection.klass.respond_to?(method) && Class.respond_to?(method)) > super > else > @reflection.klass.with_scope(:find => { :conditions => > @finder_sql, :joins => @join_sql, :readonly => false }) do > @reflection.klass.send(method, *args, &block) > end > end > end > > I''m guessing it''s the: > @reflection.klass.with_scope(:find => {:conditions => @find_sql,...}) > > portion that is narrowing the scope of the find method in > user.roles.find(:all)??Not in that call, no. The above gets caught by the proxy class'' own definition of find. It doesn''t hit the method_missing code.> Also, I was aware of the proxy class prior to posting, I''m just not far > enough along in my railing to dig through all that code to find where > the magic was happening, and for that I appologize.Absolutely no need to appologize! Particularly not to me. :-)> Now with that said, did I just discover a safe way to implement future > collect type definitions? My primary concern was that the "magic" was > unintentional. As long as I know that this way is the best way to get > what I want then I''ll be happy. > > Thanks guys, > JimYou can in fact implement things that way, but I don''t recommend it. I am not convinced that it was intentionally designed to be used for YOUR class methods, or if you should rely on this behavior for a long time (you probably could, but I don''t). For the sake of clarity, I tend to prefer just calling ClassName.my_class_method(my_parameters) from my own code, because then the scope of all the finds inside the class method is obvious just by looking at the class method call. But certainly, in the example you cited, the find(:all) inside your "names" class method gets a narrower scope tossed to it by the method_missing/proxy class code above. Cheers, -- Bosko Milekic <bosko.milekic@gmail.com> http://www.crowdedweb.com/
Justin Forder
2006-Aug-18 20:23 UTC
[Rails] Rails is doing what I want - but I don''t understand how.
Tim McIntyre wrote:> Not sure I''m seeing straight or completely understanding the question > but it seems to me that that will only work when the given user only has > a single role, otherwise you''ll have to iterate over each roll to get > the names?No, this is about how the names method that the OP defined on Role is getting magically called via the roles collection on User, simultaneously passing in an extra condition. See Bosko''s contributions to this thread. regards Justin
Jim Fisher
2006-Aug-18 20:50 UTC
[Rails] Re: Re: Rails is doing what I want - but I don''t understand
Bosko Milekic wrote:> On 8/18/06, Jim Fisher <jim@paperfuse.com> wrote: >> > Hi Jim >> > Cheers, >> end >> end >> end >> >> I''m guessing it''s the: >> @reflection.klass.with_scope(:find => {:conditions => @find_sql,...}) >> >> portion that is narrowing the scope of the find method in >> user.roles.find(:all)?? > > Not in that call, no. The above gets caught by the proxy class'' own > definition of find. It doesn''t hit the method_missing code. > >> Also, I was aware of the proxy class prior to posting, I''m just not far >> enough along in my railing to dig through all that code to find where >> the magic was happening, and for that I appologize. > > Absolutely no need to appologize! Particularly not to me. :-) > >> Now with that said, did I just discover a safe way to implement future >> collect type definitions? My primary concern was that the "magic" was >> unintentional. As long as I know that this way is the best way to get >> what I want then I''ll be happy. >> >> Thanks guys, >> Jim > > You can in fact implement things that way, but I don''t recommend it. > I am not convinced that it was intentionally designed to be used for > YOUR class methods, or if you should rely on this behavior for a long > time (you probably could, but I don''t). For the sake of clarity, I > tend to prefer just calling ClassName.my_class_method(my_parameters) > from my own code, because then the scope of all the finds inside the > class method is obvious just by looking at the class method call. But > certainly, in the example you cited, the find(:all) inside your > "names" class method gets a narrower scope tossed to it by the > method_missing/proxy class code above. > > Cheers,Thank, I''m going to put this down as "undiscovered/unintended feature" and steer clear until someone at the top confirms that it will stick around permanently. Thanks everyone, Jim -- Posted via http://www.ruby-forum.com/.