
Hoe de plugin werkt kun je lezen op: http://agilewebdevelopment.com/plugins/acts_as_taggable_on_steroids
Nu hebben we op deze site tenminste twee typen content die we willen kunnen taggen, Events en Articles. En daar komen er vast nog meer bij. Denk aan vaactures, projecten en misschien wel werknemers. Een ActiveRecord model “taggable” maken is betrekkelijk eenvoudig:
class Article
acts_as_taggable
end
Zo, je kunt nu alle artikelen taggen, met methods als article.taglist.add(), article.taglist = “tag1, tag2, tag3” en meer zulks. Tag clouds genereren is ook een eitje. Maar hoe gaan we met de controllers om? Je zou iets kunnen maken als een ArticleTagsController en een EventTagsController.
Op zich prima, maar niet erg DRY. In plaats daarvan heb ik een TaggingsController opgezet die oneindig veel soorten models kan taggen, zolang ze maar acts_as_taggable implementeren.
class TaggingsController < ApplicationController
def create
# object to be tagged really exist and is taggable
# an expecpetion will be raised if we can't find an object
@tagged_model = instance_eval("model = #{params[:taggable_type]}.find(#{params[:taggable_id]})")
# check if the model can be tagged
raise Exception("objects of type #{params[:taggable_type]} cannot be tagged") unless @tagged_model.respond_to?(:tag_list)
params[:tags].split(",").each do |tag|
@tagged_model.tag_list.add(tag)
end
@tagged_model.save
respond_to do |format|
format.js # render create.ejs
format.html do
flash[:notice] = "tag toegevoegd"
# generate dynamic redirect
# should work if RESTful routes are defined for the model
instance_eval "redirect_to #{params[:taggable_type].downcase}_url(\"#{@tagged_model.to_param}\")"
return
end
end
end
end
Hoe werkt dit nou?
We krijgen via een POST drie parameters binnen: taggable_type, taggable_id en tags Mbv van de taggable_type bepalen we de class van het object dat we willen gaan taggen. Eerst halen we dat object op met een dynamisch geconstrueerde ActiveRecord find. Ik gebruik hier instance_eval. Hier komt bijvoorbeeld dit uit: Article.find(39) – welke artikel nummer 39 uit de database ophaalt (duh!).
Vervolgens gaan we checken of dit object wel getagged kan worden. Met Ruby doe je dat meestal simpelweg door te checken of een object een bepaalde method heeft : responds_to?
Dan gebruiken we tags_list.add() om alle tags toe te kennen. acts_as_taggable_on_steroids zorgt er achter de schermen voor dat de juiste database kolommen gevuld worden. En dat er geen dubbele tags worden bewaard. Prettig.
Daarna komen we in een responds_to blok, voor de doorgwinterede railzer gesneden koek. Was deze request een gewone ouderwetse request, dan redirecten we terug naar de Show pagina van het zojuist getagde model. Wederom met een instance_eval. Dit genereert een call als deze:
redirect_to article_url(@tagged_model.to_param)
Dit zal in rails2 trouwens niet meer nodig zijn. je kunt dan gewoon dit doen: redirect_to @tagged_model en rails weet precies waar je naar toe wilt.
Maar eigenlijk, en dat bedenk ik nu ter plekke, kan dit gewoon redirect_to :back worden, want je wilt toch altjd weer terug naar de pagina waar je vandaag komt.
Dat alles in 1 controller wordt geregeld is misschien niet zo RESTful, maar in dit geval is het volgens mij wel een goede keuze. Ik vraag me trouwens af of die instance_eval in per se nodig is… Dat mag een meer ervaren Rubyist me nog eens vertellen.

6 reacties
Reacties zijn 10 maanden geleden gesloten
reacties
Waarom gebruik je eval? De code die je hebt staan is zo lek als een mandje en extreem gevoelig voor Ruby injection. Wat nou als ik als parameter voor taggable_type Dir.... enz mee geef?
Gebruik liever iets als:
model = Object.const_get(params[:taggable_type]).find(SNIP) if Dir.entries(RAILS_ROOT+'/app/models').include?(params[:taggable_type].downcase+'.rb')
model.find(......)
Woops, geen bevestiging op het plaatsen van een comment (i.i.g. in Firefox 3b2)
Sorry, nee, eigen schuld :P
Als je als taggable_type Dir meegeeft krijg je volgens mij gewoon
"Dir.find: NoMethodError: undefined method 'find' for Dir:Class", maar ik hoor je, dit is niet fraai.
Volgens mij is dit ook een oplossing:
klass = params[:taggable_type].constantize
raise "objects of type #{klass} cannot be tagged" unless klass.respond_to?(:acts_as_taggable)
klass.find(taggable_id)
Ik heb een klein foutje gemaakt, .find(SNIP) had er niet ingemoeten.
Je wilt controleren of het model ook echt een model van jou is, dus daarom loop ik door alle bestanden in app/models en controleer of het juiste rb bestand er is... ik ga er ook pas een constante van maken zodra ik weet dat het echt model is.
Jij hebt ergens Dir.find gedaan, iets wat niet in mijn code staat... doordat het een lange regel zonder spaties is valt er ook iets weg zie ik..
Die Dir.find wordt uitgevoerd als je als parameter voor taggable_type "Dir" meegeeft, waar jij voor waarschuwde.
Zie jij nog problemen met de code in mijn vorige reactie? Het taggen van content kan trouwens alleen als je bent ingelogd, maar toch..
Dank weer voor je reactie. O en vrees niet, binnenkort regelen we een stoerdere avatar voor bij comments.
LOL!! Helemaal goed gekomen, met die stoerdere avatar.