Permalink
Browse files

initial commit

  • Loading branch information...
0 parents commit ef8b2d9ec133b77f86e3fa235f6bda28474f724b @codeprimate committed Aug 30, 2009
Showing with 440 additions and 0 deletions.
  1. +3 −0 .gitignore
  2. +4 −0 History.txt
  3. +11 −0 Manifest.txt
  4. 0 PostInstall.txt
  5. +122 −0 README.rdoc
  6. +27 −0 Rakefile
  7. +221 −0 lib/codeprimate-generator.rb
  8. +10 −0 script/console
  9. +14 −0 script/destroy
  10. +14 −0 script/generate
  11. +11 −0 test/test_codeprimate-generator.rb
  12. +3 −0 test/test_helper.rb
@@ -0,0 +1,3 @@
+pkg/
+*.*~
+*~
@@ -0,0 +1,4 @@
+=== 0.0.1 2009-08-30
+
+* 1 major enhancement:
+ * Repackaged as a Gem.
@@ -0,0 +1,11 @@
+History.txt
+Manifest.txt
+PostInstall.txt
+README.rdoc
+Rakefile
+lib/codeprimate-generator.rb
+script/console
+script/destroy
+script/generate
+test/test_codeprimate-generator.rb
+test/test_helper.rb
No changes.
@@ -0,0 +1,122 @@
+= codeprimate-generator
+
+http://github.com/codeprimate/codeprimate-generator
+
+== DESCRIPTION:
+
+GenericGenerator is a Ruby/Rails data generator intended for creating large amounts of test
+data as flexibly as possible, in the shortest amount of time. If you want a simple data
+generator that actually uses ActiveRecord and your model hooks, please look elsewhere,
+Thoughtbot's FactoryGirl gem would be an excellent option (and one I personally use).
+
+== FEATURES/PROBLEMS:
+
+Simplifies the creation of a data generator class by exposing a simple DSL
+for describing model fields, model relationships, optional fixture data sources,
+caching data, and generating data using Faker.
+
+== SYNOPSIS:
+
+ class FooGenerator < Codeprimate::GenericGenerator
+ # Required. Specify the Class
+ set_model Foo
+
+ # Required. Specify all fields in class to be assigned
+ has_fields :foo1, :foo2, :foo3, :user_id
+
+ # Optional. For every Foo, generate (5) dependent Foobars using
+ # the FoobarGenerator GenericGenerator model
+ #
+ has_many :foobars, :generator => FoobarGenerator, :count => 5
+
+ # Optional. For every Foo, generate (10) dependent Bazz's which are polymorphic
+ #
+ # has_many :bazzes, :as => :bazzable, :generator => BazzGenerator,
+ # :count => 5
+
+ # Optional. For every Foo, generate (10) dependent Bazz's which are polymorphic.
+ # using the specified fixture (this will override the fixture declaration
+ # of the generator if already specified.
+ #
+ has_many :bazzes, :as => :bazzable, :generator => BazzGenerator,
+ :count => 10, :fixture => "config/test_data/foo_bazzes.yml"
+
+ # Optional. Cache data for use inside generator, using the key ":user_id".
+ # You can call cache_data several times, using different keys.
+ cache_data(:user_ids) do
+ User.find(:all).collect{|u| u.id}
+ end
+
+
+ # Required. The generator block must return a Hash.
+ # The local variable 'defaults' allows you to access the defaults Hash
+ # as provided when generate!() is called. Use your imagination.
+ generator do |defaults|
+ {
+ :foo1 => Faker::Lorem::words(3).join(' '),
+ :foo2 => rand(1000),
+ :foo3 => rand(1000),
+
+ # Get a random user_id from the cached data.
+ :user_id => get_random(:user_id)
+ }
+ end
+
+ # Optional.
+ before do
+ # Do whatever before running the generator
+ end
+
+ # Optional.
+ after do
+ # Do whatever after running the generator
+ end
+ end
+
+ class FoobarGenerator < GenericGenerator
+ set_model Foobar
+ has_fields :bar1, :bar2, :bar3, :user_id
+
+ fixture "test/fixtures/foobars.yml
+
+ generator do |defaults|
+ # Just get a random entry from the fixtures
+ get_random(:fixture)
+ end
+ end
+
+== USAGE:
+
+ FooGenerator.generate!(100) => Makes 100 Foo's (and 10 bazzes)
+ FooGenerator.generate!(50, {:user_id => 5}) => Makes 50 Foo's with user_id of 5
+
+== REQUIREMENTS:
+
+* Faker and AR-Extensions gems must be installed
+
+== INSTALL:
+
+* sudo gem install codeprimate-generator
+
+== LICENSE:
+
+Copyright (c) 2009 Patrick Morgan and Master Web Design, released under the MIT license
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,27 @@
+require 'rubygems'
+gem 'hoe', '>= 2.1.0'
+require 'hoe'
+require 'fileutils'
+require './lib/codeprimate-generator'
+
+Hoe.plugin :newgem
+# Hoe.plugin :website
+# Hoe.plugin :cucumberfeatures
+
+# Generate all the Rake tasks
+# Run 'rake -T' to see list of generated tasks (from gem root directory)
+$hoe = Hoe.spec 'codeprimate-generator' do
+ self.developer 'Patrick Morgan', 'patrick.morgan@masterwebdesign.net'
+ #self.post_install_message = 'PostInstall.txt' # TODO remove if post-install message not required
+ self.rubyforge_name = self.name # TODO this is default value
+ # self.extra_deps = [['activesupport','>= 2.0.2']]
+ self.version = "0.1.0"
+ self.extra_deps = ["faker","ar-extensions"]
+end
+
+require 'newgem/tasks'
+Dir['tasks/**/*.rake'].each { |t| load t }
+
+# TODO - want other tests/tasks run by default? Add them to the list
+# remove_task :default
+# task :default => [:spec, :features]
@@ -0,0 +1,221 @@
+module Codeprimate
+ VERSION = "0.1.0"
+ class GenericGenerator
+ require 'faker'
+
+ @fields = []
+ @model_klass = nil
+ @generator = lambda{nil}
+ @associated = {}
+ @validated = false
+ @generated = false
+ @before_block = Proc.new{}
+ @after_block = Proc.new{}
+
+ # Set the generator model, accepts a Class
+ def self.set_model(model)
+ @model_klass = model
+ end
+
+ # Specify a list of field names used by the generator. Accepts an arbitrary
+ # number of Symbol's as arguments
+ def self.has_fields(*field_names)
+ @fields = field_names
+ end
+
+ # Specify a dependent Model, and generate data for it.
+ #
+ # Possible options:
+ #
+ # :generator => GenericGenerator (Required. Specify the generator)
+ # :count => Integer (Required. Specify the number of records to create)
+ # :as => Symbol (Optional. Specify a polymorphic type)
+ # :fixture => String (Optional. Override with filename)
+ #
+ def self.has_many(assoc_model, options={})
+ raise "Specify generator for 'has_many'" if options[:generator].to_s.empty?
+ raise "Invalid generator" unless options[:generator].is_a?(Class)
+ raise "Invalid generator" unless options[:generator].respond_to?("generate!")
+ @associated ||= {}
+ @associated[assoc_model] = options
+ end
+
+ # private
+ def self.fields
+ @fields
+ end
+
+ # private
+ def self.model
+ @model_klass
+ end
+
+ # private
+ def self.before_block
+ @before_block
+ end
+
+ # private
+ def self.after_block
+ @after_block
+ end
+
+ # Generate Random Data
+ #
+ # Example:
+ #
+ # FoobarGenerator.generate!(100) => Generate 100 Foobars
+ # FoobarGenerator.generate!(50, {:user_id => 10}) =>
+ # Generate 50 Foobars, with default data
+ def self.generate!(count=1, default_values={})
+ validate_generator
+ puts " * Generating #{count} #{model.to_s.pluralize}..."
+ run_before
+ data = self.generate_data(count, default_values)
+ model.import(fields, data, {:validate => false})
+ @generated = true
+ generate_associations!
+ run_after
+ return true
+ end
+
+ # private
+ def self.generate_associations!
+ raise "Cannot create associations until generate! is called" unless @generated
+ puts " - Generating associations for #{model.to_s}..."
+ assoc_data = []
+ @associated ||= {}
+ @associated.each_pair do |assoc, options|
+ opts = {:foreign_key => model.to_s.foreign_key, :count => 1, :as => false}.merge(options)
+ default_values = {}
+ puts " - Generating #{assoc.to_s} associations (#{opts[:count]} per #{model.to_s})..."
+
+ # Load fixture if specified
+ if opts[:fixture]
+ options[:generator].class_eval "fixture '#{opts[:fixture]}'"
+ end
+
+ options[:generator].run_before
+
+ model.find(:all, :select => :id).each do |m|
+ id = m.id
+ if opts[:as]
+ # Add polymorphic association keys
+ polymorphic_base = opts[:as].to_s
+ polymorphic_id = (polymorphic_base + '_id').to_sym
+ polymorphic_type = (polymorphic_base + '_type').to_sym
+ default_values[polymorphic_id] = id
+ default_values[polymorphic_type] = m.class.to_s
+ else
+ # Or just set the foreign key
+ default_values[opts[:foreign_key]] = id
+ end
+ default_values = default_values.symbolize_keys.merge((options[:defaults] || {}))
+ assoc_data += options[:generator].generate_data(opts[:count].to_i, default_values)
+ end
+
+ options[:generator].model.import(options[:generator].fields, assoc_data, {:validate => false})
+ options[:generator].run_after
+ end
+ end
+
+ # private
+ def self.generate_data(count=1, default_values={})
+ return Array.new(count){new_record(default_values)}
+ end
+
+ # private
+ def self.new_record(default_values={})
+ validate_generator
+ data = run_generator(default_values)
+ return fields.inject(out=[]){|out, f| out << data[f]}
+ end
+
+ # private
+ def self.run_generator(default_values={})
+ return @generator.call(default_values).merge(default_values)
+ end
+
+ # private
+ def self.validate_generator
+ unless @validated
+ raise "Model not specified, use 'set_model Modelname' declaration" if model.nil?
+ raise "No fields specified, use 'has_fields :field1, :field2' declaration" if fields.empty?
+ raise "Is the 'ar-extensions' module loaded?" unless @model_klass.respond_to?("import")
+ @validated = true
+ end
+ end
+
+ # Cache data for key using value returned from block
+ def self.cache_data(key, &block)
+ @cache_block ||= {}
+ @cache_block[key] = block || Proc.new{}
+ end
+
+ # private
+ def self.cache_for(key)
+ @cache ||= {}
+ @cache[key] ||= @cache_block[key].call
+ end
+
+ # private
+ def self.cache_size(key)
+ @cache_size ||= {}
+ @cache_size[key] ||= cache_for(key).size
+ end
+
+ # Clear cache for key. Use ":all" as argument to clear entire cache
+ def self.clear_cache(key = :all)
+ if key == :all
+ @cache = {}
+ @cache_size = {}
+ @cache_block = {}
+ else
+ @cache ||= {}
+ @cache_size ||= {}
+ @cache_block ||= {}
+ @cache[key] = nil
+ @cache_size[key] = nil
+ @cache_block[key] = nil
+ end
+ end
+
+ # Get random data from cache identified by key
+ def self.get_random(key)
+ cache_for(key)[rand(cache_size(key) - 1)]
+ end
+
+ # Specify random data generator block
+ def self.generator(&block)
+ @generator = block
+ end
+
+ # Load YAML into cache for key ":fixture"
+ def self.fixture(filename)
+ clear_cache(:fixture)
+ cache_data(:fixture) do
+ YAML.load(File.read(filename))
+ end
+ end
+
+ # Specify block to be executed before generation
+ def self.before(&block)
+ @before_block = block
+ end
+
+ # Specify block to be executed after generation
+ def self.after(&block)
+ @after_block = block
+ end
+
+ # private
+ def self.run_before
+ before_block.call if before_block
+ end
+
+ # private
+ def self.run_after
+ after_block.call if after_block
+ end
+ end
+end
Oops, something went wrong.

0 comments on commit ef8b2d9

Please sign in to comment.