model_grinder 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/lib/model_grinder.rb +133 -0
- data/lib/model_grinder/class_methods.rb +148 -0
- data/lib/model_grinder/ruby_hacks.rb +18 -0
- metadata +64 -0
@@ -0,0 +1,133 @@
|
|
1
|
+
module ModelGrinder; end
|
2
|
+
|
3
|
+
require 'model_grinder/class_methods'
|
4
|
+
|
5
|
+
##
|
6
|
+
# = Introduction
|
7
|
+
# Model Grinder is a library to make the generation of data for use in tests. It is inspired by dm-sweatshop and is
|
8
|
+
# intended for use in all ORMs, not just DataMapper.
|
9
|
+
#
|
10
|
+
# == Setup
|
11
|
+
# To use with one ORM, pass the name of the ORM to ModelGrinder.integrate via a symbol. Like so:
|
12
|
+
# ModelGrinder.integrate(:datamapper)
|
13
|
+
# Currently the options are :datamapper, :activerecord, and :mongoid. I'm still trying to get Mongoid to work
|
14
|
+
# properly, so be patient. If you wish to simply have it load for all available ORMs, pass the argument of :all instead
|
15
|
+
# of the ORM name.
|
16
|
+
#
|
17
|
+
# Note that you do not have to integrate if you wish to use the ModelGrinder to make all the calls instead of calling
|
18
|
+
# directly on the models.
|
19
|
+
#
|
20
|
+
# == Usage
|
21
|
+
# === Templates
|
22
|
+
# An example is worth ten million words and two examples are worth twenty million, I suppose. Both are identical -
|
23
|
+
# the second sets up and calls the same methods the first one does.
|
24
|
+
#
|
25
|
+
# ModelGrinder.template(YourModel, :valid ) {{
|
26
|
+
# name: /\w+/.gen,
|
27
|
+
# some_text: /[:sentence]/.gen
|
28
|
+
# some_number: rand(5938571234)
|
29
|
+
# }}
|
30
|
+
#
|
31
|
+
# YourModel.template(:valid) {{
|
32
|
+
# name: /\w+/.gen,
|
33
|
+
# some_text: /[:sentence]/.gen
|
34
|
+
# some_number: rand(5938571234) + 6
|
35
|
+
# }}
|
36
|
+
#
|
37
|
+
# Your first question might be: "what the crap is up with the double curly braces??" Well, the first curly brace is
|
38
|
+
# the beginning of the block, the second curly brace is the beginning of the hash. You pass a hash inside a block so
|
39
|
+
# that the library can call this block multiple times and get different results. Anything you want to stay the same
|
40
|
+
# you pass in as a static value, anything you want to be different you pass in some sort of random generator.
|
41
|
+
#
|
42
|
+
# This library includes the randexp gem, which is used in these examples to generate random data. You can read more about
|
43
|
+
# it here at https://github.com/benburkert/randexp. However, you can use whatever libraries or methods you would like
|
44
|
+
# to generate the data.
|
45
|
+
#
|
46
|
+
# === Using The Templates
|
47
|
+
# If you'd like to get the hash by itself:
|
48
|
+
# ModelGrinder.gen_hash(YourModel, :valid, name: "Your mom" )
|
49
|
+
# or
|
50
|
+
# YourModel.gen_hash(:valid, name: "Your mom")
|
51
|
+
# Anything you pass in the last hash will override the values in the hash generated by the template.
|
52
|
+
#
|
53
|
+
# If you'd like to have them assigned to a model instance, use:
|
54
|
+
# ModelGrinder.generate(YourModel, :valid, name: "Your mom")
|
55
|
+
# or
|
56
|
+
# YourModel.generate(:valid, name: "Your mom")
|
57
|
+
#
|
58
|
+
# If you'd like to persist the model to the data store (by calling "save" on the instance), use the build method instead
|
59
|
+
# with the same arguments. It will return the instance for you to manipulate later.
|
60
|
+
#
|
61
|
+
module ModelGrinder
|
62
|
+
|
63
|
+
extend ActiveSupport::Concern if const_defined?(:ActiveSupport)
|
64
|
+
|
65
|
+
# List of supported ORMs
|
66
|
+
# TODO: get Mongoid working!
|
67
|
+
ORMS = {
|
68
|
+
datamapper: lambda { |obj| DataMapper::Model.append_extensions obj },
|
69
|
+
activerecord: lambda { |obj| ActiveRecord::Base.extend obj },
|
70
|
+
mongoid: lambda { |obj|
|
71
|
+
if Mongoid.respond_to?(:models)
|
72
|
+
Mongoid.models.each { |o| o.extend obj}
|
73
|
+
else
|
74
|
+
# Find all the mongoids, find them all!
|
75
|
+
ObjectSpace.each_object(Class) { |o|
|
76
|
+
next unless o.ancestors.include?(Mongoid::Components)
|
77
|
+
o.extend obj
|
78
|
+
}
|
79
|
+
end
|
80
|
+
},
|
81
|
+
all: nil
|
82
|
+
}
|
83
|
+
|
84
|
+
extend ClassMethods
|
85
|
+
|
86
|
+
def self.extended(klass) # :nodoc:
|
87
|
+
klass.send(:extend, ModelGrinder::ClassMethods)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Integrate Model Grinder into an ORM, or to all available ORMs
|
91
|
+
#
|
92
|
+
# @param [Symbol] orm can either be :datamapper, :activerecord, :mongoid, or :all
|
93
|
+
#
|
94
|
+
def self.integrate(orm)
|
95
|
+
if orm == :all
|
96
|
+
ORMS.each { |k,v|
|
97
|
+
next if v.nil?
|
98
|
+
begin
|
99
|
+
v.call(self)
|
100
|
+
rescue
|
101
|
+
puts "Unable to extend #{k}"
|
102
|
+
end
|
103
|
+
}
|
104
|
+
else
|
105
|
+
if ORMS[orm]
|
106
|
+
ORMS[orm].call(self)
|
107
|
+
else
|
108
|
+
raise ArgumentError.new(
|
109
|
+
"Unsupported argument: #{orm.inspect}. Valid options: #{ORMS.keys.map { |v| v.inspect }.join(', ')}"
|
110
|
+
)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Clears all currently defined templates
|
116
|
+
#
|
117
|
+
def self.clear_templates!
|
118
|
+
@_mg_templates = {}
|
119
|
+
end
|
120
|
+
|
121
|
+
# The templates currently defined
|
122
|
+
#
|
123
|
+
# @return [Hash]
|
124
|
+
#
|
125
|
+
def self.templates
|
126
|
+
@_mg_templates ||= {}
|
127
|
+
end
|
128
|
+
|
129
|
+
def self._generated
|
130
|
+
@_mg_generated ||= {}
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
module ModelGrinder
|
2
|
+
##
|
3
|
+
# Please see the documentation for the module ModelGrinder for full documentation.
|
4
|
+
#
|
5
|
+
module ClassMethods
|
6
|
+
|
7
|
+
# Sets up a template that's a block which contains a hash (for pseudo-random generation later). A bit of magic is
|
8
|
+
# used to make the first argument optional on all methods.
|
9
|
+
#
|
10
|
+
# @param [Class] klass The class that's attached to the template (optional)
|
11
|
+
# @param [Symbol] name The name of the template (optional, defaults to :default)
|
12
|
+
# @raise [ArgumentError] on no block passed
|
13
|
+
# @return [Proc] The passed block
|
14
|
+
#
|
15
|
+
def template(*args, &blk)
|
16
|
+
#TODO: check to make sure block returns hash
|
17
|
+
raise ArgumentError.new('A block that contains a hash must be passed to template.') unless block_given?
|
18
|
+
klass, name = parse_args(args)
|
19
|
+
_mg_templates[klass] ||= {}
|
20
|
+
_mg_templates[klass][name] = blk
|
21
|
+
end
|
22
|
+
|
23
|
+
alias :fix :template
|
24
|
+
alias :fixture :template
|
25
|
+
alias :t :template
|
26
|
+
|
27
|
+
|
28
|
+
# Generates a new hash from a template found by the Class and name passed. A bit of magic is used to make the first
|
29
|
+
# argument optional on all methods.
|
30
|
+
#
|
31
|
+
# @param [Class] klass The class that's attached to the template (optional)
|
32
|
+
# @param [Symbol] name The name of the template (optional, defaults to :default)
|
33
|
+
# @param [Hash] override_attrs Values to assign to the hash irrespective of the template
|
34
|
+
# @raise [ArgumentError] if template does not exist.
|
35
|
+
#
|
36
|
+
def gen_hash(*args)
|
37
|
+
klass, name, override_attrs = parse_args(args)
|
38
|
+
raise ArgumentError.new("The template `#{name}` for `#{klass}` does not exist.") unless _mg_templates[klass] && _mg_templates[klass][name]
|
39
|
+
attrs = _mg_templates[klass][name].call
|
40
|
+
attrs.merge!(override_attrs) if override_attrs
|
41
|
+
attrs
|
42
|
+
end
|
43
|
+
|
44
|
+
alias :genh :gen_hash
|
45
|
+
|
46
|
+
# Generates a new hash from a template on a class and creates a new object. Class must respond to new and accept
|
47
|
+
# a hash as the first and only argument.
|
48
|
+
#
|
49
|
+
# @param (see #gen_hash)
|
50
|
+
# @return [Class]
|
51
|
+
#
|
52
|
+
def generate(*args)
|
53
|
+
klass, name, attrs = parse_args(args)
|
54
|
+
# This returns the model instance
|
55
|
+
_mg_store_generated(
|
56
|
+
klass,
|
57
|
+
name,
|
58
|
+
klass.new(gen_hash(*args) || {})
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
alias :gen :generate
|
63
|
+
alias :make :generate
|
64
|
+
|
65
|
+
|
66
|
+
# Picks a specified number of already generated models
|
67
|
+
#
|
68
|
+
# @param [Class] klass The class that's attached to the template (optional)
|
69
|
+
# @param [Symbol] name The name of the template (optional, defaults to :default)
|
70
|
+
# @param [Hash] options Currently the only option is number of models returned (e.g. number: 3)
|
71
|
+
# @return [Array] An array of the number of generated models, up to the number actually generated.
|
72
|
+
def pick(*args)
|
73
|
+
klass, name, hash = parse_args(args)
|
74
|
+
hash[:number] ||= 1
|
75
|
+
return [] unless _mg_generated[klass].is_a?(Hash) && _mg_generated[klass][name].is_a?(Array)
|
76
|
+
_mg_generated[klass][name].sample hash[:number]
|
77
|
+
end
|
78
|
+
|
79
|
+
# Generates a new hash from a template on a class, creates a new object, and persists it to the datastore. Class must
|
80
|
+
# respond to new, accept a hash as the first and only argument, and respond to save.
|
81
|
+
#
|
82
|
+
# @param (see #gen_hash)
|
83
|
+
# @return [Class]
|
84
|
+
#
|
85
|
+
def build(*args)
|
86
|
+
model = generate(*args)
|
87
|
+
model.save
|
88
|
+
model
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
# Parses out the class, fixture name, and override attributes from the arguments
|
94
|
+
#
|
95
|
+
# @param [Array] args The array of arguments passed to the calling method.
|
96
|
+
# @raise [ArgumentError] if invalid/out of order arguments are passed
|
97
|
+
# @return [Class/Module, Symbol, Hash]
|
98
|
+
#
|
99
|
+
def parse_args(args)
|
100
|
+
error = false
|
101
|
+
new_args = []
|
102
|
+
args.each { |arg|
|
103
|
+
case arg
|
104
|
+
when Class, Module
|
105
|
+
new_args[0] = arg
|
106
|
+
when Symbol, String
|
107
|
+
new_args[1] = arg.to_sym
|
108
|
+
when Hash
|
109
|
+
new_args[2] = arg
|
110
|
+
end
|
111
|
+
}
|
112
|
+
args = new_args
|
113
|
+
case args.size
|
114
|
+
when 3,2,1 # do nothing
|
115
|
+
else error = true
|
116
|
+
end
|
117
|
+
args[0] ||= self
|
118
|
+
args[1] ||= :default
|
119
|
+
args[2] ||= {}
|
120
|
+
error = true unless args[0].is_a?(Class) || args[0].is_a?(Module)
|
121
|
+
error = true unless args[1].is_a?(Symbol)
|
122
|
+
error = true unless args[2].is_a?(Hash)
|
123
|
+
raise ArgumentError.new(
|
124
|
+
'Invalid arguments passed. Valid syntax: class(opt), fixture_name (symbol, required), hash (opt)'
|
125
|
+
) if error == true
|
126
|
+
return *args
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
# References the global template hash
|
132
|
+
def _mg_templates
|
133
|
+
@_mg_templates ||= ModelGrinder.templates
|
134
|
+
end
|
135
|
+
|
136
|
+
def _mg_generated
|
137
|
+
@_mg_generated ||= ModelGrinder._generated
|
138
|
+
end
|
139
|
+
|
140
|
+
def _mg_store_generated(klass, name, instance)
|
141
|
+
_mg_generated[klass] ||= {}
|
142
|
+
_mg_generated[klass][name] ||= []
|
143
|
+
_mg_generated[klass][name] << instance
|
144
|
+
instance
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
unless Array.respond_to?(:sample)
|
2
|
+
class Array
|
3
|
+
def sample(num = 1)
|
4
|
+
ret = []
|
5
|
+
already_picked = []
|
6
|
+
(1..num).each { |i|
|
7
|
+
break if i > length
|
8
|
+
i2 = rand(length - 1)
|
9
|
+
while already_picked.include?(i2)
|
10
|
+
i2 = rand(length - 1)
|
11
|
+
end
|
12
|
+
already_picked << i2
|
13
|
+
ret << self[i2]
|
14
|
+
}
|
15
|
+
ret
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: model_grinder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jeremy Nicoll
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-01-07 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: randexp
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
description: An ORM agnostic way to generate models for testing
|
31
|
+
email: jrnicoll@hotmail.com
|
32
|
+
executables: []
|
33
|
+
extensions: []
|
34
|
+
extra_rdoc_files: []
|
35
|
+
files:
|
36
|
+
- lib/model_grinder/class_methods.rb
|
37
|
+
- lib/model_grinder/ruby_hacks.rb
|
38
|
+
- lib/model_grinder.rb
|
39
|
+
homepage: http://rubygems.org/gems/model_grinder
|
40
|
+
licenses: []
|
41
|
+
post_install_message:
|
42
|
+
rdoc_options: []
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
none: false
|
47
|
+
requirements:
|
48
|
+
- - ! '>='
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ! '>='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
requirements: []
|
58
|
+
rubyforge_project:
|
59
|
+
rubygems_version: 1.8.24
|
60
|
+
signing_key:
|
61
|
+
specification_version: 3
|
62
|
+
summary: Grind out model data
|
63
|
+
test_files: []
|
64
|
+
has_rdoc:
|