sequel-factory 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|