I'm using the acts_as_taggable_on plugin to provide tagging for my Framework
model. I've got the functional tests that Rails generates, as well as the fixtures it uses and I would like to expand them to add some tags so that I can test searching by tag, etc.
Do I have to create fixtures for the taggings
and tag
tables and load them at the top of my functional tests? If so, h开发者_运维问答ow do I do that? I haven't gotten my head around the syntax for relations described here. Would an alternative be to grab a Framework
instance and add the tags to it before testing the searching behavior? Or will the Rails gods strike me down if I do that?
I was faced with the same problem. I came up with following approach after I did some trial-and-error, read through documentation about fixtures, associations and @barnaclebarnes answer to this question.
I did this with rails 4.2.0 and acts-as-taggable-on 3.5.0.
Remark: My previous answer did the trick, but was a bit wacky. I completly rewrote it after @David pointed me to a cleaner way - completly ommited my Trial and Error and stuck to the solution.
An approach facilitating more of the on-board facilities of rails
@barnaclebarnes solution would provide a little more automatism, but also means much more typing and bookkeeping on ids. So I kept looking for a more concise way.
Polymorphic relations
acts_as_taggable_on uses a polymorphic relation named taggable to implement the relation between tags and different models. See the rails guide on associations for details on polymorphic relations.
Advanced Fixtures
The rubydoc of ActiveRecord::FixtureSet describes the workings of ActiveRecord and what it does with relations of fixtures (chapter Label references for associations):
Active Record reflects on the fixture's model class, finds all the belongs_to associations, and allows you to specify a target label for the association [...] rather than a target id for the FK [...].
A bit further down on the page, there are also some details on polymorphic belongs_to.
This would allow for a tagging fixture definition like this:
tagging:
taggable: foo (Klass)
context: tags
tag: bar
Namespaced Fixtures and Models
ActiveRecord::TestFixtures provides a method to explicitly set the model class for a fixture in case it can not be inferred.
In other words: you can use directories to namespace fixtures and match them with their namespaced models.
To load test data for tags and taggings of acts_as_taggable_on we can put their fixtures in the subfolder fixtures/acts_as_taggable_on/
(Remark: fixtures/ActsAsTaggableOn/
would also work)
Put It All Together
# models/item.rb
class Item < ActiveRecord::Base
acts_as_taggable_on
end
# fixtures/items.yml
item_1: {}
item_2: {}
# fixtures/acts_as_taggable_on/tags.yml
tag_1:
name: tag_1
tag_2:
name: tag_2
# fixtures/acts_as_taggable_on/taggings.yml
tagging_1
taggable: item_1 (Item)
context: tags
tag: tag_1
If you erb up the yaml files this allows for a pretty low maintanance definition of taggable models and their instances.
If you want to use TestUnit then set up some tags (in fixture file tags.yml):
tag_one:
name: tag one
tag_two:
name: tag two
And then set up the taggings (in fixture file taggings.yml):
tagging_one:
tag_id: <%= ActiveRecord::Fixtures.identify(:tag_one) %>
taggable_id: <%= ActiveRecord::Fixtures.identify(:framework_one) %>
taggable_type: Framework
context: tags
Basically the ActiveRecord::Fixtures.identify(:tag_one)
gets the ID for the tag to put into the right column.
It's generally best to create these kinds of things on the fly as you need them. Fixtures can quickly become pretty unmanageable, and when you look at the test in the future you will need to look at three or four fixture files to unpick what is happening.
I'd recommend taking a little time out to look at the factory_girl gem, it will save you loads of time in the future. You'd do something like this:
# test/factories.rb
Factory.define :framework do |f|
# Add any properties to make a valid Framework instance here, i.e. if you have
# validates_presence_of :name on the Framework model ...
f.name 'Test Name'
end
Then in your functional or unit tests you can easily create objects with the specific properties you need for an individual test:
# Create and save in the DB with default values
@framework = Factory.create(:framework)
# Build an object with a different name and don't save it in the DB
@framework = Factory.build(:framework, :name => 'Other name'
# Create with tags
@framework = Factory.build(:framework, :tags_list => 'foo, bar')
using :tags_list did not work for me:
> undefined method `tags_list=' for #<Project:0xb610b24>
What did work was in your actual factory, you need to add it like such:
Factory.define(:project) do |f|
f.tags_list ("factory")
end
I have also found that this needs to be in the parent-level factory, for some reason it does not work from children. I have also found that calling
@framework = Factory.build(:framework, :tag_list => 'foo, bar')
Doesn't throw an error, but it quietly does NOT create a tag.
Hope this helps!
This is how I add tags (using acts-as-taggable-on
) to my user model (using factory_girl):
FactoryGirl.define do
factory :post do
...
trait :poetry do
after(:create) { |post| post.update_attributes(tag_list: 'poetry') }
end
end
end
This way when I want to create just a regular Post
object, I write:
create(:post)
but when I want to create a Post
tagged with poetry
, I write:
create(:post, :poetry)
And it works pretty well.
This is the easiest way I have found to add tags in your tests. If you have a model set up like this:
class User < ActiveRecord::Base
acts_as_taggable # Alias for acts_as_taggable_on :tags
acts_as_taggable_on :skills, :interests
end
And you are using FactoryGirl, you can create them like this:
user1 = create(:user, tag_list: "SomeTag",
skill_list: "SomeSkillTag",
interest_list: "SomeInterestTag")
精彩评论