sequel-factory 1.0.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/README.md +105 -0
- data/Rakefile +62 -0
- data/lib/sequel/factory.rb +123 -0
- data/sequel-factory.gemspec +23 -0
- metadata +89 -0
data/README.md
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
**Sequel::Factory** is a little [RubyGem](http://rubygems.org) that lets you
|
2
|
+
easily specify factories for your [Sequel](http://sequel.rubyforge.org/) models.
|
3
|
+
A factory is an object that knows how to generate an instance of a model. They
|
4
|
+
are very useful in testing and development scenarios when you need to simulate
|
5
|
+
the existence of real data in your system.
|
6
|
+
|
7
|
+
Sequel::Factory supports the following features:
|
8
|
+
|
9
|
+
* Multiple factories per model, with different attributes
|
10
|
+
* Inclusion of the attributes of one factory into another
|
11
|
+
* Ability to configure the `Sequel::Model` method used by a factory to
|
12
|
+
generate instances (defaults to `create`)
|
13
|
+
* Ability to override factory-generated values on instance creation
|
14
|
+
* Sequential attributes (e.g. auto-incrementing ids)
|
15
|
+
|
16
|
+
Also, the actual code is only ~100 lines of Ruby, so it should be fairly
|
17
|
+
straightforward for you to understand.
|
18
|
+
|
19
|
+
## Installation
|
20
|
+
|
21
|
+
Using [RubyGems](http://rubygems.org/):
|
22
|
+
|
23
|
+
$ sudo gem install sequel-factory
|
24
|
+
|
25
|
+
From a local copy:
|
26
|
+
|
27
|
+
$ git clone git://github.com/mjijackson/sequel-factory.git
|
28
|
+
$ cd sequel-factory
|
29
|
+
$ rake package && sudo rake install
|
30
|
+
|
31
|
+
## Usage
|
32
|
+
|
33
|
+
Sequel::Factory adds a `factory` method to `Sequel::Model`. You use this
|
34
|
+
method to define your factories by passing a block and (optionally) a name. When
|
35
|
+
you pass a block a new instance of `Sequel::Factory` is created.
|
36
|
+
|
37
|
+
You call a factory using `Sequel::Model.make`. Each time the factory is called
|
38
|
+
its block is `instance_eval`'d in the context of the factory. The factory uses
|
39
|
+
`method_missing` to catch all unknown method calls and their arguments, which
|
40
|
+
should correspond to the names and values of attributes to use for the model.
|
41
|
+
|
42
|
+
This may sound a bit complex, but it works out to be very simple in practice.
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
User.factory do
|
46
|
+
# self is User.factory (or User.factories[:default])
|
47
|
+
name Randgen.name
|
48
|
+
end
|
49
|
+
|
50
|
+
User.factory(:with_email) do
|
51
|
+
# self is User.factory(:with_email)
|
52
|
+
include_factory User.factory
|
53
|
+
email Randgen.email
|
54
|
+
end
|
55
|
+
|
56
|
+
user1 = User.make # Has a "name" attribute
|
57
|
+
user2 = User.make(:with_email) # Has both "name" and "email" attributes
|
58
|
+
```
|
59
|
+
|
60
|
+
The above example defines two factories on the `User` model: a "default" factory
|
61
|
+
and another factory named `:with_email`. The `:with_email` factory _includes_
|
62
|
+
the default factory (using `Sequel::Factory#include_factory`), which just means
|
63
|
+
that all attributes defined in the default factory will also be set on instances
|
64
|
+
that are generated with the `:with_email` factory in addition to any attributes
|
65
|
+
it defines itself.
|
66
|
+
|
67
|
+
In the example above I'm using the helpful [randexp gem](http://rubygems.org/gems/randexp)
|
68
|
+
to generate my factory values, but you can generate them however you like.
|
69
|
+
|
70
|
+
If you need to generate unique sequential values you can pass a block to the
|
71
|
+
attribute name when you call it in the factory. Each time this block is called
|
72
|
+
it takes an incrementing integer value as its argument. The return value of the
|
73
|
+
block is used as the value of the attribute.
|
74
|
+
|
75
|
+
The following example defines a factory on the `User` model that is able to
|
76
|
+
generate a new `User` object with unique `id` and `handle` attributes.
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
User.factory do
|
80
|
+
id {|n| n }
|
81
|
+
handle "user#{id}"
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
85
|
+
## License
|
86
|
+
|
87
|
+
Copyright 2012 Michael Jackson
|
88
|
+
|
89
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
90
|
+
of this software and associated documentation files (the "Software"), to deal
|
91
|
+
in the Software without restriction, including without limitation the rights
|
92
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
93
|
+
copies of the Software, and to permit persons to whom the Software is
|
94
|
+
furnished to do so, subject to the following conditions:
|
95
|
+
|
96
|
+
The above copyright notice and this permission notice shall be included in
|
97
|
+
all copies or substantial portions of the Software.
|
98
|
+
|
99
|
+
The software is provided "as is", without warranty of any kind, express or
|
100
|
+
implied, including but not limited to the warranties of merchantability,
|
101
|
+
fitness for a particular purpose and non-infringement. In no event shall the
|
102
|
+
authors or copyright holders be liable for any claim, damages or other
|
103
|
+
liability, whether in an action of contract, tort or otherwise, arising from,
|
104
|
+
out of or in connection with the software or the use or other dealings in
|
105
|
+
the software.
|
data/Rakefile
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'rake/clean'
|
2
|
+
|
3
|
+
task :default => :package
|
4
|
+
|
5
|
+
# DOCS ########################################################################
|
6
|
+
|
7
|
+
desc "Generate API documentation"
|
8
|
+
task :api => FileList['lib/**/*.rb'] do |t|
|
9
|
+
output_dir = ENV['OUTPUT_DIR'] || 'api'
|
10
|
+
rm_rf output_dir
|
11
|
+
sh((<<-SH).gsub(/[\s\n]+/, ' ').strip)
|
12
|
+
hanna
|
13
|
+
--op #{output_dir}
|
14
|
+
--promiscuous
|
15
|
+
--charset utf8
|
16
|
+
--fmt html
|
17
|
+
--inline-source
|
18
|
+
--line-numbers
|
19
|
+
--accessor option_accessor=RW
|
20
|
+
--main Sequel::Factory
|
21
|
+
--title 'Sequel::Factory API Documentation'
|
22
|
+
#{t.prerequisites.join(' ')}
|
23
|
+
SH
|
24
|
+
end
|
25
|
+
|
26
|
+
CLEAN.include 'api'
|
27
|
+
|
28
|
+
# PACKAGING & INSTALLATION ####################################################
|
29
|
+
|
30
|
+
if defined?(Gem)
|
31
|
+
$spec = eval("#{File.read('sequel-factory.gemspec')}")
|
32
|
+
|
33
|
+
directory 'dist'
|
34
|
+
|
35
|
+
def package(ext)
|
36
|
+
"dist/#{$spec.name}-#{$spec.version}#{ext}"
|
37
|
+
end
|
38
|
+
|
39
|
+
file package('.gem') => %w< dist > + $spec.files do |f|
|
40
|
+
sh "gem build sequel-factory.gemspec"
|
41
|
+
mv File.basename(f.name), f.name
|
42
|
+
end
|
43
|
+
|
44
|
+
file package('.tar.gz') => %w< dist > + $spec.files do |f|
|
45
|
+
sh "git archive --format=tar HEAD | gzip > #{f.name}"
|
46
|
+
end
|
47
|
+
|
48
|
+
desc "Build packages"
|
49
|
+
task :package => %w< .gem .tar.gz >.map {|e| package(e) }
|
50
|
+
|
51
|
+
desc "Build and install as local gem"
|
52
|
+
task :install => package('.gem') do |t|
|
53
|
+
sh "gem install #{package('.gem')}"
|
54
|
+
end
|
55
|
+
|
56
|
+
desc "Upload gem to rubygems.org"
|
57
|
+
task :release => package('.gem') do |t|
|
58
|
+
sh "gem push #{package('.gem')}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
CLOBBER.include 'dist'
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
class Factory
|
5
|
+
def initialize(values_proc)
|
6
|
+
@values_proc = values_proc
|
7
|
+
end
|
8
|
+
|
9
|
+
# The Proc this factory uses to generate a new set of values.
|
10
|
+
attr_reader :values_proc
|
11
|
+
|
12
|
+
# Merges all key/value pairs generated by this factory into the
|
13
|
+
# given +values+ hash.
|
14
|
+
def apply_values(values={})
|
15
|
+
@values = values
|
16
|
+
instance_eval(&values_proc)
|
17
|
+
@values
|
18
|
+
end
|
19
|
+
|
20
|
+
# Merges all key/value pairs that are generated by the given +factory+ into
|
21
|
+
# the values currently being generated by this factory. Should be called
|
22
|
+
# inside a values Proc to include values from some higher-level factory.
|
23
|
+
#
|
24
|
+
# User.factory do
|
25
|
+
# name Randgen.name
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# User.factory(:with_url) do
|
29
|
+
# include_factory User.factory
|
30
|
+
# url "http://www.example.com"
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# User.make # Has only a name property
|
34
|
+
# User.make(:with_url) # Has both name *and* url properties
|
35
|
+
def include_factory(factory)
|
36
|
+
factory.apply_values(@values)
|
37
|
+
end
|
38
|
+
|
39
|
+
alias_method :include, :include_factory
|
40
|
+
|
41
|
+
# Gets/sets the value of the given +key+. If any +args+ are provided, the
|
42
|
+
# value is set to the first one. Otherwise, if a block is given it will be
|
43
|
+
# called with a number unique to the given +key+ (like a sequence) and the
|
44
|
+
# return value of the block is used as the value.
|
45
|
+
def method_missing(key, *args)
|
46
|
+
if args.any?
|
47
|
+
@values[key] = args.first
|
48
|
+
elsif block_given?
|
49
|
+
@counts ||= {}
|
50
|
+
@counts[key] ||= 0
|
51
|
+
@counts[key] += 1
|
52
|
+
@values[key] = yield(@counts[key])
|
53
|
+
end
|
54
|
+
|
55
|
+
@values[key]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class Model
|
60
|
+
class << self
|
61
|
+
attr_writer :factory_method
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns the name of the Sequel::Model class method that this factory uses
|
65
|
+
# to make new instances. Defaults to +:create+. Other useful methods are
|
66
|
+
# +:new+ (to prevent saving to the database) and +:find_or_create+ (to avoid
|
67
|
+
# violating uniqueness constraints in the database).
|
68
|
+
def self.factory_method
|
69
|
+
return @factory_method unless @factory_method.nil?
|
70
|
+
return superclass.factory_method if superclass.respond_to?(:factory_method)
|
71
|
+
:create
|
72
|
+
end
|
73
|
+
|
74
|
+
# A Hash of factories for this model, keyed by factory name.
|
75
|
+
def self.factories
|
76
|
+
@factories ||= {}
|
77
|
+
end
|
78
|
+
|
79
|
+
# Gets/sets the factory with the given +name+. If a block is given, uses that
|
80
|
+
# block to create a new factory.
|
81
|
+
def self.factory(name=:default)
|
82
|
+
factories[name] = Factory.new(Proc.new) if block_given?
|
83
|
+
factories[name]
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.has_factory?(name)
|
87
|
+
not factory(name).nil?
|
88
|
+
end
|
89
|
+
|
90
|
+
# Makes an instance of this model using the factories with the given
|
91
|
+
# +factory_names+. If a Hash is given as the *last* argument it is used to
|
92
|
+
# override any factory-produced values with the same name after all factory
|
93
|
+
# values have been generated. The default factory is used if no factory
|
94
|
+
# names are given.
|
95
|
+
def self.make(*factory_names)
|
96
|
+
values = Hash === factory_names.last ? factory_names.pop : Hash.new
|
97
|
+
factory_names << :default if factory_names.empty?
|
98
|
+
|
99
|
+
factory_values = factory_names.inject({}) do |memo, name|
|
100
|
+
fac = factory(name) or raise "Unknown #{self} factory: #{name}"
|
101
|
+
fac.apply_values(memo)
|
102
|
+
end
|
103
|
+
|
104
|
+
send factory_method, factory_values.merge(values)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Forces the #factory_method to be the given +method+ temporarily while an
|
108
|
+
# instance is made, then reverts to the old factory method.
|
109
|
+
def self.make_with(method, *args)
|
110
|
+
tmp = @factory_method
|
111
|
+
@factory_method = method
|
112
|
+
obj = make(*args)
|
113
|
+
@factory_method = tmp
|
114
|
+
obj
|
115
|
+
end
|
116
|
+
|
117
|
+
# Sugar for +make_with(:new, *args)+. Useful when the #factory_method is
|
118
|
+
# something other than +:new+ but you still want to use it.
|
119
|
+
def self.build(*args)
|
120
|
+
make_with(:new, *args)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path('../lib', __FILE__))
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'sequel-factory'
|
5
|
+
s.version = '1.0.0'
|
6
|
+
s.date = Time.now.strftime('%Y-%m-%d')
|
7
|
+
|
8
|
+
s.summary = 'Simple, powerful factories for Sequel models'
|
9
|
+
s.description = 'Simple, powerful factories for Sequel models'
|
10
|
+
|
11
|
+
s.author = 'Michael Jackson'
|
12
|
+
s.email = 'mjijackson@gmail.com'
|
13
|
+
|
14
|
+
s.require_paths = %w< lib >
|
15
|
+
|
16
|
+
s.files = Dir['lib/**/*.rb'] +
|
17
|
+
%w< sequel-factory.gemspec Rakefile README.md >
|
18
|
+
|
19
|
+
s.add_dependency('sequel', '>= 3.32')
|
20
|
+
s.add_development_dependency('rake')
|
21
|
+
|
22
|
+
s.homepage = 'http://mjijackson.github.com/sequel-factory'
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sequel-factory
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 1
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
version: 1.0.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Michael Jackson
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2012-06-23 00:00:00 -07:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: sequel
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 3
|
29
|
+
- 32
|
30
|
+
version: "3.32"
|
31
|
+
type: :runtime
|
32
|
+
version_requirements: *id001
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: rake
|
35
|
+
prerelease: false
|
36
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
segments:
|
41
|
+
- 0
|
42
|
+
version: "0"
|
43
|
+
type: :development
|
44
|
+
version_requirements: *id002
|
45
|
+
description: Simple, powerful factories for Sequel models
|
46
|
+
email: mjijackson@gmail.com
|
47
|
+
executables: []
|
48
|
+
|
49
|
+
extensions: []
|
50
|
+
|
51
|
+
extra_rdoc_files: []
|
52
|
+
|
53
|
+
files:
|
54
|
+
- lib/sequel/factory.rb
|
55
|
+
- sequel-factory.gemspec
|
56
|
+
- Rakefile
|
57
|
+
- README.md
|
58
|
+
has_rdoc: true
|
59
|
+
homepage: http://mjijackson.github.com/sequel-factory
|
60
|
+
licenses: []
|
61
|
+
|
62
|
+
post_install_message:
|
63
|
+
rdoc_options: []
|
64
|
+
|
65
|
+
require_paths:
|
66
|
+
- lib
|
67
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
segments:
|
72
|
+
- 0
|
73
|
+
version: "0"
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
segments:
|
79
|
+
- 0
|
80
|
+
version: "0"
|
81
|
+
requirements: []
|
82
|
+
|
83
|
+
rubyforge_project:
|
84
|
+
rubygems_version: 1.3.6
|
85
|
+
signing_key:
|
86
|
+
specification_version: 3
|
87
|
+
summary: Simple, powerful factories for Sequel models
|
88
|
+
test_files: []
|
89
|
+
|