journeyman 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.
- checksums.yaml +7 -0
- data/README.md +189 -0
- data/lib/journeyman.rb +65 -0
- data/lib/journeyman/builder.rb +66 -0
- data/lib/journeyman/configuration.rb +168 -0
- data/lib/journeyman/definition.rb +55 -0
- data/lib/journeyman/integration.rb +38 -0
- data/lib/journeyman/load.rb +52 -0
- metadata +57 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: cc88ba880fcd67214a896dac32304a317037a538
|
4
|
+
data.tar.gz: 5e943948aea70eb2bcc2f4e58ce65671ef04a691
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4d2deabc6944da9b531b69861d78a7867d74a0f05eb1b7f8dffe5620916fd7d64190a8d265715f5c0fd5b046a79b24130701de0c234680a45a9363b57dc0f786
|
7
|
+
data.tar.gz: 0ed0da538b523579bf98500ff50e70de99ea7b80384b60190d9810279b8acbb981c9c6122f499f662b12b54cadee4e65404a577013c23be86579f8fb2f7021f7
|
data/README.md
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
Journeyman [](http://badge.fury.io/rb/journeyman) [](https://travis-ci.org/ElMassimo/journeyman) [](https://codeclimate.com/repos/5372dce769568034ff0304c2/feed) [](https://codeclimate.com/repos/5372dce769568034ff0304c2/feed) [](http://inch-ci.org/github/ElMassimo/journeyman) [](https://github.com/ElMassimo/journeyman/blob/master/LICENSE.txt)
|
2
|
+
=====================
|
3
|
+
|
4
|
+
Journeyman is a fixtures replacement with an extremely light definition syntax,
|
5
|
+
providing two default build strategies, but allowing full customization on how
|
6
|
+
to build an object.
|
7
|
+
|
8
|
+
## Usage
|
9
|
+
Journeyman is built to work out of the box with RSpec and Cucumber, and any
|
10
|
+
console environment, if you need support for other testing frameworks we can
|
11
|
+
work it out :smiley:.
|
12
|
+
|
13
|
+
Since it has no dependencies, it's possible to use it in any Ruby project.
|
14
|
+
|
15
|
+
### Load
|
16
|
+
Journeyman will attempt to load files under the `spec/factories` directory, but
|
17
|
+
you may overwrite `Journeyman.factories_paths` by providing an Array that
|
18
|
+
containst different paths for factories.
|
19
|
+
|
20
|
+
### Definition
|
21
|
+
Journeyman allows you to provide the default attributes for creation as the
|
22
|
+
return value of the definition block.
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
Journeyman.define :album do |c|
|
26
|
+
{
|
27
|
+
title: 'Wish You Were Here',
|
28
|
+
recorded_ago: -> { (Date.today - Date.new(1975, 9, 12)).round / 365 },
|
29
|
+
band: -> { Journeyman.create(:band, name: 'Pink Floyd') }
|
30
|
+
}
|
31
|
+
end
|
32
|
+
```
|
33
|
+
The default values are superseded by the value you provide to `build` or `create`.
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
pink_floyd = find_band('Pink Floyd')
|
37
|
+
|
38
|
+
Journeyman.build(:album, band: pink_floyd) == Album.new({
|
39
|
+
title: 'Wish You Were Here',
|
40
|
+
recorded_ago: -> { ... }.call,
|
41
|
+
band: pink_floyd
|
42
|
+
})
|
43
|
+
```
|
44
|
+
The default values can be static, or dynamic. If you specify a `Proc` or `lambda`
|
45
|
+
as a default value for an attribute, it will be evaluated whenever the attribute
|
46
|
+
is not provided when an object is built.
|
47
|
+
|
48
|
+
### Configuration
|
49
|
+
Journeyman is a configurable beast, yet has really strong defaults.
|
50
|
+
```ruby
|
51
|
+
# DSL Methods
|
52
|
+
find, process, ignore, build, after_create
|
53
|
+
|
54
|
+
# Configuration Options
|
55
|
+
[:parent, :model, :finder_attribute]
|
56
|
+
```
|
57
|
+
|
58
|
+
#### DSL
|
59
|
+
Journeyman provides a nice DSL that lets you provide a block or lambda with your
|
60
|
+
own builder, or finder, and other miscellanous (and handy) hooks.
|
61
|
+
|
62
|
+
* `find:` Allows you to define the finder, takes a single argument.
|
63
|
+
|
64
|
+
* `build:` You can provide a custom builder, receives a Hash of attributes, but
|
65
|
+
you have full liberty of what you do with them, the return value must be the
|
66
|
+
built object.
|
67
|
+
|
68
|
+
* `process:` If you need to process the attributes before building you can
|
69
|
+
provide a block to do just that, make sure to return the attributes at the end.
|
70
|
+
|
71
|
+
* `ignore:` There are cases where you want to ignore certain attributes during
|
72
|
+
the `build`, but you want them in the `after_create` callback. You can ignore
|
73
|
+
those attributes by passing a list, or Array with the attributes you wish to
|
74
|
+
ignore.
|
75
|
+
|
76
|
+
* `after_create`: Callback that takes the newly built object, and the original
|
77
|
+
attributes. Specially useful when combined with ignore.
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
Journeyman.define :employee do
|
81
|
+
find { |id| Person.find(id) }
|
82
|
+
|
83
|
+
build { |attrs|
|
84
|
+
attrs.delete(:company).new_employee(attrs)
|
85
|
+
}
|
86
|
+
|
87
|
+
ignore :work_history
|
88
|
+
|
89
|
+
after_create { |employee, attrs|
|
90
|
+
attrs[:work_history].each do |history|
|
91
|
+
check_references(history)
|
92
|
+
end
|
93
|
+
}
|
94
|
+
end
|
95
|
+
```
|
96
|
+
|
97
|
+
#### Configuration Options
|
98
|
+
Configuration options can be passed alongside the name in the factory definition:
|
99
|
+
|
100
|
+
* `parent:` Name of the factory that is going to be used as a parent, if `parent`
|
101
|
+
is set, the default builder consists of invoking the parent factory builder.
|
102
|
+
|
103
|
+
* `model:` Expects a class that will be used for the default builder and finder.
|
104
|
+
Useful for cases where the inferrence from the name does not work, or the factory
|
105
|
+
name is simply different than the object class it's building.
|
106
|
+
|
107
|
+
* `finder_attribute:` Name of the attribute used to find an object by the default
|
108
|
+
finder. The default is `:name`.
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
Journeyman.define :employee, model: People, finder_attribute: :social_security_id do
|
112
|
+
...
|
113
|
+
end
|
114
|
+
|
115
|
+
Journeyman.define :journeyman, parent: :employee do
|
116
|
+
...
|
117
|
+
end
|
118
|
+
```
|
119
|
+
|
120
|
+
## Setup
|
121
|
+
```ruby
|
122
|
+
# Gemfile
|
123
|
+
group :test do
|
124
|
+
gem 'journeyman'
|
125
|
+
end
|
126
|
+
|
127
|
+
# Generic Use (mock script)
|
128
|
+
require 'journeyman'
|
129
|
+
|
130
|
+
Journeyman.load(self)
|
131
|
+
````
|
132
|
+
### RSpec
|
133
|
+
```ruby
|
134
|
+
# spec/support/spec_helper.rb or similar
|
135
|
+
require 'journeyman'
|
136
|
+
|
137
|
+
Journeyman.load(self, framework: :rspec)
|
138
|
+
````
|
139
|
+
|
140
|
+
### Cucumber
|
141
|
+
```ruby
|
142
|
+
# features/support/journeyman.rb or similar
|
143
|
+
require 'journeyman'
|
144
|
+
|
145
|
+
Journeyman.load(self, framework: :cucumber)
|
146
|
+
````
|
147
|
+
|
148
|
+
## Advantages
|
149
|
+
|
150
|
+
* You have full control of how your objects are created, *and* have to write
|
151
|
+
less boilerplate.
|
152
|
+
* You can chain several factories using parent, which allows you to create
|
153
|
+
different factories for the same object with less effort.
|
154
|
+
* Code is highly optimized, so the library is much faster than say, FactoryGirl,
|
155
|
+
specially when building objects without database interaction.
|
156
|
+
|
157
|
+
|
158
|
+
### Examples
|
159
|
+
|
160
|
+
You can check the [specs](https://github.com/ElMassimo/journeyman/tree/master/spec) of the project
|
161
|
+
to check how to check some basic factories, and learn how to set it up.
|
162
|
+
|
163
|
+
## Notes
|
164
|
+
* The DSL does not use instance_exec to allow access to the external context.
|
165
|
+
|
166
|
+
|
167
|
+
License
|
168
|
+
--------
|
169
|
+
|
170
|
+
Copyright (c) 2014 Máximo Mussini
|
171
|
+
|
172
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
173
|
+
a copy of this software and associated documentation files (the
|
174
|
+
"Software"), to deal in the Software without restriction, including
|
175
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
176
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
177
|
+
permit persons to whom the Software is furnished to do so, subject to
|
178
|
+
the following conditions:
|
179
|
+
|
180
|
+
The above copyright notice and this permission notice shall be
|
181
|
+
included in all copies or substantial portions of the Software.
|
182
|
+
|
183
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
184
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
185
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
186
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
187
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
188
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
189
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/lib/journeyman.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'journeyman/load'
|
2
|
+
require 'journeyman/integration'
|
3
|
+
require 'journeyman/definition'
|
4
|
+
|
5
|
+
# Public: Allows to define and use factory methods. It is capable of providing
|
6
|
+
# `build`, `create`, `find`, and `default` methods, the last two are optional.
|
7
|
+
#
|
8
|
+
# Examples:
|
9
|
+
#
|
10
|
+
# Journeyman.define :user do |t|
|
11
|
+
# {
|
12
|
+
# name: "Johnnie Walker",
|
13
|
+
# date_of_birth: ->{ 150.years.ago }
|
14
|
+
# }
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# Journeyman.build(:user) => build_user
|
18
|
+
# Journeyman.create(:user) => create_user
|
19
|
+
#
|
20
|
+
module Journeyman
|
21
|
+
extend Load
|
22
|
+
extend Integration
|
23
|
+
extend Definition
|
24
|
+
|
25
|
+
# Public: Initializes Journeyman by loading the libraries, attaching to the
|
26
|
+
# current context, and configuring the testing libraries.
|
27
|
+
def self.load(env, framework: nil)
|
28
|
+
@helpers = Module.new
|
29
|
+
attach(env)
|
30
|
+
load_factories
|
31
|
+
setup_integration(env, framework)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Public: Attaches Journeyman to the specified context, which enables the use
|
35
|
+
# of the convenience acessors for the factory methods, like `Journeyman.build`.
|
36
|
+
def self.attach(context)
|
37
|
+
@context = context
|
38
|
+
end
|
39
|
+
|
40
|
+
# Public: Convenience accessor for build methods.
|
41
|
+
def self.build(name, *args, &block)
|
42
|
+
@context.send("build_#{name}", *args, &block)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Public: Convenience accessor for create methods.
|
46
|
+
def self.create(name, *args, &block)
|
47
|
+
@context.send("create_#{name}", *args, &block)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Public: Convenience accessor for default methods.
|
51
|
+
def self.default(name)
|
52
|
+
@context.send("default_#{name}")
|
53
|
+
end
|
54
|
+
|
55
|
+
# Internal: Executes a proc in the context that is currently attached.
|
56
|
+
def self.execute(proc, *args)
|
57
|
+
if proc
|
58
|
+
if proc.arity == 0
|
59
|
+
@context.instance_exec(&proc)
|
60
|
+
else
|
61
|
+
@context.instance_exec(*args, &proc)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'journeyman/configuration'
|
2
|
+
module Journeyman
|
3
|
+
|
4
|
+
# Internal: Builds and creates objects, using the configuration provided.
|
5
|
+
class Builder
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
attr_reader :config
|
9
|
+
def_delegators(:config, *Configuration::OPTIONS, *Configuration::METHOD_OPTIONS, :name)
|
10
|
+
|
11
|
+
def initialize(name, options, config)
|
12
|
+
@config = Configuration.new(name, options, config)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Internal: Executes the finder.
|
16
|
+
def find(id)
|
17
|
+
config.finder.call(id)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Internal: Builds a new instance, using the configuration specified in the
|
21
|
+
# factory.
|
22
|
+
#
|
23
|
+
# attrs - The attributes used to build the object
|
24
|
+
#
|
25
|
+
# Returns a new instance of the object.
|
26
|
+
def build(attrs={})
|
27
|
+
check_build_arguments(attrs)
|
28
|
+
attrs = merge_defaults(attrs)
|
29
|
+
attrs = Journeyman.execute(processor, attrs) if processor
|
30
|
+
config.builder.call(attrs)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Internal: Create a new instance, using the configuration specified in the
|
34
|
+
# factory.
|
35
|
+
#
|
36
|
+
# attrs - The attributes used to build the object
|
37
|
+
#
|
38
|
+
# Returns a new instance of the object.
|
39
|
+
def create(attrs={})
|
40
|
+
build(attrs).tap { |instance|
|
41
|
+
instance.save!
|
42
|
+
Journeyman.execute(after_create_callback, instance, attrs)
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# Internal: Merges the default attributes to the specified attributes Hash.
|
49
|
+
#
|
50
|
+
# Returns the modified Hash.
|
51
|
+
def merge_defaults(attributes={})
|
52
|
+
attributes.tap do |attrs|
|
53
|
+
attrs.merge!(static_defaults){ |key, user, default| user } # Reverse Merge
|
54
|
+
dynamic_defaults.each { |key, value| attrs[key] ||= Journeyman.execute(value, attrs) }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Internal: Checks the attributes to make sure it's a Hash, to provide more
|
59
|
+
# insight on wrong usage.
|
60
|
+
def check_build_arguments(attrs)
|
61
|
+
unless attrs.is_a?(Hash)
|
62
|
+
raise ArgumentError, "Journeyman expected a Hash, but received: #{attrs}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
module Journeyman
|
2
|
+
|
3
|
+
# Public: Provides a DSL for configuration of the factories.
|
4
|
+
class Configuration
|
5
|
+
|
6
|
+
METHOD_OPTIONS = [:finder, :builder, :processor]
|
7
|
+
|
8
|
+
OPTIONS = [
|
9
|
+
:parent, :defaults, :finder_attribute, # Public
|
10
|
+
:static_defaults, :dynamic_defaults, :after_create_callback # Internal
|
11
|
+
]
|
12
|
+
|
13
|
+
# Internal: Name of the factory, and configuration options.
|
14
|
+
attr_reader :name, :options
|
15
|
+
|
16
|
+
# Public: Receives the name of the factory, and configuration options.
|
17
|
+
#
|
18
|
+
# Yields itself to the block passed to the `Journeyman.define`.
|
19
|
+
def initialize(name, options, config)
|
20
|
+
@name, @options = name, options
|
21
|
+
extract_defaults config.call(self)
|
22
|
+
verify_valid_or_exit
|
23
|
+
end
|
24
|
+
|
25
|
+
# Public: DSL to configure a Journeyman definition. The following are only
|
26
|
+
# the methods that take a block, other configuration options exist but take
|
27
|
+
# a single argument.
|
28
|
+
|
29
|
+
# Public: Defines how to find an instance.
|
30
|
+
#
|
31
|
+
# find { |id|
|
32
|
+
# User.find_by(name_or_email(id) => id)
|
33
|
+
# }
|
34
|
+
#
|
35
|
+
# Yields the find argument passed to the `find_#{name}` method.
|
36
|
+
def find(proc=nil, &block)
|
37
|
+
options[:finder] = proc || block
|
38
|
+
end
|
39
|
+
|
40
|
+
# Public: Defines how to build an instance. Highly customizable.
|
41
|
+
#
|
42
|
+
# build { |attributes|
|
43
|
+
# blueprint, patient = attributes.delete(:blueprint), attributes.delete(:patient)
|
44
|
+
# blueprint.enroll(patient)
|
45
|
+
# }
|
46
|
+
#
|
47
|
+
# Yields the arguments passed to the `build_#{name}` method after adding the
|
48
|
+
# defaults and invoking the processor.
|
49
|
+
def build(proc=nil, &block)
|
50
|
+
options[:builder] ||= proc || block
|
51
|
+
end
|
52
|
+
|
53
|
+
# Public: Attributes processor, allows to modify the passed attributes
|
54
|
+
# before building an instance.
|
55
|
+
def process(proc=nil, &block)
|
56
|
+
options[:processor] ||= proc || block
|
57
|
+
end
|
58
|
+
|
59
|
+
# Public: Allows to ignore certain attributes, that can be accessed in the
|
60
|
+
# after_create callback.
|
61
|
+
def ignore(*ignored)
|
62
|
+
options[:ignored] = ->(attrs) do
|
63
|
+
attrs = attrs.dup; ignored.each { |key| attrs.delete(key) }; attrs
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Public: Invoked after creating an object, useful for setting up secondary
|
68
|
+
# or optional relations.
|
69
|
+
def after_create(proc=nil, &block)
|
70
|
+
options[:after_create_callback] ||= proc || block
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
# Internal: Configuration Options provided for the Journeyman builder. Here
|
75
|
+
# is where you can stop reading unless you are interested in the internals.
|
76
|
+
|
77
|
+
# Internal: Class of the model to build, used in the default build strategy.
|
78
|
+
def model
|
79
|
+
options[:model] ||= infer_model_class(name)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Internal: Returns the finder proc, or the default finder strategy.
|
83
|
+
def finder
|
84
|
+
options[:finder] ||= default_finder
|
85
|
+
end
|
86
|
+
|
87
|
+
# Internal: Returns the finder proc, or the default builder strategy.
|
88
|
+
def builder
|
89
|
+
options[:builder] ||= parent_builder || default_builder
|
90
|
+
end
|
91
|
+
|
92
|
+
# Internal: Returns a custom processor, or ignore directive.
|
93
|
+
def processor
|
94
|
+
options[:ignored] || options[:processor]
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
# Internal: Default finder strategy used.
|
100
|
+
def default_finder
|
101
|
+
options[:finder_attribute] ||= :name
|
102
|
+
->(name) { model.find_by(finder_attribute => name) }
|
103
|
+
end
|
104
|
+
|
105
|
+
# Internal: Creates the object by simply using the initializer.
|
106
|
+
def default_builder
|
107
|
+
->(attrs={}) { model.new(attrs) }
|
108
|
+
end
|
109
|
+
|
110
|
+
# Internal: Uses the builder of the parent to construct the object.
|
111
|
+
#
|
112
|
+
# Returns the builder if a :parent was set, or nil.
|
113
|
+
def parent_builder
|
114
|
+
->(attrs={}) { Journeyman.build(parent, attrs) } if parent
|
115
|
+
end
|
116
|
+
|
117
|
+
# Internal: Prepares the default arguments for the builder.
|
118
|
+
# The return value of the configuration block may provide the defaults.
|
119
|
+
#
|
120
|
+
# result - The return value of the configuration block.
|
121
|
+
#
|
122
|
+
# Returns nothing.
|
123
|
+
def extract_defaults(result)
|
124
|
+
defaults = options[:defaults] || (result if result.is_a?(Hash)) || {}
|
125
|
+
|
126
|
+
options[:dynamic_defaults], options[:static_defaults] = partition_defaults(defaults)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Internal: Splits static from dynamic arguments for runtime performance.
|
130
|
+
def partition_defaults(defaults)
|
131
|
+
defaults.partition { |key, value| value.is_a?(Proc) }.map(&:to_h)
|
132
|
+
end
|
133
|
+
|
134
|
+
# Internal: Infers a model class.
|
135
|
+
def infer_model_class(name)
|
136
|
+
if defined? ActiveSupport
|
137
|
+
name.to_s.classify.constantize
|
138
|
+
else
|
139
|
+
Object.const_get(name.to_s.split('_').collect!{ |w| w.capitalize }.join)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Internal: Checks the configuration's consistency
|
144
|
+
def verify_valid_or_exit
|
145
|
+
if options[:parent] && options[:builder]
|
146
|
+
raise InvalidArgumentError, 'custom builder can not be used in combination with `parent: true`'
|
147
|
+
end
|
148
|
+
if options[:ignored] && options[:processor]
|
149
|
+
raise InvalidArgumentError, 'custom processor can not be used in combination with `ignore`'
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Internal: Performance optimization, the option method is defined the first
|
154
|
+
# time it's accessed.
|
155
|
+
def self.define_option_method(name)
|
156
|
+
class_eval <<-OPTION
|
157
|
+
def #{name}(value=nil)
|
158
|
+
options[:#{name}] = value if value
|
159
|
+
options[:#{name}]
|
160
|
+
end
|
161
|
+
OPTION
|
162
|
+
end
|
163
|
+
|
164
|
+
OPTIONS.each do |name|
|
165
|
+
define_option_method(name)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'journeyman/builder'
|
2
|
+
module Journeyman
|
3
|
+
|
4
|
+
# Internal: Contains all the factory method definition logic.
|
5
|
+
module Definition
|
6
|
+
|
7
|
+
# Public: Defines a new factory for Journeyman, which consists in a build
|
8
|
+
# and create method, and may optionally include finder and default methods.
|
9
|
+
#
|
10
|
+
# Returns Builder for debug purposes.
|
11
|
+
def define(name, options={}, &config)
|
12
|
+
finder, default = options.delete(:include_finder), options.delete(:include_default)
|
13
|
+
|
14
|
+
Builder.new(name, options, config).tap do |builder|
|
15
|
+
define_find_method(name, builder) unless finder == false
|
16
|
+
define_build_method(name, builder)
|
17
|
+
define_create_method(name, builder)
|
18
|
+
define_default_method(name) if default
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# Factories Definitions
|
25
|
+
|
26
|
+
# Internal: Defines the finder method.
|
27
|
+
def define_find_method(name, builder)
|
28
|
+
define_helper "find_#{name}", ->(id) { builder.find(id) }
|
29
|
+
end
|
30
|
+
|
31
|
+
# Internal: Defines the builder method.
|
32
|
+
def define_build_method(name, builder)
|
33
|
+
define_helper "build_#{name}", ->(attrs={}) { builder.build(attrs) }
|
34
|
+
end
|
35
|
+
|
36
|
+
# Internal: Defines the create method.
|
37
|
+
def define_create_method(name, builder)
|
38
|
+
define_helper "create_#{name}", ->(attrs={}) { builder.create(attrs) }
|
39
|
+
end
|
40
|
+
|
41
|
+
# Internal: Defines the default method.
|
42
|
+
def define_default_method(name)
|
43
|
+
@helpers.send :class_eval, <<-EVAL
|
44
|
+
def default_#{name}
|
45
|
+
@#{name} ||= Journeyman.create(:'#{name}')
|
46
|
+
end
|
47
|
+
EVAL
|
48
|
+
end
|
49
|
+
|
50
|
+
# Internal: Syntax sugar to define a method in the helpers module.
|
51
|
+
def define_helper(name, proc)
|
52
|
+
@helpers.send :define_method, name, proc
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Journeyman
|
2
|
+
|
3
|
+
# Internal: Integrations with testing frameworks.
|
4
|
+
module Integration
|
5
|
+
|
6
|
+
# Internal: Sets up the integration with the framework being used.
|
7
|
+
def setup_integration(env, framework)
|
8
|
+
case framework
|
9
|
+
when :rspec then setup_rspec_integration(env)
|
10
|
+
when :cucumber then setup_cucumber_integration(env)
|
11
|
+
else setup_default_integration(env)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
# Internal: Sets up the default integration, which is helpful for console and
|
18
|
+
# mock scripts.
|
19
|
+
def setup_default_integration(env)
|
20
|
+
env.send :include, @helpers
|
21
|
+
Journeyman.attach(env)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Internal: Attaches Journeyman to the RSpec context, and adds the helpers.
|
25
|
+
def setup_rspec_integration(env)
|
26
|
+
RSpec.configure do |config|
|
27
|
+
config.include @helpers
|
28
|
+
config.before(:each) { Journeyman.attach(self) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Internal: Attaches Journeyman to the Cucumber context, and adds the helpers.
|
33
|
+
def setup_cucumber_integration(cucumber)
|
34
|
+
cucumber.World(@helpers)
|
35
|
+
cucumber.Before { Journeyman.attach(self) }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Journeyman
|
2
|
+
|
3
|
+
# Internal: Contains all the file requirement logic, to load the factory definitions.
|
4
|
+
module Load
|
5
|
+
|
6
|
+
# Public: Paths that will be loaded expecting factory definitions.
|
7
|
+
attr_accessor :factories_paths
|
8
|
+
|
9
|
+
def self.extended(journeyman)
|
10
|
+
journeyman.factories_paths = %w(spec/factories)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Internal: Loads all the factory files and processes the factory definitions.
|
14
|
+
def load_factories
|
15
|
+
absolute_factories_paths.each do |path|
|
16
|
+
load_factories_if_file(path)
|
17
|
+
load_factories_if_directory(path)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# Internal: Builds the absolute path for the factories location.
|
24
|
+
def absolute_factories_paths
|
25
|
+
if root_path
|
26
|
+
factories_paths.map { |path| root_path.join(path) }
|
27
|
+
else
|
28
|
+
factories_paths.map { |path| File.expand_path(path) }.uniq
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Internal: If the path matches a file, it loads the factories defined in it.
|
33
|
+
def load_factories_if_file(path)
|
34
|
+
Kernel.load("#{path}.rb") if File.exists?("#{path}.rb")
|
35
|
+
end
|
36
|
+
|
37
|
+
# Internal: If the path is a directory, it loads all the factories in that path.
|
38
|
+
def load_factories_if_directory(path)
|
39
|
+
if File.directory?(path)
|
40
|
+
Dir[File.join(path, '**', '*.rb')].sort.each { |file| Kernel.load file }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Internal: Returns the root path of the project
|
45
|
+
# TODO: Extract Rails and Sinatra integration.
|
46
|
+
def root_path
|
47
|
+
defined?(Rails) && Rails.root ||
|
48
|
+
defined?(Sinatra::Application) && Pathname.new(Sinatra::Application.root) ||
|
49
|
+
defined?(ROOT_DIR) && Pathname.new(ROOT_DIR)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
metadata
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: journeyman
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Máximo Mussini
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-08-16 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Journeyman allows you to define factories with custom build methods,
|
14
|
+
allowing you to easily bend object creation to the nuances of your application and
|
15
|
+
domain. This means you can rely more in Ruby, and less on your ORM
|
16
|
+
email:
|
17
|
+
- maximomussini@gmail.com
|
18
|
+
executables: []
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files:
|
21
|
+
- README.md
|
22
|
+
files:
|
23
|
+
- README.md
|
24
|
+
- lib/journeyman.rb
|
25
|
+
- lib/journeyman/builder.rb
|
26
|
+
- lib/journeyman/configuration.rb
|
27
|
+
- lib/journeyman/definition.rb
|
28
|
+
- lib/journeyman/integration.rb
|
29
|
+
- lib/journeyman/load.rb
|
30
|
+
homepage: https://github.com/ElMassimo/journeyman
|
31
|
+
licenses:
|
32
|
+
- MIT
|
33
|
+
metadata: {}
|
34
|
+
post_install_message:
|
35
|
+
rdoc_options:
|
36
|
+
- "--charset=UTF-8"
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.9.3
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
requirements: []
|
50
|
+
rubyforge_project:
|
51
|
+
rubygems_version: 2.2.2
|
52
|
+
signing_key:
|
53
|
+
specification_version: 4
|
54
|
+
summary: Let your factories use your business logic, keeping them flexible and making
|
55
|
+
them easier to update.
|
56
|
+
test_files: []
|
57
|
+
has_rdoc:
|