mirror of
https://github.com/TracksApp/tracks.git
synced 2025-12-25 19:48:48 +01:00
262 lines
No EOL
9.7 KiB
Text
262 lines
No EOL
9.7 KiB
Text
== Rails Scenarios Plugin
|
|
|
|
Who hasn't experienced the pain of dozens of YAML files filled with hundreds
|
|
of inter-related data structures? When do you look at People of an
|
|
Organization and not have to look at the organization_id, open the
|
|
organizations.yml file, and search for 'id: X'?
|
|
|
|
In a nutshell, scenarios are a drop in replacement for YAML fixtures. Instead
|
|
of encouraging you to create a mindless amount of raw data in the form of
|
|
YAML, scenarios encourage you to create code that populates your tables with
|
|
the appropriate records.
|
|
|
|
How is it different from other solutions? A few things:
|
|
|
|
* It continues to provide a fundamental, fast insertion method using attributes
|
|
written directly to a table. This is the
|
|
Scenarios::TableMethods#create_record method.
|
|
|
|
* It allows you to create records using validations if you prefer, or if it's
|
|
important to have all your callbacks be invoked. See
|
|
Scenarios::TableMethods#create_model. Both create_record and create_model
|
|
allow you to name your instances for retrieval by the instance and id reader
|
|
methods (more below).
|
|
|
|
* Nothing stops you from simply invoking YouModel.create!, etc. We'll still
|
|
keep track of the tables the scenario modifies and clean things up afterward.
|
|
|
|
* It allows you to create re-usable scenarios as classes. These classes are
|
|
like any other class - they may include modules, subclass, and be composed of
|
|
other scenarios. See Scenarios::Base.uses. This also means that you can load
|
|
any scenario into any Rails environment. That's what the 'rake
|
|
db:scenario:load' task is good for (more below). Very handy for re-using all
|
|
that test support code to create populated demos!
|
|
|
|
=== Quick Start
|
|
|
|
Since Scenarios is a Rails plugin at this time, you should get it installed,
|
|
using the appropriate method (script/plugin, svn, piston) into your
|
|
vendor/plugins directory. Once you have this, in your spec_helper.rb or
|
|
test_helper.rb, add the following line after the spec requires:
|
|
|
|
require 'scenarios'
|
|
|
|
The Scenarios you write should be placed in the spec/scenarios directory of your
|
|
Rails project if you're using RSpec, or the test/scenarios directory of your
|
|
Rails project if you're using Test::Unit. Scenario file names always end in
|
|
"_scenario.rb" and classes end in "Scenario".
|
|
|
|
A simple scenario looks like this:
|
|
|
|
# in spec/scenarios/users_scenario.rb or test/scenarios/users_scenario.rb
|
|
class UsersScenario < Scenario::Base
|
|
def load
|
|
create_record :user, :john, :name => 'John', :password => 'doodaht'
|
|
create_record :user, :cindy, :name => 'Cindy', :password => 'whoot!'
|
|
end
|
|
end
|
|
|
|
In the example above, I'm using the <tt>create_record</tt> instance method to
|
|
create two users: John and Cindy. Notice the calls to <tt>create_record</tt>.
|
|
There are three parameters. The first is the singular name of the table to
|
|
insert the record into, the second is the symbolic name of the record (more on
|
|
that later), and the third is a hash of the attributes of the record.
|
|
|
|
To use the UsersScenario in a description, you should declare it using
|
|
the <tt>scenario</tt> method. Here it is within a spec file (RSpec):
|
|
|
|
# in spec/models/user_spec.rb
|
|
describe User do
|
|
scenario :users
|
|
|
|
it "should allow me to do something with John" do
|
|
user = users(:john)
|
|
user.password.should == "doodaht"
|
|
end
|
|
end
|
|
|
|
and here it is within a standard Test::Unit test:
|
|
|
|
# in test/unit/user_test.rb
|
|
class UserTest < Test::Unit::TestCase
|
|
|
|
scenario :users
|
|
|
|
def test_do_something
|
|
user = users(:john)
|
|
assert_equal "doodaht", user.password
|
|
end
|
|
end
|
|
|
|
Notice that it is easy to load an instance of a model object using its
|
|
symbolic name with a reader method, similar to that of Rails' fixtures. In the
|
|
example above, I loaded John with the reader method <tt>users</tt> and the
|
|
symbolic name <tt>:john</tt>. (Remember that in the Users scenario I declared
|
|
that John should be accessible through the symbolic name <tt>:john</tt>.)
|
|
|
|
I could also have retrieved an array of user fixtures by passing in
|
|
multiple symbolic names to the reader method:
|
|
|
|
# in spec/models/user_spec.rb
|
|
describe User do
|
|
scenario :users
|
|
|
|
it "should allow me to get all admins" do
|
|
admins = users(:john, :ryan)
|
|
User.admins.should eql(admins)
|
|
end
|
|
end
|
|
|
|
=== Composition
|
|
|
|
In real life your scenarios will probably grow quite complicated. The
|
|
scenarios plugin allows you to deal with this complexity through composition.
|
|
|
|
Here's a simple example:
|
|
|
|
# in spec/scenarios/posts_scenario.rb or test/scenarios/posts_scenario.rb
|
|
class PostsScenario < Scenario::Base
|
|
def load
|
|
create_record :post, :first, :title => "First Post"
|
|
create_record :post, :second, :title => "Second Post"
|
|
end
|
|
end
|
|
|
|
# in spec/scenarios/comments_scenario.rb or test/scenarios/comments_scenario.rb
|
|
class CommentsScenario < Scenario::Base
|
|
uses :posts
|
|
|
|
def load
|
|
create_record :comment, :first, :body => "Nice post!", :post_id => post_id(:first)
|
|
create_record :comment, :second, :body => "I like it.", :post_id => post_id(:first)
|
|
create_record :comment, :third, :body => "I thoroughly disagree.", :post_id => post_id(:second)
|
|
end
|
|
end
|
|
|
|
In the example above, the CommentsScenario declares that it depends on the
|
|
Posts scenario with the <tt>uses</tt> class method. This means that if you
|
|
load the CommentsScenario, the PostsScenario will be loaded first and the
|
|
CommentsScenario will have access to all the data loaded by the PostsScenario
|
|
in its own <tt>load</tt> method. Note that inside the load method I'm using
|
|
another form of reader methed which simply gives you the id for a symbolic
|
|
name (in this case: <tt>post_id</tt>). This is most useful for making
|
|
associations, as done here with comments and posts.
|
|
|
|
=== Helper Methods
|
|
|
|
Another way of simplifying your scenarios and specs/tests is through helper
|
|
methods. The Scenarios plugin provides a handy way to declare helper methods
|
|
that are accessible from inside the scenario and also from inside related
|
|
RSpec/Test::Unit examples:
|
|
|
|
# in spec/scenarios/users_scenario.rb or test/scenarios/users_scenario.rb
|
|
class UsersScenario < Scenario::Base
|
|
def load
|
|
create_user :name => "John"
|
|
end
|
|
|
|
helpers do
|
|
def create_user(attributes={})
|
|
create_record :user, attributes[:name].downcase.intern, attributes
|
|
end
|
|
def login_as(user)
|
|
@request.session[:user_id] = user.id
|
|
end
|
|
end
|
|
end
|
|
|
|
Helper methods declared inside the helpers block are mixed into the scenario
|
|
when it is instantiated and mixed into examples that declare that they are using
|
|
the scenario. Also, in the case where one scenario <tt>uses</tt> another, the
|
|
using scenario will have the helper methods of the used scenario.
|
|
|
|
# in spec/controllers/projects_controller_spec.rb
|
|
describe "Projects screen" do
|
|
scenario :users
|
|
|
|
it "should show active projects" do
|
|
login_as(users(:john))
|
|
get :projects
|
|
@response.should have_tag('#active_projects')
|
|
end
|
|
end
|
|
|
|
# in test/functional/projects_controller_test.rb
|
|
class PeopleControllerTest < Test::Unit::TestCase
|
|
scenario :users
|
|
|
|
def test_index
|
|
login_as(users(:john))
|
|
get :projects
|
|
assert_tag('#active_projects')
|
|
end
|
|
end
|
|
|
|
Notice that within my specs/tests I have access to the login_as helper method
|
|
declared inside the <tt>helpers</tt> block of the UsersScenario. Scenario
|
|
helpers are a great way to share helper methods between specs/tests that use a
|
|
specific scenario.
|
|
|
|
=== Built-in Scenario
|
|
|
|
There is a scenario named 'blank' that comes with the plugin. This scenario is
|
|
useful when you want to express, and guarantee, that the database is empty. It
|
|
works by using your db/schema.rb, so if the table isn't created in there, it
|
|
won't be cleaned up.
|
|
|
|
Scenario.load_paths is an array of the locations to look for scenario
|
|
definitions. The built-in scenarios directory is consulted last, so if you'd
|
|
like to re-define, for instance, the 'blank' scenario, simply create
|
|
'blank_scenario.rb' in your spec/scenarios or test/scenarios directory.
|
|
|
|
=== Load Rake Task
|
|
|
|
The Scenarios plugin provides a single Rake task, <tt>db:scenario:load</tt>,
|
|
which you may use in a fashion similar to Rails fixtures'
|
|
<tt>db:fixtures:load</tt>.
|
|
|
|
rake db:scenario:load SCENARIO=comments
|
|
|
|
When invoked, this task will populate the development database with the named
|
|
scenario.
|
|
|
|
If you do not specify SCENARIO, the task will expect to find a default scenario
|
|
(a file 'default_scenario.rb' having DefaultScenario defined in it). It is our
|
|
practice to have it such that this scenario <tt>uses</tt> a number of our other
|
|
scenarios, thereby:
|
|
|
|
* encouraging us to use test data that looks good in the running development
|
|
application
|
|
|
|
* allowing us to troubleshoot failing tests in the running development
|
|
application
|
|
|
|
=== More Information
|
|
|
|
For more information, be sure to look through the documentation over at RubyForge:
|
|
|
|
* http://faithfulcode.rubyforge.org/docs/scenarios
|
|
|
|
You might also enjoy taking a look at the specs for the plugin and the example
|
|
scenarios:
|
|
|
|
* http://faithfulcode.rubyforge.org/svn/plugins/trunk/scenarios/spec/scenarios_spec.rb
|
|
* http://faithfulcode.rubyforge.org/svn/plugins/trunk/scenarios/spec/scenarios
|
|
|
|
Browse the complete source code:
|
|
|
|
* http://faithfulcode.rubyforge.org/svn/plugins/trunk/scenarios
|
|
|
|
=== Running Tests
|
|
|
|
You should be able to simply run rake. Notice in testing/environment.rb the
|
|
revisions under which this project will work. If you intend to test against
|
|
HEAD, you will need to delete the directory testing/tmp/trunk/HEAD. At some
|
|
point, it would be nice to have the script track the revision of HEAD that we
|
|
have, and update the directory automatically.
|
|
|
|
=== License
|
|
|
|
The Scenarios plugin is released under the MIT-License and is Copyright (c)
|
|
2007, Adam Williams and John W. Long. Special thanks to Chris Redinger for his
|
|
part in helping us get this plugin ready for the public. |