#167 More on Virtual Attributes
Use a virtual attribute to implement a simple tagging feature. In this episode I show you how to assign virtual attributes through a callback instead of a setter method.
- Download:
- source code
- mp4
- m4v
- webm
- ogv
Thanks for this great episode :) However, I did have one question. What happens if the virtual attribute model fails validation?
Dear Ryan,
This episode is as great as it always use to be. I have one doubt though...
You have defined task_names as writer. It means it wont be defining
def task_names=(name)
@task_names = name
end
Then how does its value get assigned?
Thanks,
Madan Kumar Rajan
and don't forget that this code also allows you to remove tags from an article.
and be aware of unused tags if you heavily add and remove tags, since only taggings are destroyed.
Boo to sniffles, get well soon.
That's the first time I've ever seen f.error_messages.. How long has that been available?
Madan Kumar Rajan,
attr_writer creates that code automatically. Search about meta-programming :)
This code could potentially lead to orphaned tags and taggings, couldn't it? I don't know what meta-magic goes on behind the stage, but the code for cleaning up after yourself does seem to be missing?
Otherwise, great job :)
Two years ago this stuff could be called complex, but Ryan, as you just say, today we are using it in all our everyday projects.
These days it really shouldn't be called hard or complex, we probably need a different approach to the problem.
@J, good question. Here Tag doesn't have any validations (it is too simple), but if there are validations on the model you'll definitely need to take that into consideration.
One solution is to do "create!" (with a bang) to ensure an exception is raised if the validation fails. However this is not pretty to the end user. Unfortunately I don't know of an easy way to handle validations in a pretty way here.
@Vincent, f.error_messages has been around for quite some time, I think since Rails 2.0 but I'm not positive.
@Robin, It should not orphan any Tagging records (the tags= method takes care of this), but Tag models will continue to exist even if they do not have articles assigned to them.
I don't think that is much of an issue here, but there are a couple ways around this. One is to add an after_destroy hook on tagging to check if it is the last one and remove the tag as well. Alternatively you could set up an external recurring task to loop through all tags and remove empty ones.
Thanks for this Ryan!
I'm trying to use virtual attributes on a model that maintains a 'duration' field, which is an integer of seconds. In my model I'd like to be able to represent this duration value as 'hours' & 'minutes'. The model saves correctly, however I can't seem to find a way to initialise hours & minutes from the database. Could you help?
http://pastie.org/524093
Are you using :value for your textfields? :value=>@course.duration/3600
Hi!
I like that stylesheet. Is it made by you and can I use it for my testing?
Keep up with the good work!
Felix
Man I love this site. Every single screencast has somehow been included in a project i'm working on, and this one is no exception :)
Now taking this further, how would i go about doing a Article.find_by_tag_name method or named_scope?
Any help would be greatly appreciated.
Thx.
Well i think i might have found a way.. I've created a named_scope in the Articles model:
named_scope :find_by_tag, lambda { |*args| {:include => :tags, :conditions => ["tags.name ILIKE ?", '%'+args.first+'%']} }
Not sure if it's the best way (probably not) but seems to work... any better ideas?
Karim:
sorry if this is not what you are looking for but what about:
def find_by_tag name
Tag.find_by_name(name).articles
end
I'm attempting to clean up orphaned tags by using a after_destroy hook, but it doesn't seem to even be fired?
Does the virtual writer not fire the destroy events?
Thanks!
Hi Ryan, would it not be better to use a before_save callback to call assign_tags, and then use find_or_initialize_by_name instead of find_or_create_by_name. That way, the tags all get saved in one atomic action.
I have used this code in my own app but took it one step further by adding a counter cache to the tags table called taggings_count. This is where the problems start. It increments the count when taggings are added but does not decrement the count when taggings are deleted. Any suggestions would be appreciated.
Great cast. Great site. I wake up on Mondays ready to watch another cast.
I recently needed to start using tagging in my application, so I immediately came to railscasts.com
Instead of articles, my model is entry. Companies have many entries. Entries have many tags. A tag belongs to a company. A user belongs to a company. I would like to track the tags by company by adding a company_id to the tag table. What code would I need to modify to be able to add the company_id that is a hidden field in the form?
@Brad. I have the same problem with decrementing the counters. I even tried adding before_destroy and after_destroy to the taggings, but they don't even get called. I guess that they are deleted and not destroyed. Thoughts Ryan?
I managed to get the counter caches to decrement. It's ugly and probably excessive. But it does work.
def assign_tags
if @tag_names
new_tags = @tag_names.split(/\s+/).map do |name|
Tag.find_or_create_by_name(name)
end
# destroy taggings to decrement counter cache
self.taggings.each do |t|
t.destroy if !new_tags.collect(&:id).include?(t.tag_id)
end
# assign any new tags
self.tags = new_tags
end
end
Yes, it's so.
I got a question here, how to do this tagging thing with a polymorphic association? for example:
Article has_many :taggings, :as => 'taggable', :dependent => :destroy
Article has_many :tags, :through => 'taggings'
-------------------------------
Taggings belongs_to :taggable, :polymorphic => true
Taggings belongs_to :tags
-------------------------------
Tag has_many :taggings
Im getting an error with the setter method "Could not find the association "taggings" in model Article". I think something wrong with "Article has_many :tags, :through => 'taggings'", but how to correct this, Please help thx
I'm sure you found you solution by now, but if anyone else is looking around for this:
And since this is polymorphic these two lines would go into whatever model you wanted to be taggable.
I've just implemented the tagging thing right into my system and built around it - and now I'm realising that I don't have a clue how to search for the tags. Is there any easy way to search (using something similar to the simple searching railscast) both my tutorial names and their tags (in which taggings are done via this tutorial).
My current super simple search code is below:
Any help?
I get the following error:
SQLite3::SQLException: no such column: taggings.
Sadface.
did you run
rake db:migrate
?Hi Rails Newbie here. I am using Rails 4 and trying to implement tagging as instructed in this rails cast.
When I try to view the tags in the rails console with a.tags I get
#
Any help would be greatly appreciated.
Sorry for some reason the text regarding error didn't show up.
a.tags give the following error
#<ActiveRecord::Associations::CollectionProxy []>
The code for this episode has been updated to Rails 5, you can download it from virta
Special skills are specific and they can trigger a massive amount of damage on specific challengers