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 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
+