blueprints 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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