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