Creating fixture data for model using acts_as_taggable_on
Asked Answered
C

6

5

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, how 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?

Colloquialism answered 1/11, 2010 at 17:5 Comment(0)
M
2

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')
Maddux answered 20/11, 2010 at 10:33 Comment(0)
G
10

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.

Gormand answered 22/3, 2015 at 2:12 Comment(5)
In your "a bit whacky" solution, there is no need to define new models. You can use the ones in ActsAsTaggableOn with set_fixture_class. Eg. in your test_helper.rb, right after fixtures :all, add set_fixture_class "tags" => ActsAsTaggableOn::Tag and set_fixture_class "taggings" => ActsAsTaggableOn::Tagging. And voila! No need to have new models named Tag and Tagging. hthDextrorotation
Thanks @Dextrorotation for pointing me in that direction. According to documentation set_fixture_class could be used if the model class can not be inferred from the fixture, so I thought "how are they inferred?". Turns out you can namespace fixtures by putting them in dirs (i.e. acts_as_taggable_on/tags.yml) which makes the explicit assignment obsolete. -> I will update my answer to reflect that resultGormand
yep, that's even better!Dextrorotation
Thanks. Works like a charm. This should be the accepted answer!Threeply
This works great, just make sure that on the line where Florian has: taggable: item_1 (Item), you are literally including (Item) there, otherwise the polymorphic Taggable Type will not be set.Mistress
C
5

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.

Cornela answered 17/8, 2012 at 10:30 Comment(3)
Thanks for your idea. In my case (ruby-on-rails4), tags.yml needs to have id with explicit value. For example, id: <%= ActiveRecord::Fixtures.identify(:tag_one) %>Cerebrum
Thanks a lot! Just one note: starting with Rails 4 ActiveRecord::Fixtures is deprecated, just use ActiveRecord::FixtureSet instead.Puke
This demonstrates why the originally accepted answer suggested that writing multiple fixtures for the same object can quickly become unmanageable. It's good to see how a single object can end up across multiple fixtures. One can see how easy it might be to lose track of which fixtures are associated with which other fixtures as part of a single object. Thanks for this example.Waterage
M
2

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')
Maddux answered 20/11, 2010 at 10:33 Comment(0)
U
1

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!

Unsustainable answered 8/8, 2011 at 4:2 Comment(0)
B
0

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.

Bedad answered 15/8, 2013 at 15:3 Comment(0)
V
0

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")
Vivica answered 10/12, 2014 at 14:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.