blueprints 0.5.1 → 0.6.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.
@@ -75,7 +75,11 @@ And if you also need it to depend on other blueprints:
75
75
 
76
76
  ...or...
77
77
 
78
- SomeModel.blueprint(:something, :associated_column => :@some_iv).depends_on(:some_blueprint) # I prefer this one
78
+ SomeModel.blueprint(:something, :associated_column => :@some_iv).depends_on(:some_blueprint)
79
+
80
+ ...or if name of associated blueprint and instance variable are same...
81
+
82
+ SomeModel.blueprint(:something, :associated_column => d(:some_blueprint)) # I prefer this one
79
83
 
80
84
  === Blueprints file
81
85
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.1
1
+ 0.6.0
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{blueprints}
8
- s.version = "0.5.1"
8
+ s.version = "0.6.0"
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-02-24}
12
+ s.date = %q{2010-05-25}
13
13
  s.description = %q{Another replacement for factories and fixtures. The library that lazy typists will love}
14
14
  s.email = %q{sinsiliux@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -59,7 +59,7 @@ Gem::Specification.new do |s|
59
59
  s.homepage = %q{http://github.com/sinsiliux/blueprints}
60
60
  s.rdoc_options = ["--charset=UTF-8"]
61
61
  s.require_paths = ["lib"]
62
- s.rubygems_version = %q{1.3.5}
62
+ s.rubygems_version = %q{1.3.7}
63
63
  s.summary = %q{Another replacement for factories and fixtures}
64
64
  s.test_files = [
65
65
  "spec/no_db/fixtures/fruit.rb",
@@ -80,7 +80,7 @@ Gem::Specification.new do |s|
80
80
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
81
81
  s.specification_version = 3
82
82
 
83
- if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
83
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
84
84
  else
85
85
  end
86
86
  else
@@ -70,6 +70,10 @@ module Blueprints
70
70
  @@orm.delete_tables(@@delete_policy, *tables)
71
71
  end
72
72
 
73
+ def self.warn(message, blueprint)
74
+ $stderr.puts("**WARNING** #{message}: '#{blueprint}'")
75
+ end
76
+
73
77
  protected
74
78
 
75
79
  # Loads blueprints file and creates blueprints from data it contains. Is run by setup method
@@ -1,5 +1,13 @@
1
1
  module Blueprints
2
2
  class Buildable
3
+ class Dependency < Struct.new(:name)
4
+ alias :to_sym :name
5
+
6
+ def iv_name
7
+ :"@#{name}"
8
+ end
9
+ end
10
+
3
11
  attr_reader :name
4
12
  attr_accessor :namespace
5
13
 
@@ -7,30 +15,53 @@ module Blueprints
7
15
  @name, parents = parse_name(name)
8
16
  depends_on(*parents)
9
17
 
18
+ Blueprints.warn("Overwriting existing blueprint", @name) if Namespace.root and Namespace.root.children[@name]
10
19
  Namespace.root.add_child(self) if Namespace.root
11
20
  end
12
21
 
13
22
  # Defines blueprint dependencies. Used internally, but can be used externally too.
14
23
  def depends_on(*scenarios)
15
24
  @parents = (@parents || []) + scenarios.map{|s| s.to_sym}
25
+ self
16
26
  end
17
27
 
18
28
  # Builds dependencies of blueprint and then blueprint itself.
19
- def build(options = {})
20
- namespace = self
21
- namespace.build_parent_plans while namespace = namespace.namespace
22
- build_parent_plans
29
+ def build(build_once = true, options = {})
30
+ each_namespace {|namespace| namespace.build_parents }
31
+ build_parents
32
+
23
33
  Namespace.root.context.options = options
24
- build_plan.tap { Namespace.root.context.options = {} }
34
+ Namespace.root.context.attributes = attributes.merge(options)
35
+ each_namespace {|namespace| Namespace.root.context.attributes.reverse_merge! namespace.attributes }
36
+
37
+ build_self(build_once).tap do
38
+ Namespace.root.context.options = {}
39
+ Namespace.root.context.attributes = {}
40
+ end
41
+ end
42
+
43
+ def attributes(value = nil)
44
+ if value
45
+ raise value.inspect + @name if @name == ''
46
+ @attributes = value
47
+ self
48
+ else
49
+ @attributes ||= {}
50
+ end
25
51
  end
26
52
 
27
53
  protected
28
54
 
55
+ def each_namespace
56
+ namespace = self
57
+ yield(namespace) while namespace = namespace.namespace
58
+ end
59
+
29
60
  def path
30
61
  @path = (namespace.path + "_" unless namespace.nil? or namespace.path.empty?).to_s + @name.to_s
31
62
  end
32
63
 
33
- def build_parent_plans
64
+ def build_parents
34
65
  @parents.each do |p|
35
66
  parent = begin
36
67
  namespace[p]
@@ -1,7 +1,7 @@
1
1
  module Blueprints
2
2
  # Class that blueprint blocks are evaluated against. Allows you to access options that were passed to build method.
3
3
  class Context
4
- attr_accessor :options
4
+ attr_accessor :options, :attributes
5
5
 
6
6
  def build(*names)
7
7
  Namespace.root.build(*names)
@@ -52,26 +52,36 @@ module Blueprints
52
52
  # The second form is used when you want to define new blueprint. It takes first argument as name of blueprint
53
53
  # and second one as hash of attributes. As you cannot use instance variables outside of blueprint block, you need
54
54
  # to prefix them with colon. So the example above could be rewritten like this:
55
+ # Post.blueprint(:post, :title => 'first post', :text => 'My first post', :user => d(:user))
56
+ # or like this:
55
57
  # Post.blueprint(:post, :title => 'first post', :text => 'My first post', :user => :@user).depends_on(:user)
56
58
  # or like this:
57
59
  # Post.blueprint({:post => :user}, :title => 'first post', :text => 'My first post', :user => :@user)
58
60
  def blueprint(*args)
59
- attributes = args.extract_options!
61
+ attrs = args.pop
60
62
  if args.present?
61
63
  klass = self
62
- Blueprints::Plan.new(*args) do
63
- klass.blueprint attributes.merge(options)
64
- end
64
+ blueprint = Blueprints::Plan.new(*args) { klass.blueprint attributes }
65
+ blueprint.depends_on(*attrs.values.select {|attr| attr.is_a?(Blueprints::Buildable::Dependency) }).attributes(attrs)
66
+ blueprint
65
67
  else
66
- returning(self.new) { |object| object.blueprint(attributes) }
68
+ if attrs.is_a?(Array)
69
+ attrs.collect { |attr| blueprint(attr) }
70
+ else
71
+ returning(self.new) { |object| object.blueprint(attrs) }
72
+ end
67
73
  end
68
74
  end
69
75
  end
70
76
 
71
77
  module Instance
72
- # Updates attributes of object and calls save!. Bypasses attr_protected anduep attr_accessible.
78
+ # Updates attributes of object and calls save!. Bypasses attr_protected and attr_accessible.
73
79
  def blueprint(attributes)
74
- attributes.each {|attr, value| attributes[attr] = Blueprints::Namespace.root.context.instance_variable_get(value) if value.is_a? Symbol and value.to_s =~ /^@.+$/ }
80
+ attributes.each do |attr, value|
81
+ iv_name = value.iv_name if value.is_a?(Blueprints::Buildable::Dependency)
82
+ iv_name = value if value.is_a? Symbol and value.to_s =~ /^@.+$/
83
+ attributes[attr] = Blueprints::Namespace.root.context.instance_variable_get(iv_name) if iv_name
84
+ end
75
85
  send(:attributes=, attributes, false)
76
86
  save!
77
87
  end
@@ -14,6 +14,13 @@ module Blueprints
14
14
  yield
15
15
  old_namespace.add_child(namespace)
16
16
  Namespace.root = old_namespace
17
+ namespace
18
+ end
19
+
20
+ # Creates dependency for current blueprint on some other blueprint and marks that instance variable with same name
21
+ # should be used for value of column. Only works on "Class.blueprint :name" type of blueprints
22
+ def self.d(name)
23
+ Buildable::Dependency.new(name)
17
24
  end
18
25
  end
19
26
  end
@@ -11,11 +11,21 @@ module Blueprints
11
11
  #
12
12
  # # options can also be passed for several blueprints
13
13
  # build :pear, :apple => {:color => 'red'}, :orange => {:color => 'orange'}
14
- def build_plan(*names)
15
- returning(Namespace.root.build(*names)) { Namespace.root.copy_ivars(self) }
14
+ def build_blueprint(*names)
15
+ Namespace.root.build(names, self, true)
16
16
  end
17
17
 
18
- alias :build :build_plan
18
+ # Same as #build_blueprint except that you can use it to build same blueprint several times.
19
+ def build_blueprint!(*names)
20
+ Namespace.root.build(names, self, false)
21
+ end
22
+
23
+ def build_attributes(name)
24
+ Namespace.root[name].attributes
25
+ end
26
+
27
+ alias :build :build_blueprint
28
+ alias :build! :build_blueprint!
19
29
 
20
30
  # Clears all tables in database. You can pass table names to clear only those tables. You can also pass <tt>:undo</tt> option
21
31
  # to remove scenarios from built scenarios cache.
@@ -3,6 +3,7 @@ module Blueprints
3
3
  # all it's children.
4
4
  class Namespace < Buildable
5
5
  cattr_accessor :root
6
+ attr_reader :children
6
7
  delegate :empty?, :size, :to => :@children
7
8
 
8
9
  def initialize(name)
@@ -11,7 +12,6 @@ module Blueprints
11
12
  end
12
13
 
13
14
  def add_child(child)
14
- #TODO: Raise error for duplicate children!
15
15
  @children[child.name] = child
16
16
  child.namespace = self
17
17
  end
@@ -26,7 +26,7 @@ module Blueprints
26
26
  end
27
27
  end
28
28
 
29
- def build_plan
29
+ def build_self(build_once = true)
30
30
  Namespace.root.add_variable(path, @children.collect {|p| p.last.build }.uniq)
31
31
  end
32
32
  end
@@ -8,13 +8,17 @@ module Blueprints
8
8
  end
9
9
 
10
10
  # Builds plan and adds it to executed plan hash. Setups instance variable with same name as plan if it is not defined yet.
11
- def build_plan
12
- surface_errors do
13
- if @block
14
- @result = Namespace.root.context.instance_eval(&@block)
15
- Namespace.root.add_variable(path, @result)
11
+ def build_self(build_once = true)
12
+ if build_once and Namespace.root.executed_plans.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
16
20
  end
17
- end unless Namespace.root.executed_plans.include?(path)
21
+ end
18
22
  Namespace.root.executed_plans << path
19
23
  @result
20
24
  end
@@ -17,7 +17,7 @@ module Blueprints
17
17
  # Loads all instance variables from global context to current one.
18
18
  def setup
19
19
  @context = Blueprints::Context.new
20
- YAML.load(@global_variables).each { |name, value| @context.instance_variable_set(name, value) }
20
+ Marshal.load(@global_variables).each { |name, value| @context.instance_variable_set(name, value) }
21
21
  @executed_plans = @global_executed_plans.clone
22
22
  end
23
23
 
@@ -31,21 +31,25 @@ module Blueprints
31
31
  # Sets up global context and executes prebuilt blueprints against it.
32
32
  def prebuild(plans)
33
33
  @context = Blueprints::Context.new
34
- @global_scenarios = build(*plans) if plans
34
+ @global_scenarios = build(plans) if plans
35
35
  @global_executed_plans = @executed_plans
36
36
 
37
- @global_variables = YAML.dump(@context.instance_variables.each_with_object({}) {|iv, hash| hash[iv] = @context.instance_variable_get(iv) })
37
+ @global_variables = Marshal.dump(@context.instance_variables.each_with_object({}) {|iv, hash| hash[iv] = @context.instance_variable_get(iv) })
38
38
  end
39
39
 
40
- # Builds blueprints that are passed against current context.
41
- def build(*names)
42
- names.inject(nil) do |result, member|
40
+ # Builds blueprints that are passed against current context. Copies instance variables to context given if one is given.
41
+ def build(names, context = nil, build_once = true)
42
+ names = [names] unless names.is_a?(Array)
43
+ result = names.inject(nil) do |result, member|
43
44
  if member.is_a?(Hash)
44
- member.map {|name, options| self[name].build(options) }.last
45
+ member.map {|name, options| self[name].build(build_once, options) }.last
45
46
  else
46
- self[member].build
47
+ self[member].build(build_once)
47
48
  end
48
49
  end
50
+
51
+ copy_ivars(context) if context
52
+ result
49
53
  end
50
54
 
51
55
  # Sets instance variable in current context to passed value. If instance variable with same name already exists, it
@@ -25,7 +25,7 @@ blueprint :cherry do
25
25
  end
26
26
 
27
27
  blueprint :big_cherry => :cherry do
28
- Fruit.create! :species => @cherry.species, :average_diameter => 7
28
+ Fruit.create! options.reverse_merge(:species => @cherry.species, :average_diameter => 7)
29
29
  end
30
30
 
31
31
  blueprint :cherry_basket => [:big_cherry, :cherry] do
@@ -41,7 +41,7 @@ blueprint :pine do
41
41
  @the_pine = Tree.blueprint :name => 'Pine', :size => 'medium'
42
42
  end
43
43
 
44
- Fruit.blueprint(:acorn, :species => 'Acorn', :tree => :@oak).depends_on(:oak)
44
+ Fruit.blueprint(:acorn, :species => 'Acorn', :tree => d(:oak))
45
45
  blueprint :small_acorn do
46
46
  build :acorn => {:average_diameter => 1}
47
47
  end
@@ -60,3 +60,11 @@ end
60
60
  blueprint :apple_with_params do
61
61
  Fruit.create! options.reverse_merge(:species => 'apple')
62
62
  end
63
+
64
+ namespace :attributes do
65
+ blueprint :cherry do
66
+ Fruit.blueprint attributes
67
+ end.attributes(:species => 'cherry')
68
+
69
+ Fruit.blueprint :shortened_cherry, :species => 'cherry'
70
+ end.attributes(:average_diameter => 10, :species => 'fruit with attributes')
@@ -258,6 +258,12 @@ describe Blueprints do
258
258
  @oak.name.should == 'Oak'
259
259
  @oak.size.should == 'optional'
260
260
  end
261
+
262
+ it "should allow to pass array of hashes to blueprint method" do
263
+ Fruit.create
264
+ fruits = Fruit.blueprint([{:species => 'fruit1'}, {:species => 'fruit2'}])
265
+ fruits.collect(&:species).should == %w{fruit1 fruit2}
266
+ end
261
267
  end
262
268
 
263
269
  describe "with pitted namespace" do
@@ -364,5 +370,45 @@ describe Blueprints do
364
370
  @huge_acorn.tree.size.should == 'huge'
365
371
  end
366
372
  end
373
+
374
+ it "should allow to build! without checking if it was already built" do
375
+ build! :big_cherry, :big_cherry => {:species => 'not so big cherry'}
376
+ Fruit.count.should == 4
377
+ Fruit.find_by_species('not so big cherry').should_not be_nil
378
+ end
379
+
380
+ it "should warn when blueprint with same name exists" do
381
+ $stderr.expects(:puts).with("**WARNING** Overwriting existing blueprint: 'overwritten'")
382
+ Blueprints::Plan.new(:overwritten)
383
+ Blueprints::Plan.new(:overwritten)
384
+ end
385
+
386
+ it "should warn when building with options and blueprint is already built" do
387
+ STDERR.expects(:puts).with("**WARNING** Building with options, but blueprint was already built: 'big_cherry'")
388
+ build :big_cherry => {:species => 'some species'}
389
+ end
390
+
391
+ describe 'attributes' do
392
+ it "should allow to extract attributes from blueprint" do
393
+ build_attributes('attributes.cherry').should == {:species => 'cherry'}
394
+ build_attributes('attributes.shortened_cherry').should == {:species => 'cherry'}
395
+ build_attributes(:big_cherry).should == {}
396
+ end
397
+
398
+ it "should use attributes when building" do
399
+ build 'attributes.cherry'
400
+ @attributes_cherry.species.should == 'cherry'
401
+ end
402
+
403
+ it "should automatically merge options to attributes" do
404
+ build 'attributes.cherry' => {:species => 'a cherry'}
405
+ @attributes_cherry.species.should == 'a cherry'
406
+ end
407
+
408
+ it "should reverse merge attributes from namespaces" do
409
+ build 'attributes.cherry'
410
+ @attributes_cherry.average_diameter.should == 10
411
+ end
412
+ end
367
413
  end
368
414
 
@@ -2,7 +2,7 @@ test:
2
2
  adapter: mysql
3
3
  socket: /var/run/mysqld/mysqld.sock
4
4
  encoding: utf8
5
- database: hornsby_test
5
+ database: blueprints_test
6
6
  username: root
7
7
  password:
8
- host: localhost
8
+ host: localhost
@@ -258,6 +258,12 @@ class BlueprintsTest < ActiveSupport::TestCase
258
258
  assert(@oak.name == 'Oak')
259
259
  assert(@oak.size == 'optional')
260
260
  end
261
+
262
+ should "allow to pass array of hashes to blueprint method" do
263
+ Fruit.create
264
+ fruits = Fruit.blueprint([{:species => 'fruit1'}, {:species => 'fruit2'}])
265
+ assert(fruits.collect(&:species) == %w{fruit1 fruit2})
266
+ end
261
267
  end
262
268
 
263
269
  context "with pitted namespace" do
@@ -364,5 +370,45 @@ class BlueprintsTest < ActiveSupport::TestCase
364
370
  assert(@huge_acorn.tree.size == 'huge')
365
371
  end
366
372
  end
373
+
374
+ should "allow to build! without checking if it was already built" do
375
+ build! :big_cherry, :big_cherry => {:species => 'not so big cherry'}
376
+ assert(Fruit.count == 4)
377
+ assert(!(Fruit.find_by_species('not so big cherry').nil?))
378
+ end
379
+
380
+ should "warn when blueprint with same name exists" do
381
+ $stderr.expects(:puts).with("**WARNING** Overwriting existing blueprint: 'overwritten'")
382
+ Blueprints::Plan.new(:overwritten)
383
+ Blueprints::Plan.new(:overwritten)
384
+ end
385
+
386
+ should "warn when building with options and blueprint is already built" do
387
+ STDERR.expects(:puts).with("**WARNING** Building with options, but blueprint was already built: 'big_cherry'")
388
+ build :big_cherry => {:species => 'some species'}
389
+ end
390
+
391
+ context 'attributes' do
392
+ should "allow to extract attributes from blueprint" do
393
+ assert(build_attributes('attributes.cherry') == {:species => 'cherry'})
394
+ assert(build_attributes('attributes.shortened_cherry') == {:species => 'cherry'})
395
+ assert(build_attributes(:big_cherry) == {})
396
+ end
397
+
398
+ should "use attributes when building" do
399
+ build 'attributes.cherry'
400
+ assert(@attributes_cherry.species == 'cherry')
401
+ end
402
+
403
+ should "automatically merge options to attributes" do
404
+ build 'attributes.cherry' => {:species => 'a cherry'}
405
+ assert(@attributes_cherry.species == 'a cherry')
406
+ end
407
+
408
+ should "reverse merge attributes from namespaces" do
409
+ build 'attributes.cherry'
410
+ assert(@attributes_cherry.average_diameter == 10)
411
+ end
412
+ end
367
413
  end
368
414
 
metadata CHANGED
@@ -1,7 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blueprints
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ hash: 7
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 6
9
+ - 0
10
+ version: 0.6.0
5
11
  platform: ruby
6
12
  authors:
7
13
  - Andrius Chamentauskas
@@ -9,7 +15,7 @@ autorequire:
9
15
  bindir: bin
10
16
  cert_chain: []
11
17
 
12
- date: 2010-02-24 00:00:00 +02:00
18
+ date: 2010-05-25 00:00:00 +03:00
13
19
  default_executable:
14
20
  dependencies: []
15
21
 
@@ -71,21 +77,27 @@ rdoc_options:
71
77
  require_paths:
72
78
  - lib
73
79
  required_ruby_version: !ruby/object:Gem::Requirement
80
+ none: false
74
81
  requirements:
75
82
  - - ">="
76
83
  - !ruby/object:Gem::Version
84
+ hash: 3
85
+ segments:
86
+ - 0
77
87
  version: "0"
78
- version:
79
88
  required_rubygems_version: !ruby/object:Gem::Requirement
89
+ none: false
80
90
  requirements:
81
91
  - - ">="
82
92
  - !ruby/object:Gem::Version
93
+ hash: 3
94
+ segments:
95
+ - 0
83
96
  version: "0"
84
- version:
85
97
  requirements: []
86
98
 
87
99
  rubyforge_project:
88
- rubygems_version: 1.3.5
100
+ rubygems_version: 1.3.7
89
101
  signing_key:
90
102
  specification_version: 3
91
103
  summary: Another replacement for factories and fixtures