blueprints 0.9.0 → 1.0.0

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.
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