fluent_fixtures 0.0.2
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.
- 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
|
+
|