blueprints 0.3.4 → 0.4.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 CHANGED
@@ -1,4 +1,5 @@
1
1
  .idea
2
2
  *.gem
3
+ html
3
4
  spec/active_record/fixtures/database.yml
4
5
  debug.log
data/Rakefile CHANGED
@@ -9,7 +9,13 @@ begin
9
9
  gemspec.homepage = "http://github.com/sinsiliux/blueprints"
10
10
  gemspec.authors = ["Andrius Chamentauskas"]
11
11
  end
12
- Jeweler::GemcutterTasks.new
12
+ Jeweler::GemcutterTasks.new
13
13
  rescue LoadError
14
14
  puts "Jeweler not available. Install it with: sudo gem install jeweler"
15
15
  end
16
+
17
+ require 'rake/rdoctask'
18
+ Rake::RDocTask.new do |rd|
19
+ rd.main = "README.rdoc"
20
+ rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
21
+ end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.4
1
+ 0.4.0
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.3.4"
8
+ s.version = "0.4.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{2009-12-10}
12
+ s.date = %q{2009-12-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 = [
@@ -27,6 +27,7 @@ Gem::Specification.new do |s|
27
27
  "install.rb",
28
28
  "lib/blueprints.rb",
29
29
  "lib/blueprints/buildable.rb",
30
+ "lib/blueprints/context.rb",
30
31
  "lib/blueprints/database_backends/abstract.rb",
31
32
  "lib/blueprints/database_backends/active_record.rb",
32
33
  "lib/blueprints/database_backends/none.rb",
data/lib/blueprints.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  require 'activesupport'
2
2
  files = %w{
3
- buildable namespace root_namespace plan file_context helper errors
3
+ context buildable namespace root_namespace plan file_context helper errors
4
4
  database_backends/abstract database_backends/active_record database_backends/none
5
5
  }
6
6
  files << if defined? Spec or $0 =~ /script.spec$/
@@ -18,24 +18,37 @@ module Blueprints
18
18
  end
19
19
  end.flatten
20
20
 
21
+ # Returns a list of supported ORMs. For now it supports ActiveRecord and None.
21
22
  def self.supported_orms
22
23
  (DatabaseBackends.constants - ['Abstract']).collect {|class_name| class_name.underscore.to_sym }
23
24
  end
24
25
 
26
+ # Returns root of project blueprints is used in. Automatically detected for rails and merb. Can be overwritten by using
27
+ # <tt>:root</tt> options when loading blueprints. If root can't be determined, returns nil which means that current
28
+ # directory is asumed as root.
25
29
  def self.framework_root
26
30
  @@framework_root ||= RAILS_ROOT rescue Rails.root rescue Merb.root rescue nil
27
31
  end
28
32
 
33
+ # Setups variables from global context and starts transaction. Should be called before every test case.
29
34
  def self.setup(current_context)
30
35
  Namespace.root.setup
31
36
  Namespace.root.copy_ivars(current_context)
32
37
  @@orm.start_transaction
33
38
  end
34
39
 
40
+ # Rollbacks transaction returning everything to state before test. Should be called after every test case.
35
41
  def self.teardown
36
42
  @@orm.rollback_transaction
37
43
  end
38
44
 
45
+ # Sets up configuration, clears database, runs scenarios that have to be prebuilt. Should be run before all test cases and before <tt>setup</tt>.
46
+ # Accepts following options:
47
+ # * <tt>:delete_policy</tt> - allows changing how tables in database should be cleared. By default simply uses delete statement. Supports :delete and :truncate options.
48
+ # * <tt>:filename</tt> - Allows passing custom filename pattern in case blueprints are held in place other than spec/blueprint, test/blueprint, blueprint.
49
+ # * <tt>:prebuild</tt> - Allows passing scenarios that should be prebuilt and available in all tests. Works similarly to fixtures.
50
+ # * <tt>:root</tt> - Allows passing custom root folder to use in case of non rails and non merb project.
51
+ # * <tt>:orm</tt> - Allows specifying what orm should be used. Default to <tt>:active_record</tt>, also allows <tt>:none</tt>
39
52
  def self.load(options = {})
40
53
  options.assert_valid_keys(:delete_policy, :filename, :prebuild, :root, :orm)
41
54
  options.symbolize_keys!
@@ -52,6 +65,14 @@ module Blueprints
52
65
  Namespace.root.prebuild(options[:prebuild])
53
66
  end
54
67
 
68
+ # Clears all tables in database. Also accepts a list of tables in case not all tables should be cleared.
69
+ def self.delete_tables(*tables)
70
+ @@orm.delete_tables(@@delete_policy, *tables)
71
+ end
72
+
73
+ protected
74
+
75
+ # Loads blueprints file and creates blueprints from data it contains. Is run by setup method
55
76
  def self.load_scenarios_files(*patterns)
56
77
  patterns.flatten!
57
78
  patterns.collect! {|pattern| File.join(framework_root, pattern)} if framework_root
@@ -65,8 +86,4 @@ module Blueprints
65
86
 
66
87
  raise "Plans file not found! Put plans in #{patterns.join(' or ')} or pass custom filename pattern with :filename option"
67
88
  end
68
-
69
- def self.delete_tables(*tables)
70
- @@orm.delete_tables(@@delete_policy, *tables)
71
- end
72
89
  end
@@ -10,10 +10,12 @@ module Blueprints
10
10
  Namespace.root.add_child(self) if Namespace.root
11
11
  end
12
12
 
13
+ # Defines blueprint dependencies. Used internally, but can be used externally too.
13
14
  def depends_on(*scenarios)
14
15
  @parents = (@parents || []) + scenarios.map{|s| s.to_sym}
15
16
  end
16
17
 
18
+ # Builds dependencies of blueprint and then blueprint itself.
17
19
  def build
18
20
  namespace = self
19
21
  namespace.build_parent_plans while namespace = namespace.namespace
@@ -51,4 +53,4 @@ module Blueprints
51
53
  end
52
54
  end
53
55
  end
54
- end
56
+ end
@@ -0,0 +1,6 @@
1
+ module Blueprints
2
+ # Class that blueprint blocks are evaluated against. Allows you to access options that were passed to build method.
3
+ class Context
4
+ attr_accessor :options
5
+ end
6
+ end
@@ -1,14 +1,18 @@
1
1
  module Blueprints
2
2
  module DatabaseBackends
3
3
  class Abstract
4
+ # Method to start transaction. Needs to be implemented in child class.
4
5
  def start_transaction
5
6
  raise NotImplementedError
6
7
  end
7
8
 
9
+ # Method to revert transaction. Needs to be implemented in child class.
8
10
  def revert_transaction
9
11
  raise NotImplementedError
10
12
  end
11
13
 
14
+ # Method to clear tables. Should accept delete policy and list of tables to delete. If list of tables is empty, should
15
+ # delete all tables. Needs to be implemented in child class.
12
16
  def delete_tables(delete_policy, *args)
13
17
  raise NotImplementedError
14
18
  end
@@ -3,21 +3,26 @@ module Blueprints
3
3
  class ActiveRecord
4
4
  DELETE_POLICIES = {:delete => "DELETE FROM %s", :truncate => "TRUNCATE %s"}
5
5
 
6
+ # Extends active record with blueprint method
6
7
  def initialize
7
- ::ActiveRecord::Base.extend(ActiveRecordExtensions)
8
+ ::ActiveRecord::Base.send(:include, ActiveRecordExtensions::Instance)
9
+ ::ActiveRecord::Base.extend(ActiveRecordExtensions::Class)
8
10
  end
9
11
 
12
+ # Starts new transaction and marks it as unjoinable so that test case could use transactions too.
10
13
  def start_transaction
11
14
  ::ActiveRecord::Base.connection.increment_open_transactions
12
15
  ::ActiveRecord::Base.connection.transaction_joinable = false
13
16
  ::ActiveRecord::Base.connection.begin_db_transaction
14
17
  end
15
18
 
19
+ # Rollbacks transaction
16
20
  def rollback_transaction
17
21
  ::ActiveRecord::Base.connection.rollback_db_transaction
18
22
  ::ActiveRecord::Base.connection.decrement_open_transactions
19
23
  end
20
24
 
25
+ # Clears all tables using delete policy specified. Also accepts list of tables to delete.
21
26
  def delete_tables(delete_policy, *args)
22
27
  delete_policy ||= :delete
23
28
  raise ArgumentError, "Unknown delete policy #{delete_policy}" unless DELETE_POLICIES.keys.include?(delete_policy)
@@ -25,30 +30,52 @@ module Blueprints
25
30
  args.each { |t| ::ActiveRecord::Base.connection.delete(DELETE_POLICIES[delete_policy] % t) }
26
31
  end
27
32
 
33
+ # Returns all tables without skipped ones.
28
34
  def tables
29
35
  ::ActiveRecord::Base.connection.tables - skip_tables
30
36
  end
31
37
 
38
+ # Returns tables that should never be cleared (those that contain migrations information).
32
39
  def skip_tables
33
40
  %w( schema_info schema_migrations )
34
41
  end
35
42
 
43
+ # Extensions for active record class
36
44
  module ActiveRecordExtensions
37
- def blueprint(*args)
38
- options = args.extract_options!
39
- if args.present?
40
- klass = self
41
- Blueprints::Plan.new(*args) do
42
- klass.blueprint options
43
- end
44
- else
45
- returning(self.new) do |object|
46
- options.each do |attr, value|
47
- value = Blueprints::Namespace.root.context.instance_variable_get(value) if value.is_a? Symbol and value.to_s =~ /^@.+$/
48
- object.send("#{attr}=", value)
45
+ module Class
46
+ # Two forms of this method can be used. First one is typically used inside blueprint block. Essentially it does
47
+ # same as <tt>create!</tt>, except it does bypass attr_protected and attr_accessible. It accepts only a hash or attributes,
48
+ # same as <tt>create!</tt> does.
49
+ # blueprint :post => :user do
50
+ # @user.posts.blueprint(:title => 'first post', :text => 'My first post')
51
+ # end
52
+ # The second form is used when you want to define new blueprint. It takes first argument as name of blueprint
53
+ # and second one as hash of attributes. As you cannot use instance variables outside of blueprint block, you need
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 => :@user).depends_on(:user)
56
+ # or like this:
57
+ # Post.blueprint({:post => :user}, :title => 'first post', :text => 'My first post', :user => :@user)
58
+ def blueprint(*args)
59
+ attributes = args.extract_options!
60
+ if args.present?
61
+ klass = self
62
+ Blueprints::Plan.new(*args) do
63
+ klass.blueprint attributes.merge(options)
49
64
  end
50
- object.save!
65
+ else
66
+ returning(self.new) { |object| object.blueprint(attributes) }
67
+ end
68
+ end
69
+ end
70
+
71
+ module Instance
72
+ # Updates attributes of object and calls save!. Bypasses attr_protected ant attr_accessible.
73
+ def blueprint(attributes)
74
+ attributes.each do |attr, value|
75
+ value = Blueprints::Namespace.root.context.instance_variable_get(value) if value.is_a? Symbol and value.to_s =~ /^@.+$/
76
+ send("#{attr}=", value)
51
77
  end
78
+ save!
52
79
  end
53
80
  end
54
81
  end
@@ -1,12 +1,16 @@
1
1
  module Blueprints
2
2
  module DatabaseBackends
3
+ # Database backend when no orm is actually used. Can be adapted to work with any kind of data.
3
4
  class None
5
+ # Dummy method
4
6
  def start_transaction
5
7
  end
6
8
 
9
+ # Dummy method
7
10
  def revert_transaction
8
11
  end
9
12
 
13
+ # Dummy method
10
14
  def delete_tables(delete_policy, *args)
11
15
  end
12
16
  end
@@ -1,4 +1,5 @@
1
1
  module Blueprints
2
+ # Is raised when blueprint or namespace is not found.
2
3
  class PlanNotFoundError < NameError
3
4
  def initialize(*args)
4
5
  @plans = args
@@ -8,4 +9,4 @@ module Blueprints
8
9
  "Plan/namespace not found '#{@plans.join(',')}'"
9
10
  end
10
11
  end
11
- end
12
+ end
@@ -1,6 +1,9 @@
1
- module Spec
2
- module Runner
1
+ module Spec #:nodoc:
2
+ module Runner #:nodoc:
3
3
  class Configuration
4
+ # Enables blueprints in rspec. Is automatically added if <tt>Spec</tt> is defined at loading time or <tt>script/spec</tt>
5
+ # is used. You might need to require it manually in certain case (eg. running specs from metrics).
6
+ # Accepts options hash. For supported options please check Blueprints.load.
4
7
  def enable_blueprints(options = {})
5
8
  Blueprints.load(options)
6
9
 
@@ -14,4 +17,4 @@ module Spec
14
17
  end
15
18
  end
16
19
  end
17
- end
20
+ end
@@ -1,12 +1,16 @@
1
- module Test
2
- module Unit
1
+ module Test #:nodoc:
2
+ module Unit #:nodoc:
3
3
  class TestCase
4
+ # Runs tests with blueprints support
4
5
  def run_with_blueprints(result, &progress_block)
5
6
  Blueprints.setup(self)
6
7
  run_without_blueprints(result, &progress_block)
7
8
  Blueprints.teardown
8
9
  end
9
10
 
11
+ # Enables blueprints in test/unit. Is automatically added if <tt>Spec</tt> is not defined at loading time.
12
+ # You might need to require it manually in certain case (eg. using both rspec and test/unit).
13
+ # Accepts options hash. For supported options please check Blueprints.load.
10
14
  def self.enable_blueprints(options = {})
11
15
  include Blueprints::Helper
12
16
  Blueprints.load(options)
@@ -14,4 +18,4 @@ module Test
14
18
  end
15
19
  end
16
20
  end
17
- end
21
+ end
@@ -1,9 +1,12 @@
1
1
  module Blueprints
2
+ # Module that blueprints file is executed against. Defined <tt>blueprint</tt> and <tt>namespace</tt> methods.
2
3
  module FileContext
4
+ # Creates a new plan by name and block passed
3
5
  def self.blueprint(plan, &block)
4
6
  Plan.new(plan, &block)
5
7
  end
6
8
 
9
+ # Creates new namespace by name, and evaluates block against it.
7
10
  def self.namespace(name)
8
11
  old_namespace = Namespace.root
9
12
  namespace = Namespace.new(name)
@@ -13,4 +16,4 @@ module Blueprints
13
16
  Namespace.root = old_namespace
14
17
  end
15
18
  end
16
- end
19
+ end
@@ -1,5 +1,13 @@
1
1
  module Blueprints
2
+ # A helper module that should be included in test framework. Adds methods <tt>build</tt> and <tt>demolish</tt>
2
3
  module Helper
4
+ # Builds one or more blueprints by their names. You can pass names as symbols or strings. You can also pass additional
5
+ # options hash which will be available by calling <tt>options</tt> in blueprint block. Returns result of blueprint block.
6
+ # # build :apple and orange blueprints
7
+ # build :apple, :orange
8
+ #
9
+ # # build :apple scenario with additional options
10
+ # build :apple, :color => 'red'
3
11
  def build_plan(*names)
4
12
  result = Namespace.root.build(*names).last
5
13
  Namespace.root.copy_ivars(self)
@@ -8,6 +16,10 @@ module Blueprints
8
16
 
9
17
  alias :build :build_plan
10
18
 
19
+ # Clears all tables in database. You can pass table names to clear only those tables. You can also pass <tt>:undo</tt> option
20
+ # to remove scenarios from built scenarios cache.
21
+ #
22
+ # TODO: add sample usage
11
23
  def demolish(*args)
12
24
  options = args.extract_options!
13
25
  Blueprints.delete_tables(*args)
@@ -23,4 +35,4 @@ module Blueprints
23
35
  end
24
36
  end
25
37
  end
26
- end
38
+ end
@@ -1,4 +1,6 @@
1
1
  module Blueprints
2
+ # Namespace class, inherits from <tt>Buildable</tt>. Allows adding and finding child blueprints/namespaces and building
3
+ # all it's children.
2
4
  class Namespace < Buildable
3
5
  cattr_accessor :root
4
6
  delegate :empty?, :size, :to => :@children
@@ -28,4 +30,4 @@ module Blueprints
28
30
  Namespace.root.add_variable(path, @children.collect {|p| p.last.build }.uniq)
29
31
  end
30
32
  end
31
- end
33
+ end
@@ -1,14 +1,17 @@
1
1
  module Blueprints
2
+ # Class for actual blueprints. Allows building itself by executing block passed against current context.
2
3
  class Plan < Buildable
4
+ # Initializes blueprint by name and block
3
5
  def initialize(name, &block)
4
6
  super(name)
5
7
  @block = block
6
8
  end
7
9
 
10
+ # Builds plan and adds it to executed plan hash. Setups instance variable with same name as plan if it is not defined yet.
8
11
  def build_plan
9
12
  surface_errors do
10
13
  if @block
11
- @result = Namespace.root.context.module_eval(&@block)
14
+ @result = Namespace.root.context.instance_eval(&@block)
12
15
  Namespace.root.add_variable(path, @result)
13
16
  end
14
17
  end unless Namespace.root.executed_plans.include?(path)
@@ -25,4 +28,4 @@ module Blueprints
25
28
  raise error
26
29
  end
27
30
  end
28
- end
31
+ end
@@ -1,40 +1,50 @@
1
1
  module Blueprints
2
+ # Defines a root namespace that is used when no other namespace is. Apart from functionality in namespace it also allows
3
+ # building blueprints/namespaces by name. Is also used for copying instance variables between blueprints/contexts/global
4
+ # context.
2
5
  class RootNamespace < Namespace
3
6
  attr_reader :context, :plans
4
7
  attr_accessor :executed_plans
5
-
8
+
6
9
  def initialize
7
10
  @executed_plans = Set.new
8
11
  @global_executed_plans = Set.new
9
- @global_context = Module.new
10
12
 
11
13
  super ''
12
14
  end
13
15
 
16
+ # Loads all instance variables from global context to current one.
14
17
  def setup
15
- @context = YAML.load(@global_context)
18
+ @context = Blueprints::Context.new
19
+ YAML.load(@global_variables).each { |name, value| @context.instance_variable_set(name, value) }
16
20
  @executed_plans = @global_executed_plans.clone
17
21
  end
18
22
 
23
+ # Copies all instance variables from current context to another one.
19
24
  def copy_ivars(to)
20
25
  @context.instance_variables.each do |iv|
21
26
  to.instance_variable_set(iv, @context.instance_variable_get(iv))
22
27
  end
23
28
  end
24
29
 
30
+ # Sets up global context and executes prebuilt blueprints against it.
25
31
  def prebuild(plans)
26
- @context = @global_context
32
+ @context = Blueprints::Context.new
27
33
  @global_scenarios = build(*plans) if plans
28
34
  @global_executed_plans = @executed_plans
29
- @global_context = YAML.dump(@global_context)
35
+
36
+ @global_variables = YAML.dump(@context.instance_variables.each_with_object({}) {|iv, hash| hash[iv] = @context.instance_variable_get(iv) })
30
37
  end
31
38
 
39
+ # Builds blueprints that are passed against current context.
32
40
  def build(*names)
41
+ @context.options = names.extract_options!
33
42
  names.map {|name| self[name].build}
34
43
  end
35
44
 
45
+ # Sets instance variable in current context to passed value.
36
46
  def add_variable(name, value)
37
- name = "@#{name}" unless name.to_s[0, 1] == "@"
47
+ name = "@#{name}" unless name.to_s[0, 1] == "@"
38
48
  @context.instance_variable_set(name, value) unless @context.instance_variable_get(name)
39
49
  end
40
50
 
@@ -50,4 +50,8 @@ namespace :pitted => :pine do
50
50
  namespace :red => :orange do
51
51
  Fruit.blueprint(:apple, :species => 'pitted red apple')
52
52
  end
53
- end
53
+ end
54
+
55
+ blueprint :apple_with_params do
56
+ Fruit.create! options.merge(:species => 'apple')
57
+ end
@@ -7,7 +7,7 @@ describe Blueprints do
7
7
  end
8
8
 
9
9
  it "should support required ORMS" do
10
- Blueprints.supported_orms.should == [:active_record, :none]
10
+ Blueprints.supported_orms.should =~ [:active_record, :none]
11
11
  end
12
12
  end
13
13
 
@@ -221,7 +221,7 @@ describe Blueprints do
221
221
  Blueprints::Namespace.root.expects(:empty?).returns(true)
222
222
  lambda {
223
223
  Blueprints.load(:orm => :unknown)
224
- }.should raise_error(ArgumentError, "Unsupported ORM unknown. Blueprints supports only active_record, none")
224
+ }.should raise_error(ArgumentError, "Unsupported ORM unknown. Blueprints supports only #{Blueprints.supported_orms.join(', ')}")
225
225
  end
226
226
  end
227
227
 
@@ -246,6 +246,18 @@ describe Blueprints do
246
246
  @acorn.should_not be_nil
247
247
  @acorn.tree.should == @oak
248
248
  end
249
+
250
+ it "should allow updating object using blueprint method" do
251
+ build :oak
252
+ @oak.blueprint(:size => 'updated')
253
+ @oak.reload.size.should == 'updated'
254
+ end
255
+
256
+ it "should automatically merge passed options" do
257
+ build :oak, :size => 'optional'
258
+ @oak.name.should == 'Oak'
259
+ @oak.size.should == 'optional'
260
+ end
249
261
  end
250
262
 
251
263
  describe "with pitted namespace" do
@@ -294,5 +306,19 @@ describe Blueprints do
294
306
  end
295
307
  end
296
308
  end
309
+
310
+ describe 'extra parameters' do
311
+ it "should allow passing extra parameters when building" do
312
+ build :apple_with_params, :average_diameter => 14
313
+ @apple_with_params.average_diameter.should == 14
314
+ @apple_with_params.species.should == 'apple'
315
+ end
316
+
317
+ it "should allow set options to empty hash if no parameters are passed" do
318
+ build :apple_with_params
319
+ @apple_with_params.average_diameter.should == nil
320
+ @apple_with_params.species.should == 'apple'
321
+ end
322
+ end
297
323
  end
298
324
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blueprints
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrius Chamentauskas
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-12-10 00:00:00 +02:00
12
+ date: 2009-12-25 00:00:00 +02:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -33,6 +33,7 @@ files:
33
33
  - install.rb
34
34
  - lib/blueprints.rb
35
35
  - lib/blueprints/buildable.rb
36
+ - lib/blueprints/context.rb
36
37
  - lib/blueprints/database_backends/abstract.rb
37
38
  - lib/blueprints/database_backends/active_record.rb
38
39
  - lib/blueprints/database_backends/none.rb