josevalim-nested_scenarios 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2007 Thomas Preston-Werner
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,168 @@
1
+ NestedScenarios v0.1
2
+ ====================
3
+
4
+ This plugin is based on FixtureScenarios and FixtureScenarioBuilder.
5
+ It includes both worlds in just one plugin with some fixes, new features
6
+ and Rails 2.2 support.
7
+
8
+ You can check them at:
9
+
10
+ FixtureScenarios
11
+ Info: http://code.google.com/p/fixture-scenarios/
12
+ SVN : http://fixture-scenarios.googlecode.com/svn/trunk/fixture_scenarios
13
+
14
+ FixtureScenariosBuilder
15
+ Info: http://errtheblog.com/post/7708
16
+ SVN : svn://errtheblog.com/svn/plugins/fixture_scenarios_builder
17
+
18
+ NestedScenarios
19
+ Info: http://josevalim.blogspot.com/
20
+ Git : http://github.com/josevalim/nested_scenarios
21
+
22
+ == Install
23
+
24
+ Install NestedScenarios is very easy. It is stored in GitHub, so if you have
25
+ never installed a gem via GitHub run the following:
26
+
27
+ gem sources -a http://gems.github.com
28
+
29
+ Then install the gem:
30
+
31
+ sudo gem install josevalim-nested_scenarios
32
+
33
+ In RAILS_ROOT/config/environment.rb:
34
+
35
+ config.gem "josevalim-nested_scenarios", :lib => "nested_scenarios", :source => "http://gems.github.com"
36
+
37
+ == Why
38
+
39
+ You may, from time to time, wish to build your fixtures entirely in Ruby.
40
+ Doing so has its advantages, such as automatically created join tables
41
+ and default attributes. YAML files, however, bring with them some real
42
+ nice features in Rails which are difficult to abandon: transactional fixtures,
43
+ table_name(:key) helpers, and auto-clearing between tests. How does one get
44
+ the best of both worlds?
45
+
46
+ == Usage
47
+
48
+ Using the +scenario+ method within <tt>scenarios.rb</tt> file,
49
+ FixtureScenariosBuilder can create your YAML fixture scenarios automatically
50
+ at run time from Ruby-created fixtures.
51
+
52
+ Any file inside the +fixture_path+ called scenarios.rb is loaded to
53
+ generating scenarios:
54
+
55
+ [RAILS_ROOT]
56
+ +-test/
57
+ +-fixtures/
58
+ +-scenarios.rb
59
+
60
+ Or:
61
+
62
+ [RAILS_ROOT]
63
+ +-spec/
64
+ +-fixtures/
65
+ +-models/
66
+ +-scenarios.rb
67
+ +-controllers/
68
+ +-scenarios.rb
69
+ +-helpers/
70
+ +-scenarios.rb
71
+
72
+ Now build your scenarios in those file, wrapping scenarios in the
73
+ +scenario+ method and providing it with the name of your scenario.
74
+ A brief example of a complete <tt>scenarios.rb</tt> file:
75
+
76
+ scenario :banned_users do
77
+ %w( Tom Chris Kevin ).each_with_index do |user, index|
78
+ User.create(:name => user, :banned => index.odd?)
79
+ end
80
+ end
81
+
82
+ Assuming +banned+ is a boolean field, this will create for us:
83
+
84
+ [RAILS_ROOT]
85
+ +-test/
86
+ +-fixtures/
87
+ +-banned_users/
88
+ +-users.yml
89
+
90
+ Our generated <tt>users.yml</tt> file will look something like this:
91
+
92
+ chris:
93
+ name: Chris
94
+ id: "2"
95
+ banned: "1"
96
+ updated_at: 2007-05-09 09:08:04
97
+ created_at: 2007-05-09 09:08:04
98
+ kevin:
99
+ name: Kevin
100
+ id: "3"
101
+ banned: "0"
102
+ updated_at: 2007-05-09 09:08:04
103
+ created_at: 2007-05-09 09:08:04
104
+ tom:
105
+ name: Tom
106
+ id: "1"
107
+ banned: "0"
108
+ updated_at: 2007-05-09 09:08:04
109
+ created_at: 2007-05-09 09:08:04
110
+
111
+ Notice how the keys correspond to the user names. You can register fields that
112
+ can be used as fixtures names by:
113
+
114
+ NestedScenarios.record_name_fields << :nickname
115
+
116
+ You can assign your records to instance variables, then call +names_from_ivars+
117
+ at the conclusion of your +scenario+ block.
118
+
119
+ scenario :foo do
120
+ @small_red_widget = Widget.create(:size => 'small', :color => 'red')
121
+ @big_blue_widget = Widget.create(:size => 'big', :color => 'blue')
122
+
123
+ names_from_ivars!
124
+ end
125
+
126
+ The above produces the following YAML:
127
+
128
+ small_red_widget:
129
+ size: small
130
+ color: red
131
+ updated_at: 2007-12-27 10:09:05
132
+ created_at: 2007-12-27 10:09:05
133
+ big_blue_widget:
134
+ size: big
135
+ color: blue
136
+ updated_at: 2007-12-27 10:19:23
137
+ created_at: 2007-12-27 10:19:23
138
+
139
+ To build the scenario you have to run:
140
+
141
+ rake db:build:scenario
142
+
143
+ In NestedScenarios, scenarios are not generated automatically. Another change
144
+ is how scenarios are nested:
145
+
146
+ scenario :models => { :users => :banned } do
147
+ User.create(:name => 'Kevin', :banned => true)
148
+ end
149
+
150
+ This will create an YAML in the following dir:
151
+
152
+ [RAILS_ROOT]
153
+ +-test/
154
+ +-fixtures/
155
+ +-models/
156
+ +-users/
157
+ +-banned/
158
+ +-users.yml
159
+
160
+ Finally, you can choose which scenario to use in your tests by:
161
+
162
+ scenario :users
163
+
164
+ Or, in the case of nested scenarios:
165
+
166
+ scenario :models => { :users => :banned }
167
+
168
+ If no scenario is sent, the default behaviour is adopted.
@@ -0,0 +1,12 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Generate documentation for Footnotes plugin.'
6
+ Rake::RDocTask.new(:rdoc) do |rdoc|
7
+ rdoc.rdoc_dir = 'rdoc'
8
+ rdoc.title = 'NestedScenarios'
9
+ rdoc.options << '--line-numbers' << '--inline-source'
10
+ rdoc.rdoc_files.include('README')
11
+ rdoc.rdoc_files.include('lib/**/*.rb')
12
+ end
data/init.rb ADDED
@@ -0,0 +1,11 @@
1
+ if RAILS_ENV == 'test'
2
+ require 'test/unit/testcase'
3
+ require 'test/unit/testsuite'
4
+
5
+ require 'active_record/fixtures'
6
+
7
+ require File.join(File.dirname(__FILE__), 'lib', 'join')
8
+ require File.join(File.dirname(__FILE__), 'lib', 'nested_scenarios')
9
+ require File.join(File.dirname(__FILE__), 'lib', 'builder')
10
+ require File.join(File.dirname(__FILE__), 'lib', 'fixtures')
11
+ end
@@ -0,0 +1,131 @@
1
+ require 'fileutils'
2
+
3
+ class Object
4
+ def scenario(scenario, &block)
5
+ if block.nil?
6
+ raise NoMethodError, "undefined method `scenario' for #{inspect}"
7
+ else
8
+ NestedScenarios::Builder.new(scenario, &block).build
9
+ end
10
+ end
11
+
12
+ alias_method :build_scenario, :scenario
13
+ end
14
+
15
+ class NestedScenarios::Builder
16
+ @@select_sql = "SELECT * FROM %s"
17
+
18
+ def initialize(scenario, &block)
19
+ case scenario
20
+ when Hash
21
+ @scenario = scenario.join('/')
22
+ when Symbol, String
23
+ @scenario = scenario.to_s
24
+ else
25
+ raise "I don't know how to build `#{scenario.inspect}'"
26
+ end
27
+
28
+ @block = block
29
+ @custom_names = {}
30
+ end
31
+
32
+ def build
33
+ say "Building scenario `#{@scenario}'"
34
+ NestedScenarios.delete_tables
35
+
36
+ surface_errors { instance_eval(&@block) }
37
+ FileUtils.mkdir_p self.class.fixtures_dir(@scenario)
38
+
39
+ dump_tables
40
+ end
41
+
42
+ def names_from_ivars!
43
+ instance_values.each do |var, value|
44
+ name(var, value) if value.is_a? ActiveRecord::Base
45
+ end
46
+ end
47
+
48
+ def self.fixtures_dir(*paths)
49
+ File.join(RAILS_ROOT, spec_or_test_dir, 'fixtures', *paths)
50
+ end
51
+
52
+ def self.spec_or_test_dir
53
+ File.exists?(File.join(RAILS_ROOT, 'spec')) ? 'spec' : 'test'
54
+ end
55
+
56
+ def self.fixtures_dir_exists?(dir = @scenario)
57
+ File.exists? fixtures_dir(dir)
58
+ end
59
+
60
+ def self.fixture_file_exists?
61
+ File.exists? fixture_file
62
+ end
63
+
64
+ protected
65
+ def say(*messages)
66
+ puts messages.map { |message| "=> #{message}" }
67
+ end
68
+
69
+ def write_fixture_file(fixture_data)
70
+ File.open(fixture_file, 'w') do |file|
71
+ file.write fixture_data.to_yaml
72
+ end
73
+ end
74
+
75
+ def fixture_file
76
+ self.class.fixtures_dir(@scenario, "#{@table_name}.yml")
77
+ end
78
+
79
+ def surface_errors
80
+ yield
81
+ rescue Object => error
82
+ puts
83
+ say "There was an error building scenario `#{@scenario}'", error.inspect
84
+ puts
85
+ puts error.backtrace
86
+ puts
87
+ exit!
88
+ end
89
+
90
+ def name(custom_name, model_object)
91
+ key = [model_object.class.name, model_object.id]
92
+ @custom_names[key] = custom_name
93
+ model_object
94
+ end
95
+
96
+ def record_name(record_hash)
97
+ key = [@table_name.classify, record_hash['id'].to_i]
98
+ @record_names << (name = @custom_names[key] || inferred_record_name(record_hash) )
99
+ name
100
+ end
101
+
102
+ def inferred_record_name(record_hash)
103
+ NestedScenarios.record_name_fields.each do |try|
104
+ if name = record_hash[try]
105
+ inferred_name = name.underscore.gsub(/\W/, ' ').squeeze(' ').tr(' ', '_')
106
+ count = @record_names.select { |name| name == inferred_name }.size
107
+ return count.zero? ? inferred_name : "#{inferred_name}_#{count}"
108
+ end
109
+ end
110
+
111
+ "#{@table_name}_#{@row_index.succ!}"
112
+ end
113
+
114
+ def dump_tables
115
+ fixtures = NestedScenarios.tables.inject([]) do |files, @table_name|
116
+ rows = ActiveRecord::Base.connection.select_all(@@select_sql % @table_name)
117
+ next files if rows.empty?
118
+
119
+ @row_index = '000'
120
+ @record_names = []
121
+ fixture_data = rows.inject({}) do |hash, record|
122
+ hash.merge(record_name(record) => record)
123
+ end
124
+
125
+ write_fixture_file fixture_data
126
+
127
+ files + [File.basename(fixture_file)]
128
+ end
129
+ say "Built scenario `#{@scenario}' with #{fixtures.to_sentence}"
130
+ end
131
+ end
@@ -0,0 +1,72 @@
1
+ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
2
+ def self.destroy_fixtures(table_names)
3
+ NestedScenarios.delete_tables(table_names)
4
+ end
5
+ end
6
+
7
+ module Test #:nodoc:
8
+ module Unit #:nodoc:
9
+ class TestCase #:nodoc:
10
+ superclass_delegating_accessor :scenario_path
11
+
12
+ def self.scenario(scenario_name = nil, options = {})
13
+ case scenario_name
14
+ when Hash
15
+ scenario_name = scenario_name.join('/')
16
+ when Symbol, String
17
+ scenario_name = scenario_name.to_s
18
+ end
19
+
20
+ self.scenario_path = "#{self.fixture_path}/#{scenario_name}/" if scenario_name
21
+
22
+ self.fixtures(:all)
23
+ end
24
+
25
+ def self.fixtures(*table_names)
26
+ if table_names.first == :all
27
+ table_names = Dir["#{self.scenario_path || self.fixture_path}/*.yml"]
28
+ table_names += Dir["#{self.scenario_path || self.fixture_path}/*.csv"]
29
+ table_names.map! { |f| File.basename(f).split('.')[0..-2].join('.') }
30
+ else
31
+ table_names = table_names.flatten.map { |n| n.to_s }
32
+ end
33
+
34
+ self.fixture_table_names |= table_names
35
+
36
+ require_fixture_classes(table_names)
37
+ setup_fixture_accessors(table_names)
38
+ end
39
+
40
+ private
41
+ def load_fixtures
42
+ @loaded_fixtures = {}
43
+ fixtures = Fixtures.create_fixtures(self.scenario_path || self.fixture_path, fixture_table_names, fixture_class_names)
44
+ unless fixtures.nil?
45
+ if fixtures.instance_of?(Fixtures)
46
+ @loaded_fixtures[fixtures.table_name] = fixtures
47
+ else
48
+ fixtures.each { |f| @loaded_fixtures[f.table_name] = f }
49
+ end
50
+ end
51
+ end
52
+
53
+ def teardown_fixtures
54
+ return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
55
+
56
+ Fixtures.destroy_fixtures(self.fixture_table_names)
57
+
58
+ unless use_transactional_fixtures?
59
+ Fixtures.reset_cache
60
+ end
61
+
62
+ # Rollback changes if a transaction is active.
63
+ if use_transactional_fixtures? && ActiveRecord::Base.connection.open_transactions != 0
64
+ ActiveRecord::Base.connection.rollback_db_transaction
65
+ ActiveRecord::Base.connection.decrement_open_transactions
66
+ end
67
+
68
+ ActiveRecord::Base.clear_active_connections!
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,36 @@
1
+ module ActiveSupport #:nodoc:
2
+ module CoreExtensions #:nodoc:
3
+ module Hash #:nodoc:
4
+ module Join
5
+
6
+ # Returns a string created by converting each element of the hash to
7
+ # a string, separated by <i>sep</i>.
8
+ #
9
+ # { :a => :b, :c => :d }.join #=> "abcd"
10
+ # { :a => :b, :c => :d }.join('-') #=> "a-b-c-d"
11
+ #
12
+ # If the hash has a key or value as a hash also, it's also joined
13
+ # (just as it works with arrays).
14
+ #
15
+ # { :a => { :b => :c } }.join(' ') #=> "a b c"
16
+ #
17
+ # Note: hash order is just preserved in Ruby 1.9
18
+ #
19
+ def join(sep = $,)
20
+ array = []
21
+ sep = sep.to_s
22
+
23
+ self.each_pair do |k, v|
24
+ array << (k.is_a?(Hash) ? k.join(sep) : k.to_s)
25
+ array << (v.is_a?(Hash) ? v.join(sep) : v.to_s)
26
+ end
27
+
28
+ array.join(sep)
29
+ end
30
+
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ Hash.__send__ :include, ActiveSupport::CoreExtensions::Hash::Join
@@ -0,0 +1,20 @@
1
+ class NestedScenarios
2
+ cattr_accessor :record_name_fields, :skip_tables
3
+ @@record_name_fields = %w( name username title )
4
+ @@skip_tables = %w( schema_migrations )
5
+
6
+ def self.delete_tables(table_names = self.tables)
7
+ connection = ActiveRecord::Base.connection
8
+ ActiveRecord::Base.silence do
9
+ connection.disable_referential_integrity do
10
+ (table_names - @@skip_tables).each do |table_name|
11
+ connection.delete "DELETE FROM #{table_name}", 'Fixture Delete'
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ def self.tables
18
+ ActiveRecord::Base.connection.tables - @@skip_tables
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ namespace :db do
2
+ namespace :scenario do
3
+ desc 'Build scenarios in test environment'
4
+ task :build do
5
+ # Load environment using test environment
6
+ ENV['RAILS_ENV'] = RAILS_ENV = 'test'
7
+ Rake::Task['environment'].invoke
8
+
9
+ # Force ActionMailer to delivery method :test
10
+ # while building scenarios
11
+ ActionMailer::Base.delivery_method = :test
12
+
13
+ Dir.glob(NestedScenarios::Builder.fixtures_dir + '/**/scenarios.rb').each do |scenario_rb|
14
+ puts "Reading #{scenario_rb.gsub(RAILS_ROOT, '')} scenario file:"
15
+ require scenario_rb
16
+ puts
17
+ end
18
+ end
19
+ end
20
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: josevalim-nested_scenarios
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.1"
5
+ platform: ruby
6
+ authors:
7
+ - "Jos\xC3\xA9 Valim"
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-11-29 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: FixtureScenarios, FixtureScenariosBuilder, Yaml and Ruby in one big mix for Rails
17
+ email: jose.valim@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ files:
25
+ - MIT-LICENSE
26
+ - README
27
+ - Rakefile
28
+ - init.rb
29
+ - lib/join.rb
30
+ - lib/fixtures.rb
31
+ - lib/nested_scenarios.rb
32
+ - lib/builder.rb
33
+ - tasks/builder_tasks.rake
34
+ has_rdoc: true
35
+ homepage: http://github.com/josevalim/nested_scenarios
36
+ post_install_message:
37
+ rdoc_options:
38
+ - --main
39
+ - README
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: "0"
47
+ version:
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
54
+ requirements: []
55
+
56
+ rubyforge_project:
57
+ rubygems_version: 1.2.0
58
+ signing_key:
59
+ specification_version: 2
60
+ summary: FixtureScenarios, FixtureScenariosBuilder, Yaml and Ruby in one big mix for Rails
61
+ test_files: []
62
+