开发者

Ruby on Rails - Which method should I override for Album.first.photos?

I've overrode the method find for ActiveRecord::Base and that went pretty well, now I can customize it nicely.

def self.find(*args)
  # my custom actions 
  super(*args)
end

then

Album.find(1) #=> My custom result

But now I would like to override this:

Album.first.photos

and I'm not sure what method exactly I should override to get the job done... I'm thinking of something specifically for associative queries, but I'm not sure :(

I need to act on 'ActiveRecord::Base' suggesting dynamic classes so I couldn't create a method photos for that, but a method that will interact within all the models I try.

Thanks a lot


Update

For better 开发者_StackOverflow社区explaining what I'm trying to achieve:

Is to create a pluggable ruby gem for use with rails and that gem can simply take you ID structure from the database and convert it on the fly to a shorten ID just like bit.ly system. but this is done on the fly with only declaring has_shortened :id to your model, and then all the interface and queries return for example DH3 instead of 12 for example, I've managed to get this working this the point I need to deal with associataions.

Follow the gem url http://github.com/ludicco/shortener so you can check it out, and feel free to come with ideas you might have to implement it if you think it's a nice idea.

Cheers


Association accessors like you define with has_many are pretty complicated stuff in ActiveRecord behind the scene. They actually use a reflection class to return a proxy object that behaves like a model class, but restrict its finders to the foreign key (and maybe other conditions). If you like to get deeper into it, you might want to have a look at reflection.rb, associations.rb, associations/association_proxy.rb and associations/association_collection.rb in the ActiveRecord gem.

However, in the end, we can use an association accessor as if it would be the class itself and ActiveRecord takes care to pass the required additional conditions to the class finder.

That means, that if you call Album.first.photos, behind the scene, ActiveRecord calls Photo.find (with additional condition to only return records that have :album_id => Album.first.id).

So, if you override Photo.find, it'll return your modified results as well if you use it through Album.first.photos. That's ActiveRecord magic :)

My quick test (ran with Rails 2.3.8):

class Album < ActiveRecord::Base
  has_many :photos
end

class Photo < ActiveRecord::Base
  belongs_to :album

  def self.find (*args)
    puts "XXX running custom finder"
    super
  end
end

Rails console:

> Photo.all
XXX running custom finder
Photo Load (0.4ms)   SELECT * FROM "photos" 
=> [#<Photo id: 1, album_id: 1>, … ] 

> Album.first.photos
Album Load (0.4ms)   SELECT * FROM "albums" LIMIT 1
XXX running custom finder
Photo Load (0.3ms)   SELECT * FROM "photos" WHERE ("photos".album_id = 1) 
=> [#<Photo id: 1, album_id: 1>, … ] 


Since Album.first returns an instance of Album, you should override the photos method on the Album class itself:

class Album < ActiveRecord::Base

  # ...

  def photos(*args)
    if my_condition_is_met
      // my custom handler
    else
      super
    end
  end

  # ...

end

Now if you are sure you need this for every single model instance, I'd rather isolate it on a module:

module Photo
  def photos(*args)
    if my_condition_is_met
      // my custom handler
    else
      super
    end
  end
end

And reopen ActiveRecord::Base class:

class ActiveRecord::Base
  include Photo
end

This way you get modularization for your solution, making it act like a "plugin".

One additional and unrelated note: you don't have to call super(*args), since a call to only super will pass along all the parameters you received.

EDIT: Minor formatting


Take a look at friendly_id plugin (http://github.com/norman/friendly_id http://norman.github.com/friendly_id/file.Guide.html), it does what you need: generate custom id alias for model. You can define custom method to generate slug.

Leave id field in model

For custom #find use :finder_sql option:

class Album < ActiveRecord::Base
  has_friendly_id :short_id

  has_many :photos, :finder_sql => 'select * from photos where album_id="#{short_id}"'
end

>> Album.last.photos
select * from photos where album_id="t54"


Is there an particular reason you can't do this with an association extension?

http://ryandaigle.com/articles/2006/12/3/extend-your-activerecord-association-methods

Messing with the proxy objects directly is asking for trouble. But if you just want to override the find, this might do it.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜