blueprints 0.9.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/.gitignore +1 -0
  2. data/Gemfile +14 -1
  3. data/README.rdoc +3 -173
  4. data/Rakefile +32 -15
  5. data/blueprints.gemspec +2 -16
  6. data/lib/blueprints.rb +17 -10
  7. data/lib/blueprints/blueprint.rb +85 -41
  8. data/lib/blueprints/blueprint_name_proxy.rb +34 -0
  9. data/lib/blueprints/buildable.rb +53 -26
  10. data/lib/blueprints/context.rb +8 -0
  11. data/lib/blueprints/extensions.rb +22 -4
  12. data/lib/blueprints/extensions/rspec.rb +28 -14
  13. data/lib/blueprints/helper.rb +17 -9
  14. data/lib/blueprints/namespace.rb +20 -14
  15. data/lib/blueprints/railtie.rb +3 -0
  16. data/lib/blueprints/root_namespace.rb +16 -23
  17. data/lib/blueprints/version.rb +1 -1
  18. data/lib/generators/blueprints/model/model_generator.rb +29 -0
  19. data/spec/blueprints_spec.rb +18 -1
  20. data/spec/support/active_record/initializer.rb +9 -5
  21. data/spec/support/none/initializer.rb +4 -0
  22. data/spec/unit/active_record_spec.rb +58 -4
  23. data/spec/unit/blueprint_name_proxy_spec.rb +31 -0
  24. data/spec/unit/blueprint_spec.rb +160 -22
  25. data/spec/unit/blueprints_spec.rb +4 -4
  26. data/spec/unit/context_spec.rb +8 -1
  27. data/spec/unit/dependency_spec.rb +1 -5
  28. data/spec/unit/fixtures.rb +69 -47
  29. data/spec/unit/namespace_spec.rb +23 -5
  30. data/spec/unit/root_namespace_spec.rb +9 -0
  31. data/spec/unit/spec_helper.rb +3 -4
  32. data/test/blueprints_test.rb +18 -1
  33. data/test/test_helper.rb +1 -0
  34. data/test_all.sh +6 -33
  35. metadata +43 -276
  36. data/Gemfile.lock +0 -108
  37. data/lib/blueprints/database_cleaner_fix.rb +0 -9
  38. data/lib/blueprints/eval_context.rb +0 -51
  39. data/spec/unit/eval_context_spec.rb +0 -56
data/.gitignore CHANGED
@@ -7,3 +7,4 @@ debug.log
7
7
  *.rbc
8
8
  doc
9
9
  .yardoc
10
+ Gemfile.lock
data/Gemfile CHANGED
@@ -1,3 +1,16 @@
1
- source :gemcutter
1
+ source 'http://rubygems.org'
2
2
 
3
3
  gemspec
4
+ gem 'rspec', '~> 2.0'
5
+ gem 'mysql2', '>= 0.2.0'
6
+ gem 'activerecord', '>= 3.0.0'
7
+ gem 'bson_ext', '>= 1.1.4'
8
+ gem 'mongoid', '>= 2.0.0'
9
+ gem 'mongo_mapper', '>= 0.9.0'
10
+ gem 'dm-migrations', '>= 1.0.0'
11
+ gem 'dm-transactions', '>= 1.0.0'
12
+ gem 'dm-mysql-adapter', '>= 1.0.0'
13
+ gem 'mocha'
14
+ gem 'shoulda'
15
+ gem 'cucumber'
16
+ gem 'bundler', '>= 1.0.0'
data/README.rdoc CHANGED
@@ -1,183 +1,13 @@
1
1
  = Blueprints
2
2
 
3
3
  Awesome replacement for factories and fixtures that focuses on being DRY and making developers type as little as possible.
4
-
5
- == Setup
6
-
7
- The easiest way to install this gem for Ruby on Rails is just add this line to your Gemfile:
8
-
9
- gem 'blueprints'
10
-
11
- If you're not using bundler, then you can install it through command line
12
-
13
- sudo gem install blueprints
14
-
15
- Lastly you could use it as plugin:
16
-
17
- ruby script/plugin install git://github.com/sinsiliux/blueprints.git
18
-
19
- Blueprints is activated by calling Blueprints.enable at the bottom of your spec_helper/test_helper. If you're using RSpec
20
- make sure you call Blueprints.enable after requiring RSpec, otherwise it will lead to strange behaviour. This method accepts
21
- block and yields Blueprints::Configuration object.
22
-
23
- These options can be set on blueprint configuration object:
24
- * root - custom framework root if automatic detection fails for some reason (eg. not rails/merb project)
25
- * filename - custom patterns of files that contain your blueprints (in case one of automatic ones doesn't fit your needs)
26
- * prebuild - list of blueprints that should be preloaded (available in all tests, never reloaded so they're much faster)
27
- * transactions - set this to false if you don't want to use transactions. This will severely slow the tests but sometimes transactions can't be used.
28
-
29
- Sample usage:
30
-
31
- Blueprints.enable do |config|
32
- config.filename = 'my_blueprints.rb'
33
- config.prebuild = :preloaded_blueprint
34
- end
35
-
36
- == Blueprints file
37
-
38
- Blueprints file is the file that contains all definitions of blueprints. This can either be single file or whole folder
39
- if you have many blueprints.
40
-
41
- By default blueprints are searched in these files in this particular order in application root (which is either Rails.root if it's defined or current folder by default):
42
- * blueprint.rb
43
- * blueprint/*.rb
44
- * spec/blueprint.rb
45
- * spec/blueprint/*.rb
46
- * test/blueprint.rb
47
- * test/blueprint/*.rb
48
- You can set root option to override application root and filename option to pass custom filename pattern.
49
-
50
- == Usage
51
-
52
- Definitions of blueprints look like this:
53
-
54
- blueprint :apple do
55
- Fruit.blueprint :species => 'apple'
56
- end
57
-
58
- blueprint :orange do
59
- Fruit.create! :species => 'orange'
60
- end
61
-
62
- blueprint :fruitbowl => [:apple, :orange] do
63
- @fruits = [@apple,@orange]
64
- FruitBowl.blueprint :fruits => @fruits
65
- end
66
-
67
- Kitchen.blueprint :kitchen, :fruitbowl => d(:fruitbowl)
68
-
69
- ...and you use them in specs/tests like this:
70
-
71
- describe Fruit, "apple" do
72
- before do
73
- build :apple
74
- end
75
-
76
- it "should be an apple" do
77
- @apple.species.should == 'apple'
78
- end
79
- end
80
-
81
- describe FruitBowl, "with and apple and an orange" do
82
- before do
83
- build :fruitbowl
84
- end
85
-
86
- it "should have 2 fruits" do
87
- @fruits.should == [@apple, @orange]
88
- @fruitbowl.should have(2).fruits
89
- end
90
- end
91
-
92
- Result of 'blueprint' block is assigned to an instance variable with the same name. You can also assign your own instance variables
93
- inside 'blueprint' block and they will be accessible in tests that build this blueprint.
94
-
95
- Instead of SomeModel.create! you can also use SomeModel.blueprint, which does the same thing but also bypasses attr_protected
96
- and attr_accessible restrictions (which is what you usually want in tests).
97
-
98
- All blueprints are run only once, no matter how many times they were called, meaning that you don't need to worry about
99
- duplicating data.
100
-
101
- === Shorthands
102
-
103
- There's a shorthand for these type of scenarios:
104
-
105
- blueprint :something do
106
- @something = SomeModel.blueprint :field => 'value'
107
- end
108
-
109
- You can just type:
110
-
111
- SomeModel.blueprint :something, :field => 'value'
112
-
113
- If you need to make associations then:
114
-
115
- SomeModel.blueprint(:something, :associated_column => d(:some_blueprint))
116
-
117
- ...or if the name of blueprint and the name of instance variable are not the same:
118
-
119
- SomeModel.blueprint(:something, :associated_column => d(:some_blueprint, :some_instance_variable))
120
-
121
- ...and when you need to pass options to associated blueprint:
122
-
123
- SomeModel.blueprint(:something, :associated_column => d(:some_blueprint, :option => 'value'))
124
-
125
- You can learn more about blueprint method in http://wiki.github.com/sinsiliux/blueprints/method-blueprint
126
-
127
- === Advanced Usage
128
-
129
- Its just ruby, right? So go nuts:
130
-
131
- 1.upto(9) do |i|
132
- blueprint("user_#{i}") do
133
- User.blueprint :name => "user#{i}"
134
- end
135
- end
136
-
137
- You can also read more about advanced usages in http://wiki.github.com/sinsiliux/blueprints/advanced-usages
138
-
139
- == Transactions
140
-
141
- Blueprints by default is transactional, meaning that before every test transaction is started and after every test that transaction is dropped
142
- which resets database to the state before the test. This state is empty database + any scenarios that you specify in enable_blueprints.
143
-
144
- Sometimes using transactions is not possible (eg. using MongoDB or in cucumber scenarios). In that case you can turn off transactions when
145
- enabling Blueprints. Be aware though that disabling transactions on relational databases is quite a major performance loss.
146
-
147
- == ORM support
148
-
149
- Blueprints is not tied to any ORM, however it does use Database Cleaner gem which currently supports Active Record, Data Mapper, Mongo Mapper, Mongoid and Couch Potato.
150
-
151
- === Active Record
152
-
153
- Blueprints support Active Record >= 2.3 (yes that includes 3.0). Lower versions are not supported due to lack of nested transactions,
154
- however they should probably work without transactions. Class and instance 'blueprint' method is added to all models.
155
-
156
- === Mongoid
157
-
158
- Blueprints was tested with Mongoid 2.0 only. It does support lower versions, but 'blueprint' method might not be available prior 2.0.
159
-
160
- === Mongo Mapper
161
-
162
- Tested with Mongo Mapper 0.8.4, but should work with all prior versions too. Class and instance 'blueprint' method is added to all models.
163
-
164
- === Data Mapper
165
-
166
- Is not fully supported (does not work with transactions). Maybe some Data Mapper guru can help me with that? Class and instance 'blueprint' method is added to all models.
167
-
168
- === Other ORMs and not ORMs
169
-
170
- If you're using some other ORM (except Couch Potato) you will need to manually clean database before all tests. If you want to have blueprint method in your
171
- models you should take a look at Blueprints::Extensions modules (I will gladly help adding support to other ORMs).
4
+ Blueprints allows you to create and manage data for tests in simple but poweful ways. For documentation please see
5
+ http://sinsiliux.github.com/blueprints.
172
6
 
173
7
  == Links
174
8
 
9
+ * Homepage: http://sinsiliux.github.com/blueprints
175
10
  * Official google group: http://groups.google.com/group/ruby-blueprints
176
- * Homepage: http://github.com/sinsiliux/blueprints
177
-
178
- == TODO
179
-
180
- * Add support for other test frameworks
181
11
 
182
12
  == Credits
183
13
 
data/Rakefile CHANGED
@@ -1,23 +1,21 @@
1
- require 'rubygems'
2
- require 'bundler'
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
3
  Bundler::GemHelper.install_tasks
4
4
 
5
- require 'rake/rdoctask'
6
- Rake::RDocTask.new do |rd|
7
- rd.main = "README.rdoc"
8
- rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
9
- end
10
-
11
5
  namespace :db do
12
6
  desc "Create database structure"
13
7
  task :prepare do
14
- require 'rubygems'
15
8
  require 'active_record'
9
+ require File.expand_path("../spec/support/active_record/initializer", __FILE__)
10
+ require File.expand_path("../spec/support/active_record/schema", __FILE__)
11
+ end
16
12
 
17
- Root = Pathname.new(__FILE__).dirname
18
- require Root.join("spec/support/active_record/initializer")
19
-
20
- load("spec/support/active_record/schema.rb")
13
+ desc "Copy all database.yml.example files to database.yml"
14
+ task :config do
15
+ Dir.glob('spec/support/*/database.yml.example').each do |example_yml|
16
+ database_yml = example_yml.sub(/\.example$/, '')
17
+ FileUtils.copy(example_yml, database_yml) unless File.exists?(database_yml)
18
+ end
21
19
  end
22
20
  end
23
21
 
@@ -26,7 +24,7 @@ task :rspec_to_test do
26
24
  Dir.chdir File.dirname(__FILE__)
27
25
  data = IO.read('spec/blueprints_spec.rb')
28
26
 
29
- data.gsub!("require File.dirname(__FILE__) + '/spec_helper'", "require File.dirname(__FILE__) + '/test_helper'")
27
+ data.gsub!("spec_helper", "test_helper")
30
28
  data.gsub!("describe Blueprints do", 'class BlueprintsTest < ActiveSupport::TestCase')
31
29
 
32
30
  # lambda {
@@ -54,5 +52,24 @@ task :rspec_to_test do
54
52
  data.gsub!(/^(\s+)before.*do/, '\1setup do')
55
53
  data.gsub!(/^(\s+)after.*do/, '\1teardown do')
56
54
 
57
- File.open('test/blueprints_test.rb', 'w') {|f| f.write(data)}
55
+ File.open('test/blueprints_test.rb', 'w') { |f| f.write(data) }
56
+ end
57
+
58
+ task :default => [:rspec_to_test, 'db:config', 'db:prepare'] do
59
+ commands = [
60
+ ["Unit specs", "rspec -c spec/unit/*_spec.rb"],
61
+ ["Active record integration", "rspec -c spec/blueprints_spec.rb"],
62
+ ["No ORM integration", "rspec -c spec/blueprints_spec.rb", 'none'],
63
+ ["Mongoid integration", "rspec -c spec/blueprints_spec.rb", 'mongoid'],
64
+ ["Mongo mapper integration", "rspec -c spec/blueprints_spec.rb", 'mongo_mapper'],
65
+ ["Test::Unit", "ruby test/blueprints_test.rb"],
66
+ ["Cucumber", "cucumber features/blueprints.feature -f progress"],
67
+ ]
68
+
69
+ statuses = commands.collect do |label, command, orm|
70
+ puts "#{label}:"
71
+ ENV['ORM'] = orm
72
+ system command
73
+ end
74
+ exit 1 unless statuses.all? { |status| status }
58
75
  end
data/blueprints.gemspec CHANGED
@@ -11,7 +11,6 @@ Gem::Specification.new do |s|
11
11
 
12
12
  s.authors = ["Andrius Chamentauskas"]
13
13
  s.email = %q{sinsiliux@gmail.com}
14
- s.default_executable = %q{blueprintify}
15
14
  s.homepage = %q{http://sinsiliux.github.com/blueprints}
16
15
  s.summary = %q{Awesome replacement for factories and fixtures}
17
16
  s.description = %q{Awesome replacement for factories and fixtures that focuses on being DRY and making developers type as little as possible.}
@@ -21,20 +20,7 @@ Gem::Specification.new do |s|
21
20
  s.files = `git ls-files`.split("\n")
22
21
  s.require_path = "lib"
23
22
 
24
- s.add_runtime_dependency(%q<activesupport>, [">= 2.3.0"])
25
- s.add_runtime_dependency(%q<database_cleaner>, ["~> 0.5.0"])
26
- s.add_development_dependency(%q<rspec>, ["~> 2.2.0"])
27
- s.add_development_dependency(%q<mysql2>)
28
- s.add_development_dependency(%q<activerecord>, [">= 2.3.0"])
29
- s.add_development_dependency(%q<bson_ext>, [">= 1.1.4"])
30
- s.add_development_dependency(%q<mongoid>, [">= 2.0.0.beta"])
31
- s.add_development_dependency(%q<mongo_mapper>, [">= 0.8.0"])
32
- s.add_development_dependency(%q<dm-migrations>, [">= 1.0.0"])
33
- s.add_development_dependency(%q<dm-transactions>, [">= 1.0.0"])
34
- s.add_development_dependency(%q<dm-mysql-adapter>, [">= 1.0.0"])
35
- s.add_development_dependency(%q<mocha>, [">= 0.9.8"])
36
- s.add_development_dependency(%q<shoulda>, [">= 2.10.0"])
37
- s.add_development_dependency(%q<cucumber>, [">= 0.7.0"])
38
- s.add_development_dependency(%q<bundler>, [">= 1.0.0"])
23
+ s.add_runtime_dependency("activesupport", ">= 2.3.0")
24
+ s.add_runtime_dependency("database_cleaner", "~> 0.6.1")
39
25
  end
40
26
 
data/lib/blueprints.rb CHANGED
@@ -10,10 +10,18 @@ require 'active_support/core_ext/enumerable'
10
10
  require 'database_cleaner'
11
11
  require 'set'
12
12
 
13
- files = %w{
14
- configuration context eval_context buildable namespace root_namespace blueprint helper errors dependency extensions database_cleaner_fix
15
- }
16
- files.each { |f| require "blueprints/#{f}" }
13
+ require 'blueprints/configuration'
14
+ require 'blueprints/context'
15
+ require 'blueprints/buildable'
16
+ require 'blueprints/namespace'
17
+ require 'blueprints/root_namespace'
18
+ require 'blueprints/blueprint'
19
+ require 'blueprints/helper'
20
+ require 'blueprints/errors'
21
+ require 'blueprints/dependency'
22
+ require 'blueprints/extensions'
23
+ require 'blueprints/blueprint_name_proxy'
24
+ require 'blueprints/railtie' if defined?(Rails)
17
25
 
18
26
  # Main namespace of blueprints. Contains methods for Blueprints setup.
19
27
  module Blueprints
@@ -26,8 +34,7 @@ module Blueprints
26
34
  # Setups variables from global context and starts transaction. Should be called before every test case.
27
35
  # @param current_context Object to copy instance variables for prebuilt blueprints/namespaces.
28
36
  def self.setup(current_context)
29
- Namespace.root.setup
30
- Namespace.root.eval_context.copy_instance_variables(current_context)
37
+ Namespace.root.setup(current_context)
31
38
  if_orm { DatabaseCleaner.start }
32
39
  end
33
40
 
@@ -74,7 +81,7 @@ module Blueprints
74
81
  blueprints_path = File.expand_path(File.dirname(__FILE__))
75
82
 
76
83
  bc.add_filter { |line| line.sub(root_sub, '') }
77
- bc.add_silencer { |line| [blueprints_path, *Gem.path].any? { |path| File.expand_path(File.dirname(line)).starts_with?(path) } }
84
+ bc.add_silencer { |line| [blueprints_path, *Gem.path].any? { |path| File.expand_path(File.dirname(line)).start_with?(path) } }
78
85
  end
79
86
  end
80
87
 
@@ -101,7 +108,7 @@ module Blueprints
101
108
  # @param [Blueprints::Blueprint] blueprint Name of blueprint that this occurred in.
102
109
  def self.warn(message, blueprint)
103
110
  $stderr.puts("**WARNING** #{message}: '#{blueprint.name}'")
104
- $stderr.puts(backtrace_cleaner.clean(blueprint.backtrace(caller)).first)
111
+ $stderr.puts(backtrace_cleaner.clean(caller).first)
105
112
  end
106
113
 
107
114
  protected
@@ -124,7 +131,7 @@ module Blueprints
124
131
 
125
132
  def self.each_blueprint(from = Namespace.root)
126
133
  enumerator_class.new do |enum|
127
- from.children.values.collect do |child|
134
+ from.children.collect do |child|
128
135
  if child.is_a?(Blueprints::Blueprint)
129
136
  enum.yield child
130
137
  else
@@ -145,6 +152,6 @@ module Blueprints
145
152
 
146
153
  def self.if_orm
147
154
  yield
148
- rescue DatabaseCleaner::NoORMDetected, DatabaseCleaner::NoStrategySetError
155
+ rescue DatabaseCleaner::NoORMDetected
149
156
  end
150
157
  end
@@ -10,29 +10,13 @@ module Blueprints
10
10
  def initialize(name, context, &block)
11
11
  super(name, context)
12
12
 
13
- ivname = variable_name
14
- @block = block
15
- @demolish_block = Proc.new { instance_variable_get(ivname).destroy }
16
- @update_block = Proc.new { instance_variable_get(ivname).blueprint(options) }
13
+ @strategies = {}
14
+ @strategies[:default] = block || Proc.new { dependencies.collect { |dep| instance_variable_get(:"@#{dep}") } }
15
+ @strategies[:demolish] = Proc.new { instance_variable_get(variable_name).destroy }
16
+ @strategies[:update] = Proc.new { instance_variable_get(variable_name).blueprint(options) }
17
17
  @uses = 0
18
18
  end
19
19
 
20
- # Builds blueprint and adds it to executed blueprint array. Setups instance variable with same name as blueprint if it is not defined yet.
21
- # Marks blueprint as used.
22
- # @param eval_context (see Buildable#build)
23
- # @param build_once (see Buildable#build)
24
- # @param options (see Buildable#build)
25
- def build_self(eval_context, build_once, options)
26
- @uses += 1 unless built?
27
- surface_errors do
28
- if built? and build_once
29
- eval_context.instance_eval(@context, options, &@update_block) if options.present?
30
- elsif @block
31
- result(eval_context) { eval_context.instance_eval(@context, options, &@block) }
32
- end
33
- end
34
- end
35
-
36
20
  # Changes blueprint block to build another blueprint by passing additional options to it. Usually used to dry up
37
21
  # blueprints that are often built with some options.
38
22
  # @example Extending blueprints
@@ -40,29 +24,22 @@ module Blueprints
40
24
  # blueprint(:published_post).extends(:post, :published_at => Time.now)
41
25
  # @param [Symbol, String] parent Name of parent blueprint.
42
26
  # @param [Hash] options Options to be passed when building parent.
43
- def extends(parent, options)
44
- attributes(options)
45
- @block = Proc.new { build parent => attributes }
46
- end
47
-
48
- # Changes backtrace to include what blueprint was being built.
49
- # @param [Array<String>] trace Current trace
50
- # @return [Array<String>] Changed trace with included currently built blueprint name.
51
- def backtrace(trace)
52
- trace.collect! { |line| line.sub(/^#{@context.file}:(\d+).*/, "#{@context.file}:\\1:in blueprint '#{@name}'") }
27
+ def extends(parent, options = {})
28
+ attributes(options).blueprint(:default) { build parent => attributes }
53
29
  end
54
30
 
55
31
  # @overload demolish(&block)
56
32
  # Sets custom block for demolishing this blueprint.
57
- # @overload demolish(eval_context)
33
+ # @overload demolish(environment)
58
34
  # Demolishes blueprint by calling demolish block.
59
- # @param [Blueprints::EvalContext] eval_context Context where blueprint was built in.
35
+ # @param [Object] environment Context where blueprint was built in.
36
+ # @param [Symbol] current_name Current name of blueprint (used when demolishing blueprints with regexp name). When nil is passed then @name is used.
60
37
  # @raise [Blueprints::DemolishError] If blueprint has not been built yet.
61
- def demolish(eval_context = nil, &block)
38
+ def demolish(environment = nil, current_name = nil, &block)
62
39
  if block
63
- @demolish_block = block
64
- elsif eval_context and built?
65
- eval_context.instance_eval(@context, {}, &@demolish_block)
40
+ blueprint(:demolish, &block)
41
+ elsif environment and built?
42
+ eval_block(environment, {}, current_name, &@strategies[:demolish])
66
43
  undo!
67
44
  else
68
45
  raise DemolishError, @name
@@ -71,16 +48,83 @@ module Blueprints
71
48
 
72
49
  # Allows customizing what happens when blueprint is already built and it's being built again.
73
50
  def update(&block)
74
- @update_block = block
51
+ blueprint(:update, &block)
52
+ end
53
+
54
+ # Defines strategy for this blueprint. Blueprint can later be built using this strategy by passing :strategy option
55
+ # to Buildable#build method.
56
+ # @param [#to_sym] name Name of strategy.
57
+ # @return [Blueprints::Blueprint] self.
58
+ def blueprint(name, &block)
59
+ @strategies[name.to_sym] = block
60
+ self
61
+ end
62
+
63
+ # Returns normalized attributes for this blueprint. Normalized means that all dependencies are replaced by real
64
+ # instances and all procs evaluated.
65
+ # @param environment Context that blueprints are built against
66
+ # @param [Hash] options Options hash, merged into attributes
67
+ # @return [Hash] normalized attributes for this blueprint
68
+ def normalized_attributes(environment, options = {})
69
+ normalize_hash(environment, @context.attributes.merge(options))
75
70
  end
76
71
 
77
72
  private
78
73
 
79
- def surface_errors
74
+ def build_self(environment, options)
75
+ @uses += 1 unless built?
76
+ opts = options[:options] || {}
77
+ strategy = (options[:strategy] || :default).to_sym
78
+ current_name = options[:name] || @name
79
+
80
+ if built? and not options[:rebuild]
81
+ eval_block(environment, opts, current_name, &@strategies[:update]) if opts.present?
82
+ elsif @strategies[strategy]
83
+ result(environment, current_name) { eval_block(environment, opts, current_name, &@strategies[strategy]) }
84
+ end
85
+ end
86
+
87
+ def eval_block(environment, options, current_name, &block)
88
+ with_method(environment, :options, options = normalize_hash(environment, options)) do
89
+ with_method(environment, :attributes, normalized_attributes(environment, options)) do
90
+ with_method(environment, :variable_name, variable_name(current_name)) do
91
+ with_method(environment, :dependencies, dependencies) do
92
+ environment.instance_eval(&block)
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ def normalize_hash(environment, hash)
100
+ hash.each_with_object({}) do |(attr, value), normalized|
101
+ normalized[attr] = if value.respond_to?(:to_proc) and not Symbol === value
102
+ environment.instance_exec(&value)
103
+ else
104
+ value
105
+ end
106
+ end
107
+ end
108
+
109
+ def with_method(environment, name, value)
110
+ old_method = nil
111
+ environment.singleton_class.class_eval do
112
+ if method_defined?(name)
113
+ old_method = environment.method(name)
114
+ if old_method.owner == self
115
+ remove_method(name)
116
+ else
117
+ old_method = nil
118
+ end
119
+ end
120
+ define_method(name) { value }
121
+ end
80
122
  yield
81
- rescue StandardError => error
82
- backtrace(error.backtrace)
83
- raise error
123
+ ensure
124
+ environment.singleton_class.class_eval do
125
+ remove_method(name)
126
+ define_method(name, old_method) if old_method
127
+ end
84
128
  end
85
129
  end
86
130
  end