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.
精彩评论