blueprints 0.9.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/Gemfile +14 -1
- data/README.rdoc +3 -173
- data/Rakefile +32 -15
- data/blueprints.gemspec +2 -16
- data/lib/blueprints.rb +17 -10
- data/lib/blueprints/blueprint.rb +85 -41
- data/lib/blueprints/blueprint_name_proxy.rb +34 -0
- data/lib/blueprints/buildable.rb +53 -26
- data/lib/blueprints/context.rb +8 -0
- data/lib/blueprints/extensions.rb +22 -4
- data/lib/blueprints/extensions/rspec.rb +28 -14
- data/lib/blueprints/helper.rb +17 -9
- data/lib/blueprints/namespace.rb +20 -14
- data/lib/blueprints/railtie.rb +3 -0
- data/lib/blueprints/root_namespace.rb +16 -23
- data/lib/blueprints/version.rb +1 -1
- data/lib/generators/blueprints/model/model_generator.rb +29 -0
- data/spec/blueprints_spec.rb +18 -1
- data/spec/support/active_record/initializer.rb +9 -5
- data/spec/support/none/initializer.rb +4 -0
- data/spec/unit/active_record_spec.rb +58 -4
- data/spec/unit/blueprint_name_proxy_spec.rb +31 -0
- data/spec/unit/blueprint_spec.rb +160 -22
- data/spec/unit/blueprints_spec.rb +4 -4
- data/spec/unit/context_spec.rb +8 -1
- data/spec/unit/dependency_spec.rb +1 -5
- data/spec/unit/fixtures.rb +69 -47
- data/spec/unit/namespace_spec.rb +23 -5
- data/spec/unit/root_namespace_spec.rb +9 -0
- data/spec/unit/spec_helper.rb +3 -4
- data/test/blueprints_test.rb +18 -1
- data/test/test_helper.rb +1 -0
- data/test_all.sh +6 -33
- metadata +43 -276
- data/Gemfile.lock +0 -108
- data/lib/blueprints/database_cleaner_fix.rb +0 -9
- data/lib/blueprints/eval_context.rb +0 -51
- data/spec/unit/eval_context_spec.rb +0 -56
data/.gitignore
CHANGED
data/Gemfile
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
-
source
|
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
|
-
|
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
|
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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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!("
|
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(
|
25
|
-
s.add_runtime_dependency(
|
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
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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)).
|
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(
|
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.
|
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
|
155
|
+
rescue DatabaseCleaner::NoORMDetected
|
149
156
|
end
|
150
157
|
end
|
data/lib/blueprints/blueprint.rb
CHANGED
@@ -10,29 +10,13 @@ module Blueprints
|
|
10
10
|
def initialize(name, context, &block)
|
11
11
|
super(name, context)
|
12
12
|
|
13
|
-
|
14
|
-
@
|
15
|
-
@
|
16
|
-
@
|
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(
|
33
|
+
# @overload demolish(environment)
|
58
34
|
# Demolishes blueprint by calling demolish block.
|
59
|
-
# @param [
|
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(
|
38
|
+
def demolish(environment = nil, current_name = nil, &block)
|
62
39
|
if block
|
63
|
-
|
64
|
-
elsif
|
65
|
-
|
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
|
-
|
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
|
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
|
-
|
82
|
-
|
83
|
-
|
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
|