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.
@@ -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: