model_grinder 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: