floor_manager 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+
2
+ Copyright (c) 2010 Kaspar Schiess
3
+
4
+ Permission is hereby granted, free of charge, to any person
5
+ obtaining a copy of this software and associated documentation
6
+ files (the "Software"), to deal in the Software without
7
+ restriction, including without limitation the rights to use,
8
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the
10
+ Software is furnished to do so, subject to the following
11
+ conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23
+ OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,58 @@
1
+
2
+ FLOOR MANAGER - when your job is handling the girls
3
+
4
+ The floor manager manages a whole graph of objects and lets you create them
5
+ in memory or the database for the duration of a test.
6
+
7
+ You need two posts that are linked up to the same author? [1] Easy:
8
+
9
+ FloorManager.define :first do
10
+ one :author do
11
+ name 'John Smith'
12
+ end
13
+
14
+ any :post do
15
+ title 'The posts title'
16
+ author.set :author
17
+ end
18
+ end
19
+
20
+ And in your tests:
21
+
22
+ before(:each) do
23
+ FloorManager.reset
24
+
25
+ floor = FloorManager.get(:floor)
26
+ posts = [floor.create(:post), floor.create(:post)]
27
+ end
28
+
29
+ This gives you two posts to test against. Both posts will have the same
30
+ author, right down to the database id.
31
+
32
+ INSTALLATION
33
+
34
+ Either by installing from http://github.com/kschiess/floor_manager as a rails
35
+ plugin or by using rubygems:
36
+
37
+ gem install floor_manager
38
+
39
+ Then just require it atop of your floor definitions as follows:
40
+
41
+ require 'floor_manager'
42
+ # Your floors go here...
43
+
44
+ COMPATIBILITY
45
+
46
+ This has only been tested with rails 2. Both Ruby 1.8 and Ruby 1.9 should
47
+ work.
48
+
49
+ STATUS
50
+
51
+ Useful in daily life.
52
+
53
+ AUTHOR
54
+
55
+ Kaspar Schiess (kaspar.schiess@absurd.li)
56
+
57
+ [1] http://robots.thoughtbot.com/post/159807023/waiting-for-a-factory-girl
58
+
data/Rakefile ADDED
@@ -0,0 +1,75 @@
1
+
2
+
3
+ require "rubygems"
4
+ require "rake/gempackagetask"
5
+ require "rake/rdoctask"
6
+
7
+ require "spec"
8
+ require "spec/rake/spectask"
9
+ Spec::Rake::SpecTask.new
10
+
11
+
12
+ task :default => ["spec"]
13
+
14
+ # This builds the actual gem. For details of what all these options
15
+ # mean, and other ones you can add, check the documentation here:
16
+ #
17
+ # http://rubygems.org/read/chapter/20
18
+ #
19
+ spec = Gem::Specification.new do |s|
20
+
21
+ # Change these as appropriate
22
+ s.name = "floor_manager"
23
+ s.version = "0.1.0"
24
+ s.summary = "Allows creation of a whole graph of objects on the fly during testing"
25
+ s.author = "Kaspar Schiess"
26
+ s.email = "kaspar.schiess@absurd.li"
27
+ s.homepage = "http://github.com/kschiess/floor_manager"
28
+
29
+ s.has_rdoc = true
30
+ s.extra_rdoc_files = %w(README)
31
+ s.rdoc_options = %w(--main README)
32
+
33
+ # Add any extra files to include in the gem
34
+ s.files = %w(LICENSE Rakefile README) + Dir.glob("{spec,lib/**/*}")
35
+ s.require_paths = ["lib"]
36
+
37
+ # If you want to depend on other gems, add them here, along with any
38
+ # relevant versions
39
+ s.add_dependency("activesupport", "~> 2.3.5")
40
+
41
+ # If your tests use any gems, include them here
42
+ s.add_development_dependency("rspec")
43
+ s.add_development_dependency("flexmock")
44
+ end
45
+
46
+ # This task actually builds the gem. We also regenerate a static
47
+ # .gemspec file, which is useful if something (i.e. GitHub) will
48
+ # be automatically building a gem for this project. If you're not
49
+ # using GitHub, edit as appropriate.
50
+ #
51
+ # To publish your gem online, install the 'gemcutter' gem; Read more
52
+ # about that here: http://gemcutter.org/pages/gem_docs
53
+ Rake::GemPackageTask.new(spec) do |pkg|
54
+ pkg.gem_spec = spec
55
+ end
56
+
57
+ desc "Build the gemspec file #{spec.name}.gemspec"
58
+ task :gemspec do
59
+ file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
60
+ File.open(file, "w") {|f| f << spec.to_ruby }
61
+ end
62
+
63
+ task :package => :gemspec
64
+
65
+ # Generate documentation
66
+ Rake::RDocTask.new do |rd|
67
+ rd.main = "README"
68
+ rd.rdoc_files.include("README", "lib/**/*.rb")
69
+ rd.rdoc_dir = "rdoc"
70
+ end
71
+
72
+ desc 'Clear out RDoc and generated packages'
73
+ task :clean => [:clobber_rdoc, :clobber_package] do
74
+ rm "#{spec.name}.gemspec"
75
+ end
@@ -0,0 +1,71 @@
1
+ module FloorManager::Employee
2
+ module AttributeAction
3
+ # Base class for stored actions.
4
+ class Base
5
+ def initialize(name)
6
+ @name = name
7
+ end
8
+ def set(obj, value)
9
+ if obj.kind_of?(Hash)
10
+ obj[@name] = value
11
+ else
12
+ obj.send("#{@name}=", value)
13
+ end
14
+ end
15
+ def get(obj)
16
+ if obj.kind_of?(Hash)
17
+ obj[@name]
18
+ else
19
+ obj.send(@name)
20
+ end
21
+ end
22
+ end
23
+
24
+ # Stores the action of producing another employee via a collection proxy
25
+ # stored in field.
26
+ class AssocAppend < Base
27
+ def initialize(field, create_args)
28
+ super field
29
+ @create_args = create_args
30
+ end
31
+ def apply(obj, floor, employee)
32
+ instance = floor.build(*@create_args)
33
+ get(obj) << instance
34
+ end
35
+ end
36
+
37
+ # Stores the action of producing another employee via the floor and then
38
+ # storing that as a value.
39
+ class AssocSet < Base
40
+ def initialize(field, create_args)
41
+ super field
42
+ @create_args = create_args
43
+ end
44
+ def apply(obj, floor, employee)
45
+ set(obj, floor.build(*@create_args))
46
+ end
47
+ end
48
+
49
+ # Stores the action of setting an attribute to an immediate value.
50
+ class Immediate < Base
51
+ def initialize(name, value)
52
+ super(name)
53
+ @value = value
54
+ end
55
+ def apply(obj, floor, employee)
56
+ set(obj, @value)
57
+ end
58
+ end
59
+
60
+ # Stores the action of setting an attribute to the result of a block.
61
+ class Block < Base
62
+ def initialize(name, block)
63
+ super(name)
64
+ @block = block
65
+ end
66
+ def apply(obj, floor, employee)
67
+ set(obj, @block.call(obj, floor))
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,78 @@
1
+ module FloorManager::Employee
2
+ class DSL < BlankSlate
3
+ # A proxy that is the receiver of #set and #append in a construct like this:
4
+ #
5
+ # one :spy do
6
+ # relationship.set :gun
7
+ # end
8
+ #
9
+ class AssocProxy < Struct.new(:employee, :field, :dsl)
10
+ def set(*create_args)
11
+ dsl._add_attribute AttributeAction::AssocSet.new(field, create_args)
12
+ end
13
+ def append(*create_args)
14
+ dsl._add_attribute AttributeAction::AssocAppend.new(field, create_args)
15
+ end
16
+ def string(chars=10)
17
+ dsl._add_attribute AttributeAction::Block.new(field, proc {
18
+ (0...chars).map{ ('a'..'z').to_a[rand(26)] }.join
19
+ })
20
+ end
21
+ end
22
+
23
+ def initialize(employee, filter=:none, &block)
24
+ @employee = employee
25
+ @filter = filter
26
+
27
+ instance_eval(&block)
28
+ end
29
+
30
+ # Register actions to be taken if the object gets saved (floor#create)
31
+ #
32
+ def after_create(&block)
33
+ FloorManager::Employee::DSL.new(@employee, :after_create, &block)
34
+ end
35
+
36
+ # This method missing handles several magic incantations:
37
+ #
38
+ # a) Setting an attribute to a value that is given:
39
+ #
40
+ # name 'john'
41
+ #
42
+ # b) Setting an attribute to a value that is returned from a block:
43
+ #
44
+ # name { |obj, floor| rand()>0.5 ? 'John' : 'Peter' }
45
+ #
46
+ # Note that the first argument is the +obj+ under construction (your
47
+ # model instance) and the second argument is the floor the model is
48
+ # being constructed in. This is useful for retrieving other objects that
49
+ # live in the same floor.
50
+ #
51
+ # c) Access to the association magic:
52
+ #
53
+ # spouse.set :linda
54
+ # friends.append :frank
55
+ #
56
+ # Please see +AssocProxy+ for further explanation on this.
57
+ #
58
+ def method_missing(sym, *args, &block)
59
+ if args.size == 1
60
+ # Immediate attribute
61
+ value = args.first
62
+ _add_attribute AttributeAction::Immediate.new(sym, value)
63
+ elsif block
64
+ # Lazy attribute
65
+ _add_attribute AttributeAction::Block.new(sym, block)
66
+ elsif args.empty?
67
+ # Maybe: #set / #append proxy?
68
+ AssocProxy.new(@employee, sym, self)
69
+ else
70
+ super
71
+ end
72
+ end
73
+
74
+ def _add_attribute(action)
75
+ @employee.add_attribute @filter, action
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,109 @@
1
+
2
+ require 'active_support'
3
+ require 'blankslate'
4
+
5
+ module FloorManager::Employee
6
+
7
+ # Base class for employees. No instances of this should be created.
8
+ class Template
9
+ def self.from_dsl(klass_name, &block)
10
+ new(klass_name).tap { |emp| DSL.new(emp, &block) }
11
+ end
12
+
13
+ def initialize(klass_name)
14
+ @klass_name = klass_name
15
+ @attributes = Hash.new { |h,k| h[k] = Array.new }
16
+ end
17
+
18
+ # Build this employee in memory.
19
+ #
20
+ def build(floor, overrides)
21
+ produce_instance.tap { |i| apply_attributes(i, :none, floor, overrides) }
22
+ end
23
+
24
+ # Create this employee in the database.
25
+ #
26
+ def create(floor, overrides)
27
+ produce_instance.tap { |i|
28
+ apply_attributes(i, :none, floor, overrides)
29
+ i.save!
30
+
31
+ unless @attributes[:after_create].empty?
32
+ apply_attributes(i, :after_create, floor)
33
+ i.save!
34
+ end
35
+ }
36
+ end
37
+
38
+ # Returns just the attributes that would be used.
39
+ #
40
+ def attrs(floor, overrides)
41
+ build(floor, overrides).attributes
42
+ end
43
+
44
+ # Reset this employee between test runs.
45
+ #
46
+ def reset
47
+ # Empty, but subclasses will override this.
48
+ end
49
+
50
+ # Add an attribute to set. The action should implement the AttributeAction
51
+ # interface. This method is mainly used by the DSL to store actions to
52
+ # take.
53
+ #
54
+ def add_attribute filter, action
55
+ @attributes[filter] << action
56
+ end
57
+ protected
58
+ def produce_instance
59
+ @klass_name.to_s.
60
+ camelcase.
61
+ constantize.
62
+ new
63
+ end
64
+
65
+ # Modify attribute values in +instance+, setting them to what was
66
+ # specified in the factory for this employee and then overriding them with
67
+ # what was given in +overrides+.
68
+ #
69
+ def apply_attributes(instance, filter, floor, overrides={})
70
+ # First apply all attributes that were given in the factory definition.
71
+ @attributes[filter].
72
+ each do |action|
73
+ action.apply(instance, floor, self)
74
+ end
75
+
76
+ # Then override with what the user just gave us.
77
+ overrides.each do |name, value|
78
+ AttributeAction::Immediate.new(name, value).apply(instance, floor, self)
79
+ end
80
+ end
81
+ end
82
+
83
+ # A unique employee that will be build/created only once in the given floor.
84
+ class Unique < Template
85
+ def reset
86
+ @instance = nil
87
+ end
88
+
89
+ # Override these to shortcut attribute setting when the instance exists
90
+ # already.
91
+ def build(floor, overrides)
92
+ @instance || super
93
+ end
94
+ def create(floor, overrides)
95
+ @instance || super
96
+ end
97
+ def attrs(floor, overrides)
98
+ @instance && @instance.attributes || super
99
+ end
100
+ protected
101
+ # Override to produce only one instance.
102
+ def produce_instance
103
+ @instance ||= super
104
+ end
105
+ end
106
+ end
107
+
108
+ require 'floor_manager/employee/dsl'
109
+ require 'floor_manager/employee/attribute_action'
@@ -0,0 +1,75 @@
1
+
2
+
3
+ # A single floor under the supervision of the manager. This is basically a
4
+ # context in which unique/singleton instances and templates can coexist.
5
+ #
6
+ class FloorManager::Floor
7
+ class DSL
8
+ def initialize(&block)
9
+ @floor = FloorManager::Floor.new
10
+ instance_eval(&block)
11
+ end
12
+
13
+ def one(name, opts={}, &block)
14
+ klass_name = opts[:class] || name
15
+ @floor.employees[name.to_sym] = FloorManager::Employee::Unique.from_dsl(klass_name, &block)
16
+ end
17
+
18
+ def any(name, opts={}, &block)
19
+ klass_name = opts[:class] || name
20
+ @floor.employees[name.to_sym] = FloorManager::Employee::Template.from_dsl(klass_name, &block)
21
+ end
22
+
23
+ def object
24
+ @floor
25
+ end
26
+ end
27
+ def self.from_dsl(&block)
28
+ DSL.new(&block).object
29
+ end
30
+
31
+ attr_reader :employees
32
+ def initialize
33
+ @employees = {}
34
+ end
35
+
36
+ # Allows production of new employees by calling their names as methods on
37
+ # the floor.
38
+ #
39
+ # With a definition of
40
+ #
41
+ # one :dog do
42
+ # end
43
+ #
44
+ # you could call
45
+ #
46
+ # floor.dog
47
+ #
48
+ # and get the same as if you had called floor.build :dog
49
+ #
50
+ def method_missing(sym, *args, &block)
51
+ if args.size <= 1 && employees.has_key?(sym)
52
+ attribute_overrides = {}
53
+ attribute_overrides = args.first unless args.empty?
54
+ employees[sym].build(self, attribute_overrides)
55
+ else
56
+ super
57
+ end
58
+ end
59
+
60
+ def create(something, overrides={})
61
+ employees[something.to_sym].create(self, overrides)
62
+ end
63
+ def build(something, overrides={})
64
+ employees[something.to_sym].build(self, overrides)
65
+ end
66
+ def attrs(something, overrides={})
67
+ employees[something.to_sym].attrs(self, overrides)
68
+ end
69
+
70
+ def reset
71
+ employees.values.each do |employee|
72
+ employee.reset
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,31 @@
1
+
2
+ class FloorManager
3
+ class <<self
4
+ attr_reader :floors
5
+
6
+ # Defines a new environment under the supervision of the floor manager.
7
+ #
8
+ def define(environment_name, &block)
9
+ @floors ||= {}
10
+
11
+ @floors[environment_name] = FloorManager::Floor.from_dsl(&block)
12
+ end
13
+
14
+ # Returns an instance of the environment.
15
+ #
16
+ def get(environment_name)
17
+ floors[environment_name]
18
+ end
19
+
20
+ # Resets all instances produced. Use this in after(:each).
21
+ #
22
+ def reset
23
+ @floors.values.each do |floor|
24
+ floor.reset
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ require 'floor_manager/floor'
31
+ require 'floor_manager/employee'
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: floor_manager
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Kaspar Schiess
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-06-21 00:00:00 +02:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: activesupport
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 2
30
+ - 3
31
+ - 5
32
+ version: 2.3.5
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: rspec
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ type: :development
47
+ version_requirements: *id002
48
+ - !ruby/object:Gem::Dependency
49
+ name: flexmock
50
+ prerelease: false
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ segments:
57
+ - 0
58
+ version: "0"
59
+ type: :development
60
+ version_requirements: *id003
61
+ description:
62
+ email: kaspar.schiess@absurd.li
63
+ executables: []
64
+
65
+ extensions: []
66
+
67
+ extra_rdoc_files:
68
+ - README
69
+ files:
70
+ - LICENSE
71
+ - Rakefile
72
+ - README
73
+ - lib/floor_manager/employee/attribute_action.rb
74
+ - lib/floor_manager/employee/dsl.rb
75
+ - lib/floor_manager/employee.rb
76
+ - lib/floor_manager/floor.rb
77
+ - lib/floor_manager.rb
78
+ has_rdoc: true
79
+ homepage: http://github.com/kschiess/floor_manager
80
+ licenses: []
81
+
82
+ post_install_message:
83
+ rdoc_options:
84
+ - --main
85
+ - README
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ segments:
94
+ - 0
95
+ version: "0"
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ segments:
102
+ - 0
103
+ version: "0"
104
+ requirements: []
105
+
106
+ rubyforge_project:
107
+ rubygems_version: 1.3.7
108
+ signing_key:
109
+ specification_version: 3
110
+ summary: Allows creation of a whole graph of objects on the fly during testing
111
+ test_files: []
112
+