fluent_fixtures 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +2 -0
- data/.document +5 -0
- data/.rdoc_options +16 -0
- data/.simplecov +9 -0
- data/ChangeLog +48 -0
- data/GettingStarted.md +26 -0
- data/History.md +9 -0
- data/LICENSE.txt +20 -0
- data/Manifest.txt +19 -0
- data/README.md +77 -0
- data/Rakefile +97 -0
- data/lib/fluent_fixtures/collection.rb +137 -0
- data/lib/fluent_fixtures/dsl.rb +106 -0
- data/lib/fluent_fixtures/factory.rb +201 -0
- data/lib/fluent_fixtures.rb +29 -0
- data/spec/fluent_fixtures/collection_spec.rb +145 -0
- data/spec/fluent_fixtures/dsl_spec.rb +108 -0
- data/spec/fluent_fixtures/factory_spec.rb +234 -0
- data/spec/fluent_fixtures_spec.rb +15 -0
- data/spec/spec_helper.rb +23 -0
- data.tar.gz.sig +0 -0
- metadata +235 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,106 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'fluent_fixtures' unless defined?( FluentFixtures )
|
5
|
+
|
6
|
+
|
7
|
+
module FluentFixtures::DSL
|
8
|
+
|
9
|
+
#
|
10
|
+
# Accessors for extended fixture modules
|
11
|
+
#
|
12
|
+
|
13
|
+
##
|
14
|
+
# The Hash of decorators declared for this fixture module
|
15
|
+
attr_reader :decorators
|
16
|
+
|
17
|
+
##
|
18
|
+
# The name of the base fixture for the fixture module as a Symbol
|
19
|
+
attr_accessor :base_fixture
|
20
|
+
|
21
|
+
|
22
|
+
##
|
23
|
+
# The FluentFixtures::Collection the fixture is part of
|
24
|
+
attr_accessor :collection
|
25
|
+
|
26
|
+
|
27
|
+
#
|
28
|
+
# Fixture API
|
29
|
+
#
|
30
|
+
|
31
|
+
### Declare a base fixture for the current module called +name+, with an optional
|
32
|
+
### initial decorator as a +block+. If no +name+ is given, one is chosen based on the
|
33
|
+
### name of the declaring module.
|
34
|
+
def base( name=nil, &block )
|
35
|
+
name ||= self.collection.default_base_fixture_name( self )
|
36
|
+
|
37
|
+
self.base_fixture = name
|
38
|
+
self.decorators[ :_ ] = block if block
|
39
|
+
|
40
|
+
self.collection.add_base_fixture( name, self )
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
### Get/set the Class the fixture will use.
|
45
|
+
def fixtured_class( new_class=nil )
|
46
|
+
@fixtured_class = new_class if new_class
|
47
|
+
return @fixtured_class
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
### Declare a decorator for the fixture with the specified +name+ that will use the
|
52
|
+
### given +block+.
|
53
|
+
def decorator( name, &block )
|
54
|
+
self.decorators[ name.to_sym ] = block
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
### Returns +true+ if there is a decorator with the specified +name+.
|
59
|
+
def decorator?( name )
|
60
|
+
return self.decorators.key?( name.to_sym )
|
61
|
+
end
|
62
|
+
alias_method :has_decorator?, :decorator?
|
63
|
+
|
64
|
+
|
65
|
+
### Declare a +new_name+ for the decorator declarted with with +original_name+.
|
66
|
+
def alias_decorator( new_name, original_name )
|
67
|
+
block = self.decorators[ original_name.to_sym ] or
|
68
|
+
raise ScriptError, "undefined decorator %p" % [ original_name ]
|
69
|
+
self.decorators[ new_name.to_sym ] = block
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
### Add a callback to the fixture that will passed new instances after all
|
74
|
+
### decorators have been applied and immediately before it's saved. The results of
|
75
|
+
### the block will be used as the fixtured instance. This can be
|
76
|
+
### used for tables with fixed rows to use `find_or_create` or similar.
|
77
|
+
def before_saving( &block )
|
78
|
+
define_singleton_method( :call_before_saving, &block )
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
### Return an instance of Cozy::FluentFixtures::FluentFactory for the base fixture
|
83
|
+
### of the receiving module.
|
84
|
+
def factory( *args, &block )
|
85
|
+
return FluentFixtures::Factory.new( self, *args, &block )
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
### Return an unsaved instance of the fixtured class with the specified +args+
|
90
|
+
### and +block+, applying the base decorator if there is one.
|
91
|
+
def fixtured_instance( *args, &block )
|
92
|
+
fixclass = self.fixtured_class or
|
93
|
+
raise ScriptError, "%p doesn't declare its fixtured class!" % [ self ]
|
94
|
+
|
95
|
+
instance = fixclass.new( *args, &block )
|
96
|
+
|
97
|
+
if (( base_decorator = self.decorators[:_] ))
|
98
|
+
self.log.debug "Applying base decorator to %p" % [ instance ]
|
99
|
+
instance.instance_exec( &base_decorator )
|
100
|
+
end
|
101
|
+
|
102
|
+
return instance
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
end # module FluentFixtures::DSL
|
@@ -0,0 +1,201 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'loggability'
|
5
|
+
require 'fluent_fixtures' unless defined?( FluentFixtures )
|
6
|
+
|
7
|
+
|
8
|
+
# The fluent fixture monadic factory class.
|
9
|
+
class FluentFixtures::Factory
|
10
|
+
extend Loggability
|
11
|
+
|
12
|
+
|
13
|
+
# The methods to look for to save new instances when #create is called
|
14
|
+
CREATE_METHODS = %i[ save_changes save ]
|
15
|
+
|
16
|
+
|
17
|
+
# Loggability API -- log to the FluentFixtures logger
|
18
|
+
log_to :fluent_fixtures
|
19
|
+
|
20
|
+
|
21
|
+
### Create a new FluentFactory that will act as a monadic factory for the specified
|
22
|
+
### +fixture_module+, and use the given +args+ in the construction of new objects.
|
23
|
+
def initialize( fixture_module, *args, &block )
|
24
|
+
@fixture_module = fixture_module
|
25
|
+
@constructor_args = args
|
26
|
+
@constructor_block = block
|
27
|
+
@decorators = []
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
### Copy constructor -- make a distinct copy of the clone's decorators.
|
32
|
+
def initialize_copy( original )
|
33
|
+
@decorators = @decorators.dup
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
##
|
38
|
+
# The fixture module that contains the decorator declarations
|
39
|
+
attr_reader :fixture_module
|
40
|
+
|
41
|
+
##
|
42
|
+
# The decorators that will be applied to the fixtured object when it's created.
|
43
|
+
attr_reader :decorators
|
44
|
+
|
45
|
+
##
|
46
|
+
# The Array of arguments to pass to the constructor when creating a new fixtured object.
|
47
|
+
attr_reader :constructor_args
|
48
|
+
|
49
|
+
##
|
50
|
+
# The block to pass to the constructor when creating a new fixtured object.
|
51
|
+
attr_reader :constructor_block
|
52
|
+
|
53
|
+
|
54
|
+
### Return a new clone of the receiver with an additional decorator composed of the
|
55
|
+
### specified +name+, +args+, and +block+.
|
56
|
+
def mutate( name, *args, &block )
|
57
|
+
new_instance = self.dup
|
58
|
+
new_instance.decorators << [ name, args, block ]
|
59
|
+
return new_instance
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
### Create an instance, apply declared decorators in order, and return the resulting
|
64
|
+
### object.
|
65
|
+
def instance( *args, &block )
|
66
|
+
instance = self.fixture_module.
|
67
|
+
fixtured_instance( *self.constructor_args, &self.constructor_block )
|
68
|
+
|
69
|
+
self.decorators.each do |decorator_name, args, block|
|
70
|
+
# :TODO: Reify other fixtures in `args` here?
|
71
|
+
if !decorator_name
|
72
|
+
self.apply_inline_decorator( instance, block )
|
73
|
+
elsif self.fixture_module.decorators.key?( decorator_name )
|
74
|
+
self.apply_named_decorator( instance, args, decorator_name )
|
75
|
+
else
|
76
|
+
self.apply_method_decorator( instance, args, decorator_name, block )
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# If the factory was called with a block, use it as a final decorator before
|
81
|
+
# returning it.
|
82
|
+
if block
|
83
|
+
self.log.debug "Applying inline decorator %p" % [ block ]
|
84
|
+
if block.arity.zero?
|
85
|
+
instance.instance_exec( *args, &block )
|
86
|
+
else
|
87
|
+
block.call( instance, *args )
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
return instance
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
### Return a saved #instance of the fixtured object.
|
96
|
+
def create( *args, &block )
|
97
|
+
obj = self.with_transaction do
|
98
|
+
obj = self.instance( *args, &block )
|
99
|
+
obj = self.fixture_module.call_before_saving( obj ) if
|
100
|
+
self.fixture_module.respond_to?( :call_before_saving )
|
101
|
+
|
102
|
+
self.try_to_save( obj )
|
103
|
+
|
104
|
+
obj
|
105
|
+
end
|
106
|
+
|
107
|
+
return obj
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
### Return a copy of the factory that will apply the specified +block+ as a decorator.
|
112
|
+
def decorated_with( &block )
|
113
|
+
return self.mutate( nil, &block )
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
### Return a human-readable representation of the object suitable for debugging.
|
118
|
+
def inspect
|
119
|
+
decorator_description = self.decorators.map( &:first ).join( ' + ' )
|
120
|
+
|
121
|
+
return "#<%p:%0#16x for %p%s>" % [
|
122
|
+
self.class,
|
123
|
+
self.__id__ * 2,
|
124
|
+
self.fixture_module,
|
125
|
+
decorator_description.empty? ? '' : ' + ' + decorator_description
|
126
|
+
]
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
#########
|
131
|
+
protected
|
132
|
+
#########
|
133
|
+
|
134
|
+
### Apply a decorator +block+ added to the factory via #decorated_with to the +instance+.
|
135
|
+
def apply_inline_decorator( instance, block )
|
136
|
+
self.log.debug "Applying anonymous inline decorator %p" % [ block ]
|
137
|
+
if block.arity.nonzero?
|
138
|
+
block.call( instance )
|
139
|
+
else
|
140
|
+
instance.instance_eval( &block )
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
### Apply a decorator declared in the fixture module by the given
|
146
|
+
### +decorator_name+ to the specified +instance+.
|
147
|
+
def apply_named_decorator( instance, args, decorator_name )
|
148
|
+
decorator_block = self.fixture_module.decorators[ decorator_name ]
|
149
|
+
self.log.debug "Applying decorator %p (%p) to a %p with args: %p" %
|
150
|
+
[ decorator_name, decorator_block, instance.class, args ]
|
151
|
+
instance.instance_exec( *args, &decorator_block )
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
### Apply a decorator declared by calling the proxy method with the specified
|
156
|
+
### +name+, +args+, and +block+ to the given +instance+.
|
157
|
+
def apply_method_decorator( instance, args, name, block )
|
158
|
+
self.log.debug "Mutating instance %p with regular method %p( %p, %p )" %
|
159
|
+
[ instance, name, args, block ]
|
160
|
+
if block
|
161
|
+
instance.public_send( name, *args )
|
162
|
+
else
|
163
|
+
instance.public_send( name, *args, &block )
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
### Look for common transaction mechanisms on the fixtured class, and wrap one of
|
169
|
+
### them around the +block+ if one exists. If no transaction mechanism can be found,
|
170
|
+
### just yield to the block.
|
171
|
+
def with_transaction( &block )
|
172
|
+
fixtured_class = self.fixture_module.fixtured_class
|
173
|
+
if fixtured_class.respond_to?( :db )
|
174
|
+
self.log.debug "Using db.transaction for creation."
|
175
|
+
return fixtured_class.db.transaction( &block )
|
176
|
+
else
|
177
|
+
yield
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
|
182
|
+
### Try various methods for saving the given +object+, logging a warning if it doesn't
|
183
|
+
### respond to any of them.
|
184
|
+
def try_to_save( object )
|
185
|
+
CREATE_METHODS.each do |methodname|
|
186
|
+
return object.public_send( methodname ) if object.respond_to?( methodname )
|
187
|
+
end
|
188
|
+
|
189
|
+
self.log.warn "create: don't know how to save %p" % [ object ]
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
### Proxy method -- look up the decorator with the same name as the method being called,
|
194
|
+
### and if one is found, returned a new instance of the factory with the additional
|
195
|
+
### decorator.
|
196
|
+
def method_missing( sym, *args, &block )
|
197
|
+
return self.mutate( sym, *args, &block )
|
198
|
+
end
|
199
|
+
|
200
|
+
end # class FluentFixtures::Factory
|
201
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require 'loggability'
|
6
|
+
|
7
|
+
|
8
|
+
# Fluent fixtures
|
9
|
+
module FluentFixtures
|
10
|
+
extend Loggability
|
11
|
+
|
12
|
+
|
13
|
+
# Package version
|
14
|
+
VERSION = '0.0.2'
|
15
|
+
|
16
|
+
# Version control revision
|
17
|
+
REVISION = %q$Revision: e500071dd9c0 $
|
18
|
+
|
19
|
+
|
20
|
+
# Loggability API -- set up a named logger
|
21
|
+
log_as :fluent_fixtures
|
22
|
+
|
23
|
+
|
24
|
+
require 'fluent_fixtures/collection'
|
25
|
+
require 'fluent_fixtures/dsl'
|
26
|
+
require 'fluent_fixtures/factory'
|
27
|
+
|
28
|
+
end # module FluentFixtures
|
29
|
+
|
@@ -0,0 +1,145 @@
|
|
1
|
+
#!/usr/bin/env rspec -cfd
|
2
|
+
|
3
|
+
require_relative '../spec_helper'
|
4
|
+
|
5
|
+
require 'fluent_fixtures/collection'
|
6
|
+
|
7
|
+
|
8
|
+
describe FluentFixtures::Collection do
|
9
|
+
|
10
|
+
let( :collection ) do
|
11
|
+
mod = Module.new
|
12
|
+
mod.extend( described_class )
|
13
|
+
mod
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
it "registers its fixture modules when they are extended if they have a name" do
|
18
|
+
mod = Module.new do
|
19
|
+
def self::name; "NamedFixture"; end
|
20
|
+
end
|
21
|
+
mod.extend( collection )
|
22
|
+
|
23
|
+
expect( collection.modules ).to include( namedfixture: mod )
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
it "registers its fixture modules when they declare a base if they are anonymous" do
|
28
|
+
mod = Module.new
|
29
|
+
mod.extend( collection )
|
30
|
+
|
31
|
+
expect( collection.modules ).to be_empty
|
32
|
+
|
33
|
+
mod.base( :namedfixture )
|
34
|
+
|
35
|
+
expect( collection.modules ).to include( namedfixture: mod )
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
it "generates a factory constructor for the fixture when it regsters a base" do
|
40
|
+
mod = Module.new
|
41
|
+
mod.extend( collection )
|
42
|
+
mod.base( :new_fixture )
|
43
|
+
|
44
|
+
expect( collection ).to respond_to( :new_fixture )
|
45
|
+
expect( collection.new_fixture ).to be_a( FluentFixtures::Factory )
|
46
|
+
expect( collection.new_fixture.fixture_module ).to be( mod )
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
it "raises if two base fixtures are registered with the same name" do
|
51
|
+
mod1 = Module.new
|
52
|
+
mod1.extend( collection )
|
53
|
+
|
54
|
+
mod2 = Module.new
|
55
|
+
mod2.extend( collection )
|
56
|
+
|
57
|
+
mod1.base( :base_fixture )
|
58
|
+
|
59
|
+
expect {
|
60
|
+
mod2.base( :base_fixture )
|
61
|
+
}.to raise_error( ScriptError, /already have a base fixture.*base_fixture/i )
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
it "can be reset, removing all of its registered fixture modules and their factory methods" do
|
66
|
+
mod1 = Module.new
|
67
|
+
mod1.extend( collection )
|
68
|
+
|
69
|
+
mod2 = Module.new
|
70
|
+
mod2.extend( collection )
|
71
|
+
|
72
|
+
mod1.base( :fixture1 )
|
73
|
+
mod2.base( :fixture2 )
|
74
|
+
|
75
|
+
expect( collection.modules ).to include( fixture1: mod1, fixture2: mod2 )
|
76
|
+
expect( collection ).to respond_to( :fixture1 )
|
77
|
+
expect( collection ).to respond_to( :fixture2 )
|
78
|
+
|
79
|
+
collection.reset!
|
80
|
+
|
81
|
+
expect( collection.modules ).to be_empty
|
82
|
+
expect( collection ).to_not respond_to( :fixture1 )
|
83
|
+
expect( collection ).to_not respond_to( :fixture2 )
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
it "can load fixtures by name" do
|
88
|
+
# Mocking Kernel.require via the class under test
|
89
|
+
expect( collection ).to receive( :require ).
|
90
|
+
with( "fixtures/foo" ).and_return( false )
|
91
|
+
expect( collection ).to receive( :require ).
|
92
|
+
with( "fixtures/bar" ).and_return( false )
|
93
|
+
|
94
|
+
collection.load( :foo, :bar )
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
it "can load all fixtures" do
|
99
|
+
expect( Gem ).to receive( :find_files ).with( "fixtures/*.rb" ).and_return([
|
100
|
+
"fixtures/foo.rb",
|
101
|
+
"fixtures/bar.rb",
|
102
|
+
"fixtures/baz.rb"
|
103
|
+
])
|
104
|
+
|
105
|
+
# Mocking Kernel.require via the class under test
|
106
|
+
expect( collection ).to receive( :require ).
|
107
|
+
with( "fixtures/foo" ).and_return( false )
|
108
|
+
expect( collection ).to receive( :require ).
|
109
|
+
with( "fixtures/bar" ).and_return( false )
|
110
|
+
expect( collection ).to receive( :require ).
|
111
|
+
with( "fixtures/baz" ).and_return( false )
|
112
|
+
|
113
|
+
collection.load_all
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
it "allows the loading prefix to be customized" do
|
118
|
+
collection.fixture_path_prefix( 'acme/fixtures' )
|
119
|
+
expect( collection ).to receive( :require ).
|
120
|
+
with( "acme/fixtures/foo" ).and_return( false )
|
121
|
+
|
122
|
+
collection.load( :foo )
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
describe "fixture extension" do
|
127
|
+
|
128
|
+
let( :extending_module ) do
|
129
|
+
mod = Module.new do
|
130
|
+
def self::name ; "CollectionTests"; end
|
131
|
+
end
|
132
|
+
mod.extend( collection )
|
133
|
+
mod
|
134
|
+
end
|
135
|
+
|
136
|
+
|
137
|
+
it "adds instance data to its fixture modules" do
|
138
|
+
expect( extending_module.decorators ).to be_a( Hash )
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
end
|
145
|
+
|