floor_manager 0.1.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/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
+