blueprints 0.7.2 → 0.7.3

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/VERSION CHANGED
@@ -1 +1 @@
1
- 0.7.2
1
+ 0.7.3
data/blueprints.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{blueprints}
8
- s.version = "0.7.2"
8
+ s.version = "0.7.3"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Andrius Chamentauskas"]
12
- s.date = %q{2010-07-12}
12
+ s.date = %q{2010-07-17}
13
13
  s.default_executable = %q{blueprintify}
14
14
  s.description = %q{Another replacement for factories and fixtures. The library that lazy typists will love}
15
15
  s.email = %q{sinsiliux@gmail.com}
data/lib/blueprints.rb CHANGED
@@ -59,7 +59,6 @@ module Blueprints
59
59
  root_sub = /^#{config.root}[\\\/]/
60
60
  blueprints_path = File.dirname(__FILE__).sub(root_sub, '')
61
61
 
62
- bc.add_filter {|line| line.sub('(eval)', @@file) }
63
62
  bc.add_filter {|line| line.sub(root_sub, '') }
64
63
 
65
64
  bc.add_silencer {|line| File.dirname(line).starts_with?(blueprints_path) }
@@ -68,31 +67,20 @@ module Blueprints
68
67
  end
69
68
 
70
69
  def self.warn(message, blueprint)
71
- $stderr.puts("**WARNING** #{message}: '#{blueprint}'")
72
- $stderr.puts(backtrace_cleaner.clean(caller).first)
70
+ $stderr.puts("**WARNING** #{message}: '#{blueprint.name}'")
71
+ $stderr.puts(backtrace_cleaner.clean(blueprint.backtrace(caller)).first)
73
72
  end
74
73
 
75
74
  protected
76
75
 
77
76
  # Loads blueprints file and creates blueprints from data it contains. Is run by setup method
78
- def self.load_scenarios_files(*patterns)
79
- FileContext.evaluating = true
80
-
81
- patterns.flatten!
82
- patterns.collect! {|pattern| File.join(config.root, pattern)} if config.root
83
-
77
+ def self.load_scenarios_files(patterns)
84
78
  patterns.each do |pattern|
85
- unless (files = Dir.glob(pattern)).empty?
86
- files.each do |f|
87
- @@file = f
88
- FileContext.module_eval File.read(f)
89
- end
90
- FileContext.evaluating = false
91
- return
92
- end
79
+ pattern = config.root.join(pattern)
80
+ Dir[pattern].each {|f| FileContext.new f }
81
+ return if Dir[pattern].size > 0
93
82
  end
94
83
 
95
- FileContext.evaluating = false
96
84
  raise "Blueprints file not found! Put blueprints in #{patterns.join(' or ')} or pass custom filename pattern with :filename option"
97
85
  end
98
86
  end
@@ -1,26 +1,17 @@
1
1
  module Blueprints
2
2
  # Class for actual blueprints. Allows building itself by executing block passed against current context.
3
3
  class Blueprint < Buildable
4
+ attr_reader :file
4
5
  # Initializes blueprint by name and block
5
- def initialize(name, &block)
6
+ def initialize(name, file, &block)
7
+ @file = file
6
8
  super(name)
7
9
  @block = block
8
10
  end
9
11
 
10
12
  # Builds blueprint and adds it to executed blueprint hash. Setups instance variable with same name as blueprint if it is not defined yet.
11
13
  def build_self(build_once = true)
12
- if build_once and Namespace.root.executed_blueprints.include?(path)
13
- Blueprints.warn("Building with options, but blueprint was already built", @name) if Namespace.root.context.options.present?
14
- else
15
- surface_errors do
16
- if @block
17
- @result = Namespace.root.context.instance_eval(&@block)
18
- Namespace.root.add_variable(path, @result)
19
- end
20
- end
21
- end
22
- Namespace.root.executed_blueprints << path
23
- @result
14
+ surface_errors { @result = Namespace.root.context.instance_eval(&@block) if @block }
24
15
  end
25
16
 
26
17
  # Changes blueprint block to build another blueprint by passing additional options to it. Usually used to dry up
@@ -30,12 +21,16 @@ module Blueprints
30
21
  @block = Proc.new { build parent => attributes }
31
22
  end
32
23
 
24
+ def backtrace(trace)
25
+ trace.collect {|line| line.sub(/^\(eval\):(\d+).*/, "#{@file}:\\1:in blueprint '#{@name}'") }
26
+ end
27
+
33
28
  private
34
29
 
35
30
  def surface_errors
36
31
  yield
37
32
  rescue StandardError => error
38
- puts "\n=> There was an error building scenario '#{@name}'", error.inspect, '', error.backtrace
33
+ error.set_backtrace(backtrace(error.backtrace))
39
34
  raise error
40
35
  end
41
36
  end
@@ -19,7 +19,7 @@ module Blueprints
19
19
  @name, parents = parse_name(name)
20
20
  depends_on(*parents)
21
21
 
22
- Blueprints.warn("Overwriting existing blueprint", @name) if Namespace.root and Namespace.root.children[@name]
22
+ Blueprints.warn("Overwriting existing blueprint", self) if Namespace.root and Namespace.root.children[@name]
23
23
  Namespace.root.add_child(self) if Namespace.root
24
24
  end
25
25
 
@@ -35,39 +35,48 @@ module Blueprints
35
35
  #
36
36
  # +options+ - list of options to be accessible in the body of a blueprint. Defaults to empty Hash.
37
37
  def build(build_once = true, options = {})
38
+ if build_once and Namespace.root.executed_blueprints.include?(path)
39
+ Blueprints.warn("Building with options, but blueprint was already built", self) if options.present?
40
+ return @result
41
+ end
42
+ Namespace.root.executed_blueprints << path
43
+
38
44
  each_namespace {|namespace| namespace.build_parents }
39
45
  build_parents
40
46
 
41
47
  old_options, old_attributes = Namespace.root.context.options, Namespace.root.context.attributes
42
- Namespace.root.context.options, Namespace.root.context.attributes = options, attributes.merge(options)
43
- each_namespace {|namespace| Namespace.root.context.attributes.reverse_merge! namespace.attributes }
48
+ Namespace.root.context.options, Namespace.root.context.attributes = options, normalized_attributes.merge(options)
49
+ each_namespace {|namespace| Namespace.root.context.attributes.reverse_merge! namespace.normalized_attributes }
44
50
 
45
- build_self(build_once).tap do
46
- Namespace.root.context.options, Namespace.root.context.attributes = old_options, old_attributes
47
- end
51
+ build_self(build_once)
52
+ Namespace.root.context.options, Namespace.root.context.attributes = old_options, old_attributes
53
+ Namespace.root.add_variable(path, @result)
48
54
  end
49
55
 
50
56
  # If value is passed then it sets attributes for this buildable object.
51
57
  # Otherwise returns attributes (defaulting to empty Hash)
52
- def attributes(value = nil)
53
- if value
54
- raise value.inspect + @name if @name == ''
55
- @attributes = value
56
- self
57
- else
58
- @attributes ||= {}
59
- end
58
+ def attributes(value)
59
+ @attributes = value
60
+ self
60
61
  end
61
62
 
62
- protected
63
-
64
- def each_namespace
65
- namespace = self
66
- yield(namespace) while namespace = namespace.namespace
63
+ # Returns normalized attributes for that particular blueprint.
64
+ def normalized_attributes
65
+ Buildable.normalize_attributes(@attributes ||= {})
67
66
  end
68
67
 
69
- def path
70
- @path = (namespace.path + "_" unless namespace.nil? or namespace.path.empty?).to_s + @name.to_s
68
+ # Normalizes attributes by changing all :@var to values of @var, and all dependencies to the result of that blueprint.
69
+ def self.normalize_attributes(attributes)
70
+ attributes = attributes.dup
71
+ attributes.each do |attr, value|
72
+ if value.is_a?(Blueprints::Buildable::Dependency)
73
+ iv_name = value.iv_name
74
+ Blueprints::Namespace.root.build(value.name)
75
+ end
76
+ iv_name = value if value.is_a? Symbol and value.to_s =~ /^@.+$/
77
+
78
+ attributes[attr] = Blueprints::Namespace.root.context.instance_variable_get(iv_name) if iv_name
79
+ end
71
80
  end
72
81
 
73
82
  def build_parents
@@ -82,6 +91,17 @@ module Blueprints
82
91
  end
83
92
  end
84
93
 
94
+ protected
95
+
96
+ def each_namespace
97
+ namespace = self
98
+ yield(namespace) while namespace = namespace.namespace
99
+ end
100
+
101
+ def path
102
+ @path = (namespace.path + "_" unless namespace.nil? or namespace.path.empty?).to_s + @name.to_s
103
+ end
104
+
85
105
  def parse_name(name)
86
106
  case name
87
107
  when Hash
@@ -2,11 +2,11 @@ module Blueprints
2
2
  class Configuration
3
3
  SUPPORTED_ORMS = [nil, :active_record]
4
4
  # Allows passing custom filename pattern in case blueprints are held in place other than spec/blueprint, test/blueprint, blueprint.
5
- attr_accessor :filename
5
+ attr_reader :filename
6
6
  # Allows passing scenarios that should be prebuilt and available in all tests. Works similarly to fixtures.
7
7
  attr_accessor :prebuild
8
8
  # Allows passing custom root folder to use in case of non rails project. Defaults to RAILS_ROOT or current folder if RAILS_ROOT is not defined.
9
- attr_accessor :root
9
+ attr_reader :root
10
10
  # By default blueprints runs each test in it's own transaction. This may sometimes be not desirable so this options allows to turn this off.
11
11
  attr_accessor :transactions
12
12
  # Returns ORM that is used, default is :active_record
@@ -14,7 +14,7 @@ module Blueprints
14
14
 
15
15
  # Sets default attributes for all attributes
16
16
  def initialize
17
- @filename = [nil, "spec", "test"].map do |dir|
17
+ self.filename = [nil, "spec", "test"].map do |dir|
18
18
  ["blueprint"].map do |file|
19
19
  path = File.join([dir, file].compact)
20
20
  ["#{path}.rb", File.join(path, "*.rb")]
@@ -23,11 +23,7 @@ module Blueprints
23
23
  @orm = :active_record
24
24
  @prebuild = []
25
25
  @transactions = true
26
- @root = if defined?(RAILS_ROOT)
27
- RAILS_ROOT
28
- else
29
- nil
30
- end
26
+ @root = defined?(Rails) ? Rails.root : Pathname.pwd
31
27
  end
32
28
 
33
29
  # Allows specifying what ORM should be used. See SUPPORTED_ORMS to check what values it can contain.
@@ -38,5 +34,13 @@ module Blueprints
38
34
  raise ArgumentError, "Unsupported ORM #{value.inspect}. Blueprints supports only #{SUPPORTED_ORMS.collect(&:inspect).join(', ')}"
39
35
  end
40
36
  end
37
+
38
+ def filename=(value)
39
+ @filename = Array(value).flatten.collect {|path| Pathname.new(path) }
40
+ end
41
+
42
+ def root=(value)
43
+ @root = Pathname.new(value)
44
+ end
41
45
  end
42
46
  end
@@ -17,10 +17,10 @@ module Blueprints
17
17
  # or like this:
18
18
  # Post.blueprint({:post => :user}, :title => 'first post', :text => 'My first post', :user => :@user)
19
19
  def blueprint(name_or_attrs, attrs = {})
20
- if Blueprints::FileContext.evaluating
20
+ if Blueprints::FileContext.current
21
21
  klass = self
22
- blueprint = Blueprints::Blueprint.new(name_or_attrs) { klass.blueprint attributes }
23
- blueprint.depends_on(*attrs.values.select {|attr| attr.is_a?(Blueprints::Buildable::Dependency) }).attributes(attrs)
22
+ blueprint = Blueprints::Blueprint.new(name_or_attrs, Blueprints::FileContext.current.file) { klass.blueprint attributes }
23
+ blueprint.attributes(attrs)
24
24
  blueprint
25
25
  else
26
26
  if name_or_attrs.is_a?(Array)
@@ -37,12 +37,7 @@ module Blueprints
37
37
  module InstanceMethods
38
38
  # Updates attributes of object and calls save!. Bypasses attr_protected and attr_accessible.
39
39
  def blueprint(attributes)
40
- attributes.each do |attr, value|
41
- iv_name = value.iv_name if value.is_a?(Blueprints::Buildable::Dependency)
42
- iv_name = value if value.is_a? Symbol and value.to_s =~ /^@.+$/
43
- attributes[attr] = Blueprints::Namespace.root.context.instance_variable_get(iv_name) if iv_name
44
- end
45
- send(:attributes=, attributes, false)
40
+ send(:attributes=, Blueprints::Blueprint.normalize_attributes(attributes.dup), false)
46
41
  save!
47
42
  end
48
43
  end
@@ -1,15 +1,25 @@
1
1
  module Blueprints
2
2
  # Module that blueprints file is executed against. Defined <tt>blueprint</tt> and <tt>namespace</tt> methods.
3
- module FileContext
4
- mattr_accessor :evaluating
3
+ class FileContext
4
+ @@current = nil
5
+ cattr_accessor :current
6
+ attr_reader :file
7
+
8
+ def initialize(file)
9
+ file = Pathname.new(file)
10
+ @file = file.relative_path_from(Blueprints.config.root)
11
+ FileContext.current = self
12
+ instance_eval(File.read(file))
13
+ FileContext.current = nil
14
+ end
5
15
 
6
16
  # Creates a new blueprint by name and block passed
7
- def self.blueprint(name, &block)
8
- Blueprint.new(name, &block)
17
+ def blueprint(name, &block)
18
+ Blueprint.new(name, @file, &block)
9
19
  end
10
20
 
11
21
  # Creates new namespace by name, and evaluates block against it.
12
- def self.namespace(name)
22
+ def namespace(name)
13
23
  old_namespace = Namespace.root
14
24
  namespace = Namespace.new(name)
15
25
  Namespace.root = namespace
@@ -21,7 +31,7 @@ module Blueprints
21
31
 
22
32
  # Creates dependency for current blueprint on some other blueprint and marks that instance variable with same name
23
33
  # should be used for value of column. Only works on "Class.blueprint :name" type of blueprints
24
- def self.d(name)
34
+ def d(name)
25
35
  Buildable::Dependency.new(name)
26
36
  end
27
37
  end
@@ -26,7 +26,8 @@ module Blueprints
26
26
  # Fruit.build attributes
27
27
  # end.attributes(:name => 'apple')
28
28
  def build_attributes(name)
29
- Namespace.root[name].attributes
29
+ Namespace.root[name].build_parents
30
+ Namespace.root[name].normalized_attributes.tap { Blueprints::Namespace.root.copy_ivars(self) }
30
31
  end
31
32
 
32
33
  alias :build :build_blueprint
@@ -45,7 +46,7 @@ module Blueprints
45
46
  if options[:undo] == :all
46
47
  Namespace.root.executed_blueprints.clear
47
48
  else
48
- undo = [options[:undo]].flatten.compact.collect {|bp| bp.to_s }
49
+ undo = [options[:undo]].flatten.compact.collect(&:to_s)
49
50
  unless (not_found = undo - Namespace.root.executed_blueprints.to_a).blank?
50
51
  raise(BlueprintNotFoundError, not_found)
51
52
  end
@@ -31,7 +31,7 @@ module Blueprints
31
31
 
32
32
  # Builds all children and sets instance variable named by name of namespace with the results.
33
33
  def build_self(build_once = true)
34
- Namespace.root.add_variable(path, @children.collect {|p| p.last.build }.uniq)
34
+ @result = @children.collect {|p| p.last.build }.uniq
35
35
  end
36
36
  end
37
37
  end
@@ -1,3 +1,7 @@
1
+ blueprint :error do
2
+ raise 'error'
3
+ end
4
+
1
5
  blueprint :apple do
2
6
  Fruit.create! :species => 'apple'
3
7
  end
@@ -69,4 +73,9 @@ namespace :attributes do
69
73
  end.attributes(:species => 'cherry')
70
74
 
71
75
  Fruit.blueprint :shortened_cherry, :species => 'cherry'
76
+
77
+ Fruit.blueprint :dependent_cherry1, :tree => d(:pine)
78
+ Fruit.blueprint(:dependent_cherry2, :tree => :@the_pine).depends_on(:pine)
72
79
  end.attributes(:average_diameter => 10, :species => 'fruit with attributes')
80
+
81
+ blueprint :circular_reference => :circular_reference
@@ -106,7 +106,7 @@ describe Blueprints do
106
106
  build :fruit
107
107
  demolish :undo => [:apple]
108
108
  Fruit.count.should == 0
109
- build :fruit
109
+ build :apple
110
110
  Fruit.count.should == 1
111
111
  end
112
112
 
@@ -178,7 +178,7 @@ describe Blueprints do
178
178
 
179
179
  it 'should raise TypeError when scenario name is not symbol or string' do
180
180
  lambda {
181
- Blueprints::Blueprint.new(1)
181
+ Blueprints::Blueprint.new(1, __FILE__)
182
182
  }.should raise_error(TypeError, "Pass blueprint names as strings or symbols only, cannot define blueprint 1")
183
183
  end
184
184
  end
@@ -211,6 +211,12 @@ describe Blueprints do
211
211
  @oak.reload.size.should == 'updated'
212
212
  end
213
213
 
214
+ it "should normalize attributes when updating with blueprint method" do
215
+ build :cherry, :oak
216
+ @cherry.blueprint(:tree => :@oak)
217
+ @cherry.tree.should == @oak
218
+ end
219
+
214
220
  it "should automatically merge passed options" do
215
221
  build :oak => {:size => 'optional'}
216
222
  @oak.name.should == 'Oak'
@@ -254,6 +260,7 @@ describe Blueprints do
254
260
  @pitted_acorn.should_not be_nil
255
261
  @pitted_red_apple.should_not be_nil
256
262
  @pitted.should =~ [@pitted_peach_tree, @pitted_peach, @pitted_acorn, [@pitted_red_apple]]
263
+ build(:pitted).should == @pitted
257
264
  end
258
265
 
259
266
  describe "with red namespace" do
@@ -353,8 +360,8 @@ describe Blueprints do
353
360
  it "should warn when blueprint with same name exists" do
354
361
  STDERR.expects(:puts).with("**WARNING** Overwriting existing blueprint: 'overwritten'")
355
362
  STDERR.expects(:puts).with(regexp_matches(/blueprints_(spec|test)\.rb:\d+:in `new'/))
356
- Blueprints::Blueprint.new(:overwritten)
357
- Blueprints::Blueprint.new(:overwritten)
363
+ Blueprints::Blueprint.new(:overwritten, __FILE__)
364
+ Blueprints::Blueprint.new(:overwritten, __FILE__)
358
365
  end
359
366
 
360
367
  it "should warn when building with options and blueprint is already built" do
@@ -384,6 +391,29 @@ describe Blueprints do
384
391
  build 'attributes.cherry'
385
392
  @attributes_cherry.average_diameter.should == 10
386
393
  end
394
+
395
+ it "should return build attributes for dependencies" do
396
+ attrs = build_attributes('attributes.dependent_cherry1')
397
+ @pine.should_not be_nil
398
+ attrs[:tree].should == @pine
399
+ end
400
+
401
+ it "should return build attributes for :@var" do
402
+ attrs = build_attributes('attributes.dependent_cherry2')
403
+ @pine.should_not be_nil
404
+ attrs[:tree].should == @pine
405
+ end
406
+ end
407
+
408
+ it "should not fail with circular reference" do
409
+ build :circular_reference
387
410
  end
388
- end
389
411
 
412
+ it "should rewrite trace" do
413
+ begin
414
+ build :error
415
+ rescue RuntimeError => e
416
+ e.backtrace[0].should == "spec/active_record/blueprint.rb:2:in blueprint 'error'"
417
+ end
418
+ end
419
+ end
@@ -31,7 +31,8 @@ config_class.configure do |config|
31
31
  end
32
32
 
33
33
  Blueprints.enable do |config|
34
- config.root = File.expand_path(File.join(File.dirname(__FILE__)))
34
+ config.root = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
35
+ config.filename = 'spec/active_record/blueprint.rb'
35
36
  config.prebuild = :big_cherry
36
37
  config.transactions = !ENV["NO_TRANSACTIONS"]
37
38
  end
@@ -6,20 +6,26 @@ describe Blueprints::Configuration do
6
6
  end
7
7
 
8
8
  it "should have filename with default value" do
9
- @config.filename.should == ["blueprint.rb", "blueprint/*.rb", "spec/blueprint.rb", "spec/blueprint/*.rb", "test/blueprint.rb", "test/blueprint/*.rb"]
9
+ @config.filename.should == %w{blueprint.rb blueprint/*.rb spec/blueprint.rb spec/blueprint/*.rb test/blueprint.rb test/blueprint/*.rb}.collect do |f|
10
+ Pathname.new(f)
11
+ end
10
12
  end
11
13
 
12
14
  it "should have correct attribute values" do
13
15
  @config.orm.should == :active_record
14
16
  @config.prebuild.should == []
15
17
  @config.transactions.should be_true
16
- @config.root.should be_nil
18
+ @config.root.should == Pathname.pwd
17
19
  end
18
20
 
19
- it "should use RAILS_ROOT for root if it's defined" do
20
- Object::RAILS_ROOT = 'rails/root'
21
- Blueprints::Configuration.new.root.should == 'rails/root'
22
- Object.send(:remove_const, :RAILS_ROOT)
21
+ it "should use Rails root for root if it's defined" do
22
+ module Rails
23
+ def self.root
24
+ Pathname.new('rails/root')
25
+ end
26
+ end
27
+ Blueprints::Configuration.new.root.should == Pathname.new('rails/root')
28
+ Object.send(:remove_const, :Rails)
23
29
  end
24
30
 
25
31
  it "should allow to set only supported orm" do
@@ -31,4 +37,14 @@ describe Blueprints::Configuration do
31
37
  @config.orm = :not_existing
32
38
  }.should raise_error(ArgumentError, 'Unsupported ORM :not_existing. Blueprints supports only nil, :active_record')
33
39
  end
40
+
41
+ it "should set root to pathname" do
42
+ @config.root = "root"
43
+ @config.root.should == Pathname.new("root")
44
+ end
45
+
46
+ it "should automatically set filename to array of path names" do
47
+ @config.filename = "my_file.rb"
48
+ @config.filename.should == [Pathname.new("my_file.rb")]
49
+ end
34
50
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blueprints
3
3
  version: !ruby/object:Gem::Version
4
- hash: 7
4
+ hash: 5
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 7
9
- - 2
10
- version: 0.7.2
9
+ - 3
10
+ version: 0.7.3
11
11
  platform: ruby
12
12
  authors:
13
13
  - Andrius Chamentauskas
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-07-12 00:00:00 +03:00
18
+ date: 2010-07-17 00:00:00 +03:00
19
19
  default_executable: blueprintify
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency