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