From b13df20630f3c1b624329ec0280dcde30a4ad578 Mon Sep 17 00:00:00 2001 From: Simon Rozet Date: Mon, 23 Jun 2008 14:07:55 +0200 Subject: [PATCH] Install the `scenarios' plugin and require it in spec_helper It is IMO a great alternative to fixtures. See http://github.com/aiwilliams/scenarios/tree/master and further commits for more information. --- spec/spec_helper.rb | 1 + vendor/plugins/scenarios/.gitignore | 4 + vendor/plugins/scenarios/LICENSE | 19 ++ vendor/plugins/scenarios/README | 262 ++++++++++++++++++ vendor/plugins/scenarios/Rakefile | 10 + vendor/plugins/scenarios/TODO | 1 + vendor/plugins/scenarios/helpers.diff | 127 +++++++++ vendor/plugins/scenarios/lib/scenarios.rb | 34 +++ .../plugins/scenarios/lib/scenarios/base.rb | 73 +++++ .../lib/scenarios/builtin/blank_scenario.rb | 18 ++ .../scenarios/lib/scenarios/configuration.rb | 55 ++++ .../scenarios/lib/scenarios/extensions.rb | 5 + .../lib/scenarios/extensions/active_record.rb | 14 + .../extensions/delegating_attributes.rb | 40 +++ .../lib/scenarios/extensions/object.rb | 5 + .../lib/scenarios/extensions/string.rb | 22 ++ .../lib/scenarios/extensions/symbol.rb | 14 + .../lib/scenarios/extensions/test_case.rb | 77 +++++ .../scenarios/lib/scenarios/loading.rb | 51 ++++ .../scenarios/lib/scenarios/table_blasting.rb | 20 ++ .../scenarios/lib/scenarios/table_methods.rb | 205 ++++++++++++++ .../scenarios/complex_composite_scenario.rb | 9 + .../spec/scenarios/composite_scenario.rb | 9 + .../spec/scenarios/empty_scenario.rb | 4 + .../spec/scenarios/people_scenario.rb | 26 ++ .../spec/scenarios/places_scenario.rb | 22 ++ .../spec/scenarios/things_scenario.rb | 22 ++ .../plugins/scenarios/spec/scenarios_spec.rb | 185 +++++++++++++ vendor/plugins/scenarios/spec/spec.opts | 7 + vendor/plugins/scenarios/spec/spec_helper.rb | 24 ++ vendor/plugins/scenarios/tasks/scenario.rake | 19 ++ .../plugins/scenarios/testing/application.rb | 2 + vendor/plugins/scenarios/testing/models.rb | 14 + .../scenarios/testing/plugit_descriptor.rb | 44 +++ vendor/plugins/scenarios/testing/schema.rb | 31 +++ 35 files changed, 1475 insertions(+) create mode 100644 vendor/plugins/scenarios/.gitignore create mode 100644 vendor/plugins/scenarios/LICENSE create mode 100644 vendor/plugins/scenarios/README create mode 100644 vendor/plugins/scenarios/Rakefile create mode 100644 vendor/plugins/scenarios/TODO create mode 100644 vendor/plugins/scenarios/helpers.diff create mode 100644 vendor/plugins/scenarios/lib/scenarios.rb create mode 100644 vendor/plugins/scenarios/lib/scenarios/base.rb create mode 100644 vendor/plugins/scenarios/lib/scenarios/builtin/blank_scenario.rb create mode 100644 vendor/plugins/scenarios/lib/scenarios/configuration.rb create mode 100644 vendor/plugins/scenarios/lib/scenarios/extensions.rb create mode 100644 vendor/plugins/scenarios/lib/scenarios/extensions/active_record.rb create mode 100644 vendor/plugins/scenarios/lib/scenarios/extensions/delegating_attributes.rb create mode 100644 vendor/plugins/scenarios/lib/scenarios/extensions/object.rb create mode 100644 vendor/plugins/scenarios/lib/scenarios/extensions/string.rb create mode 100644 vendor/plugins/scenarios/lib/scenarios/extensions/symbol.rb create mode 100644 vendor/plugins/scenarios/lib/scenarios/extensions/test_case.rb create mode 100644 vendor/plugins/scenarios/lib/scenarios/loading.rb create mode 100644 vendor/plugins/scenarios/lib/scenarios/table_blasting.rb create mode 100644 vendor/plugins/scenarios/lib/scenarios/table_methods.rb create mode 100644 vendor/plugins/scenarios/spec/scenarios/complex_composite_scenario.rb create mode 100644 vendor/plugins/scenarios/spec/scenarios/composite_scenario.rb create mode 100644 vendor/plugins/scenarios/spec/scenarios/empty_scenario.rb create mode 100644 vendor/plugins/scenarios/spec/scenarios/people_scenario.rb create mode 100644 vendor/plugins/scenarios/spec/scenarios/places_scenario.rb create mode 100644 vendor/plugins/scenarios/spec/scenarios/things_scenario.rb create mode 100644 vendor/plugins/scenarios/spec/scenarios_spec.rb create mode 100644 vendor/plugins/scenarios/spec/spec.opts create mode 100644 vendor/plugins/scenarios/spec/spec_helper.rb create mode 100644 vendor/plugins/scenarios/tasks/scenario.rake create mode 100644 vendor/plugins/scenarios/testing/application.rb create mode 100644 vendor/plugins/scenarios/testing/models.rb create mode 100644 vendor/plugins/scenarios/testing/plugit_descriptor.rb create mode 100644 vendor/plugins/scenarios/testing/schema.rb diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d584e8c0..dfc0af68 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -5,6 +5,7 @@ require File.expand_path(File.dirname(__FILE__) + "/../config/environment") require 'spec' require 'spec/rails' require 'skinny_spec' +require 'scenarios' module LuckySneaks module ModelSpecHelpers 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