Permalink
Browse files

shuffle, fixture_scenarios_with_scenario_builder => fixture_scenarios…

…_builder
  • Loading branch information...
0 parents commit a82163ca1c20a09e83ff5bbb836d6723d2f26af9 @defunkt committed Jul 23, 2007
Showing with 328 additions and 0 deletions.
  1. +20 −0 LICENSE
  2. +107 −0 README
  3. +6 −0 init.rb
  4. +4 −0 install.rb
  5. +13 −0 lib/fixture_scenarios_hack.rb
  6. +166 −0 lib/scenario_builder.rb
  7. +12 −0 tasks/fixture_scenarios_builder_tasks.rake
20 LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2007 Chris Wanstrath
+
+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.
107 README
@@ -0,0 +1,107 @@
+FixtureScenariosBuilder
+=======================
+
+This plugin is an add-on to the FixtureScenarios plugin by Tom Preston-Werner.
+
+Info: http://code.google.com/p/fixture-scenarios/
+SVN : http://fixture-scenarios.googlecode.com/svn/trunk/fixture_scenarios
+
+== The Setup
+
+You may, from time to time, wish to build your fixtures entirely in Ruby.
+Doing so has its advantages, such as automatically created join tables
+and default attributes. YAML files, however, bring with them some real
+nice features in Rails which are difficult to abandon: transactional fixtures,
+table_name(:key) helpers, and auto-clearing between tests. How does one get
+the best of both worlds?
+
+== The Download
+
+Using the +scenario+ method within <tt>scenarios.rb</tt> file,
+FixtureScenariosBuilder can create your YAML fixture scenarios automatically
+at run time from Ruby-created fixtures.
+
+Create a <tt>scenarios.rb</tt> file and place it in the root "fixtures"
+directory:
+
+ [RAILS_ROOT]
+ +-test/
+ +-fixtures/
+ +-scenarios.rb
+
+Now build your scenarios in this file, wrapping scenarios in the
++scenario+ method and providing it with the name of your scenario.
+A brief example of a complete <tt>scenarios.rb</tt> file:
+
+ scenario :banned_users do
+ %w( Tom Chris Kevin ).each_with_index do |user, index|
+ User.create(:name => user, :banned => index.odd?)
+ end
+ end
+
+Assuming +banned+ is a boolean field, this will create for us (when our tests
+are first run) the following:
+
+ [RAILS_ROOT]
+ +-test/
+ +-fixtures/
+ +-banned_users/
+ +-users.yml
+
+Our generated <tt>users.yml</tt> file will look something like this:
+
+ chris:
+ name: Chris
+ id: "2"
+ banned: "1"
+ updated_at: 2007-05-09 09:08:04
+ created_at: 2007-05-09 09:08:04
+ kevin:
+ name: Kevin
+ id: "3"
+ banned: "0"
+ updated_at: 2007-05-09 09:08:04
+ created_at: 2007-05-09 09:08:04
+ tom:
+ name: Tom
+ id: "1"
+ banned: "0"
+ updated_at: 2007-05-09 09:08:04
+ created_at: 2007-05-09 09:08:04
+
+Notice how the keys correspond to the user names. FixtureScenariosBuilder will try,
+to an extent, to guess the name of your key. If it can't figure it out,
+keys will be the standard user_001, user_002, etc format.
+
+On subsequent test runs this YAML file will not be needlessly re-created. YAML
+files will only be re-generated when the <tt>scenarios.rb</tt> file is
+modified.
+
+If you for some reason need to force your scenarios to rebuild, pass in the
+REBUILD_FIXTURES environment variables:
+
+ $ rake test:units REBUILD_FIXTURES=true
+
+Scenarios can also be nested using the familiar Rake-style dependency declaration.
+
+ scenario :users do
+ %w( Tom Chris ).each do |user)
+ User.create(:name => user)
+ end
+ end
+
+ scenario :banned_users => :users do
+ User.create(:name => 'Kevin', :banned => true)
+ end
+
+== Rake
+
+FixtureScenariosBuilder comes with one Rake task, `db:scenario:build' -- use it to
+attempt to build your scenarios on demand.
+
+== Bugs!
+
+Please report bugs here: http://err.lighthouseapp.com/projects/466-plugins/tickets
+
+>> Chris Wanstrath
+=> chris[at]ozmm[dot]org
@@ -0,0 +1,6 @@
+if Test::Unit::TestSuite.instance_methods.include? 'run_with_finish'
+ require 'scenario_builder'
+ require 'fixture_scenarios_hack'
+else
+ load File.join(File.dirname(__FILE__), 'install.rb')
+end
@@ -0,0 +1,4 @@
+puts
+puts "# Make sure you have the fixture_scenarios plugin installed."
+puts "# script/plugin install http://fixture-scenarios.googlecode.com/svn/trunk/fixture_scenarios"
+puts
@@ -0,0 +1,13 @@
+class Test::Unit::TestCase
+ def self.scenario_with_builder(*args)
+ # try to build ruby driven scenarios
+ if File.exists? scenarios_rb = File.join(fixture_path, 'scenarios.rb')
+ require scenarios_rb
+ end
+ scenario_without_builder(*args)
+ end
+
+ class << self
+ alias_method_chain :scenario, :builder
+ end
+end
@@ -0,0 +1,166 @@
+# ScenarioBuilder
+
+require 'fileutils'
+
+class Object
+ def scenario(scenario, &block)
+ if block.nil?
+ raise NoMethodError, "undefined method `scenario' for #{inspect}"
+ else
+ ScenarioBuilder.new(scenario, &block).build
+ end
+ end
+ alias_method :build_scenario, :scenario
+end
+
+class ScenarioBuilder
+ @@record_name_fields = %w( name title username login )
+
+ @@delete_sql = "DELETE FROM %s"
+ @@select_sql = "SELECT * FROM %s"
+
+ def initialize(scenario, &block)
+ case scenario
+ when Hash
+ parent = scenario.values.first
+ scenario = scenario.keys.first
+ @scenario = "#{validate_parent(parent)}/#{scenario}"
+ when Symbol, String
+ @scenario = scenario.to_s
+ else
+ raise "I don't know how to build `#{scenario.inspect}'"
+ end
+
+ @block = block
+ @children = []
+ end
+
+ def validate_parent(scenario)
+ scenario = scenario.to_s
+ unless fixtures_dir_exists?(scenario)
+ puts "WARNING: Parent scenario `#{scenario}' doesn't exist. Typo?"
+ end
+ scenario
+ end
+
+ def say(*messages)
+ puts messages.map { |message| "=> #{message}" }
+ end
+
+ def build
+ return if fixtures_dir_exists? unless rebuild_fixtures?
+ say "Building scenario `#{@scenario}'"
+ delete_tables
+ surface_errors { instance_eval(&@block) }
+ FileUtils.rm_rf fixtures_dir(@scenario) if rebuild_fixtures?
+ FileUtils.mkdir_p fixtures_dir(@scenario)
+ dump_tables
+ build_nested_scenarios
+ end
+
+ def build_scenario(scenario, &block)
+ @children << ScenarioBuilder.new(scenario, @scenario, &block)
+ end
+
+ def build_nested_scenarios
+ @children.each { |child| child.build }
+ end
+
+ def surface_errors
+ yield
+ rescue Object => error
+ puts
+ say "There was an error building scenario `#{@scenario}'", error.inspect
+ puts
+ puts error.backtrace
+ puts
+ exit!
+ end
+
+ def delete_tables
+ ActiveRecord::Base.connection.tables.each { |t| ActiveRecord::Base.connection.delete(@@delete_sql % t) }
+ end
+
+ def tables
+ ActiveRecord::Base.connection.tables - skip_tables
+ end
+
+ def skip_tables
+ %w( schema_info )
+ end
+
+ def inferred_record_name(record_hash)
+ @@record_name_fields.each do |try|
+ if name = record_hash[try]
+ inferred_name = name.underscore.gsub(/\W/, ' ').squeeze(' ').tr(' ', '_')
+ count = @inferred_names.select { |name| name == inferred_name }.size
+ @inferred_names << inferred_name
+ return count.zero? ? inferred_name : "#{inferred_name}_#{count}"
+ end
+ end
+
+ "#{@table_name}_#{@row_index.succ!}"
+ end
+
+ def dump_tables
+ fixtures = tables.inject([]) do |files, @table_name|
+ next files if fixture_file_exists? unless rebuild_fixtures?
+
+ rows = ActiveRecord::Base.connection.select_all(@@select_sql % @table_name)
+ next files if rows.empty?
+
+ @row_index = '000'
+ @inferred_names = []
+ fixture_data = rows.inject({}) do |hash, record|
+ hash.merge(inferred_record_name(record) => record)
+ end
+
+ write_fixture_file fixture_data
+
+ files + [File.basename(fixture_file)]
+ end
+ say "Built scenario `#{@scenario}' with #{fixtures.to_sentence}"
+ end
+
+ def write_fixture_file(fixture_data)
+ File.open(fixture_file, 'w') do |file|
+ file.write fixture_data.to_yaml
+ end
+ end
+
+ def fixture_file
+ fixtures_dir(@scenario, "#{@table_name}.yml")
+ end
+
+ def fixtures_dir(*paths)
+ File.join(RAILS_ROOT, 'test', 'fixtures', *paths)
+ end
+
+ def fixtures_dir_exists?(dir = @scenario)
+ File.exists? fixtures_dir(dir)
+ end
+
+ def fixture_file_exists?
+ File.exists? fixture_file
+ end
+
+ def rebuild_fixtures?
+ (%w( REBUILD_FIXTURES BUILD_FIXTURES NEW_FIXTURES NF ) & ENV.keys).any? || scenarios_file_changed?
+ end
+
+ def scenarios_file_changed?
+ can_trigger_rebuild = [
+ fixtures_dir('scenarios.rb'),
+ File.join(RAILS_ROOT, 'db', 'migrate')
+ ]
+
+ can_trigger_rebuild.any? { |file| older_than_scenario? file }
+ end
+
+ def older_than_scenario?(file)
+ scenario_dir = fixtures_dir(@scenario)
+ if File.exists?(file) && File.exists?(scenario_dir)
+ File.mtime(file) > File.mtime(scenario_dir)
+ end
+ end
+end
@@ -0,0 +1,12 @@
+namespace :db do
+ namespace :scenario do
+ desc 'Build scenarios'
+ task :build => :environment do
+ require 'active_record/fixtures'
+ require 'fixture_scenarios'
+ if File.exists? scenarios_rb = File.join(RAILS_ROOT, 'test', 'fixtures', 'scenarios.rb')
+ require scenarios_rb
+ end
+ end
+ end
+end

0 comments on commit a82163c

Please sign in to comment.