diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb
new file mode 100644
index 00000000..410860a1
--- /dev/null
+++ b/spec/models/todo_spec.rb
@@ -0,0 +1,196 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe Todo do
+ def valid_attributes(attributes={})
+ {
+ :description => "don't forget the milk",
+ :context => mock_model(Context, :name => 'errands')
+ }.merge(attributes)
+ end
+
+ def create_todo(attributes={})
+ todo = Todo.new(valid_attributes(attributes))
+ todo.stub!(:user).and_return(mock_model(User, :date => Time.now))
+ todo.save!
+ todo
+ end
+
+ before(:each) do
+ @todo = Todo.new
+ end
+
+ it_should_belong_to :context
+ it_should_belong_to :project
+ it_should_belong_to :user
+
+ it_should_validate_presence_of :description
+ it_should_validate_presence_of :context
+ it_should_validate_length_of :description, :maximum => 100
+ it_should_validate_length_of :notes, :maximum => 60_000
+
+ it 'validates presence of show_from when deferred'
+
+ it 'ensures that show_from is a date in the future' do
+ todo = Todo.new(valid_attributes)
+ todo.stub!(:user).and_return(mock_model(User, :date => Time.now))
+ todo.show_from = 3.days.ago
+ todo.should have(1).error_on(:show_from)
+ end
+
+ it 'allows show_from to be blank' do
+ todo = Todo.new(valid_attributes(:show_from => ''))
+ todo.should_not have(:any).error_on(:show_from)
+ end
+
+ describe 'states' do
+ it 'is active on initializing' do
+ create_todo.should be_active
+ end
+
+ it 'is deferred when show from is in the future' do
+ create_todo(:show_from => 1.week.from_now).should be_deferred
+ end
+
+ describe 'active' do
+ %w(project_hidden completed deferred).each do |from_state|
+ it "is activable from `#{from_state}'" do
+ todo = create_todo
+ todo.state = from_state
+ todo.send("#{from_state}?").should be_true
+ todo.activate!
+ todo.should be_active
+ end
+ end
+
+ it 'clears show_from when entering active state' do
+ todo = create_todo
+ todo.show_from = 3.days.from_now
+ todo.should be_deferred
+ todo.activate!
+ todo.should be_active
+ todo.show_from.should be_nil
+ end
+
+ it 'clears completed_at when entering active state' do
+ todo = create_todo
+ todo.complete!
+ todo.should be_completed
+ todo.activate!
+ todo.should be_active
+ todo.completed_at.should be_nil
+ end
+ end
+
+ describe 'completed' do
+ %w(active project_hidden deferred).each do |from_state|
+ it "is completable from `#{from_state}'" do
+ todo = create_todo
+ todo.state = from_state
+ todo.send("#{from_state}?").should be_true
+ todo.complete!
+ todo.should be_completed
+ end
+ end
+
+ it 'sets complated_at' do
+ todo = create_todo
+ todo.complete!
+ todo.completed_at.should_not be_nil
+ end
+ end
+
+ describe 'project_hidden' do
+ %w(active deferred).each do |from_state|
+ it "is hiddable from `#{from_state}'" do
+ todo = create_todo
+ todo.state = from_state
+ todo.send("#{from_state}?").should be_true
+ todo.hide!
+ todo.should be_project_hidden
+ end
+ end
+
+ it 'unhides to deferred when if show_from' do
+ todo = create_todo(:show_from => 4.days.from_now)
+ todo.hide!
+ todo.should be_project_hidden
+ todo.unhide!
+ todo.should be_deferred
+ end
+
+ it 'unhides to active when not show_from' do
+ todo = create_todo(:show_from => '')
+ todo.hide!
+ todo.should be_project_hidden
+ todo.unhide!
+ todo.should be_active
+ end
+ end
+
+ it "is deferrable from `active'" do
+ todo = create_todo
+ todo.activate!
+ todo.should be_active
+ todo.defer!
+ todo.should be_deferred
+ end
+ end
+
+ describe 'when toggling completion' do
+ it 'toggles to active when completed' do
+ todo = create_todo
+ todo.complete!
+ todo.should be_completed
+ todo.toggle_completion!
+ todo.should be_active
+ end
+
+ it 'toggles to completed when not completed' do
+ todo = create_todo
+ todo.should_not be_completed
+ todo.toggle_completion!
+ todo.should be_completed
+ end
+ end
+
+ describe 'when retrieving project' do
+ it 'returns project if set' do
+ project = mock_model(Project)
+ todo = Todo.new(:project => project)
+ todo.project.should == project
+ end
+
+ it 'returns a NullProject if not set' do
+ Todo.new.project.should be_an_instance_of(NullProject)
+ end
+ end
+
+ describe('when setting show_from') { it 'is speced' }
+
+ it 'is starred if tag is "starred"' do
+ todo = create_todo
+ todo.should_not be_starred
+ todo.add_tag('starred')
+ todo.reload
+ todo.should be_starred
+ end
+
+ describe 'when toggling star flag' do
+ it 'toggles to not starred when starred' do
+ todo = create_todo
+ todo.add_tag('starred')
+ todo.should be_starred
+ todo.toggle_star!
+ todo.reload
+ todo.should_not be_starred
+ end
+
+ it 'toggles to starred when not starred' do
+ todo = create_todo
+ todo.should_not be_starred
+ todo.toggle_star!
+ todo.reload
+ todo.should be_starred
+ end
+ end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
new file mode 100644
index 00000000..7f915fbd
--- /dev/null
+++ b/spec/models/user_spec.rb
@@ -0,0 +1,181 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe User do
+ def valid_attributes(attributes={})
+ {
+ :login => 'simon',
+ :password => 'foobarspam',
+ :password_confirmation => 'foobarspam'
+ }.merge(attributes)
+ end
+
+ before(:each) do
+ @user = User.new
+ end
+
+ describe 'associations' do
+ it 'has many contexts' do
+ User.should have_many(:contexts).
+ with_order('position ASC').
+ with_dependent(:delete_all)
+ end
+
+ it 'has many projects' do
+ User.should have_many(:projects).
+ with_order('projects.position ASC').
+ with_dependent(:delete_all)
+ end
+
+ # TODO: uses fixtures to test those
+ it 'has many active_projects' do
+ User.should have_many(:active_projects).
+ with_order('projects.position ASC').
+ with_conditions('state = ?', 'active').
+ with_class_name('Project')
+ end
+
+ it 'has many active contexts' do
+ User.should have_many(:active_contexts).
+ with_order('position ASC').
+ with_conditions('hide = ?', 'true').
+ with_class_name('Context')
+ end
+
+ it 'has many todos' do
+ User.should have_many(:todos).
+ with_order('todos.completed_at DESC, todos.created_at DESC').
+ with_dependent(:delete_all)
+ end
+
+ it 'has many deferred todos' do
+ User.should have_many(:deferred_todos).
+ with_order('show_from ASC, todos.created_at DESC').
+ with_conditions('state = ?', 'deferred').
+ with_class_name('Todo')
+ end
+
+ it 'has many completed todos' do
+ User.should have_many(:completed_todos).
+ with_order('todos.completed_at DESC').
+ with_conditions('todos.state = ? and todos.completed_at is not null', 'completed').
+ with_include(:project, :context).
+ with_class_name('Todo')
+ end
+
+ it 'has many notes' do
+ User.should have_many(:notes).
+ with_order('created_at DESC').
+ with_dependent(:delete_all)
+ end
+
+ it 'has many taggings' do
+ User.should have_many(:taggings)
+ end
+
+ it 'has many tags through taggings' do
+ User.should have_many(:tags).through(:taggings).with_select('DISTINCT tags.*')
+ end
+
+ it 'has one preference' do
+ User.should have_one(:preference)
+ end
+ end
+
+ it_should_validate_presence_of :login
+ it_should_validate_presence_of :password
+ it_should_validate_presence_of :password_confirmation
+
+ it_should_validate_length_of :password, :within => 5..40
+ it_should_validate_length_of :login, :within => 3..80
+
+ it_should_validate_uniqueness_of :login
+ it_should_validate_confirmation_of :password
+
+ it 'validates presence of password only when password is required'
+ it 'validates presence of password_confirmation only when password is required'
+ it 'validates confirmation of password only when password is required'
+ it 'validates presence of open_id_url only when using openid'
+
+ it 'accepts only allow auth_type authorized by the admin' do
+ Tracks::Config.should_receive(:auth_schemes).exactly(3).times.and_return(%w(database open_id))
+ User.new(valid_attributes(:auth_type => 'database')).should_not have(:any).error_on(:auth_type)
+ User.new(valid_attributes(:auth_type => 'open_id')).should_not have(:any).error_on(:auth_type)
+ User.new(valid_attributes(:auth_type => 'ldap')).should have(1).error_on(:auth_type)
+ end
+
+ it 'returns login for #to_param' do
+ @user.login = 'john'
+ @user.to_param.should == 'john'
+ end
+
+ it 'has a custom finder to find admin' do
+ User.should_receive(:find).with(:first, :conditions => ['is_admin = ?', true])
+ User.find_admin
+ end
+
+ it 'has a custom finder to find by openid url'
+ it 'knows if there is any user through #no_users_yet? (TODO: better description)'
+
+ describe 'when choosing what do display as a name' do
+ it 'displays login when no first name and last name' do
+ User.new(valid_attributes).display_name.should == 'simon'
+ end
+
+ it 'displays last name when no first name' do
+ User.new(valid_attributes(:last_name => 'foo')).display_name.should == 'foo'
+ end
+
+ it 'displays first name when no last name' do
+ User.new(valid_attributes(:first_name => 'bar')).display_name.should == 'bar'
+ end
+
+ it 'displays first name and last name when both specified' do
+ User.new(valid_attributes(:first_name => 'foo', :last_name => 'bar')).display_name.should == 'foo bar'
+ end
+ end
+
+ describe 'authentication' do
+ before(:each) do
+ @user = User.create!(valid_attributes)
+ end
+
+ it 'authenticates user' do
+ User.authenticate('simon', 'foobarspam').should == @user
+ end
+
+ it 'resets password' do
+ @user.update_attributes(
+ :password => 'new password',
+ :password_confirmation => 'new password'
+ )
+ User.authenticate('simon', 'new password').should == @user
+ end
+
+ it 'does not rehash password after update of login' do
+ @user.update_attribute(:login, 'foobar')
+ User.authenticate('foobar', 'foobarspam').should == @user
+ end
+
+ it 'sets remember token' do
+ @user.remember_me
+ @user.remember_token.should_not be_nil
+ @user.remember_token_expires_at.should_not be_nil
+ end
+
+ it 'unsets remember token' do
+ @user.remember_me
+ @user.remember_token.should_not be_nil
+ @user.forget_me
+ @user.remember_token.should be_nil
+ end
+
+ it 'remembers me default two weeks' do
+ before = 2.weeks.from_now.utc
+ @user.remember_me
+ after = 2.weeks.from_now.utc
+ @user.remember_token.should_not be_nil
+ @user.remember_token_expires_at.should_not be_nil
+ @user.remember_token_expires_at.should be_between(before, after)
+ end
+ end
+end
diff --git a/spec/scenarios/contexts_scenario.rb b/spec/scenarios/contexts_scenario.rb
new file mode 100644
index 00000000..719da5db
--- /dev/null
+++ b/spec/scenarios/contexts_scenario.rb
@@ -0,0 +1,19 @@
+class ContextsScenario < Scenario::Base
+ uses :users
+
+ def load
+ %w(Call Email Errand Someday).each_with_index do |context, index|
+ create_context context, index+1
+ end
+ end
+
+ def create_context(name, position)
+ create_model :context, name.downcase.to_sym,
+ :name => name,
+ :position => position,
+ :hide => name == 'Someday' ? true : false,
+ :created_at => Time.now,
+ :updated_at => Time.now,
+ :user_id => user_id(:sean)
+ end
+end
diff --git a/spec/scenarios/projects_scenario.rb b/spec/scenarios/projects_scenario.rb
new file mode 100644
index 00000000..3b5624f1
--- /dev/null
+++ b/spec/scenarios/projects_scenario.rb
@@ -0,0 +1,20 @@
+class ProjectsScenario < Scenario::Base
+ def load
+ create_project :build_time_machine, 'Build a working time machine'
+ create_project :make_more_money, 'Make more money than Billy Gates'
+ create_project :evict_dinosaurs, 'Evict dinosaurs from the garden'
+ create_project :attend_railsconf, 'Attend RailsConf'
+ end
+
+ def create_project(identifier, name)
+ attributes = {
+ :name => name,
+ :state => 'active',
+ :created_at => 4.day.ago,
+ :updated_at => 1.minute.ago
+ }
+ create_model :project,
+ identifier || attributes[:name].split.first.downcase.to_sym,
+ attributes
+ end
+end
diff --git a/spec/scenarios/todos_scenario.rb b/spec/scenarios/todos_scenario.rb
new file mode 100644
index 00000000..ddaa7f93
--- /dev/null
+++ b/spec/scenarios/todos_scenario.rb
@@ -0,0 +1,30 @@
+class TodosScenario < Scenario::Base
+ uses :contexts, :projects, :users
+
+ def load
+ create_todo :bill,
+ :description => 'Call Bill Gates to find out how much he makes per day',
+ :user => :sean,
+ :context => :call,
+ :project => :make_more_money
+ create_todo :bank,
+ :description => 'Call my bank',
+ :user => :sean,
+ :context => :call,
+ :project => :make_more_money
+ end
+
+ def create_todo(identifier, options={})
+ context = options.delete(:context)
+ project = options.delete(:project)
+ user = options.delete(:user)
+ attributes = {
+ :state => 'active',
+ :created_at => 1.week.ago,
+ :context_id => context_id(context),
+ :project_id => project_id(project),
+ :user_id => user_id(user)
+ }.merge(options)
+ create_model :todo, identifier, attributes
+ end
+end
diff --git a/spec/scenarios/users_scenario.rb b/spec/scenarios/users_scenario.rb
new file mode 100644
index 00000000..5ed73b83
--- /dev/null
+++ b/spec/scenarios/users_scenario.rb
@@ -0,0 +1,19 @@
+class UsersScenario < Scenario::Base
+ def load
+ create_user :login => 'johnny', :first_name => 'Johnny', :last_name => 'Smith'
+ create_user :login => 'jane', :first_name => 'Jane', :last_name => 'Pilbeam'
+ create_user :login => 'sean', :first_name => 'Sean', :last_name => 'Pallmer'
+ end
+
+ def create_user(attributes={})
+ password = attributes[:login] + Time.now.to_s
+ attributes = {
+ :password => password,
+ :password_confirmation => password,
+ :is_admin => attributes[:is_admin] || false,
+ }.merge(attributes)
+ identifier = attributes[:login].downcase.to_sym
+ user = create_model :user, identifier, attributes
+ Preference.create(:show_number_completed => 5, :user => user)
+ end
+end
diff --git a/vendor/plugins/scenarios/.gitignore b/vendor/plugins/scenarios/.gitignore
new file mode 100644
index 00000000..e8edc4ed
--- /dev/null
+++ b/vendor/plugins/scenarios/.gitignore
@@ -0,0 +1,4 @@
+environments
+*.log
+tmp
+vendor
\ No newline at end of file
diff --git a/vendor/plugins/scenarios/LICENSE b/vendor/plugins/scenarios/LICENSE
new file mode 100644
index 00000000..0c9f55a0
--- /dev/null
+++ b/vendor/plugins/scenarios/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2007, Adam Williams and John W. Long.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/vendor/plugins/scenarios/README b/vendor/plugins/scenarios/README
new file mode 100644
index 00000000..971472e8
--- /dev/null
+++ b/vendor/plugins/scenarios/README
@@ -0,0 +1,262 @@
+== 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 create_record instance method to
+create two users: John and Cindy. Notice the calls to create_record.
+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 scenario 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 users and the
+symbolic name :john. (Remember that in the Users scenario I declared
+that John should be accessible through the symbolic name :john.)
+
+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 uses 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 load 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: post_id). 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 uses 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 helpers 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, db:scenario:load,
+which you may use in a fashion similar to Rails fixtures'
+db:fixtures:load.
+
+ 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 uses 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.
\ No newline at end of file
diff --git a/vendor/plugins/scenarios/Rakefile b/vendor/plugins/scenarios/Rakefile
new file mode 100644
index 00000000..bd0deb95
--- /dev/null
+++ b/vendor/plugins/scenarios/Rakefile
@@ -0,0 +1,10 @@
+require File.expand_path(File.dirname(__FILE__) + '/testing/plugit_descriptor')
+
+require 'rake/rdoctask'
+Rake::RDocTask.new(:doc) do |r|
+ r.title = "Rails Scenarios Plugin"
+ r.main = "README"
+ r.options << "--line-numbers"
+ r.rdoc_files.include("README", "LICENSE", "lib/**/*.rb")
+ r.rdoc_dir = "doc"
+end
\ No newline at end of file
diff --git a/vendor/plugins/scenarios/TODO b/vendor/plugins/scenarios/TODO
new file mode 100644
index 00000000..9be8fb9d
--- /dev/null
+++ b/vendor/plugins/scenarios/TODO
@@ -0,0 +1 @@
+Make sure before :all's that use scenario methods work. They don't right now.
\ No newline at end of file
diff --git a/vendor/plugins/scenarios/helpers.diff b/vendor/plugins/scenarios/helpers.diff
new file mode 100644
index 00000000..340068bb
--- /dev/null
+++ b/vendor/plugins/scenarios/helpers.diff
@@ -0,0 +1,127 @@
+Only in /Users/aiwilliams/Workspaces/faithfulcode/scenarios/: .git
+Only in scenarios/: .svn
+Only in scenarios/: .tm_last_run_ruby
+diff -r scenarios/Rakefile /Users/aiwilliams/Workspaces/faithfulcode/scenarios/Rakefile
+3c3
+< TESTING_ENVIRONMENTS["rspec_3317_rails_8956"].load
+---
+> TESTING_ENVIRONMENTS["rspec_3119_rails_8375"].load
+Only in /Users/aiwilliams/Workspaces/faithfulcode/scenarios/: helpers.diff
+Only in scenarios/lib: .svn
+Only in scenarios/lib/scenarios: .svn
+diff -r scenarios/lib/scenarios/base.rb /Users/aiwilliams/Workspaces/faithfulcode/scenarios/lib/scenarios/base.rb
+11,14c11,12
+< # be included into the scenario and all specs that include the scenario.
+< # You may also provide names of helpers from your scenarios/helpers
+< # directory, or any other module you'd like included in your Scenario.
+< def helpers(helper_names_or_modules = [], &block)
+---
+> # be included into the scenario and all specs that include the scenario
+> def helpers(&block)
+17,19d14
+< mod.module_eval do
+< [helper_names_or_modules].flatten.each {|h| include h.is_a?(Module) ? h : h.to_scenario_helper}
+< end
+Only in scenarios/lib/scenarios/builtin: .svn
+Only in scenarios/lib/scenarios/extensions: .svn
+diff -r scenarios/lib/scenarios/extensions/string.rb /Users/aiwilliams/Workspaces/faithfulcode/scenarios/lib/scenarios/extensions/string.rb
+22,39d21
+< # Convert a string into the associated scenario helper module:
+< #
+< # "basic".to_scenario_helper #=> BasicScenarioHelper
+< # "basic_scenario".to_scenario_helper #=> BasicScenarioHelper
+< #
+< # Raises Scenario::NameError if the the helper cannot be loacated as
+< # 'helpers/_helper' in Scenario.load_paths.
+< def to_scenario_helper
+< class_name = "#{self.strip.camelize.sub(/ScenarioHelper$/, '')}ScenarioHelper"
+< Scenario.load_paths.each do |path|
+< filename = "#{path}/#{class_name.underscore}.rb"
+< if File.file?(filename)
+< require filename
+< break
+< end
+< end
+< class_name.constantize rescue raise Scenario::NameError, "Expected to find #{class_name} in #{Scenario.load_paths.inspect}"
+< end
+diff -r scenarios/lib/scenarios/extensions/symbol.rb /Users/aiwilliams/Workspaces/faithfulcode/scenarios/lib/scenarios/extensions/symbol.rb
+14,23d13
+< # Convert a symbol into the associated scenario helper module:
+< #
+< # :basic.to_scenario_helper #=> BasicScenarioHelper
+< # :basic_scenario.to_scenario_helper #=> BasicScenarioHelper
+< #
+< # Raises Scenario::NameError if the the helper cannot be loacated as
+< # 'helpers/_helper' in Scenario.load_paths.
+< def to_scenario_helper
+< to_s.to_scenario_helper
+< end
+Only in scenarios/spec: .svn
+Only in scenarios/spec: environments.rb
+Only in scenarios/spec/scenarios: .svn
+Only in scenarios/spec/scenarios: helpers
+diff -r scenarios/spec/scenarios_spec.rb /Users/aiwilliams/Workspaces/faithfulcode/scenarios/spec/scenarios_spec.rb
+23,27d22
+< it 'should allow us to have helpers in scenarios/helpers directory which we can get through the helpers class method' do
+< klass = :empty.to_scenario
+< klass.helpers :myown
+< end
+<
+diff -r scenarios/spec/spec_helper.rb /Users/aiwilliams/Workspaces/faithfulcode/scenarios/spec/spec_helper.rb
+1,20c1,6
+< $LOAD_PATH << File.dirname(__FILE__) + '/../testing'
+<
+< require File.dirname(__FILE__) + "/environments"
+<
+< require 'active_support'
+< require 'active_record'
+< require 'action_controller'
+< require 'action_view'
+<
+< module Spec
+< module Rails
+< module Example
+< end
+< end
+< end
+<
+< require 'spec/rails'
+< require 'scenarios'
+<
+< require 'models'
+\ No newline at end of file
+---
+> require File.expand_path(File.dirname(__FILE__) + "/../testing/environment")
+> TESTING_ENVIRONMENTS[TESTING_ENVIRONMENT].load(DATABASE_ADAPTER)
+> require "models"
+> require "spec"
+> require "spec/rails"
+> require "scenarios"
+Only in scenarios/tasks: .svn
+Only in scenarios/test: .svn
+Only in scenarios/testing: .svn
+diff -r scenarios/testing/environment.rb /Users/aiwilliams/Workspaces/faithfulcode/scenarios/testing/environment.rb
+15c15
+< TESTING_ENVIRONMENT = "rspec_3317_rails_8956" unless defined?(TESTING_ENVIRONMENT)
+---
+> TESTING_ENVIRONMENT = "rspec_3119_rails_8375" unless defined?(TESTING_ENVIRONMENT)
+31c31
+< # system "cd #{lib.support_directory} && patch -p0 < #{File.join(TESTING_ROOT, "rspec_on_rails_3119.patch")}"
+---
+> system "cd #{lib.support_directory} && patch -p0 < #{File.join(TESTING_ROOT, "rspec_on_rails_3119.patch")}"
+36,38c36,38
+< TESTING_ENVIRONMENTS << TestingLibrary::Environment.new("rspec_3317_rails_8956", SUPPORT_TEMP, DB_CONFIG_FILE, DB_SCHEMA_FILE) do |env|
+< env.package "rails", "http://svn.rubyonrails.org/rails", "trunk", "8956", &rails_package
+< env.package "rspec", "http://rspec.rubyforge.org/svn", "trunk", "3317", &rspec_package
+---
+> TESTING_ENVIRONMENTS << TestingLibrary::Environment.new("rspec_3119_rails_8375", SUPPORT_TEMP, DB_CONFIG_FILE, DB_SCHEMA_FILE) do |env|
+> env.package "rails", "http://svn.rubyonrails.org/rails", "trunk", "8375", &rails_package
+> env.package "rspec", "http://rspec.rubyforge.org/svn", "trunk", "3119", &rspec_package
+40c40
+< TESTING_ENVIRONMENTS << TestingLibrary::Environment.new("rspec_3317_rails_1_2_6", SUPPORT_TEMP, DB_CONFIG_FILE, DB_SCHEMA_FILE) do |env|
+---
+> TESTING_ENVIRONMENTS << TestingLibrary::Environment.new("rspec_3119_rails_1_2_6", SUPPORT_TEMP, DB_CONFIG_FILE, DB_SCHEMA_FILE) do |env|
+42c42
+< env.package "rspec", "http://rspec.rubyforge.org/svn", "trunk", "3317", &rspec_package
+---
+> env.package "rspec", "http://rspec.rubyforge.org/svn", "trunk", "3119", &rspec_package
diff --git a/vendor/plugins/scenarios/lib/scenarios.rb b/vendor/plugins/scenarios/lib/scenarios.rb
new file mode 100644
index 00000000..5950e8ec
--- /dev/null
+++ b/vendor/plugins/scenarios/lib/scenarios.rb
@@ -0,0 +1,34 @@
+module Scenarios
+ # Thrown by Scenario.load when it cannot find a specific senario.
+ class NameError < ::NameError; end
+
+ class << self
+ # The locations from which scenarios will be loaded.
+ mattr_accessor :load_paths
+ self.load_paths = ["#{RAILS_ROOT}/spec/scenarios", "#{RAILS_ROOT}/test/scenarios", "#{File.dirname(__FILE__)}/scenarios/builtin"]
+
+ # Load a scenario by name. scenario_name can be a string, symbol,
+ # or the scenario class.
+ def load(scenario_name)
+ klass = scenario_name.to_scenario
+ klass.load
+ klass
+ end
+ end
+end
+
+# The Scenario namespace makes for Scenario::Base
+Scenario = Scenarios
+
+# For Rails 1.2 compatibility
+unless Class.instance_methods.include?(:superclass_delegating_reader)
+ require File.dirname(__FILE__) + "/scenarios/extensions/delegating_attributes"
+end
+
+require 'active_record/fixtures'
+require 'scenarios/configuration'
+require 'scenarios/table_blasting'
+require 'scenarios/table_methods'
+require 'scenarios/loading'
+require 'scenarios/base'
+require 'scenarios/extensions'
\ No newline at end of file
diff --git a/vendor/plugins/scenarios/lib/scenarios/base.rb b/vendor/plugins/scenarios/lib/scenarios/base.rb
new file mode 100644
index 00000000..3885af3c
--- /dev/null
+++ b/vendor/plugins/scenarios/lib/scenarios/base.rb
@@ -0,0 +1,73 @@
+module Scenarios
+ class Base
+ class << self
+ # Class method to load the scenario. Used internally by the Scenarios
+ # plugin.
+ def load
+ new.load_scenarios(used_scenarios + [self])
+ end
+
+ # Class method for your own scenario to define helper methods that will
+ # be included into the scenario and all specs that include the scenario
+ def helpers(&block)
+ mod = (const_get(:Helpers) rescue const_set(:Helpers, Module.new))
+ mod.module_eval(&block) if block_given?
+ mod
+ end
+
+ # Class method for your own scenario to define the scenarios that it
+ # depends on. If your scenario depends on other scenarios those
+ # scenarios will be loaded before the load method on your scenario is
+ # executed.
+ def uses(*scenarios)
+ names = scenarios.map(&:to_scenario).reject { |n| used_scenarios.include?(n) }
+ used_scenarios.concat(names)
+ end
+
+ # Class method that returns the scenarios used by your scenario.
+ def used_scenarios # :nodoc:
+ @used_scenarios ||= []
+ @used_scenarios = (@used_scenarios.collect(&:used_scenarios) + @used_scenarios).flatten.uniq
+ end
+
+ # Returns the scenario class.
+ def to_scenario
+ self
+ end
+ end
+
+ include TableMethods
+ include Loading
+
+ attr_reader :table_config
+
+ # Initialize a scenario with a Configuration. Used internally by the
+ # Scenarios plugin.
+ def initialize(config = Configuration.new)
+ @table_config = config
+ table_config.update_scenario_helpers self.class
+ self.extend table_config.table_readers
+ self.extend table_config.scenario_helpers
+ end
+
+ # This method should be implemented in your scenarios. You may also have
+ # scenarios that simply use other scenarios, so it is not required that
+ # this be overridden.
+ def load
+ end
+
+ # Unload a scenario, sort of. This really only deletes the records, all of
+ # them, of every table this scenario modified. The goal is to maintain a
+ # clean database for successive runs. Used internally by the Scenarios
+ # plugin.
+ def unload
+ return if unloaded?
+ record_metas.each_value { |meta| blast_table(meta.table_name) }
+ @unloaded = true
+ end
+
+ def unloaded?
+ @unloaded == true
+ end
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/scenarios/lib/scenarios/builtin/blank_scenario.rb b/vendor/plugins/scenarios/lib/scenarios/builtin/blank_scenario.rb
new file mode 100644
index 00000000..1ab74d3c
--- /dev/null
+++ b/vendor/plugins/scenarios/lib/scenarios/builtin/blank_scenario.rb
@@ -0,0 +1,18 @@
+class BlankScenario < Scenarios::Base
+ def load
+ table_names.each do |table|
+ blast_table(table)
+ end
+ end
+
+ def table_names
+ self.class.table_names
+ end
+
+ def self.table_names
+ @table_names ||= begin
+ schema = (open(RAILS_ROOT + '/db/schema.rb') { |f| f.read } rescue '')
+ schema.grep(/create_table\s+(['"])(.+?)\1/m) { $2 }
+ end
+ end
+end
diff --git a/vendor/plugins/scenarios/lib/scenarios/configuration.rb b/vendor/plugins/scenarios/lib/scenarios/configuration.rb
new file mode 100644
index 00000000..6becffcf
--- /dev/null
+++ b/vendor/plugins/scenarios/lib/scenarios/configuration.rb
@@ -0,0 +1,55 @@
+module Scenarios
+ class Configuration # :nodoc:
+ attr_reader :blasted_tables, :loaded_scenarios, :record_metas, :table_readers, :scenario_helpers, :symbolic_names_to_id
+
+ def initialize
+ @blasted_tables = Set.new
+ @record_metas = Hash.new
+ @table_readers = Module.new
+ @scenario_helpers = Module.new
+ @symbolic_names_to_id = Hash.new {|h,k| h[k] = Hash.new}
+ @loaded_scenarios = Array.new
+ end
+
+ # Given a created record (currently ScenarioModel or ScenarioRecord),
+ # update the table readers module appropriately such that this record and
+ # it's id are findable via methods like 'people(symbolic_name)' and
+ # 'person_id(symbolic_name)'.
+ def update_table_readers(record)
+ ids, record_meta = symbolic_names_to_id, record.record_meta # scoping assignments
+
+ ids[record_meta.table_name][record.symbolic_name] = record.id
+ table_readers.send :define_method, record_meta.id_reader do |*symbolic_names|
+ record_ids = symbolic_names.flatten.collect do |symbolic_name|
+ if symbolic_name.kind_of?(ActiveRecord::Base)
+ symbolic_name.id
+ else
+ record_id = ids[record_meta.table_name][symbolic_name.to_sym]
+ raise ActiveRecord::RecordNotFound, "No object is associated with #{record_meta.table_name}(:#{symbolic_name})" unless record_id
+ record_id
+ end
+ end
+ record_ids.size > 1 ? record_ids : record_ids.first
+ end
+
+ table_readers.send :define_method, record_meta.record_reader do |*symbolic_names|
+ results = symbolic_names.flatten.collect do |symbolic_name|
+ symbolic_name.kind_of?(ActiveRecord::Base) ?
+ symbolic_name :
+ record_meta.record_class.find(send(record_meta.id_reader, symbolic_name))
+ end
+ results.size > 1 ? results : results.first
+ end
+ end
+
+ def update_scenario_helpers(scenario_class)
+ scenario_helpers.module_eval do
+ include scenario_class.helpers
+ end
+ end
+
+ def scenarios_loaded?
+ !loaded_scenarios.blank?
+ end
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/scenarios/lib/scenarios/extensions.rb b/vendor/plugins/scenarios/lib/scenarios/extensions.rb
new file mode 100644
index 00000000..f442786e
--- /dev/null
+++ b/vendor/plugins/scenarios/lib/scenarios/extensions.rb
@@ -0,0 +1,5 @@
+require File.dirname(__FILE__) + "/extensions/object"
+require File.dirname(__FILE__) + "/extensions/string"
+require File.dirname(__FILE__) + "/extensions/symbol"
+require File.dirname(__FILE__) + "/extensions/active_record"
+require File.dirname(__FILE__) + "/extensions/test_case" rescue nil
\ No newline at end of file
diff --git a/vendor/plugins/scenarios/lib/scenarios/extensions/active_record.rb b/vendor/plugins/scenarios/lib/scenarios/extensions/active_record.rb
new file mode 100644
index 00000000..509abd27
--- /dev/null
+++ b/vendor/plugins/scenarios/lib/scenarios/extensions/active_record.rb
@@ -0,0 +1,14 @@
+module ActiveRecord
+ class Base
+ cattr_accessor :table_config
+ include Scenarios::TableBlasting
+
+ # In order to guarantee that tables are tracked when _create_model_ is
+ # used, and those models cause other models to be created...
+ def create_with_table_blasting
+ prepare_table(self.class.table_name)
+ create_without_table_blasting
+ end
+ alias_method_chain :create, :table_blasting
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/scenarios/lib/scenarios/extensions/delegating_attributes.rb b/vendor/plugins/scenarios/lib/scenarios/extensions/delegating_attributes.rb
new file mode 100644
index 00000000..f5f0ef87
--- /dev/null
+++ b/vendor/plugins/scenarios/lib/scenarios/extensions/delegating_attributes.rb
@@ -0,0 +1,40 @@
+# These class attributes behave something like the class
+# inheritable accessors. But instead of copying the hash over at
+# the time the subclass is first defined, the accessors simply
+# delegate to their superclass unless they have been given a
+# specific value. This stops the strange situation where values
+# set after class definition don't get applied to subclasses.
+class Class
+ def superclass_delegating_reader(*names)
+ class_name_to_stop_searching_on = self.superclass.name.blank? ? "Object" : self.superclass.name
+ names.each do |name|
+ class_eval <<-EOS
+ def self.#{name}
+ if defined?(@#{name})
+ @#{name}
+ elsif superclass < #{class_name_to_stop_searching_on} && superclass.respond_to?(:#{name})
+ superclass.#{name}
+ end
+ end
+ def #{name}
+ self.class.#{name}
+ end
+ EOS
+ end
+ end
+
+ def superclass_delegating_writer(*names)
+ names.each do |name|
+ class_eval <<-EOS
+ def self.#{name}=(value)
+ @#{name} = value
+ end
+ EOS
+ end
+ end
+
+ def superclass_delegating_accessor(*names)
+ superclass_delegating_reader(*names)
+ superclass_delegating_writer(*names)
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/scenarios/lib/scenarios/extensions/object.rb b/vendor/plugins/scenarios/lib/scenarios/extensions/object.rb
new file mode 100644
index 00000000..efd28197
--- /dev/null
+++ b/vendor/plugins/scenarios/lib/scenarios/extensions/object.rb
@@ -0,0 +1,5 @@
+class Object
+ def metaclass
+ (class << self; self; end)
+ end unless method_defined?(:metaclass)
+end
diff --git a/vendor/plugins/scenarios/lib/scenarios/extensions/string.rb b/vendor/plugins/scenarios/lib/scenarios/extensions/string.rb
new file mode 100644
index 00000000..830aa791
--- /dev/null
+++ b/vendor/plugins/scenarios/lib/scenarios/extensions/string.rb
@@ -0,0 +1,22 @@
+class String
+
+ # Convert a string into the associated scenario class:
+ #
+ # "basic".to_scenario #=> BasicScenario
+ # "basic_scenario".to_scenario #=> BasicScenario
+ #
+ # Raises Scenario::NameError if the the scenario cannot be loacated in
+ # Scenario.load_paths.
+ def to_scenario
+ class_name = "#{self.strip.camelize.sub(/Scenario$/, '')}Scenario"
+ Scenario.load_paths.each do |path|
+ filename = "#{path}/#{class_name.underscore}.rb"
+ if File.file?(filename)
+ require filename
+ break
+ end
+ end
+ class_name.constantize rescue raise Scenario::NameError, "Expected to find #{class_name} in #{Scenario.load_paths.inspect}"
+ end
+
+end
\ No newline at end of file
diff --git a/vendor/plugins/scenarios/lib/scenarios/extensions/symbol.rb b/vendor/plugins/scenarios/lib/scenarios/extensions/symbol.rb
new file mode 100644
index 00000000..35e5f090
--- /dev/null
+++ b/vendor/plugins/scenarios/lib/scenarios/extensions/symbol.rb
@@ -0,0 +1,14 @@
+class Symbol
+
+ # Convert a symbol into the associated scenario class:
+ #
+ # :basic.to_scenario #=> BasicScenario
+ # :basic_scenario.to_scenario #=> BasicScenario
+ #
+ # Raises Scenario::NameError if the the scenario cannot be located in
+ # Scenario.load_paths.
+ def to_scenario
+ to_s.to_scenario
+ end
+
+end
\ No newline at end of file
diff --git a/vendor/plugins/scenarios/lib/scenarios/extensions/test_case.rb b/vendor/plugins/scenarios/lib/scenarios/extensions/test_case.rb
new file mode 100644
index 00000000..a9f0ef6a
--- /dev/null
+++ b/vendor/plugins/scenarios/lib/scenarios/extensions/test_case.rb
@@ -0,0 +1,77 @@
+module Test #:nodoc:
+ module Unit #:nodoc:
+ class TestCase #:nodoc:
+ superclass_delegating_accessor :scenario_classes
+ superclass_delegating_accessor :table_config
+
+ # Changing either of these is not supported at this time.
+ self.use_transactional_fixtures = true
+ self.use_instantiated_fixtures = false
+
+ include Scenarios::TableMethods
+ include Scenarios::Loading
+
+ class << self
+ # This class method is mixed into RSpec and allows you to declare that
+ # you are using a given scenario or set of scenarios within a spec:
+ #
+ # scenario :basic # loads BasicScenario and any dependencies
+ # scenario :posts, :comments # loads PostsScenario and CommentsScenario
+ #
+ # It accepts an array of scenarios (strings, symbols, or classes) and
+ # will load them roughly in the order that they are specified.
+ def scenario(*names)
+ self.scenario_classes = []
+ names.each do |name|
+ scenario_class = name.to_scenario
+ scenario_classes.concat(scenario_class.used_scenarios + [scenario_class])
+ end
+ scenario_classes.uniq!
+ end
+
+ # Overridden to provide before all and after all code which sets up and
+ # tears down scenarios
+ def suite_with_scenarios
+ suite = suite_without_scenarios
+ class << suite
+ attr_accessor :test_class
+ def run_with_scenarios(*args, &block)
+ debugger
+ run_without_scenarios(*args, &block)
+ test_class.table_config.loaded_scenarios.each { |s| s.unload } if test_class.table_config
+ end
+ alias_method_chain :run, :scenarios
+ end
+ suite.test_class = self
+ suite
+ end
+ alias_method_chain :suite, :scenarios
+ end
+
+ # Hook into fixtures loading lifecycle to instead load scenarios. This
+ # is expected to be called in a fashion respective of
+ # use_transactional_fixtures. I feel like a leech.
+ def load_fixtures
+ if !scenarios_loaded? || !use_transactional_fixtures?
+ self.class.table_config = Scenarios::Configuration.new if !use_transactional_fixtures? || table_config.nil?
+ load_scenarios(scenario_classes)
+ end
+ self.extend scenario_helpers
+ self.extend table_readers
+ end
+
+ # Here we are counting on existing logic to allow teardown method
+ # overriding as done in fixtures.rb. Only if transaction fixtures are
+ # not being used do we unload scenarios after a test. Otherwise, we wait
+ # until the end of the run of all tests on this test_case (collection of
+ # tests, right!). See the TestSuite extension done in _suite_ for
+ # behaviour when using transaction fixtures.
+ def teardown_with_scenarios
+ teardown_without_scenarios
+ loaded_scenarios.each { |s| s.unload } unless use_transactional_fixtures?
+ end
+ alias_method_chain :teardown, :scenarios
+
+ end
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/scenarios/lib/scenarios/loading.rb b/vendor/plugins/scenarios/lib/scenarios/loading.rb
new file mode 100644
index 00000000..b369196e
--- /dev/null
+++ b/vendor/plugins/scenarios/lib/scenarios/loading.rb
@@ -0,0 +1,51 @@
+module Scenarios
+ # Provides scenario loading and convenience methods around the Configuration
+ # that must be made available through a method _table_config_.
+ module Loading # :nodoc:
+ def load_scenarios(scenario_classes)
+ install_active_record_tracking_hook
+ scenario_classes.each do |scenario_class|
+ scenario = scenario_class.new(table_config)
+ scenario.load
+ table_config.loaded_scenarios << scenario
+ end if scenario_classes
+ end
+
+ def loaded_scenarios
+ table_config.loaded_scenarios
+ end
+
+ def scenarios_loaded?
+ table_config && table_config.scenarios_loaded?
+ end
+
+ # The sum of all the loaded scenario's helper methods. These can be mixed
+ # into anything you like to gain access to them.
+ def scenario_helpers
+ table_config.scenario_helpers
+ end
+
+ # The sum of all the available table reading methods. These will only
+ # include readers for which data has been placed into the table. These can
+ # be mixed into anything you like to gain access to them.
+ def table_readers
+ table_config.table_readers
+ end
+
+ # # This understand nesting descriptions one deep
+ # def table_config
+ # on_my_class = self.class.instance_variable_get("@table_config")
+ # return on_my_class if on_my_class
+ #
+ # if self.class.superclass
+ # on_super_class = self.class.superclass.instance_variable_get("@table_config")
+ # return on_super_class if on_super_class
+ # end
+ # end
+
+ private
+ def install_active_record_tracking_hook
+ ActiveRecord::Base.table_config = table_config
+ end
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/scenarios/lib/scenarios/table_blasting.rb b/vendor/plugins/scenarios/lib/scenarios/table_blasting.rb
new file mode 100644
index 00000000..9b2efc3d
--- /dev/null
+++ b/vendor/plugins/scenarios/lib/scenarios/table_blasting.rb
@@ -0,0 +1,20 @@
+module Scenarios
+ module TableBlasting
+ def self.included(base)
+ base.module_eval do
+ delegate :blasted_tables, :to => :table_config
+ end
+ end
+
+ def blast_table(name) # :nodoc:
+ ActiveRecord::Base.silence do
+ ActiveRecord::Base.connection.delete "DELETE FROM #{name}", "Scenario Delete"
+ end
+ blasted_tables << name
+ end
+
+ def prepare_table(name)
+ blast_table(name) unless blasted_tables.include?(name)
+ end
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/scenarios/lib/scenarios/table_methods.rb b/vendor/plugins/scenarios/lib/scenarios/table_methods.rb
new file mode 100644
index 00000000..a9f2be34
--- /dev/null
+++ b/vendor/plugins/scenarios/lib/scenarios/table_methods.rb
@@ -0,0 +1,205 @@
+module Scenarios
+ # This helper module contains the #create_record method. It is made
+ # available to all Scenario instances, test and example classes, and test
+ # and example instances.
+ module TableMethods
+ include TableBlasting
+
+ delegate :record_metas, :to => :table_config
+
+ # Insert a record into the database, add the appropriate helper methods
+ # into the scenario and spec, and return the ID of the inserted record:
+ #
+ # create_record :event, :name => "Ruby Hoedown"
+ # create_record Event, :hoedown, :name => "Ruby Hoedown"
+ #
+ # The first form will create a new record in the given class identifier
+ # and no symbolic name (essentially).
+ #
+ # The second form is exactly like the first, except for that it provides a
+ # symbolic name as the second parameter. The symbolic name will allow you
+ # to access the record through a couple of helper methods:
+ #
+ # events(:hoedown) # The hoedown event
+ # event_id(:hoedown) # The ID of the hoedown event
+ #
+ # These helper methods are only accessible for a particular table after
+ # you have inserted a record into that table using create_record.
+ def create_record(class_identifier, *args)
+ insert(ScenarioRecord, class_identifier, *args) do |record|
+ meta = record.record_meta
+ fixture = record.to_fixture
+ begin
+ meta.connection.insert_fixture(fixture, record.record_meta.table_name)
+ rescue # Rails 1.2 compatible!
+ meta.connection.execute "INSERT INTO #{meta.table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})"
+ end
+ record.id
+ end
+ end
+
+ # Instantiate and save! a model, add the appropriate helper methods into
+ # the scenario and spec, and return the new model instance:
+ #
+ # create_model :event, :name => "Ruby Hoedown"
+ # create_model Event, :hoedown, :name => "Ruby Hoedown"
+ #
+ # The first form will create a new model with no symbolic name
+ # (essentially).
+ #
+ # The second form is exactly like the first, except for that it provides a
+ # symbolic name as the second parameter. The symbolic name will allow you
+ # to access the record through a couple of helper methods:
+ #
+ # events(:hoedown) # The hoedown event
+ # event_id(:hoedown) # The ID of the hoedown event
+ #
+ # These helper methods are only accessible for a particular table after
+ # you have inserted a record into that table using create_model.
+ def create_model(class_identifier, *args)
+ insert(ScenarioModel, class_identifier, *args) do |record|
+ model = record.to_model
+ model.save!
+ model
+ end
+ end
+
+ private
+ def insert(record_or_model, class_identifier, *args, &insertion)
+ symbolic_name, attributes = extract_creation_arguments(args)
+ record_meta = (record_metas[class_identifier] ||= RecordMeta.new(class_identifier))
+ record = record_or_model.new(record_meta, attributes, symbolic_name)
+ return_value = nil
+ ActiveRecord::Base.silence do
+ prepare_table(record_meta.table_name)
+ return_value = insertion.call record
+ table_config.update_table_readers(record)
+ self.extend table_config.table_readers
+ end
+ return_value
+ end
+
+ def extract_creation_arguments(arguments)
+ if arguments.size == 2 && arguments.last.kind_of?(Hash)
+ arguments
+ elsif arguments.size == 1 && arguments.last.kind_of?(Hash)
+ [nil, arguments[0]]
+ else
+ [nil, Hash.new]
+ end
+ end
+
+ class RecordMeta # :nodoc:
+ attr_reader :class_name, :record_class, :table_name
+
+ def initialize(class_identifier)
+ @class_identifier = class_identifier
+ @class_name = resolve_class_name(class_identifier)
+ @record_class = class_name.constantize
+ @table_name = record_class.table_name
+ end
+
+ def timestamp_columns
+ @timestamp_columns ||= begin
+ timestamps = %w(created_at created_on updated_at updated_on)
+ columns.select do |column|
+ timestamps.include?(column.name)
+ end
+ end
+ end
+
+ def columns
+ @columns ||= connection.columns(table_name)
+ end
+
+ def connection
+ record_class.connection
+ end
+
+ def id_reader
+ @id_reader ||= begin
+ reader = ActiveRecord::Base.pluralize_table_names ? table_name.singularize : table_name
+ "#{reader}_id".to_sym
+ end
+ end
+
+ def record_reader
+ table_name.to_sym
+ end
+
+ def resolve_class_name(class_identifier)
+ case class_identifier
+ when Symbol
+ class_identifier.to_s.singularize.camelize
+ when Class
+ class_identifier.name
+ when String
+ class_identifier
+ end
+ end
+
+ def to_s
+ "#"
+ end
+ end
+
+ class ScenarioModel # :nodoc:
+ attr_reader :attributes, :model, :record_meta, :symbolic_name
+ delegate :id, :to => :model
+
+ def initialize(record_meta, attributes, symbolic_name = nil)
+ @record_meta = record_meta
+ @attributes = attributes.stringify_keys
+ @symbolic_name = symbolic_name || object_id
+ end
+
+ def to_hash
+ to_model.attributes
+ end
+
+ def to_model
+ @model ||= record_meta.record_class.new(attributes)
+ end
+ end
+
+ class ScenarioRecord # :nodoc:
+ attr_reader :record_meta, :symbolic_name
+
+ def initialize(record_meta, attributes, symbolic_name = nil)
+ @record_meta = record_meta
+ @attributes = attributes.stringify_keys
+ @symbolic_name = symbolic_name || object_id
+
+ install_default_attributes!
+ end
+
+ def id
+ @attributes['id']
+ end
+
+ def to_hash
+ @attributes
+ end
+
+ def to_fixture
+ Fixture.new(to_hash, record_meta.class_name)
+ end
+
+ def install_default_attributes!
+ @attributes['id'] ||= symbolic_name.to_s.hash.abs
+ install_timestamps!
+ end
+
+ def install_timestamps!
+ record_meta.timestamp_columns.each do |column|
+ @attributes[column.name] = now(column) unless @attributes.key?(column.name)
+ end
+ end
+
+ def now(column)
+ now = ActiveRecord::Base.default_timezone == :utc ? column.klass.now.utc : column.klass.now
+ now.to_s(:db)
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/scenarios/spec/scenarios/complex_composite_scenario.rb b/vendor/plugins/scenarios/spec/scenarios/complex_composite_scenario.rb
new file mode 100644
index 00000000..1d3c3318
--- /dev/null
+++ b/vendor/plugins/scenarios/spec/scenarios/complex_composite_scenario.rb
@@ -0,0 +1,9 @@
+class ComplexCompositeScenario < Scenario::Base
+ uses :composite, :places
+
+ helpers do
+ def method_from_complex_composite_scenario
+ :method_from_complex_composite_scenario
+ end
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/scenarios/spec/scenarios/composite_scenario.rb b/vendor/plugins/scenarios/spec/scenarios/composite_scenario.rb
new file mode 100644
index 00000000..09903b28
--- /dev/null
+++ b/vendor/plugins/scenarios/spec/scenarios/composite_scenario.rb
@@ -0,0 +1,9 @@
+class CompositeScenario < Scenario::Base
+ uses :people, :things
+
+ helpers do
+ def method_from_composite_scenario
+ :method_from_composite_scenario
+ end
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/scenarios/spec/scenarios/empty_scenario.rb b/vendor/plugins/scenarios/spec/scenarios/empty_scenario.rb
new file mode 100644
index 00000000..0ea2ba64
--- /dev/null
+++ b/vendor/plugins/scenarios/spec/scenarios/empty_scenario.rb
@@ -0,0 +1,4 @@
+class EmptyScenario < Scenario::Base
+ def load
+ end
+end
diff --git a/vendor/plugins/scenarios/spec/scenarios/people_scenario.rb b/vendor/plugins/scenarios/spec/scenarios/people_scenario.rb
new file mode 100644
index 00000000..792e0f4a
--- /dev/null
+++ b/vendor/plugins/scenarios/spec/scenarios/people_scenario.rb
@@ -0,0 +1,26 @@
+class PeopleScenario < Scenario::Base
+
+ def load
+ create_person "John Long"
+ create_person "Adam Williams"
+ end
+
+ helpers do
+ def create_person(attributes = {})
+ if attributes.kind_of?(String)
+ first, last = attributes.split(/\s+/)
+ attributes = { :first_name => first, :last_name => last }
+ end
+ attributes = person_params(attributes)
+ create_record(:person, attributes[:first_name].strip.gsub(' ', '_').underscore.to_sym, attributes)
+ end
+
+ def person_params(attributes = {})
+ attributes = {
+ :first_name => "John",
+ :last_name => "Q."
+ }.update(attributes)
+ end
+ end
+
+end
\ No newline at end of file
diff --git a/vendor/plugins/scenarios/spec/scenarios/places_scenario.rb b/vendor/plugins/scenarios/spec/scenarios/places_scenario.rb
new file mode 100644
index 00000000..50d7ddf9
--- /dev/null
+++ b/vendor/plugins/scenarios/spec/scenarios/places_scenario.rb
@@ -0,0 +1,22 @@
+class PlacesScenario < Scenario::Base
+
+ def load
+ create_place "Taj Mahal", "India"
+ create_place "Whitehouse", "Washington DC"
+ end
+
+ helpers do
+ def create_place(name, location)
+ attributes = place_params(:name => name, :location => location)
+ create_record(:place, name.strip.gsub(' ', '_').underscore.to_sym, attributes)
+ end
+
+ def place_params(attributes = {})
+ attributes = {
+ :name => "Noplace",
+ :location => "Nowhere"
+ }.update(attributes)
+ end
+ end
+
+end
\ No newline at end of file
diff --git a/vendor/plugins/scenarios/spec/scenarios/things_scenario.rb b/vendor/plugins/scenarios/spec/scenarios/things_scenario.rb
new file mode 100644
index 00000000..d37abd39
--- /dev/null
+++ b/vendor/plugins/scenarios/spec/scenarios/things_scenario.rb
@@ -0,0 +1,22 @@
+class ThingsScenario < Scenario::Base
+
+ def load
+ create_thing "one"
+ create_thing "two"
+ end
+
+ helpers do
+ def create_thing(attributes = {})
+ attributes = { :name => attributes } if attributes.kind_of?(String)
+ attributes = thing_params(attributes)
+ create_record(:thing, attributes[:name].strip.gsub(' ', '_').underscore.to_sym, attributes)
+ end
+
+ def thing_params(attributes = {})
+ attributes = {
+ :name => "Unnamed Thing",
+ :description => "I'm not sure what this is."
+ }.update(attributes)
+ end
+ end
+end
diff --git a/vendor/plugins/scenarios/spec/scenarios_spec.rb b/vendor/plugins/scenarios/spec/scenarios_spec.rb
new file mode 100644
index 00000000..c74fd5e0
--- /dev/null
+++ b/vendor/plugins/scenarios/spec/scenarios_spec.rb
@@ -0,0 +1,185 @@
+require File.expand_path(File.dirname(__FILE__) + "/spec_helper")
+
+class ExplodesOnSecondInstantiationScenario < Scenario::Base
+ cattr_accessor :instance
+ def initialize(*args)
+ raise "Should only be created once" if self.class.instance
+ self.class.instance = super(*args)
+ end
+end
+
+describe "Scenario loading" do
+ scenario :explodes_on_second_instantiation
+
+ it "should work" do
+ end
+
+ it 'should work again' do
+ end
+end
+
+describe "Scenario loading" do
+ it "should load from configured directories" do
+ Scenario.load(:empty)
+ EmptyScenario
+ end
+
+ it "should raise Scenario::NameError when the scenario does not exist" do
+ lambda { Scenario.load(:whatcha_talkin_bout) }.should raise_error(Scenario::NameError)
+ end
+
+ it "should allow us to add helper methods through the helpers class method" do
+ klass = :empty.to_scenario
+ klass.helpers do
+ def hello
+ "Hello World"
+ end
+ end
+ klass.new.methods.should include('hello')
+ end
+
+ it "should provide a built-in scenario named :blank which clears all tables found in schema.rb" do
+ Scenario.load(:blank)
+ BlankScenario
+ end
+end
+
+describe Scenarios::TableMethods do
+ scenario :things
+
+ it "should understand namespaced models" do
+ create_record "ModelModule::Model", :raking, :name => "Raking", :description => "Moving leaves around"
+ models(:raking).should_not be_nil
+ end
+
+ it "should include record creation methods" do
+ create_record(:thing, :three, :name => "Three")
+ things(:three).name.should == "Three"
+ end
+
+ it "should include other example helper methods" do
+ create_thing("The Thing")
+ things(:the_thing).name.should == "The Thing"
+ end
+
+ describe "for retrieving objects" do
+ it "should have a pluralized name" do
+ should respond_to("things")
+ should_not respond_to("thing")
+ end
+
+ it "should answer a single object given a single name" do
+ things(:one).should be_kind_of(Thing)
+ things("one").should be_kind_of(Thing)
+ things(:two).name.should == "two"
+ end
+
+ it "should answer an array of objects given multiple names" do
+ things(:one, :two).should be_kind_of(Array)
+ things(:one, :two).should eql([things(:one), things(:two)])
+ end
+
+ it "should just return the argument if an AR instance is given" do
+ thing = things(:one)
+ things(thing).should eql(thing)
+ end
+ end
+
+ describe "for retrieving ids" do
+ it "should have a singular name" do
+ should respond_to("thing_id")
+ should_not respond_to("thing_ids")
+ should_not respond_to("things_id")
+ end
+
+ it "should answer a single id given a single name" do
+ thing_id(:one).should be_kind_of(Fixnum)
+ thing_id("one").should be_kind_of(Fixnum)
+ end
+
+ it "should answer an array of ids given multiple names" do
+ thing_id(:one, :two).should be_kind_of(Array)
+ thing_id(:one, :two).should eql([thing_id(:one), thing_id(:two)])
+ thing_id("one", "two").should eql([thing_id(:one), thing_id(:two)])
+ end
+
+ it "should answer the id of the argument if an AR instance id given" do
+ thing = things(:one)
+ thing_id(thing).should == thing.id
+ end
+ end
+end
+
+describe "it uses people and things scenarios", :shared => true do
+ it "should have reader helper methods for each used scenario" do
+ should respond_to(:things)
+ should respond_to(:people)
+ end
+
+ it "should allow us to use helper methods from each scenario inside an example" do
+ should respond_to(:create_thing)
+ should respond_to(:create_person)
+ end
+end
+
+describe "A composite scenario" do
+ scenario :composite
+
+ it_should_behave_like "it uses people and things scenarios"
+
+ it "should allow us to use helper methods scenario" do
+ should respond_to(:method_from_composite_scenario)
+ end
+end
+
+describe "Multiple scenarios" do
+ scenario :things, :people
+
+ it_should_behave_like "it uses people and things scenarios"
+end
+
+describe "A complex composite scenario" do
+ scenario :complex_composite
+
+ it_should_behave_like "it uses people and things scenarios"
+
+ it "should have correct reader helper methods" do
+ should respond_to(:places)
+ end
+
+ it "should allow us to use correct helper methods" do
+ should respond_to(:create_place)
+ end
+end
+
+describe "Overlapping scenarios" do
+ scenario :composite, :things, :people
+
+ it "should not cause scenarios to be loaded twice" do
+ Person.find_all_by_first_name("John").size.should == 1
+ end
+end
+
+describe "create_record table method" do
+ scenario :empty
+
+ it "should automatically set timestamps" do
+ create_record :note, :first, :content => "first note"
+ note = notes(:first)
+ note.created_at.should be_instance_of(Time)
+ end
+end
+
+describe "create_model table method" do
+ scenario :empty
+
+ it "should support symbolic names" do
+ thing = create_model Thing, :mything, :name => "My Thing", :description => "For testing"
+ things(:mything).should == thing
+ end
+
+ it "should blast any table touched as a side effect of creating a model (callbacks, observers, etc.)" do
+ create_model SideEffectyThing
+ blasted_tables.should include(Thing.table_name)
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/scenarios/spec/spec.opts b/vendor/plugins/scenarios/spec/spec.opts
new file mode 100644
index 00000000..2144705e
--- /dev/null
+++ b/vendor/plugins/scenarios/spec/spec.opts
@@ -0,0 +1,7 @@
+--colour
+--format
+progress
+--loadby
+mtime
+--reverse
+--backtrace
\ No newline at end of file
diff --git a/vendor/plugins/scenarios/spec/spec_helper.rb b/vendor/plugins/scenarios/spec/spec_helper.rb
new file mode 100644
index 00000000..b641894f
--- /dev/null
+++ b/vendor/plugins/scenarios/spec/spec_helper.rb
@@ -0,0 +1,24 @@
+require File.expand_path(File.dirname(__FILE__) + '/../testing/plugit_descriptor')
+
+TESTING_ROOT = File.expand_path("#{File.dirname(__FILE__)}/../testing")
+TESTING_TMP = "#{TESTING_ROOT}/tmp"
+
+require 'fileutils'
+FileUtils.mkdir_p(TESTING_TMP)
+FileUtils.touch("#{TESTING_TMP}/test.log")
+
+require 'logger'
+RAILS_DEFAULT_LOGGER = Logger.new("#{TESTING_TMP}/test.log")
+RAILS_DEFAULT_LOGGER.level = Logger::DEBUG
+
+ActiveRecord::Base.silence do
+ ActiveRecord::Base.configurations = {'sqlite3' => {
+ 'adapter' => 'sqlite3',
+ 'database' => "#{TESTING_TMP}/sqlite3.db"
+ }}
+ ActiveRecord::Base.establish_connection 'sqlite3'
+ load "#{TESTING_ROOT}/schema.rb"
+end
+
+require "models"
+require "scenarios"
\ No newline at end of file
diff --git a/vendor/plugins/scenarios/tasks/scenario.rake b/vendor/plugins/scenarios/tasks/scenario.rake
new file mode 100644
index 00000000..37b56ebb
--- /dev/null
+++ b/vendor/plugins/scenarios/tasks/scenario.rake
@@ -0,0 +1,19 @@
+namespace :db do
+ namespace :scenario do
+ desc "Load a scenario into the current environment's database using SCENARIO=scenario_name"
+ task :load => 'db:reset' do
+ scenario_name = ENV['SCENARIO'] || 'default'
+ begin
+ klass = Scenarios.load(scenario_name)
+ puts "Loaded #{klass.name.underscore.gsub('_', ' ')}."
+ rescue Scenarios::NameError => e
+ if scenario_name == 'default'
+ puts "Error! Set the SCENARIO environment variable or define a DefaultScenario class."
+ else
+ puts "Error! Invalid scenario name [#{scenario_name}]."
+ end
+ exit(1)
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/scenarios/testing/application.rb b/vendor/plugins/scenarios/testing/application.rb
new file mode 100644
index 00000000..87f91f64
--- /dev/null
+++ b/vendor/plugins/scenarios/testing/application.rb
@@ -0,0 +1,2 @@
+class ApplicationController < ActionController::Base
+end
\ No newline at end of file
diff --git a/vendor/plugins/scenarios/testing/models.rb b/vendor/plugins/scenarios/testing/models.rb
new file mode 100644
index 00000000..34801db4
--- /dev/null
+++ b/vendor/plugins/scenarios/testing/models.rb
@@ -0,0 +1,14 @@
+class Person < ActiveRecord::Base; end
+class Place < ActiveRecord::Base; end
+class Thing < ActiveRecord::Base; end
+class Note < ActiveRecord::Base; end
+
+class SideEffectyThing < ActiveRecord::Base
+ after_create do
+ Thing.create!
+ end
+end
+
+module ModelModule
+ class Model < ActiveRecord::Base; end
+end
\ No newline at end of file
diff --git a/vendor/plugins/scenarios/testing/plugit_descriptor.rb b/vendor/plugins/scenarios/testing/plugit_descriptor.rb
new file mode 100644
index 00000000..17b19e67
--- /dev/null
+++ b/vendor/plugins/scenarios/testing/plugit_descriptor.rb
@@ -0,0 +1,44 @@
+require 'rubygems'
+gem 'plugit'
+require 'plugit'
+
+$LOAD_PATH << File.expand_path("#{File.dirname(__FILE__)}/../lib")
+$LOAD_PATH << File.expand_path(File.dirname(__FILE__))
+RAILS_ROOT = File.expand_path("#{File.dirname(__FILE__)}/..")
+
+Plugit.describe do |scenarios|
+ scenarios.environments_root_path = File.dirname(__FILE__) + '/environments'
+ vendor_directory = File.expand_path(File.dirname(__FILE__) + '/../vendor/plugins')
+
+ scenarios.environment :default, 'Released versions of Rails and RSpec' do |env|
+ env.library :rails, :export => "git clone git://github.com/rails/rails.git" do |rails|
+ rails.after_update { `git co v2.1.0_RC1` }
+ rails.load_paths = %w{/activesupport/lib /activerecord/lib /actionpack/lib}
+ rails.requires = %w{active_support active_record action_controller action_view}
+ end
+ env.library :rspec, :export => "git clone git://github.com/dchelimsky/rspec.git" do |rspec|
+ rspec.after_update { `git co 1.1.4 && mkdir -p #{vendor_directory} && ln -sF #{File.expand_path('.')} #{vendor_directory + '/rspec'}` }
+ rspec.requires = %w{spec}
+ end
+ env.library :rspec_rails, :export => "git clone git://github.com/dchelimsky/rspec-rails.git" do |rspec_rails|
+ rspec_rails.after_update { `git co 1.1.4` }
+ rspec_rails.requires = %w{spec/rails}
+ end
+ end
+
+ scenarios.environment :edge, 'Edge versions of Rails and RSpec' do |env|
+ env.library :rails, :export => "git clone git://github.com/rails/rails.git --depth 1" do |rails|
+ rails.before_install { `git pull` }
+ rails.load_paths = %w{/activesupport/lib /activerecord/lib /actionpack/lib}
+ rails.requires = %w{active_support active_record action_controller action_view}
+ end
+ env.library :rspec, :export => "git clone git://github.com/dchelimsky/rspec.git --depth 1" do |rspec|
+ rspec.after_update { `git pull && mkdir -p #{vendor_directory} && ln -sF #{File.expand_path('.')} #{vendor_directory + '/rspec'}` }
+ rspec.requires = %w{spec}
+ end
+ env.library :rspec_rails, :export => "git clone git://github.com/dchelimsky/rspec-rails.git --depth 1" do |rspec_rails|
+ rspec_rails.after_update { `git pull` }
+ rspec_rails.requires = %w{spec/rails}
+ end
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/scenarios/testing/schema.rb b/vendor/plugins/scenarios/testing/schema.rb
new file mode 100644
index 00000000..30a1326c
--- /dev/null
+++ b/vendor/plugins/scenarios/testing/schema.rb
@@ -0,0 +1,31 @@
+ActiveRecord::Schema.define do
+ create_table :people, :force => true do |t|
+ t.column :first_name, :string
+ t.column :last_name, :string
+ end
+
+ create_table :places, :force => true do |t|
+ t.column :name, :string
+ t.column :location, :string
+ end
+
+ create_table :things, :force => true do |t|
+ t.column :name, :string
+ t.column :description, :string
+ end
+
+ create_table :side_effecty_things, :force => true do |t|
+ end
+
+ create_table :models, :force => true do |t|
+ t.column :name, :string
+ t.column :description, :string
+ end
+
+ create_table :notes, :force => true do |t|
+ t.column :content, :string
+ t.column :created_at, :datetime
+ t.column :updated_at, :datetime
+ end
+end
+
\ No newline at end of file
diff --git a/vendor/plugins/skinny_spec/.gitignore b/vendor/plugins/skinny_spec/.gitignore
new file mode 100644
index 00000000..28f7b7da
--- /dev/null
+++ b/vendor/plugins/skinny_spec/.gitignore
@@ -0,0 +1,2 @@
+.DS_Store
+doc
\ No newline at end of file
diff --git a/vendor/plugins/skinny_spec/README.rdoc b/vendor/plugins/skinny_spec/README.rdoc
new file mode 100644
index 00000000..49cfbf60
--- /dev/null
+++ b/vendor/plugins/skinny_spec/README.rdoc
@@ -0,0 +1,267 @@
+= Skinny Spec
+
+Skinny Spec is a collection of spec helper methods designed to help trim the fat and DRY up
+some of the bloat that sometimes results from properly specing your classes and templates.
+
+== Requirements and Recommendations
+
+Obviously you'll need to be using RSpec[http://github.com/dchelimsky/rspec/tree/master] and
+Rspec-Rails[http://github.com/dchelimsky/rspec-rails/tree/master] as your testing framework.
+
+Skinny Spec was originally designed [and best enjoyed] if you're using
+Haml[http://github.com/nex3/haml/tree/master] and
+make_resourceful[http://github.com/rsl/make_resourceful/tree/master] but will default to
+ERb and a facsimile of Rails' default scaffolding [for the views and controllers, respectively]
+if Haml and/or make_resourceful are not available. I recommend using them though. :)
+
+In addition, Skinny Spec uses Ruby2Ruby to make nicer expectation messages and you'll want to
+have that installed as well. It's not a dependency or anything but it is highly
+recommended.
+
+== Setup
+
+Once you've installed the plugin in your app's vendor/plugins folder, you're ready to rock!
+Skinny Spec includes itself into the proper RSpec classes so there's no configuration on your
+part. Sweet!
+
+== Usage
+
+The simplest way to use Skinny Specs is to generate a resource scaffold:
+
+ script/generate skinny_scaffold User
+
+This command takes the usual complement of attribute definitions like
+script/generate scaffold. Then have a look at the generated files (particularly the
+specs) to see what's new and different with Skinny Spec.
+
+=== Controller Specs
+
+Let's look at the controller specs.
+
+ describe UsersController do
+ def valid_attributes(args = {})
+ {
+ # Add valid attributes for the your params[:user] here!
+ }.merge(args)
+ end
+
+ describe "GET :index" do
+ before(:each) do
+ @users = stub_index(User)
+ end
+
+ it_should_find_and_assign :users
+ it_should_render :template, "index"
+ end
+
+ # ...
+
+ describe "POST :create" do
+ describe "when successful" do
+ before(:each) do
+ @user = stub_create(User)
+ end
+
+ it_should_initialize_and_save :user
+ it_should_redirect_to { user_url(@user) }
+ end
+
+ # ...
+
+First thing you should see is a method definition for
+valid_attributes. This will be used later by the create and update
+specs to more accurately represent how the controller works in actual practice by supplying
+somewhat real data for the params coming from the HTML forms.
+
+Next we find an example group for GET :index. That stub_index method there
+does a lot of work behind the curtain. I'll leave it up to you to check the documentation for it
+(and its brothers and sister methods like stub_new) but I will point out that the
+methods named stub_controller_method should only be used for stubbing and
+mocking the main object of the method. To create mocks for other ancillary objects, please
+use stub_find_all, stub_find_one, and stub_initialize. The reason
+for this is because the former methods actually save us a step by defining an implicit
+controller method request. If you add a new method to your resource routing, you'll want to
+use the helper method define_request in those example groups to define an explicit
+request. You can also define a method called shared_request to "share a
+define_request" across shared describe blocks, like so:
+
+ describe "POST :create" do
+ def shared_request
+ post :create
+ end
+
+ describe "when successful" do
+ # ...
+ end
+
+ describe "when unsuccessful" do
+ # ...
+ end
+ end
+
+Note: When you're adding longer, more complicated controller specs you can still leverage
+implicit and shared requests by calling do_request in your spec as in the following
+example:
+
+ # Let's assume this controller is _not_ CategoriesController
+ # and that loading the categories isn't part of the default actions
+ describe "GET :index" do
+ before(:each) do
+ @categories = stub_find_all(Category)
+ end
+
+ it "should preload categories" do
+ Category.should_receive(:find).with(:all)
+ do_request
+ end
+
+ it "should assign @categories" do
+ do_request
+ assigns[:categories].should == @categories
+ end
+ end
+
+Finally we get to the meat of the spec and of Skinny Specs itself: the actual expectations.
+The first thing you'll notice is the use of example group (read: "describe" block) level methods
+instead of the usual example (read: "it") blocks. Using this helper at the example group level
+saves us three lines over using an example block. (If this isn't significant to you, this is
+probably the wrong plugin for you as well. Sorry.) Note that none of these methods use the
+instance variables defined in the "before" block because they are all nil at the example block
+level. Let's look at a sample method to see how it works:
+
+ it_should_find_and_assign :users
+
+This actually wraps two different expectations: one that User.should_receive(:find).with(:all)
+and another that the instance variable @users is assigned with the return value from that finder call.
+If you need to add more detailed arguments to the find, you can easily break this into two different
+expectations like:
+
+ it_should_find :users, :limit => 2
+ it_should_assign :users
+
+See the documentation for the it_should_find for more information. You might have guessed that
+it_should_initialize_assign and it_should_render_template work in a similar
+fashion and you'd be right. Again, see the documentation for these individual methods for more
+information. Lots of information in those docs.
+
+A useful helper method that doesn't appear in any of the scaffolding is with_default_restful_actions
+which takes a block and evaluates it for each of the RESTful controller actions. Very useful for
+spec'ing that these methods redirect to the login page when the user isn't logged in, for example. This
+method is designed to be used inside an example like so:
+
+ describe "when not logged in" do
+ it "should redirect all requests to the login page" do
+ with_default_restful_actions do
+ response.should redirect_to(login_url)
+ end
+ end
+ end
+
+Before we're through with the controller specs, let me point out one more important detail. In
+order to use it_should_redirect_to we have to send the routing inside a block argument
+there so it can be evaluated in the example context instead of the example group, where it
+completely blows up. This methodology is used anywhere routing is referred to in a "skinny",
+example group level spec.
+
+=== View Specs
+
+Now let's move to the view specs!
+
+ describe "/users/form.html.haml" do
+ before(:each) do
+ @user = mock_and_assign(User, :stub => {
+ :name => "foo",
+ :birthday => 1.week.ago,
+ :adult => false
+ })
+ end
+
+ it_should_have_form_for :user
+
+ it_should_allow_editing :user, :name
+ it_should_allow_editing :user, :birthday
+ it_should_allow_editing :user, :adult
+
+ it_should_link_to_show :user
+ it_should_link_to { users_path }
+ end
+
+Like the special stub_index methods in the controller
+specs, the view specs have a shorthand mock and stub helpers: mock_and_assign and
+mock_and_assign_collection. These are well documented so please check them out.
+
+There are also some really nice helper methods that I'd like point out. First is
+it_should_have_form_for. This is a really good convenience wrapper that basically wraps
+the much longer:
+
+ it "should use form_for to generate the proper form action and options" do
+ template.should_receive(:form_for).with(@user)
+ do_render
+ end
+
+Next up is the it_should_allow_editing helper. I love this method the most because it
+really helps DRY up that view spec while at the same time being amazingly unbrittle. Instead of
+creating an expectation for a specific form element, this method creates a generalized expectation
+that there's a form element with the name attribute set in such away that it will
+generate the proper params to use in the controller to edit or create the instance.
+Check out the docs and the source for more information on this. Also check out
+it_should_have_form_element_for which is roughly equivalent for those times when you use
+form_tag instead.
+
+Finally let's look at those it_should_link_to_controller_method helpers.
+These methods (and there's one each for the controller methods
+new, edit, show, and delete) point to instance variables
+which you should be created in the "before" blocks with mock_and_assign. The other is
+it_should_allow_editing which is likewise covered extensively in the documentation and
+I will just point out here that, like it_should_link_to_edit and such, it takes a
+symbol for the name of the instance variable it refers to and additionally takes
+a symbol for the name of the attribute to be edited.
+
+Also note that, when constructing a long form example, instead of defining an instance variable
+for the name of the template and calling render @that_template you can simply call
+do_render which takes the name of the template from the outermost example group where
+it is customarily stated.
+
+=== Model Specs
+
+Skinny Spec adds a matcher for the various ActiveRecord associations. On the example group level
+you call them like:
+
+ it_should_belong_to :manager
+ it_should_have_many :clients
+
+Within an example you can call them on either the class or the instance setup in the
+"before" block. These are equivalent:
+
+ @user.should belong_to(:group)
+ User.should belong_to(:group)
+
+I've also added some very basic validation helpers like it_should_validate_presence_of,
+it_should_validate_uniqueness_of, it_should_not_mass_assign. Please consult
+the documentation for more information.
+
+== Miscellaneous Notes
+
+In the scaffolding, I have used my own idiomatic Rails usage:
+
+* All controller actions which use HTML forms [new, edit, etc] use a shared
+ form and leverage form_for to its fullest by letting it create the appropriate
+ action and options.
+* Some instances where you might expect link_to are button_to. This is to provide a common
+ interface element which can be styled the same instead of a mishmash of links and buttons and
+ inputs everywhere. To take full advantage of this, I usually override many of Rails' default
+ helpers with custom ones that all use actual HTML BUTTON elements which are much
+ easier to style than "button" typed INPUT. I've provided a text file in the
+ "additional" folder of this plugin which you can use in your ApplicationHelper. (I also
+ provide an optional override helper for the label method which uses
+ #titleize instead of humanize for stylistic reasons).
+* Probably more that I can't think of.
+
+== Credits and Thanks
+
+Sections of this code were taken from or inspired by Rick Olsen's
+rspec_on_rails_on_crack[http://github.com/technoweenie/rspec_on_rails_on_crack/tree/master].
+Also thanks and props to Hampton Catlin and Nathan Weizenbaum for the lovely and imminently useable
+Haml and make_resourceful. Also also praises and glory to David Chelimsky and the Rspec crew.
+
+Also thanks to Don Petersen for his suggestions and fixes.
\ No newline at end of file
diff --git a/vendor/plugins/skinny_spec/Rakefile b/vendor/plugins/skinny_spec/Rakefile
new file mode 100644
index 00000000..b0adbc43
--- /dev/null
+++ b/vendor/plugins/skinny_spec/Rakefile
@@ -0,0 +1,11 @@
+require 'rake'
+require 'rake/rdoctask'
+
+desc 'Generate documentation for the Skinny Spec plugin'
+Rake::RDocTask.new(:rdoc) do |rdoc|
+ rdoc.rdoc_dir = 'doc'
+ rdoc.title = 'Skinny Spec'
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.rdoc_files.include('README.rdoc')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
\ No newline at end of file
diff --git a/vendor/plugins/skinny_spec/additional/helper_overrides.txt b/vendor/plugins/skinny_spec/additional/helper_overrides.txt
new file mode 100644
index 00000000..a15ec2d3
--- /dev/null
+++ b/vendor/plugins/skinny_spec/additional/helper_overrides.txt
@@ -0,0 +1,58 @@
+# Please insert these into your ApplicationHelper
+
+# Replacement for Rails' default submit_tag helper
+# using HTML button element rather than HTML input element
+def submit_tag(text, options = {})
+ content_tag :button, text, options.merge(:type => :submit)
+end
+
+# Replacement for Rails' default button_to helper
+# using HTML button element rather than HTML input element
+def button_to(name, options = {}, html_options = {})
+ html_options = html_options.stringify_keys
+ convert_boolean_attributes!(html_options, %w( disabled ))
+
+ method_tag = ''
+ if (method = html_options.delete('method')) && %w{put delete}.include?(method.to_s)
+ method_tag = tag('input', :type => 'hidden', :name => '_method', :value => method.to_s)
+ end
+
+ form_method = method.to_s == 'get' ? 'get' : 'post'
+
+ request_token_tag = ''
+ if form_method == 'post' && protect_against_forgery?
+ request_token_tag = tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token)
+ end
+
+ if confirm = html_options.delete("confirm")
+ html_options["onclick"] = "return #{confirm_javascript_function(confirm)};"
+ end
+
+ url = options.is_a?(String) ? options : self.url_for(options)
+ name ||= url
+
+ html_options.merge!("type" => "submit", "value" => name)
+
+ ""
+end
+
+# Replacement for Rails' default button_to_function helper
+# using HTML button element rather than HTML input element
+def button_to_function(name, *args, &block)
+ html_options = args.extract_options!
+ function = args[0] || ''
+
+ html_options.symbolize_keys!
+ function = update_page(&block) if block_given?
+ content_tag(:button, name, html_options.merge({
+ :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
+ }))
+end
+
+# Replacement for Rails' default label helper
+# using String#titleize rather than String#humanize
+def label(object_name, method, text = nil, options = {})
+ text ||= method.to_s[].titleize
+ super
+end
\ No newline at end of file
diff --git a/vendor/plugins/skinny_spec/generators/skinny_scaffold/skinny_scaffold_generator.rb b/vendor/plugins/skinny_spec/generators/skinny_scaffold/skinny_scaffold_generator.rb
new file mode 100644
index 00000000..7f6f2b25
--- /dev/null
+++ b/vendor/plugins/skinny_spec/generators/skinny_scaffold/skinny_scaffold_generator.rb
@@ -0,0 +1,93 @@
+class SkinnyScaffoldGenerator < Rails::Generator::NamedBase
+ attr_reader :controller_class_path, :controller_file_path, :controller_class_nesting,
+ :controller_class_nesting_depth, :controller_class_name, :controller_underscore_name,
+ :controller_plural_name, :template_language
+ alias_method :controller_file_name, :controller_underscore_name
+ alias_method :controller_singular_name, :controller_file_name
+ alias_method :controller_table_name, :controller_plural_name
+
+ def initialize(runtime_args, runtime_options = {})
+ super
+
+ base_name, @controller_class_path, @controller_file_path, @controller_class_nesting, @controller_class_nesting_depth = extract_modules(@name.pluralize)
+ @controller_class_name_without_nesting, @controller_underscore_name, @controller_plural_name = inflect_names(base_name)
+
+ if @controller_class_nesting.empty?
+ @controller_class_name = @controller_class_name_without_nesting
+ else
+ @controller_class_name = "#{@controller_class_nesting}::#{@controller_class_name_without_nesting}"
+ end
+ end
+
+ def manifest
+ record do |m|
+ # Check for class naming collisions
+ m.class_collisions controller_class_path, "#{controller_class_name}Controller", "#{controller_class_name}Helper"
+ m.class_collisions class_path, "#{class_name}"
+
+ # # Controller, helper, and views directories
+ m.directory File.join('app', 'views', controller_class_path, controller_file_name)
+ m.directory File.join('spec', 'views', controller_class_path, controller_file_name)
+ m.directory File.join('app', 'helpers', controller_class_path)
+ m.directory File.join('spec', 'helpers', controller_class_path)
+ m.directory File.join('app', 'controllers', controller_class_path)
+ m.directory File.join('spec', 'controllers', controller_class_path)
+ m.directory File.join('app', 'models', class_path)
+ m.directory File.join('spec', 'models', class_path)
+
+ # Views
+ @template_language = defined?(Haml) ? "haml" : "erb"
+ %w{index show form}.each do |action|
+ m.template "#{action}.html.#{template_language}",
+ File.join('app/views', controller_class_path, controller_file_name, "#{action}.html.#{template_language}")
+ m.template "#{action}.html_spec.rb",
+ File.join('spec/views', controller_class_path, controller_file_name, "#{action}.html.#{template_language}_spec.rb")
+ end
+ m.template "index_partial.html.#{template_language}",
+ File.join('app/views', controller_class_path, controller_file_name, "_#{file_name}.html.#{template_language}")
+ m.template 'index_partial.html_spec.rb',
+ File.join('spec/views', controller_class_path, controller_file_name, "_#{file_name}.html.#{template_language}_spec.rb")
+
+ # Helper
+ m.template 'helper.rb',
+ File.join('app/helpers', controller_class_path, "#{controller_file_name}_helper.rb")
+ m.template 'helper_spec.rb',
+ File.join('spec/helpers', controller_class_path, "#{controller_file_name}_helper_spec.rb")
+
+ # Controller
+ m.template 'controller.rb',
+ File.join('app/controllers', controller_class_path, "#{controller_file_name}_controller.rb")
+ m.template 'controller_spec.rb',
+ File.join('spec/controllers', controller_class_path, "#{controller_file_name}_controller_spec.rb")
+
+ # Model
+ m.template 'model.rb',
+ File.join('app/models', class_path, "#{file_name}.rb")
+ m.template 'model_spec.rb',
+ File.join('spec/models', class_path, "#{file_name}_spec.rb")
+
+ # Routing
+ m.route_resources controller_file_name
+
+ unless options[:skip_migration]
+ m.migration_template(
+ 'migration.rb', 'db/migrate',
+ :assigns => {
+ :migration_name => "Create#{class_name.pluralize.gsub(/::/, '')}",
+ :attributes => attributes
+ },
+ :migration_file_name => "create_#{file_path.gsub(/\//, '_').pluralize}"
+ )
+ end
+ end
+ end
+
+protected
+ def banner
+ "Usage: #{$0} skinny_scaffold ModelName [field:type, field:type]"
+ end
+
+ def model_name
+ class_name.demodulize
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/controller.rb b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/controller.rb
new file mode 100644
index 00000000..ea9b617d
--- /dev/null
+++ b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/controller.rb
@@ -0,0 +1,105 @@
+class <%= controller_class_name %>Controller < ApplicationController
+ <%- if defined?(Resourceful::Maker) -%>
+ make_resourceful do
+ actions :all
+
+ # Let's get the most use from form_for and share a single form here!
+ response_for :new, :edit do
+ render :template => "<%= plural_name %>/form"
+ end
+
+ response_for :create_fails, :update_fails do
+ flash[:error] = "There was a problem!"
+ render :template => "<%= plural_name %>/form"
+ end
+ end
+ <%- else -%>
+ # GET /<%= table_name %>
+ # GET /<%= table_name %>.xml
+ def index
+ @<%= table_name %> = <%= class_name %>.find(:all)
+
+ respond_to do |format|
+ format.html # index.html.erb
+ format.xml { render :xml => @<%= table_name %> }
+ end
+ end
+
+ # GET /<%= table_name %>/1
+ # GET /<%= table_name %>/1.xml
+ def show
+ @<%= file_name %> = <%= class_name %>.find(params[:id])
+
+ respond_to do |format|
+ format.html # show.html.erb
+ format.xml { render :xml => @<%= file_name %> }
+ end
+ end
+
+ # GET /<%= table_name %>/new
+ # GET /<%= table_name %>/new.xml
+ def new
+ @<%= file_name %> = <%= class_name %>.new
+
+ respond_to do |format|
+ format.html { render :template => "<%= plural_name %>/form" }
+ format.xml { render :xml => @<%= file_name %> }
+ end
+ end
+
+ # GET /<%= table_name %>/1/edit
+ def edit
+ @<%= file_name %> = <%= class_name %>.find(params[:id])
+ render :template => "<%= plural_name %>/form"
+ end
+
+ # POST /<%= table_name %>
+ # POST /<%= table_name %>.xml
+ def create
+ @<%= file_name %> = <%= class_name %>.new(params[:<%= file_name %>])
+
+ respond_to do |format|
+ if @<%= file_name %>.save
+ flash[:notice] = '<%= class_name %> was successfully created.'
+ format.html { redirect_to(@<%= file_name %>) }
+ format.xml { render :xml => @<%= file_name %>, :status => :created, :location => @<%= file_name %> }
+ else
+ flash.now[:error] = '<%= class_name %> could not be created.'
+ format.html { render :template => "<%= plural_name %>/form" }
+ format.xml { render :xml => @<%= file_name %>.errors, :status => :unprocessable_entity }
+ end
+ end
+ end
+
+ # PUT /<%= table_name %>/1
+ # PUT /<%= table_name %>/1.xml
+ def update
+ @<%= file_name %> = <%= class_name %>.find(params[:id])
+
+ respond_to do |format|
+ if @<%= file_name %>.update_attributes(params[:<%= file_name %>])
+ flash[:notice] = '<%= class_name %> was successfully updated.'
+ format.html { redirect_to(@<%= file_name %>) }
+ format.xml { head :ok }
+ else
+ flash.now[:error] = '<%= class_name %> could not be created.'
+ format.html { render :template => "<%= plural_name %>/form" }
+ format.xml { render :xml => @<%= file_name %>.errors, :status => :unprocessable_entity }
+ end
+ end
+ end
+
+ # DELETE /<%= table_name %>/1
+ # DELETE /<%= table_name %>/1.xml
+ def destroy
+ @<%= file_name %> = <%= class_name %>.find(params[:id])
+ @<%= file_name %>.destroy
+
+ respond_to do |format|
+ flash[:notice] = '<%= class_name %> was successfully deleted.'
+ format.html { redirect_to(<%= table_name %>_url) }
+ format.xml { head :ok }
+ end
+ end
+ <%- end -%>
+end
diff --git a/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/controller_spec.rb b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/controller_spec.rb
new file mode 100644
index 00000000..dc6d5599
--- /dev/null
+++ b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/controller_spec.rb
@@ -0,0 +1,116 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+
+describe <%= controller_class_name %>Controller do
+ def valid_attributes(args = {})
+ {
+<% if attributes.empty? -%>
+ # Add valid attributes for the your params[:<%= singular_name %>] here!
+<% else -%>
+ <%- attributes.each_with_index do |attribute, index| -%>
+ <%- case attribute.type -%>
+ <%- when :string, :text -%>
+ "<%= attribute.name %>" => "foo"<%= index < attributes.size - 1 ? "," : "" %>
+ <%- when :integer, :float, :decimal -%>
+ "<%= attribute.name %>" => 815<%= index < attributes.size - 1 ? "," : "" %>
+ <%- when :boolean -%>
+ "<%= attribute.name %>" => false<%= index < attributes.size - 1 ? "," : "" %>
+ <%- when :date, :datetime, :time, :timestamp -%>
+ "<%= attribute.name %>" => 1.week.ago<%= index < attributes.size - 1 ? "," : "" %>
+ <%- else -%>
+ "<%= attribute.name %>" => nil<%= index < attributes.size - 1 ? "," : "" %> # Could not determine valid attribute
+ <%- end -%>
+ <%- end -%>
+<% end -%>
+ }.merge(args)
+ end
+
+ describe "GET :index" do
+ before(:each) do
+ @<%= plural_name %> = stub_index(<%= class_name %>)
+ end
+
+ it_should_find_and_assign :<%= plural_name %>
+ it_should_render_template "index"
+ end
+
+ describe "GET :new" do
+ before(:each) do
+ @<%= singular_name %> = stub_new(<%= class_name %>)
+ end
+
+ it_should_initialize_and_assign :<%= singular_name %>
+ it_should_render_template "form"
+ end
+
+ describe "POST :create" do
+ describe "when successful" do
+ before(:each) do
+ @<%= singular_name %> = stub_create(<%= class_name %>)
+ end
+
+ it_should_initialize_and_save :<%= singular_name %>
+ it_should_set_flash :notice
+ it_should_redirect_to { <%= singular_name %>_url(@<%= singular_name %>) }
+ end
+
+ describe "when unsuccessful" do
+ before(:each) do
+ @<%= singular_name %> = stub_create(<%= class_name %>, :return => :false)
+ end
+
+ it_should_initialize_and_assign :<%= singular_name %>
+ it_should_set_flash :error
+ it_should_render_template "form"
+ end
+ end
+
+ describe "GET :show" do
+ before(:each) do
+ @<%= singular_name %> = stub_show(<%= class_name %>)
+ end
+
+ it_should_find_and_assign :<%= singular_name %>
+ it_should_render_template "show"
+ end
+
+ describe "GET :edit" do
+ before(:each) do
+ @<%= singular_name %> = stub_edit(<%= class_name %>)
+ end
+
+ it_should_find_and_assign :<%= singular_name %>
+ it_should_render_template "form"
+ end
+
+ describe "PUT :update" do
+ describe "when successful" do
+ before(:each) do
+ @<%= singular_name %> = stub_update(<%= class_name %>)
+ end
+
+ it_should_find_and_update :<%= singular_name %>
+ it_should_set_flash :notice
+ it_should_redirect_to { <%= singular_name %>_url(@<%= singular_name %>) }
+ end
+
+ describe "when unsuccessful" do
+ before(:each) do
+ @<%= singular_name %> = stub_update(<%= class_name %>, :return => :false)
+ end
+
+ it_should_find_and_assign :<%= singular_name %>
+ it_should_set_flash :error
+ it_should_render_template "form"
+ end
+ end
+
+ describe "DELETE :destroy" do
+ before(:each) do
+ @<%= singular_name %> = stub_destroy(<%= class_name %>)
+ end
+
+ it_should_find_and_destroy :<%= singular_name %>
+ it_should_set_flash :notice
+ it_should_redirect_to { <%= plural_name %>_url }
+ end
+end
diff --git a/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/form.html.erb b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/form.html.erb
new file mode 100644
index 00000000..ac30dc1b
--- /dev/null
+++ b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/form.html.erb
@@ -0,0 +1,25 @@
+
diff --git a/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/show.html.haml b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/show.html.haml
new file mode 100644
index 00000000..d8afe80a
--- /dev/null
+++ b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/show.html.haml
@@ -0,0 +1,13 @@
+%h1== Show #{<%= model_name %>}
+<% if attributes.blank? -%>
+%p Add your customized markup here, please!
+<% else -%>
+ <%- attributes.each do |attribute| -%>
+%p
+ %label <%= attribute.name.titleize %>:
+ =h @<%= singular_name %>.<%= attribute.name %>
+ <%- end -%>
+<% end -%>
+#commands
+ = button_to "Edit", edit_<%= singular_name %>_path(@<%= singular_name %>), :method => "get"
+ = button_to "Back to List", <%= plural_name %>_path, :method => "get"
\ No newline at end of file
diff --git a/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/show.html_spec.rb b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/show.html_spec.rb
new file mode 100644
index 00000000..1c6c7aa8
--- /dev/null
+++ b/vendor/plugins/skinny_spec/generators/skinny_scaffold/templates/show.html_spec.rb
@@ -0,0 +1,32 @@
+require File.dirname(__FILE__) + '<%= '/..' * controller_class_nesting_depth %>/../../spec_helper'
+
+describe "<%= File.join(controller_class_path, controller_singular_name) %>/show.html.<%= template_language %>" do
+ before(:each) do
+<% if attributes.blank? -%>
+ @<%= singular_name %> = mock_and_assign(<%= model_name %>)
+<% else -%>
+ @<%= singular_name %> = mock_and_assign(<%= model_name %>, :stub => {
+ <%- attributes.each_with_index do |attribute, index| -%>
+ <%- case attribute.type -%>
+ <%- when :string, :text -%>
+ :<%= attribute.name %> => "foo"<%= index < attributes.size - 1 ? "," : "" %>
+ <%- when :integer, :float, :decimal -%>
+ :<%= attribute.name %> => 815<%= index < attributes.size - 1 ? "," : "" %>
+ <%- when :boolean -%>
+ :<%= attribute.name %> => false<%= index < attributes.size - 1 ? "," : "" %>
+ <%- when :date, :datetime, :time, :timestamp -%>
+ :<%= attribute.name %> => 1.week.ago<%= index < attributes.size - 1 ? "," : "" %>
+ <%- else -%>
+ :<%= attribute.name %> => nil<%= index < attributes.size - 1 ? "," : "" %>
+ <%- end -%>
+ <%- end -%>
+ })
+<% end -%>
+ end
+
+ # Add your specs here, please! But remember not to make them brittle
+ # by specing specing specific HTML elements and classes.
+
+ it_should_link_to_edit :<%= singular_name %>
+ it_should_link_to { <%= plural_name %>_path }
+end
\ No newline at end of file
diff --git a/vendor/plugins/skinny_spec/init.rb b/vendor/plugins/skinny_spec/init.rb
new file mode 100644
index 00000000..4906d276
--- /dev/null
+++ b/vendor/plugins/skinny_spec/init.rb
@@ -0,0 +1,3 @@
+if RAILS_ENV == "test"
+ require "skinny_spec"
+end
\ No newline at end of file
diff --git a/vendor/plugins/skinny_spec/lib/lucky_sneaks/common_spec_helpers.rb b/vendor/plugins/skinny_spec/lib/lucky_sneaks/common_spec_helpers.rb
new file mode 100644
index 00000000..2506accb
--- /dev/null
+++ b/vendor/plugins/skinny_spec/lib/lucky_sneaks/common_spec_helpers.rb
@@ -0,0 +1,46 @@
+module LuckySneaks
+ # These methods are mostly just called internally by various other spec helper
+ # methods but you're welcome to use them as needed in your own specs.
+ module CommonSpecHelpers
+ # Returns class for the specified name. Example:
+ #
+ # class_for("foo") # => Foo
+ def class_for(name)
+ name.to_s.constantize
+ rescue NameError
+ name.to_s.classify.constantize
+ # Let any other error rise!
+ end
+
+ # Returns instance variable for the specified name. Example:
+ #
+ # instance_for("foo") # => @foo
+ def instance_for(name)
+ instance_variable_get("@#{name.to_s.underscore}")
+ end
+
+ # Wraps a matcher that checks if the receiver contains an A element (link)
+ # whose href attribute is set to the specified path.
+ def have_link_to(path)
+ have_tag("a[href='#{path}']")
+ end
+
+ # Returns dummy value for specified attribute based on the datatype expected for that
+ # attribute.
+ def dummy_value_for(instance, attribute)
+ if datatype = instance.column_for_attribute(attribute)
+ actual = instance.send(attribute)
+ case datatype.type
+ when :string, :text
+ actual == "foo" ? "bar" : "food"
+ when :integer, :float, :decimal
+ actual == 108 ? 815 : 108
+ when :boolean
+ actual ? false : true
+ when :date, :datetime, :time, :timestamp
+ actual == 1.week.ago ? 2.years.ago : 1.week.ago
+ end
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/skinny_spec/lib/lucky_sneaks/controller_request_helpers.rb b/vendor/plugins/skinny_spec/lib/lucky_sneaks/controller_request_helpers.rb
new file mode 100644
index 00000000..a7cd5b94
--- /dev/null
+++ b/vendor/plugins/skinny_spec/lib/lucky_sneaks/controller_request_helpers.rb
@@ -0,0 +1,67 @@
+module LuckySneaks
+ module ControllerRequestHelpers # :nodoc:
+ def self.included(base)
+ base.extend ExampleGroupMethods
+ end
+
+ private
+ def define_implicit_request(method)
+ @controller_method = method
+ @implicit_request = case method
+ when :index, :new, :show, :edit
+ proc { get method, params }
+ when :create
+ proc { post :create, params }
+ when :update
+ proc { put :update, params }
+ when :destroy
+ proc { put :destroy, params }
+ end
+ end
+
+ def eval_request
+ instance_eval &self.class.instance_variable_get("@the_request")
+ rescue ArgumentError # missing block
+ try_shared_request_definition
+ end
+ alias do_request eval_request
+
+ def try_shared_request_definition
+ shared_request
+ rescue NameError
+ if @implicit_request
+ try_implicit_request
+ else
+ error_message = "Could not determine request definition for 'describe' context. "
+ error_message << "Please use define_request or define a shared_request."
+ raise ArgumentError, error_message
+ end
+ end
+
+ def try_implicit_request
+ @implicit_request.call
+ end
+
+ def get_response(&block)
+ eval_request
+ block.call(response) if block_given?
+ response
+ end
+
+ module ExampleGroupMethods
+ # Defines a request at the example group ("describe") level to be evaluated in the examples. Example:
+ #
+ # define_request { get :index, params }
+ #
+ # Note: The following methods all define implicit requests: stub_index, stub_new,
+ # stub_create, stub_show, stub_edit, stub_update, and
+ # stub_destroy. Using them in your before blocks will allow you to forego
+ # defining explicit requests using define_request. See
+ # LuckySneaks::ControllerStubHelpers for information on these methods.
+ def define_request(&block)
+ raise ArgumentError, "Must provide a block to define a request!" unless block_given?
+ @the_request = block
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/skinny_spec/lib/lucky_sneaks/controller_spec_helpers.rb b/vendor/plugins/skinny_spec/lib/lucky_sneaks/controller_spec_helpers.rb
new file mode 100644
index 00000000..43210f93
--- /dev/null
+++ b/vendor/plugins/skinny_spec/lib/lucky_sneaks/controller_spec_helpers.rb
@@ -0,0 +1,435 @@
+$:.unshift File.join(File.dirname(__FILE__), "..")
+require "skinny_spec"
+
+module LuckySneaks
+ module ControllerSpecHelpers # :nodoc:
+ include LuckySneaks::CommonSpecHelpers
+ include LuckySneaks::ControllerRequestHelpers
+ include LuckySneaks::ControllerStubHelpers
+
+ def self.included(base)
+ base.extend ExampleGroupMethods
+ base.extend ControllerRequestHelpers::ExampleGroupMethods
+ end
+
+ # Evaluates the specified block for each of the RESTful controller methods.
+ # This is useful to spec that all controller methods redirect when no user is
+ # logged in.
+ def with_default_restful_actions(params = {}, &block)
+ {
+ :get => :index,
+ :get => :new,
+ :post => :create
+ }.each do |method_id, message|
+ self.send method_id, message, params
+ block.call
+ end
+ {
+ :get => :edit,
+ :put => :update,
+ :delete => :destroy
+ }.each do |method_id, message|
+ if params[:before]
+ params.delete(:before).call
+ end
+ # Presuming any id will do
+ self.send method_id, message, params.merge(:id => 1)
+ block.call
+ end
+ end
+
+ private
+ def create_ar_class_expectation(name, method, argument = nil, options = {})
+ args = []
+ if [:create, :update].include?(@controller_method)
+ args << (argument.nil? ? valid_attributes : argument)
+ else
+ args << argument unless argument.nil?
+ end
+ args << options unless options.empty?
+ if args.empty?
+ return_value = class_for(name).send(method)
+ class_for(name).should_receive(method).and_return(return_value)
+ else
+ return_value = class_for(name).send(method, *args)
+ class_for(name).should_receive(method).with(*args).and_return(return_value)
+ end
+ end
+
+ def create_positive_ar_instance_expectation(name, method, *args)
+ instance = instance_for(name)
+ if args.empty?
+ return_value = instance.send(method)
+ instance.should_receive(method).and_return(true)
+ else
+ return_value = instance.send(method, *args)
+ instance.should_receive(method).with(*args).and_return(true)
+ end
+ end
+
+ # These methods are designed to be used at the example group [read: "describe"] level
+ # to simplify and DRY up common expectations.
+ module ExampleGroupMethods
+ # Creates an expectation that the controller method calls ActiveRecord::Base.find.
+ # Examples:
+ #
+ # it_should_find :foos # => Foo.should_receive(:find).with(:all)
+ # it_should_find :foos, :all # An explicit version of the above
+ # it_should_find :foos, :conditions => {:foo => "bar"} # => Foo.should_receive(:find).with(:all, :conditions => {"foo" => "bar"}
+ # it_should_find :foo # => Foo.should_recieve(:find).with(@foo.id.to_s)
+ # it_should_find :foo, :params => "id" # => Foo.should_receive(:find).with(params[:id].to_s)
+ # it_should_find :foo, 2 # => Foo.should_receive(:find).with("2")
+ #
+ # Note: All params (key and value) will be strings if they come from a form element and are handled
+ # internally with this expectation.
+ def it_should_find(name, *args)
+ name_string = name.to_s
+ name_message = if name_string == name_string.singularize
+ "a #{name}"
+ else
+ name
+ end
+ it "should find #{name_message}" do
+ options = args.extract_options!
+ # Blech!
+ argument = if param = params[options.delete(:params)]
+ param.to_s
+ else
+ if args.first
+ args.first
+ elsif (instance = instance_variable_get("@#{name}")).is_a?(ActiveRecord::Base)
+ instance.id.to_s
+ else
+ :all
+ end
+ end
+ create_ar_class_expectation name, :find, argument, options
+ eval_request
+ end
+ end
+
+ # Creates an expectation that the controller method calls ActiveRecord::Base.new.
+ # Takes optional params for the initialization arguments. Example
+ #
+ # it_should_initialize :foo # => Foo.should_receive(:new)
+ # it_should_initialize :foo, :params => :bar # => Foo.should_receive(:new).with(params[:bar])
+ # it_should_initialize :foo, :bar => "baz" # => Foo.should_receive(:new).with(:bar => "baz")
+ def it_should_initialize(name, options = {})
+ it "should initialize a #{name}" do
+ create_ar_class_expectation name, :new, params[options.delete(:params)], options
+ eval_request
+ end
+ end
+
+ # Creates an expectation that the controller method calls ActiveRecord::Base#save on the
+ # named instance. Example:
+ #
+ # it_should_save :foo # => @foo.should_receive(:save).and_return(true)
+ #
+ # Note: This helper should not be used to spec a failed save call. Use it_should_assign
+ # instead, to verify that the instance is captured in an instance variable for the inevitable re-rendering
+ # of the form template.
+ def it_should_save(name)
+ it "should save the #{name}" do
+ create_positive_ar_instance_expectation name, :save
+ eval_request
+ end
+ end
+
+ # Creates an expectation that the controller method calls ActiveRecord::Base#update_attributes
+ # on the named instance. Takes optional argument for params to specify in the
+ # expectation. Examples:
+ #
+ # it_should_update :foo # => @foo.should_receive(:update_attributes).and_return(true)
+ # it_should_update :foo, :params => :bar # => @foo.should_receive(:update_attributes).with(params[:bar]).and_return(true)
+ #
+ # Note: This helper should not be used to spec a failed update_attributes call. Use
+ # it_should_assign instead, to verify that the instance is captured in an instance variable
+ # for the inevitable re-rendering of the form template.
+ def it_should_update(name, options = {})
+ it "should update the #{name}" do
+ create_positive_ar_instance_expectation name, :update_attributes, params[options[:params]]
+ eval_request
+ end
+ end
+
+ # Creates an expectation that the controller method calls ActiveRecord::Base#destroy on the named
+ # instance. Example:
+ #
+ # it_should_destroy :foo # => @foo.should_receive(:destroy).and_return(true)
+ #
+ # Note: This helper should not be used to spec a failed destroy call. Use
+ # it_should_assign instead, if you need to verify that the instance is captured in an instance
+ # variable if it is re-rendered somehow. This is probably a really edge use case.
+ def it_should_destroy(name, options = {})
+ it "should delete the #{name}" do
+ create_positive_ar_instance_expectation name, :destroy
+ eval_request
+ end
+ end
+
+ # Creates expectation[s] that the controller method should assign the specified
+ # instance variables along with any specified values. Examples:
+ #
+ # it_should_assign :foo # => assigns[:foo].should == @foo
+ # it_should_assign :foo => "bar" # => assigns[:foo].should == "bar"
+ # it_should_assign :foo => :nil # => assigns[:foo].should be_nil
+ # it_should_assign :foo => :not_nil # => assigns[:foo].should_not be_nil
+ # it_should_assign :foo => :undefined # => controller.send(:instance_variables).should_not include("@foo")
+ #
+ # Very special thanks to Rick Olsen for the basis of this code. The only reason I even
+ # redefine it at all is purely an aesthetic choice for specs like "it should foo"
+ # over ones like "it foos".
+ def it_should_assign(*names)
+ names.each do |name|
+ if name.is_a?(Symbol)
+ it_should_assign name => name
+ elsif name.is_a?(Hash)
+ name.each do |key, value|
+ it_should_assign_instance_variable key, value
+ end
+ end
+ end
+ end
+
+ # Wraps the separate expectations it_should_find and it_should_assign
+ # for simple cases. If you need more control over the parameters of the find, this
+ # isn't the right helper method and you should write out the two expectations separately.
+ def it_should_find_and_assign(*names)
+ names.each do |name|
+ it_should_find name
+ it_should_assign name
+ end
+ end
+
+ # Wraps the separate expectations it_should_initialize and it_should_assign
+ # for simple cases. If you need more control over the parameters of the initialization, this
+ # isn't the right helper method and you should write out the two expectations separately.
+ #
+ # Note: This method is used for controller methods like new, where the instance
+ # is initialized without being saved (this includes failed create requests).
+ # If you want to spec that the controller method successfully saves the instance,
+ # please use it_should_initialize_and_save.
+ def it_should_initialize_and_assign(*names)
+ names.each do |name|
+ it_should_initialize name
+ it_should_assign name
+ end
+ end
+
+ # Wraps the separate expectations it_should_initialize and it_should_save
+ # for simple cases. If you need more control over the parameters of the initialization, this
+ # isn't the right helper method and you should write out the two expectations separately.
+ #
+ # Note: This method is used for controller methods like create, where the instance
+ # is initialized and successfully saved. If you want to spec that the instance is created
+ # but not saved, just use it_should_initialize_and_assign.
+ def it_should_initialize_and_save(*names)
+ names.each do |name|
+ it_should_initialize name
+ it_should_save name
+ end
+ end
+
+ # Wraps the separate expectations it_should_find and it_should_update
+ # for simple cases. If you need more control over the parameters of the find, this
+ # isn't the right helper method and you should write out the two expectations separately.
+ #
+ # Note: This method is used for controller methods like update, where the
+ # instance is loaded from the database and successfully saved. If you want to spec that the
+ # instance is found but not saved, just use it_should_find_and_assign.
+ def it_should_find_and_update(*names)
+ names.each do |name|
+ it_should_find name
+ it_should_update name
+ end
+ end
+
+ # Wraps the separate expectations it_should_find and it_should_destroy
+ # for simple cases. If you need more control over the parameters of the find, this
+ # isn't the right helper method and you should write out the two expectations separately.
+ def it_should_find_and_destroy(*names)
+ names.each do |name|
+ it_should_find name
+ it_should_destroy name
+ end
+ end
+
+ # Creates an expectation that the specified collection (flash or session)
+ # contains the specified key and value. To specify that the collection should be set
+ # to nil, specify the value as :nil instead.
+ def it_should_set(collection, key, value = nil, &block)
+ it "should set #{collection}[:#{key}]" do
+ # Allow flash.now[:foo] to remain in the flash
+ flash.stub!(:sweep) if collection == :flash
+ eval_request
+ if value
+ if value == :nil
+ self.send(collection)[key].should be_nil
+ else
+ self.send(collection)[key].should == value
+ end
+ elsif block_given?
+ self.send(collection)[key].should == block.call
+ else
+ self.send(collection)[key].should_not be_nil
+ end
+ end
+ end
+
+ # Wraps it_should_set :flash. To specify that the collection should be set
+ # to nil, specify the value as :nil instead.
+ def it_should_set_flash(name, value = nil, &block)
+ it_should_set :flash, name, value, &block
+ end
+
+ # Wraps it_should_set :session. To specify that the collection should be set
+ # to nil, specify the value as :nil instead.
+ def it_should_set_session(name, value = nil, &block)
+ it_should_set :session, name, value, &block
+ end
+
+ # Wraps the various it_should_render_foo methods:
+ # it_should_render_template, it_should_render_partial,
+ # it_should_render_xml, it_should_render_json,
+ # it_should_render_formatted, and it_should_render_nothing.
+ def it_should_render(render_method, *args)
+ send "it_should_render_#{render_method}", *args
+ end
+
+ # Creates an expectation that the controller method renders the specified template.
+ # Accepts the following options which create additional expectations.
+ #
+ # :content_type:: Creates an expectation that the Content-Type header for the response
+ # matches the one specified
+ # :status:: Creates an expectation that the HTTP status for the response
+ # matches the one specified
+ def it_should_render_template(name, options = {})
+ create_status_expectation options[:status] if options[:status]
+ it "should render '#{name}' template" do
+ eval_request
+ response.should render_template(name)
+ end
+ create_content_type_expectation(options[:content_type]) if options[:content_type]
+ end
+
+ # Creates an expectation that the controller method renders the specified partial.
+ # Accepts the following options which create additional expectations.
+ #
+ # :content_type:: Creates an expectation that the Content-Type header for the response
+ # matches the one specified
+ # :status:: Creates an expectation that the HTTP status for the response
+ # matches the one specified
+ def it_should_render_partial(name, options = {})
+ create_status_expectation options[:status] if options[:status]
+ it "should render '#{name}' partial" do
+ controller.expect_render(:partial => name)
+ eval_request
+ end
+ create_content_type_expectation(options[:content_type]) if options[:content_type]
+ end
+
+ # Creates an expectation that the controller method renders the specified record via to_xml.
+ # Accepts the following options which create additional expectations.
+ #
+ # :content_type:: Creates an expectation that the Content-Type header for the response
+ # matches the one specified
+ # :status:: Creates an expectation that the HTTP status for the response
+ # matches the one specified
+ def it_should_render_xml(record = nil, options = {}, &block)
+ it_should_render_formatted :xml, record, options, &block
+ end
+
+ # Creates an expectation that the controller method renders the specified record via to_json.
+ # Accepts the following options which create additional expectations.
+ #
+ # :content_type:: Creates an expectation that the Content-Type header for the response
+ # matches the one specified
+ # :status:: Creates an expectation that the HTTP status for the response
+ # matches the one specified
+ def it_should_render_json(record = nil, options = {}, &block)
+ it_should_render_formatted :json, record, options, &block
+ end
+
+ # Called internally by it_should_render_xml and it_should_render_json
+ # but should not really be called much externally unless you have defined your own
+ # formats with a matching to_foo method on the record.
+ #
+ # Which is probably never.
+ def it_should_render_formatted(format, record = nil, options = {}, &block)
+ create_status_expectation options[:status] if options[:status]
+ it "should render #{format.inspect}" do
+ if record.is_a?(Hash)
+ options = record
+ record = nil
+ end
+ if record.nil? && !block_given?
+ raise ArgumentError, "it_should_render must be called with either a record or a block and neither was given."
+ else
+ if record
+ pieces = record.to_s.split(".")
+ record = instance_variable_get("@#{pieces.shift}")
+ record = record.send(pieces.shift) until pieces.empty?
+ end
+ block ||= proc { record.send("to_#{format}") }
+ get_response do |response|
+ response.should have_text(block.call)
+ end
+ end
+ end
+ create_content_type_expectation(options[:content_type]) if options[:content_type]
+ end
+
+ # Creates an expectation that the controller method returns a blank page. You'd already
+ # know when and why to use this so I'm not typing it out.
+ def it_should_render_nothing(options = {})
+ create_status_expectation options[:status] if options[:status]
+ it "should render :nothing" do
+ get_response do |response|
+ response.body.strip.should be_blank
+ end
+ end
+ end
+
+ # Creates an expectation that the controller method redirects to the specified destination. Example:
+ #
+ # it_should_redirect_to { foos_url }
+ #
+ # Note: This method takes a block to evaluate the route in the example
+ # context rather than the example group context.
+ def it_should_redirect_to(hint = nil, &route)
+ if hint.nil? && route.respond_to?(:to_ruby)
+ hint = route.to_ruby.gsub(/(^proc \{)|(\}$)/, '').strip
+ end
+ it "should redirect to #{(hint || route)}" do
+ eval_request
+ response.should redirect_to(instance_eval(&route))
+ end
+ end
+
+ private
+ def it_should_assign_instance_variable(name, value)
+ expectation_proc = case value
+ when :nil
+ proc { assigns[name].should be_nil }
+ when :not_nil
+ proc { assigns[name].should_not be_nil }
+ when :undefined
+ proc { controller.send(:instance_variables).should_not include("@{name}") }
+ when Symbol
+ if (instance_variable = instance_variable_get("@#{name}")).nil?
+ proc { assigns[name].should_not be_nil }
+ else
+ proc { assigns[name].should == instance_variable }
+ end
+ else
+ proc { assigns[name].should == value }
+ end
+ it "should #{value == :nil ? 'not ' : ''}assign @#{name}" do
+ eval_request
+ instance_eval &expectation_proc
+ end
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/skinny_spec/lib/lucky_sneaks/controller_stub_helpers.rb b/vendor/plugins/skinny_spec/lib/lucky_sneaks/controller_stub_helpers.rb
new file mode 100644
index 00000000..950c7a17
--- /dev/null
+++ b/vendor/plugins/skinny_spec/lib/lucky_sneaks/controller_stub_helpers.rb
@@ -0,0 +1,199 @@
+module LuckySneaks # :nodoc:
+ # These methods are designed to be used in your example before blocks to accomplish
+ # a whole lot of functionality with just a tiny bit of effort. The methods which correspond
+ # to the controller methods perform the most duties as they create the mock_model instances,
+ # stub out all the necessary methods, and also create implicit requests to DRY up your spec
+ # file even more. You are encouraged to use these methods to setup the basic calls for your
+ # resources and only resort to the other methods when mocking and stubbing secondary objects
+ # and calls.
+ #
+ # Both stub_create and stub_update benefit from having a valid_attributes
+ # method defined at the top level of your example groups, ie the top-most "describe" block
+ # of the spec file. If you did not generate your specs with skinny_scaffold or
+ # skinny_resourceful generators, you can simply write a method like the following
+ # for yourself:
+ #
+ # def valid_attributes
+ # {
+ # "foo" => "bar",
+ # "baz" => "quux"
+ # }
+ # end
+ #
+ # Note this method employs strings as both the key and values to best replicate the way
+ # they are used in actual controllers where the params will come from a form.
+ module ControllerStubHelpers
+ # Stubs out find :all and returns a collection of mock_model
+ # instances of that class. Accepts the following options:
+ #
+ # :format:: Format of the request. Used to only add to_xml and
+ # to_json when actually needed.
+ # :size:: Number of instances to return in the result. Default is 3.
+ # :stub:: Additional methods to stub on the instances
+ #
+ # Any additional options will be passed as arguments to the class find.
+ # You will want to make sure to pass those arguments to the it_should_find spec as well.
+ def stub_find_all(klass, options = {})
+ returning(Array.new(options[:size] || 3){mock_model(klass)}) do |collection|
+ stub_out klass, options.delete(:stub)
+ if format = options.delete(:format)
+ stub_formatted collection, format
+ params[:format] = format
+ end
+ if options.empty?
+ klass.stub!(:find).with(:all).and_return(collection)
+ else
+ klass.stub!(:find).with(:all, options).and_return(collection)
+ end
+ end
+ end
+
+ # Alias for stub_find_all but additionally defines an implicit request get :index.
+ def stub_index(klass, options = {})
+ define_implicit_request :index
+ stub_find_all klass, options
+ end
+
+ # Stubs out new method and returns a mock_model instance marked as a new record.
+ # Accepts the following options:
+ #
+ # :format:: Format of the request. Used to only add to_xml and
+ # to_json when actually needed.
+ # :stub:: Additional methods to stub on the instances
+ #
+ # It also accepts some options used to stub out save with a specified true
+ # or false but you should be using stub_create in that case.
+ def stub_initialize(klass, options = {})
+ returning mock_model(klass) do |member|
+ stub_out member, options.delete(:stub)
+ if format = options[:format]
+ stub_formatted member, format
+ params[:format] = format
+ end
+ klass.stub!(:new).and_return(member)
+ if options[:stub_save]
+ stub_ar_method member, :save, options[:return]
+ klass.stub!(:new).with(params[options[:params]]).and_return(member)
+ else
+ member.stub!(:new_record?).and_return(true)
+ member.stub!(:id).and_return(nil)
+ end
+ end
+ end
+
+ # Alias for stub_initialize which additionally defines an implicit request get :new.
+ def stub_new(klass, options = {})
+ define_implicit_request :new
+ stub_initialize klass, options
+ end
+
+ # Alias for stub_initialize which additionally defines an implicit request post :create.
+ #
+ # Note: If stub_create is provided an optional :params hash
+ # or the method valid_attributes is defined within its scope,
+ # those params will be added to the example's params object. If neither
+ # are provided an ArgumentError will be raised.
+ def stub_create(klass, options = {})
+ define_implicit_request :create
+ if options[:params].nil?
+ if self.respond_to?(:valid_attributes)
+ params[klass.name.underscore.to_sym] = valid_attributes
+ options[:params] = valid_attributes
+ else
+ error_message = "Params for creating #{klass} could not be determined. "
+ error_message << "Please define valid_attributes method in the base 'describe' block "
+ error_message << "or manually set params in the before block."
+ raise ArgumentError, error_message
+ end
+ end
+ stub_initialize klass, options.merge(:stub_save => true)
+ end
+
+ # Stubs out find and returns a single mock_model
+ # instances of that class. Accepts the following options:
+ #
+ # :format:: Format of the request. Used to only add to_xml and
+ # to_json when actually needed.
+ # :stub:: Additional methods to stub on the instances
+ #
+ # Any additional options will be passed as arguments to find.You will want
+ # to make sure to pass those arguments to the it_should_find spec as well.
+ #
+ # Note: The option :stub_ar is used internally by stub_update
+ # and stub_destroy. If you need to stub update_attributes or
+ # destroy you should be using the aforementioned methods instead.
+ def stub_find_one(klass, options = {})
+ returning mock_model(klass) do |member|
+ stub_out member, options.delete(:stub)
+ if options[:format]
+ stub_formatted member, options[:format]
+ params[:format] = options[:format]
+ end
+ if options[:current_object]
+ params[:id] = member.id
+ if options[:stub_ar]
+ stub_ar_method member, options[:stub_ar], options[:return]
+ end
+ end
+ klass.stub!(:find).with(member.id.to_s).and_return(member)
+ end
+ end
+
+ # Alias for stub_find_one which additionally defines an implicit request get :show.
+ def stub_show(klass, options = {})
+ define_implicit_request :show
+ stub_find_one klass, options.merge(:current_object => true)
+ end
+
+ # Alias for stub_find_one which additionally defines an implicit request get :edit.
+ def stub_edit(klass, options = {})
+ define_implicit_request :edit
+ stub_find_one klass, options.merge(:current_object => true)
+ end
+
+ # Alias for stub_find_one which additionally defines an implicit request put :update
+ # and stubs out the update_attribute method on the instance as well.
+ #
+ # Note: If stub_update is provided an optional :params hash
+ # or the method valid_attributes is defined within its scope,
+ # those params will be added to the example's params object. If neither
+ # are provided an ArgumentError will be raised.
+ def stub_update(klass, options = {})
+ define_implicit_request :update
+ stub_find_one klass, options.merge(:current_object => true, :stub_ar => :update_attributes)
+ end
+
+ # Alias for stub_find_one which additionally defines an implicit request delete :destroy
+ # and stubs out the destroy method on the instance as well.
+ def stub_destroy(klass, options = {})
+ define_implicit_request :destroy
+ stub_find_one klass, options.merge(:current_object => true, :stub_ar => :destroy)
+ end
+
+ # Stubs to_xml or to_json respectively based on format argument.
+ def stub_formatted(object, format)
+ return unless format
+ object.stub!("to_#{format}").and_return("#{object.class} formatted as #{format}")
+ end
+
+ private
+ # Stubs out multiple methods. You shouldn't be calling this yourself and if you do
+ # you should be able to understand the code yourself, right?
+ def stub_out(object, stubs = {})
+ return if stubs.nil?
+ stubs.each do |method, value|
+ if value
+ object.stub!(method).and_return(value)
+ else
+ object.stub!(method)
+ end
+ end
+ end
+
+ # Stubs out ActiveRecord::Base methods like #save, #update_attributes, etc
+ # that may be called on a found or instantiated mock_model instance.
+ def stub_ar_method(object, method, return_value)
+ object.stub!(method).and_return(return_value ? false : true)
+ end
+ end
+end
diff --git a/vendor/plugins/skinny_spec/lib/lucky_sneaks/model_spec_helpers.rb b/vendor/plugins/skinny_spec/lib/lucky_sneaks/model_spec_helpers.rb
new file mode 100644
index 00000000..4d65f276
--- /dev/null
+++ b/vendor/plugins/skinny_spec/lib/lucky_sneaks/model_spec_helpers.rb
@@ -0,0 +1,326 @@
+$:.unshift File.join(File.dirname(__FILE__), "..")
+require "skinny_spec"
+
+module LuckySneaks
+ # These methods are designed to be used in your example [read: "it"] blocks
+ # to make your model specs a little more DRY. You might also be interested
+ # in checking out the example block [read: "describe"] level versions in of these
+ # methods which can DRY things up even more:
+ # LuckySneaks::ModelSpecHelpers::ExampleGroupLevelMethods
+ #
+ # Note: The validation matchers are only meant to be used for simple validation checking
+ # not as a one-size-fits-all solution.
+ module ModelSpecHelpers
+ include LuckySneaks::CommonSpecHelpers
+
+ def self.included(base) # :nodoc:
+ base.extend ExampleGroupLevelMethods
+ end
+
+ class AssociationMatcher # :nodoc:
+ def initialize(associated, macro)
+ @associated = associated
+ @macro = macro
+ @options = {}
+ end
+
+ def matches?(main_model)
+ unless main_model.respond_to?(:reflect_on_association)
+ if main_model.class.respond_to?(:reflect_on_association)
+ main_model = main_model.class
+ else
+ @not_model = main_model
+ return false
+ end
+ end
+ if @association = main_model.reflect_on_association(@associated)
+ @options.all?{|k, v| @association.options[k] == v ||
+ [@association.options[k]] == v} # Stupid to_a being obsoleted!
+ end
+ end
+
+ def failure_message
+ if @not_model
+ " expected: #{@not_model} to be a subclass of ActiveRecord::Base class, but was not"
+ elsif @association
+ " expected: #{association_with(@options)}\n got: #{association_with(@association.options)}"
+ else
+ " expected: #{association_with(@options)}, but the association does not exist"
+ end
+ end
+
+ def negative_failure_message
+ if @association
+ " expected: #{association_with(@options)}\n got: #{association_with(@association.options)}"
+ else
+ " expected: #{association_with(@options)} to not occur but it does"
+ end
+ end
+
+ # The following public methods are chainable extensions on the main matcher
+ # Examples:
+ #
+ # Foo.should have_many(:bars).through(:foobars).with_dependent(:destroy)
+ # Bar.should belong_to(:baz).with_class_name("Unbaz")
+ def through(through_model)
+ @options[:through] = through_model
+ self
+ end
+
+ def and_includes(included_models)
+ @options[:include] = included_models
+ self
+ end
+
+ def and_extends(*modules)
+ @options[:extends] = modules
+ self
+ end
+
+ def with_counter_cache(counter_cache = false)
+ if counter_cache
+ @options[:counter_cache] = counter_cache
+ end
+ self
+ end
+
+ def uniq(*irrelevant_args)
+ @options[:uniq] = true
+ self
+ end
+ alias and_is_unique uniq
+ alias with_unique uniq
+
+ def polymorphic(*irrelevant_args)
+ @options[:polymorphic] = true
+ self
+ end
+ alias and_is_polymorphic polymorphic
+ alias with_polymorphic polymorphic
+
+ def as(interface)
+ @options[:as] = interface
+ end
+
+ # Use this to just specify the options as a hash.
+ # Note: It will completely override any previously set options
+ def with_options(options = {})
+ options.each{|k, v| @options[k] = v}
+ self
+ end
+
+ private
+ # Takes care of methods like with_dependent(:destroy)
+ def method_missing(method_id, *args, &block)
+ method_name = method_id.to_s
+ if method_name =~ /^with_(.*)/
+ @options[$1.to_sym] = args
+ self
+ else
+ super method_id, *args, &block
+ end
+ end
+
+ def association_with(options)
+ option_string = (options.nil? || options.empty?) ? "" : options.inspect
+ unless option_string.blank?
+ option_string.sub! /^\{(.*)\}$/, ', \1'
+ option_string.gsub! /\=\>/, ' => '
+ end
+ "#{@macro} :#{@associated}#{option_string}"
+ end
+ end
+
+ # Creates matcher that checks if the receiver has a belongs_to association
+ # with the specified model.
+ #
+ # Note: The argument should be a symbol as in the model's association definition
+ # and not the model's class name.
+ def belong_to(model)
+ AssociationMatcher.new model, :belongs_to
+ end
+
+ # Creates matcher that checks if the receiver has a have_one association
+ # with the specified model.
+ #
+ # Note: The argument should be a symbol as in the model's association definition
+ # and not the model's class name.
+ def have_one(model)
+ AssociationMatcher.new model, :has_one
+ end
+
+ # Creates matcher that checks if the receiver has a have_many association
+ # with the specified model.
+ #
+ # Note: The argument should be a symbol as in the model's association definition
+ # and not the model's class name.
+ def have_many(models)
+ AssociationMatcher.new models, :has_many
+ end
+
+ # Creates matcher that checks if the receiver has a have_and_belong_to_many association
+ # with the specified model.
+ #
+ # Note: The argument should be a symbol as in the model's association definition
+ # and not the model's class name.
+ def have_and_belong_to_many(models)
+ AssociationMatcher.new models, :has_and_belongs_to_many
+ end
+
+ private
+ def class_or_instance
+ @model_spec_class_or_instance ||= class_for(self.class.description_text) || instance
+ end
+
+ def instance
+ @model_spec_instance ||= instance_for(self.class.description_text)
+ end
+
+ # These methods are designed to be used at the example group [read: "describe"] level
+ # to simplify and DRY up common expectations. Most of these methods are wrappers for
+ # matchers which can also be used on the example level [read: within an "it" block]. See
+ # LuckySneaks::ModelSpecHelpers for more information.
+ module ExampleGroupLevelMethods
+ # Creates an expectation that the current model being spec'd has a belongs_to
+ # association with the specified model.
+ #
+ # Note: The argument should be a symbol as in the model's association definition
+ # and not the model's class name.
+ def it_should_belong_to(model)
+ it "should belong to a #{model}" do
+ class_or_instance.should belong_to(model)
+ end
+ end
+
+ # Creates an expectation that the current model being spec'd has a have_one
+ # association with the specified model.
+ #
+ # Note: The argument should be a symbol as in the model's association definition
+ # and not the model's class name.
+ def it_should_have_one(model)
+ it "should have one #{model}" do
+ class_or_instance.should have_one(model)
+ end
+ end
+
+ # Creates an expectation that the current model being spec'd has a have_many
+ # association with the specified model.
+ #
+ # Note: The argument should be a symbol as in the model's association definition
+ # and not the model's class name.
+ def it_should_have_many(models)
+ it "should have many #{models}" do
+ class_or_instance.should have_many(models)
+ end
+ end
+
+ # Creates an expectation that the current model being spec'd has a have_and_belong_to_many
+ # association with the specified model.
+ #
+ # Note: The argument should be a symbol as in the model's association definition
+ # and not the model's class name.
+ def it_should_have_and_belong_to_many(models)
+ it "should have and belong to many #{models}" do
+ class_or_instance.should have_and_belong_to_many(models)
+ end
+ end
+
+ # Creates an expectation that the current model being spec'd validates_presence_of
+ # the specified attribute. Takes an optional custom message to match the one in the model's
+ # validation.
+ def it_should_validate_presence_of(attribute, message = ActiveRecord::Errors.default_error_messages[:blank])
+ it "should not be valid if #{attribute} is blank" do
+ instance.send "#{attribute}=", nil
+ instance.errors_on(attribute).should include(message)
+ end
+ end
+
+ # Creates an expectation that the current model being spec'd validates_numericality_of
+ # the specified attribute. Takes an optional custom message to match the one in the model's
+ # validation.
+ def it_should_validate_numericality_of(attribute, message = ActiveRecord::Errors.default_error_messages[:not_a_number])
+ it "should validate #{attribute} is a numeric" do
+ instance.send "#{attribute}=", "NaN"
+ instance.errors_on(attribute).should include(message)
+ end
+ end
+
+ # Creates an expectation that the current model being spec'd validates_confirmation_of
+ # the specified attribute. Takes an optional custom message to match the one in the model's
+ # validation.
+ def it_should_validate_confirmation_of(attribute, message = ActiveRecord::Errors.default_error_messages[:confirmation])
+ it "should validate confirmation of #{attribute}" do
+ dummy_value = dummy_value_for(instance, attribute) || "try a string"
+ instance.send "#{attribute}=", dummy_value
+ instance.send "#{attribute}_confirmation=", dummy_value.succ
+ instance.errors_on(attribute).should include(message)
+ end
+ end
+
+ # Creates an expectation that the current model being spec'd validates_uniqueness_of
+ # the specified attribute. Takes an optional custom message to match the one in the model's
+ # validation.
+ #
+ # Note: This method will fail completely if valid_attributes
+ # does not provide all the attributes needed to create a valid record.
+ def it_should_validate_uniqueness_of(attribute, message = ActiveRecord::Errors.default_error_messages[:taken])
+ it "should validate #{attribute} confirmation" do
+ previous_instance = class_for(self.class.description_text).create!(valid_attributes)
+ instance.attributes = valid_attributes
+ instance.errors_on(attribute).should include(message)
+ previous_instance.destroy
+ end
+ end
+
+ # Creates an expectation that the current model being spec'd accepts the specified values as
+ # valid for the specified attribute. This is most likely used with validates_format_of
+ # but there's nothing saying it couldn't be another validation.
+ def it_should_accept_as_valid(attribute, *values)
+ values.each do |value|
+ value_inspect = case value
+ when String : "'#{value}'"
+ when NilClass : "nil"
+ else value
+ end
+ it "should accept #{value_inspect} as a valid #{attribute}" do
+ instance.send "#{attribute}=", value
+ instance.errors_on(attribute).should == []
+ end
+ end
+ end
+
+ # Creates an expectation that the current model being spec'd does not accept the specified
+ # values as valid for the specified attribute. This is most likely used with
+ # validates_format_of but there's nothing saying it couldn't be another validation.
+ # Takes an optional argument :message => "some custom error messsage" for
+ # spec'ing the actual error message.
+ def it_should_not_accept_as_valid(attribute, *values)
+ options = values.extract_options!
+ values.each do |value|
+ value_inspect = case value
+ when String : "'#{value}'"
+ when NilClass : "nil"
+ else value
+ end
+ it "should not accept #{value_inspect} as a valid #{attribute}" do
+ instance.send "#{attribute}=", value
+ if options[:message]
+ instance.errors_on(attribute).should include(options[:message])
+ else
+ instance.should have_at_least(1).errors_on(attribute)
+ end
+ end
+ end
+ end
+ # Creates an expectation that the current model being spec'd doesn't allow mass-assignment
+ # of the specified attribute.
+ def it_should_not_mass_assign(attribute)
+ it "should not allow mass-assignment of #{attribute}" do
+ lambda {
+ instance.send :attributes=, {attribute => dummy_value_for(instance, attribute)}
+ }.should_not change(instance, attribute)
+ end
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/skinny_spec/lib/lucky_sneaks/view_spec_helpers.rb b/vendor/plugins/skinny_spec/lib/lucky_sneaks/view_spec_helpers.rb
new file mode 100644
index 00000000..eb532554
--- /dev/null
+++ b/vendor/plugins/skinny_spec/lib/lucky_sneaks/view_spec_helpers.rb
@@ -0,0 +1,460 @@
+$:.unshift File.join(File.dirname(__FILE__), "..")
+require "skinny_spec"
+
+module LuckySneaks
+ # These methods are designed to be used in your example [read: "it"] blocks
+ # to make your view specs less brittle and more DRY. You might also be interested
+ # in checking out the example block [read: "describe"] level versions in of these
+ # methods which can DRY things up even more:
+ # LuckySneaks::ViewSpecHelpers::ExampleGroupLevelMethods
+ module ViewSpecHelpers
+ include LuckySneaks::CommonSpecHelpers
+ include ActionController::PolymorphicRoutes
+
+ def self.included(base) # :nodoc:
+ base.extend ExampleGroupLevelMethods
+ end
+
+ # Wraps a matcher that checks if the receiver contains a FORM element with
+ # its action attribute set to the specified path.
+ def submit_to(path)
+ have_tag("form[action=#{path}]")
+ end
+
+ # Wraps a matcher that checks is the receiver contains any of several form elements
+ # that would return sufficient named parameters to allow editing of the specified
+ # attribute on the specified instance. Example:
+ #
+ # response.should allow_editing(@foo, "bar")
+ #
+ # can be satisfied by any of the following HTML elements:
+ #
+ #
+ #
+ #
+ #
+ #
+ def allow_editing(instance, attribute)
+ instance_name = instance.class.name.underscore.downcase
+ if instance.send(attribute).is_a?(Time)
+ have_tag(
+ "input[name='#{instance_name}[#{attribute}]'],
+ select[name=?]", /#{instance_name}\[#{attribute}\(.*\)\]/
+ )
+ else
+ have_tag(
+ "input[type='text'][name='#{instance_name}[#{attribute}]'],
+ input[type='password'][name='#{instance_name}[#{attribute}]'],
+ select[name='#{instance_name}[#{attribute}]'],
+ textarea[name='#{instance_name}[#{attribute}]'],
+ input[type='checkbox'][name='#{instance_name}[#{attribute}]'],
+ input[type='checkbox'][name='#{instance_name}[#{attribute.to_s.tableize.singularize}_ids][]']"
+ )
+ end
+ end
+
+ # Wraps a matcher that checks if the receiver contains an A element (link)
+ # whose href attribute is set to the specified path or a FORM
+ # element whose action attribute is set to the specified path.
+ def have_link_or_button_to(path)
+ have_tag(
+ "a[href='#{path}'],
+ form[action='#{path}'] input,
+ form[action='#{path}'] button"
+ )
+ end
+ alias have_link_to have_link_or_button_to
+ alias have_button_to have_link_or_button_to
+
+ # Wraps have_link_or_button_to new_polymorphic_path for the specified class which
+ # corresponds with the new method of the controller.
+ #
+ # Note: This method may takes a string or symbol representing the model's name
+ # to send to have_link_or_button_to_show or the model's name itself.
+ def have_link_or_button_to_new(name)
+ have_link_or_button_to new_polymorphic_path(name.is_a?(ActiveRecord::Base) ? name : class_for(name))
+ end
+
+ # Wraps have_link_or_button_to polymorphic_path(instance) which
+ # corresponds with the show method of the controller.
+ def have_link_or_button_to_show(instance)
+ have_link_or_button_to polymorphic_path(instance)
+ end
+ alias have_link_to_show have_link_or_button_to_show
+ alias have_button_to_show have_link_or_button_to_show
+
+ # Wraps have_link_or_button_to edit_polymorphic_path(instance) which
+ # corresponds with the edit method of the controller.
+ def have_link_or_button_to_edit(instance)
+ have_link_or_button_to edit_polymorphic_path(instance)
+ end
+ alias have_link_to_edit have_link_or_button_to_edit
+ alias have_button_to_edit have_link_or_button_to_edit
+
+ # Wraps a matcher that checks if the receiver contains the HTML created by Rails'
+ # button_to helper: to wit, a FORM element whose action
+ # attribute is pointed at the polymorphic_path of the instance
+ # and contains an INPUT named "_method" with a value of "delete".
+ def have_button_to_delete(instance)
+ path = polymorphic_path(instance)
+ have_tag(
+ "form[action='#{path}'] input[name='_method'][value='delete'] + input,
+ form[action='#{path}'] input[name='_method'][value='delete'] + button"
+ )
+ end
+
+ # Creates a mock_model instance and adds it to the assigns collection
+ # using either the name passed as the first argument or the underscore version
+ # of its class name. Accepts optional arguments to stub out additional methods
+ # (and their return values) on the mock_model instance. Example:
+ #
+ # mock_and_assign(Foo, :stub => {:bar => "bar"})
+ #
+ # is the same as running assigns[:foo] = mock_model(Foo, :bar => "bar").
+ #
+ # mock_and_assign(Foo, "special_foo", :stub => {:bar => "baz"})
+ #
+ # is the same as running assigns[:special_foo] = mock_model(Foo, :bar => "baz").
+ #
+ # Note: Adding to the assigns collection returns the object added, so this can
+ # be chained a la @foo = mock_and_assign(Foo).
+ def mock_and_assign(klass, *args)
+ options = args.extract_options!
+ mocked = if options[:stub]
+ mock_model(klass, options[:stub])
+ else
+ mock_model(klass)
+ end
+ yield mocked if block_given?
+ self.assigns[args.first || "#{klass}".underscore] = mocked
+ end
+
+ # Creates an array of mock_model instances in the manner of
+ # mock_and_assign. Accepts option[:size] which sets the size
+ # of the array (default is 3).
+ def mock_and_assign_collection(klass, *args)
+ options = args.dup.extract_options!
+ return_me = Array.new(options[:size] || 3) do
+ mocked = if options[:stub]
+ mock_model(klass, options[:stub])
+ else
+ mock_model(klass)
+ end
+ yield mocked if block_given?
+ mocked
+ end
+ self.assigns[args.first || "#{klass}".tableize] = return_me
+ end
+
+ private
+ def do_render
+ if @the_template
+ render @the_template
+ elsif File.exists?(File.join(RAILS_ROOT, "app/views", self.class.description_text))
+ render self.class.description_text
+ else
+ error_message = "Cannot determine template for render. "
+ error_message << "Please define @the_template in the before block "
+ error_message << "or name your describe block so that it indicates the correct template."
+ raise NameError, error_message
+ end
+ end
+
+ # These methods are designed to be used at the example group [read: "describe"] level
+ # to simplify and DRY up common expectations. Most of these methods are wrappers for
+ # matchers which can also be used on the example level [read: within an "it" block]. See
+ # LuckySneaks::ViewSpecHelpers for more information.
+ module ExampleGroupLevelMethods
+ include LuckySneaks::CommonSpecHelpers
+
+ # Creates an expectation which calls submit_to on the response
+ # from rendering the template. See that method for more details.
+ #
+ # Note: This method takes a Proc to evaluate the route not simply a named route
+ # helper, which would be undefined in the scope of the example block.
+ def it_should_submit_to(hint = nil, &route)
+ if hint.nil? && route.respond_to?(:to_ruby)
+ hint = route.to_ruby.gsub(/(^proc \{)|(\}$)/, '').strip
+ end
+ it "should submit to #{(hint || route)}" do
+ do_render
+ response.should submit_to(instance_eval(&route))
+ end
+ end
+
+ # Creates an expectation that the template uses Rails' form_for to generate
+ # the proper form action and method to create or update the specified object.
+ #
+ # Note: This method takes a string or symbol representing the instance
+ # variable's name to create the expectation for form_for
+ # not an instance variable, which would be nil in the scope of the example block.
+ # If you use namespacing for your form_for, you'll have to manually write out
+ # a similar spec.
+ def it_should_have_form_for(name)
+ it "should have a form_for(@#{name})" do
+ template.should_receive(:form_for).with(instance_for(name))
+ do_render
+ end
+ end
+
+ # Negative version of it_should_have_form_for. See that method for more
+ # details.
+ def it_should_not_have_form_for(name)
+ it "should not have a form_for(@#{name})" do
+ template.should_not_receive(:form_for).with(instance_for(name))
+ do_render
+ end
+ end
+
+ # Creates an expectation which calls allow_editing on the response
+ # from rendering the template. See that method for more details.
+ #
+ # Note: This method takes a string or symbol representing the instance
+ # variable's name to send to allow_editing
+ # not an instance variable, which would be nil in the scope of the example block.
+ def it_should_allow_editing(name, method)
+ it "should allow editing of @#{name}##{method}" do
+ do_render
+ response.should allow_editing(instance_for(name), method)
+ end
+ end
+
+ # Negative version of it_should_allow_editing. See that method for more
+ # details.
+ def it_should_not_allow_editing(name, method)
+ it "should not allow editing of @#{name}##{method}" do
+ do_render
+ response.should_not allow_editing(instance_for(name), method)
+ end
+ end
+
+ # Creates an expectation that the rendered template contains a FORM element
+ # (INPUT, TEXTAREA, or SELECT) with the specified name.
+ def it_should_have_form_element_for(name)
+ it "should have a form element named '#{name}'" do
+ do_render
+ response.should have_tag(
+ "form input[name='#{name}'],
+ form textarea[name='#{name}'],
+ form select[name='#{name}']"
+ )
+ end
+ end
+
+ # Negative version of it_should_have_form_element_for. See that method
+ # for more details.
+ def it_should_not_have_form_element_for(name)
+ it "should not have a form element named '#{name}'" do
+ do_render
+ response.should_not have_tag(
+ "form input[name='#{name}'],
+ form textarea[name='#{name}'],
+ form select[name='#{name}']"
+ )
+ end
+ end
+
+ # Creates an expectation which calls have_link_or_button_to on the response
+ # from rendering the template. See that method for more details.
+ #
+ # Note: This method takes a block to evaluate the route in the example context
+ # instead of the example group context.
+ def it_should_link_to(hint = nil, &route)
+ if hint.nil? && route.respond_to?(:to_ruby)
+ hint = route.to_ruby.gsub(/(^proc \{)|(\}$)/, '').strip
+ end
+ it "should have a link/button to #{(hint || route)}" do
+ do_render
+ response.should have_link_or_button_to(instance_eval(&route))
+ end
+ end
+ alias it_should_have_link_to it_should_link_to
+ alias it_should_have_button_to it_should_link_to
+ alias it_should_have_button_or_link_to it_should_link_to
+
+ # Negative version of it_should_link_to. See that method
+ # for more details.
+ def it_should_not_link_to(hint = nil, &route)
+ if hint.nil? && route.respond_to?(:to_ruby)
+ hint = route.to_ruby.gsub(/(^proc \{)|(\}$)/, '').strip
+ end
+ it "should have a link/button to #{(hint || route)}" do
+ do_render
+ response.should_not have_link_or_button_to(instance_eval(&route))
+ end
+ end
+ alias it_should_not_have_link_to it_should_not_link_to
+ alias it_should_not_have_button_to it_should_not_link_to
+ alias it_should_not_have_button_or_link_to it_should_not_link_to
+
+ # Creates an expectation which calls have_link_or_button_to_new on the response
+ # from rendering the template. See that method for more details.
+ #
+ # Note: This method may takes a string or symbol representing the model's name
+ # to send to have_link_or_button_to_show or the model's name itself.
+ def it_should_link_to_new(name)
+ it "should have a link/button to create a new #{name}" do
+ do_render
+ response.should have_link_or_button_to_new(name)
+ end
+ end
+ alias it_should_have_link_to_new it_should_link_to_new
+ alias it_should_have_button_to_new it_should_link_to_new
+ alias it_should_have_button_or_link_to_new it_should_link_to_new
+
+ # Negative version of it_should_link_to_show. See that method
+ # for more details.
+ def it_should_not_link_to_new(name)
+ it "should have a link/button to create a new #{name}" do
+ do_render
+ response.should_not have_link_or_button_to_new(name)
+ end
+ end
+ alias it_should_not_have_link_to_new it_should_not_link_to_new
+ alias it_should_not_have_button_to_new it_should_not_link_to_new
+ alias it_should_not_have_button_or_link_to_new it_should_not_link_to_new
+
+ # Creates an expectation which calls have_link_or_button_to_show on the response
+ # from rendering the template. See that method for more details.
+ #
+ # Note: This method takes a string or symbol representing the instance
+ # variable's name to send to have_link_or_button_to_show
+ # not an instance variable, which would be nil in the scope of the example block.
+ def it_should_link_to_show(name)
+ it "should have a link/button to show @#{name}" do
+ do_render
+ response.should have_link_or_button_to_show(instance_for(name))
+ end
+ end
+ alias it_should_have_link_to_show it_should_link_to_show
+ alias it_should_have_button_to_show it_should_link_to_show
+ alias it_should_have_button_or_link_to_show it_should_link_to_show
+
+ # Negative version of it_should_link_to_show. See that method
+ # for more details.
+ def it_should_not_link_to_show(name)
+ it "should have a link/button to show @#{name}" do
+ do_render
+ response.should_not have_link_or_button_to_show(instance_for(name))
+ end
+ end
+ alias it_should_not_have_link_to_show it_should_not_link_to_show
+ alias it_should_not_have_button_to_show it_should_not_link_to_show
+ alias it_should_not_have_button_or_link_to_show it_should_not_link_to_show
+
+ # Creates an expectation which calls have_link_or_button_to_show
+ # for each member of the instance variable matching the specified name
+ # on the response from rendering the template. See that method for more details.
+ #
+ # Note: This method takes a string or symbol representing the instance
+ # variable's name and not an instance variable, which would be nil
+ # in the scope of the example block.
+ def it_should_link_to_show_each(name)
+ it "should have a link/button to show each member of @#{name}" do
+ do_render
+ instance_for(name).each do |member|
+ response.should have_link_or_button_to_show(member)
+ end
+ end
+ end
+ alias it_should_have_link_to_show_each it_should_link_to_show_each
+ alias it_should_have_button_to_show_each it_should_link_to_show_each
+ alias it_should_have_button_or_link_to_show_each it_should_link_to_show_each
+
+ # Creates an expectation which calls have_link_or_button_to_edit on the response
+ # from rendering the template. See that method for more details.
+ #
+ # Note: This method takes a string or symbol representing the instance
+ # variable's name to send to have_link_or_button_to_edit
+ # not an instance variable, which would be nil in the scope of the example block.
+ def it_should_link_to_edit(name)
+ it "should have a link/button to edit @#{name}" do
+ do_render
+ response.should have_link_or_button_to_edit(instance_for(name))
+ end
+ end
+ alias it_should_have_link_to_edit it_should_link_to_edit
+ alias it_should_have_button_to_edit it_should_link_to_edit
+ alias it_should_have_button_or_link_to_edit it_should_link_to_edit
+
+ # Negative version of it_should_link_to_edit. See that method
+ # for more details.
+ def it_should_not_link_to_edit(name)
+ it "should have a link/button to edit @#{name}" do
+ do_render
+ response.should_not have_link_or_button_to_edit(instance_for(name))
+ end
+ end
+ alias it_should_not_have_link_to_edit it_should_not_link_to_edit
+ alias it_should_not_have_button_to_edit it_should_not_link_to_edit
+ alias it_should_not_have_button_or_link_to_edit it_should_not_link_to_edit
+
+
+ # Creates an expectation which calls have_link_or_button_to_edit
+ # for each member of the instance variable matching the specified name
+ # on the response from rendering the template. See that method for more details.
+ #
+ # Note: This method takes a string or symbol representing the instance
+ # variable's name and not an instance variable, which would be nil
+ # in the scope of the example block.
+ def it_should_link_to_edit_each(name)
+ it "should have a link/button to edit each member of @#{name}" do
+ do_render
+ instance_for(name).each do |member|
+ response.should have_link_or_button_to_edit(member)
+ end
+ end
+ end
+ alias it_should_have_link_to_edit_each it_should_link_to_edit_each
+ alias it_should_have_button_to_edit_each it_should_link_to_edit_each
+ alias it_should_have_button_or_link_to_edit_each it_should_link_to_edit_each
+
+ # Creates an expectation which calls have_link_or_button_to_delete on the response
+ # from rendering the template. See that method for more details.
+ #
+ # Note: This method takes a string or symbol representing the instance
+ # variable's name to send to have_link_or_button_to_delete
+ # not an instance variable, which would be nil in the scope of the example block.
+ def it_should_link_to_delete(name)
+ it "should have a link/button to delete @#{name}" do
+ do_render
+ response.should have_button_to_delete(instance_for(name))
+ end
+ end
+ alias it_should_have_link_to_delete it_should_link_to_delete
+ alias it_should_have_button_to_delete it_should_link_to_delete
+ alias it_should_have_button_or_link_to_delete it_should_link_to_delete
+
+ # Negative version of it_should_link_to_delete. See that method
+ # for more details.
+ def it_should_not_link_to_delete(name)
+ it "should not have a link/button to delete @#{name}" do
+ do_render
+ response.should_not have_button_to_delete(instance_for(name))
+ end
+ end
+ alias it_should_not_have_link_to_delete it_should_not_link_to_delete
+ alias it_should_not_have_button_to_delete it_should_not_link_to_delete
+ alias it_should_not_have_button_or_link_to_delete it_should_not_link_to_delete
+
+ # Creates an expectation which calls have_link_or_button_to_delete
+ # for each member of the instance variable matching the specified name
+ # on the response from rendering the template. See that method for more details.
+ #
+ # Note: This method takes a string or symbol representing the instance
+ # variable's name and not an instance variable, which would be nil
+ # in the scope of the example block.
+ def it_should_link_to_delete_each(name)
+ it "should have a link/button to delete each member of @#{name}" do
+ do_render
+ instance_for(name).each do |member|
+ response.should have_button_to_delete(member)
+ end
+ end
+ end
+ alias it_should_have_link_to_delete_each it_should_link_to_delete_each
+ alias it_should_have_button_to_delete_each it_should_link_to_delete_each
+ alias it_should_have_button_or_link_to_delete_each it_should_link_to_delete_each
+ end
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/skinny_spec/lib/skinny_spec.rb b/vendor/plugins/skinny_spec/lib/skinny_spec.rb
new file mode 100644
index 00000000..c7883884
--- /dev/null
+++ b/vendor/plugins/skinny_spec/lib/skinny_spec.rb
@@ -0,0 +1,26 @@
+# Let's make sure everyone else is loaded
+require File.expand_path(File.dirname(__FILE__) + "/../../../../config/environment")
+require 'spec'
+require 'spec/rails'
+begin
+ require 'ruby2ruby'
+rescue
+ puts "-----"
+ puts "Attention: skinny_spec requires ruby2ruby for nicer route descriptions"
+ puts "It is highly recommended that you install it: sudo gem install ruby2ruby"
+ puts "-----"
+end
+
+# Let's load our family now
+require "lucky_sneaks/common_spec_helpers"
+require "lucky_sneaks/controller_request_helpers"
+require "lucky_sneaks/controller_spec_helpers"
+require "lucky_sneaks/controller_stub_helpers"
+require "lucky_sneaks/model_spec_helpers"
+require "lucky_sneaks/view_spec_helpers"
+
+# Let's all come together
+Spec::Rails::Example::ViewExampleGroup.send :include, LuckySneaks::ViewSpecHelpers
+Spec::Rails::Example::HelperExampleGroup.send :include, LuckySneaks::CommonSpecHelpers
+Spec::Rails::Example::ControllerExampleGroup.send :include, LuckySneaks::ControllerSpecHelpers
+Spec::Rails::Example::ModelExampleGroup.send :include, LuckySneaks::ModelSpecHelpers
\ No newline at end of file