MarkUs Blog

MarkUs Developers Blog About Their Project

Getting Started with Machinist in Markus

with 3 comments

When running, we almost always have to set up some objects to test. We used to set up these objects by loading them from fixtures. There is a problem with fixtures. It’s hard to know the attributes of the objects or the relationships between different objects unless we delve into fixture files. This is not good, because when doing tests and making assertions, we want to know well about the objects we tested against, but looking into fixtures is a time-consuming job. Now don’t you want a mechanism that allows you to explicitly create objects and build up the relationships among them, but without having to specify their attributes that you don’t care about? This is what Machinist does! With Machinist, you can create and save an assignment object by writing one short line in your code: “assignment = Assignment.make”. Isn’t that great?

In his post, Fixtures are not as clear as Machinist, Robert has a deep insight on how Machinist is better than fixtures.

Yes, Machinist is great, and I promise you’ll find it even greater if you spend a little time reading through Machinist’s documentation. Make sure you understand what a blueprint is and how we can pass attributes to “make” method and override its default behaviour.

The documentation pretty much tells all you need to know about Machinist. I’m not going through every details here, but there are some stuffs you may want to know about how we can use Machinist with Markus.

1. Setting up the playground for Machinist

Before using Machinist, please make sure you’ve installed two gems: Machinist and Faker. If they are not on your machine, you can run the following command in a terminal:

 gem install faker machinist --source http://gemcutter.org 

Now that the gems are installed, you want to build some confidence in Machinist by making some easy objects. The easiest way to play around with Machinist is to open a script/console with test environment. You can do this by opening a terminal, change the directory to the root directory of your Markus’ project, and then run the following command:

 script/console test 

Make sure you append the “test” option, which loads test environment for you. Otherwise you’ll be at the risk of screwing up data you use for dev.

If you see something like



Loading test environment (Rails 2.3.5)
>>

you’re already in the script console. In script console, you can execute any Ruby code as you would normally write in .rb source files. If you’ve not used script console before, try to “new” an empty group object to get yourself familiar with it.



>> group = Group.new
=> #<Group id: nil, group_name: nil, repo_name: nil>


“group = Group.new” creates a new empty group, whose information is displayed in the console.

Now you probably cannot wait to use Machinist. OK, let’s start by making a group which is the easiest model I can find in Markus. Try to execute the following line in the script console.

>> group = Group.make

But you’re likely to get this:



NoMethodError: undefined method `make' for Group(id: integer, group_name: string, repo_name: string):Class
 from /var/lib/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:1959:in `method_missing_without_paginate'
 from /var/lib/gems/1.8/gems/will_paginate-2.3.12/lib/will_paginate/finder.rb:170:in `method_missing'
 from (irb):5
 >>


Why? If you’ve read the Machinist’s documentation, you should’ve already known the reason. You cannot use the “make” method of a model unless you’ve defined the its blueprint. OK, then let’s write the blueprint. Wait, in fact, you don’t need to, because blueprints for most models in Markus are already defined in /test/blueprints/blueprints.rb, which is “required” by /test/blueprints/helper.rb. To make a reference of these blueprints, you only need to run the following line in script console:

>> require "test/blueprints/helper.rb" 

Now try to “make” your group object again, and see what happens. If you still get an error, please don’t panic, and skip ahead to read the next section, where you’ll find the answer. But most likely you will be lucky to see a sign of success in your script console like the one I get here:

=> #<Group id: 930503258, group_name: "group1", repo_name: "group_930503258"> 

See, with only one simple call of “make”, you have already created a group object with all the attributes set properly. That’s why we all like Machinist!

Congratulations. You’ve already set up the playground for Machinist. At this point, you are good to go. Just try out any of your ideas with Machinist.

2. Common Issues

Sometimes, when you make a make an object, you get an error message like this: “ActiveRecord::RecordInvalid: Validation failed: Group name has already been taken”. This message means in the database there is already a group object with the same group name as the one you are just making. The test database can persists objects as a result of your last test, so you should clear the database before playing around with Machinist. To delete all the objects of a model in database, you can call “ModelName.delete_all”, where ModelName is the name of the Model whose objects you want to delete. But it’s is more desirable to clear all the table at the same time. Here is the code to do that:



(ActiveRecord::Base.send :subclasses).each {|c| c.delete_all}


I recommended you to use script console. But it has an issue. Everytime you modify and save blueprints.rb, you need to restart the script console for it to detect the change. This makes it inconvenient for you to do test your blueprints. As an alternative to script console, you can create a .rb file at your project’s root directory, and put the following lines at the beginning:



ENV["RAILS_ENV"] = "test"
require File.expand_path(File.dirname(__FILE__) + "/config/environment")
require File.join(File.dirname(__FILE__), "test/blueprints/helper")


This should also set you up a test environment where you can write code to play around with Machinist. The benefit of this approach is that it makes it easy to test blueprints.

3. Making Objects with Associations

There are three common types of associations: “belongs_to”, “has_many” and “has_one”.

belongs_to“: if A “belongs_to” B, a.b should be not nil and unique. It’s identical to a foreign key relationship from A to B. B should be made prior A. A’s blueprint should take care of making B. For example in Markus, Student belongs Section. The blueprints for Section and Student are:



Section.blueprint do

name {Sham.section_name}
end

Student.blueprint do
type {'Student'}
user_name {Sham.student_user_name}
first_name
last_name
section
end


The blueprint of Student implicitly calls Section.make. Therefore, when you call Student.make, a section will be made before student. If you already have a section object my_section and want to make a student that belongs to that section, you can call Student.make({:section => my_section}), which overrides the default behaviour specified by Student blueprint and doesn’t make a new section. When you make an object, you may end up making lots of  other objects because the “belongs_to” association “recursively” makes the dependencies.

has_many“:  if A “has_many” B, then B “belongs_to” A. Machinist encourages you to first create the object on the right hand side of “belongs_to” association (seeking for some better wording here). Using the example of Student and Section. Section belongs to Section, and thus Section has many Students. When you want to make a section with many students, you need to make a section first. We are still using the blueprints from the previous example:



section = Section.make

section.students.make


When section.students.make is called, Machinist is smart enough to call  Student.make({:section => section}). So you don’t need to worry about accidentally creating a new section. After the two lines, you have section.students.first.section == section.

has_one“: “has_one” is a special case of “has_many”. if A “has_one” B, then B “belongs_to” A. Same as “has_many”, A should be made prior to B. Suppose we have two models: Coach and Team, where Team “has_one” Coach and Coach “belongs_to” Team. You may attempt to make an object of Team in a similar way to “has_many” association:



team = Team.make

coach = team.coach.make


But it doesn’t work. In the “has_many” example, section.students is a collection which is an object not nil, but here team.coach is nil. There is no way for Machinist to know which blueprint to use for making a nil object. So what you have to do is:



team = Team.make

coach = Coach.make({:team => team})


Then you can use the team object.

Submission.blueprint do
grouping
submission_version {1}
submission_version_used {true}
revision_number {1}
revision_timestamp {1.days.ago}
end

Written by bryanshen

March 10th, 2010 at 3:51 am

Posted in Uncategorized

3 Responses to 'Getting Started with Machinist in Markus'

Subscribe to comments with RSS or TrackBack to 'Getting Started with Machinist in Markus'.

  1. I’m not sure what is wrong with the sourcecode tag. Hopefully I can fix it tomorrow.

    bryanshen

    10 Mar 10 at 3:58 am

  2. Thanks for the post. I think there is a bug with the has_many associations.

    When I do

    @object.collection.make!
    @object.collection.count.should == 1

    it fails

    But if i do

    @object.collection.create!

    or

    Collection.make!:object => @object

    Then the tests pass.

    Jovi Heng

    17 Jun 11 at 7:57 pm

  3. Forget to mention that this is for machinist 2 on rails 3

    Jovi Heng

    17 Jun 11 at 8:00 pm

Leave a Reply