seed-fu 1.2.1

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,149 @@
1
+ = Seed Fu
2
+
3
+ Seed Fu is an attempt to once and for all solve the problem of inserting and
4
+ maintaining seed data in a database. It uses a variety of techniques gathered
5
+ from various places around the web and combines them to create what is
6
+ hopefully the most robust seed data system around.
7
+
8
+
9
+ == Simple Usage
10
+
11
+ Seed data is taken from the <tt>db/fixtures</tt> directory. Simply make descriptive .rb
12
+ files in that directory (they can be named anything, but users.rb for the User
13
+ model seed data makes sense, etc.). These scripts will be run whenever the
14
+ db:seed rake task is called, and in order (you can use <tt>00_first.rb</tt>,
15
+ <tt>00_second.rb</tt>, etc). You can put arbitrary Ruby code in these files,
16
+ but remember that it will be executed every time the rake task is called, so
17
+ it needs to be runnable multiple times on the same database.
18
+
19
+ You can also have environment-specific seed data placed in
20
+ <tt>db/fixtures/ENVIRONMENT</tt> that will only be loaded if that is the current
21
+ environment.
22
+
23
+ Let's say we want to populate a few default users. In <tt>db/fixtures/users.rb</tt> we
24
+ write the following code:
25
+
26
+ User.seed(:login, :email) do |s|
27
+ s.login = "bob"
28
+ s.email = "bob@bobson.com"
29
+ s.first_name = "Bob"
30
+ s.last_name = "Bobson"
31
+ end
32
+
33
+ User.seed(:login, :email) do |s|
34
+ s.login = "bob"
35
+ s.email = "bob@stevenson.com"
36
+ s.first_name = "Bob"
37
+ s.last_name = "Stevenson"
38
+ end
39
+
40
+ That's all you have to do! You will now have two users created in the system
41
+ and you can change their first and last names in the users.rb file and it will
42
+ be updated as such.
43
+
44
+ The arguments passed to the <tt><ActiveRecord>.seed</tt> method are the constraining
45
+ attributes: these must remain unchanged between db:seed calls to avoid data
46
+ duplication. By default, seed data will constrain to the id like so:
47
+
48
+ Category.seed do |s|
49
+ s.id = 1
50
+ s.name = "Buttons"
51
+ end
52
+
53
+ Category.seed do |s|
54
+ s.id = 2
55
+ s.name = "Bobbins"
56
+ s.parent_id = 1
57
+ end
58
+
59
+ Note that any constraints that are passed in must be present in the subsequent
60
+ seed data setting.
61
+
62
+ == Seed-many Usage
63
+
64
+ The default <tt>.seed` syntax is very verbose. An alternative is the `.seed_many</tt> syntax.
65
+ Look at this refactoring of the first seed usage example above:
66
+
67
+ User.seed_many(:login, :email, [
68
+ { :login => "bob", :email => "bob@bobson.com", :first_name => "Bob", :last_name = "Bobson" },
69
+ { :login => "bob", :email => "bob@stevenson.com", :first_name => "Bob", :last_name = "Stevenson" }
70
+ ])
71
+
72
+ Not as pretty, but much more concise.
73
+
74
+ == Handling Large SeedFu Files
75
+
76
+ Seed files can be huge. To handle large files (over a million rows), try these tricks:
77
+
78
+ * Gzip your fixtures. Seed Fu will read .rb.gz files happily. <tt>gzip -9</tt> gives the
79
+ best compression, and with Seed Fu's repetitive syntax, a 160M file can shrink to 16M.
80
+ * Add lines reading <tt># BREAK EVAL</tt> in your big fixtures, and Seed Fu will avoid loading
81
+ the whole file into memory. If you use SeedFu::Writer, these breaks are built into
82
+ your generated fixtures.
83
+ * Load a single fixture with the <tt>SEED</tt> environment varialble:
84
+ <tt>SEED=cities,states rake db:seed > seed_log`. Each argument to `SEED</tt> is used as a
85
+ regex to filter fixtures by filename.
86
+
87
+ == Generating SeedFu Files
88
+
89
+ Say you have a CSV you need to massage and store as seed files. You can create
90
+ an import script using SeedFu::Writer.
91
+
92
+ #!/usr/bin/env ruby
93
+ #
94
+ # This is: script/generate_cities_seed_from_csv
95
+ #
96
+ require 'rubygems'
97
+ require 'fastercsv'
98
+ require File.join( File.dirname(__FILE__), '..', 'vendor/plugins/seed-fu/lib/seed-fu/writer' )
99
+
100
+ # Maybe SEEF_FILE could be $stdout, hm.
101
+ #
102
+ CITY_CSV = File.join( File.dirname(__FILE__), '..', 'city.csv' )
103
+ SEED_FILE = File.join( File.dirname(__FILE__), '..', 'db', 'fixtures', 'cities.rb' )
104
+
105
+ # Create a seed_writer, walk the CSV, add to the file.
106
+ #
107
+
108
+ seed_writer = SeedFu::Writer::SeedMany.new(
109
+ :seed_file => SEED_FILE,
110
+ :seed_model => 'City',
111
+ :seed_by => [ :city, :state ]
112
+ )
113
+
114
+ FasterCSV.foreach( CITY_CSV,
115
+ :return_headers => false,
116
+ :headers => :first_row
117
+ ) do |row|
118
+
119
+ # Skip all but the US
120
+ #
121
+ next unless row['country_code'] == 'US'
122
+
123
+ unless us_state
124
+ puts "No State Match for #{row['region_name']}"
125
+ next
126
+ end
127
+
128
+ # Write the seed
129
+ #
130
+ seed_writer.add_seed({
131
+ :zip => row['zipcode'],
132
+ :state => row['state'],
133
+ :city => row['city'],
134
+ :latitude => row['latitude'],
135
+ :longitude => row['longitude']
136
+ })
137
+
138
+ end
139
+
140
+ seed_writer.finish
141
+
142
+ There is also a SeedFu::Writer::Seed in case you prefere the seed()
143
+ syntax over the seen_many() syntax. Easy-peasy.
144
+
145
+ == Contributors
146
+
147
+ * Thanks to Matthew Beale for his great work in adding the writer, making it faster and better.
148
+
149
+ Copyright (c) 2008-2009 Michael Bleigh released under the MIT license
@@ -0,0 +1,23 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gemspec|
8
+ gemspec.name = "seed-fu"
9
+ gemspec.summary = "Allows easier database seeding of tables in Rails."
10
+ gemspec.email = "michael@intridea.com"
11
+ gemspec.homepage = "http://github.com/mblegih/seed-fu"
12
+ gemspec.description = "Seed Fu is an attempt to once and for all solve the problem of inserting and maintaining seed data in a database. It uses a variety of techniques gathered from various places around the web and combines them to create what is hopefully the most robust seed data system around."
13
+ gemspec.authors = ["Michael Bleigh"]
14
+ gemspec.add_dependency 'rails', '>= 2.1'
15
+ gemspec.files = FileList["[A-Z]*", "{lib,spec,rails}/**/*"] - FileList["**/*.log"]
16
+ end
17
+ rescue LoadError
18
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
19
+ end
20
+
21
+
22
+ desc 'Default: run specs.'
23
+ task :default => :spec
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.2.1
@@ -0,0 +1,87 @@
1
+ module SeedFu
2
+ class Seeder
3
+ def self.plant(model_class, *constraints, &block)
4
+ constraints = [:id] if constraints.empty?
5
+ seed = Seeder.new(model_class)
6
+ insert_only = constraints.last.is_a? TrueClass
7
+ constraints.delete_at(*constraints.length-1) if (constraints.last.is_a? TrueClass or constraints.last.is_a? FalseClass)
8
+ seed.set_constraints(*constraints)
9
+ yield seed
10
+ seed.plant!(insert_only)
11
+ end
12
+
13
+ def initialize(model_class)
14
+ @model_class = model_class
15
+ @constraints = [:id]
16
+ @data = {}
17
+ end
18
+
19
+ def set_constraints(*constraints)
20
+ raise "You must set at least one constraint." if constraints.empty?
21
+ @constraints = []
22
+ constraints.each do |constraint|
23
+ raise "Your constraint `#{constraint}` is not a column in #{@model_class}. Valid columns are `#{@model_class.column_names.join("`, `")}`." unless @model_class.column_names.include?(constraint.to_s)
24
+ @constraints << constraint.to_sym
25
+ end
26
+ end
27
+
28
+ def plant! insert_only=false
29
+ record = get
30
+ return if !record.new_record? and insert_only
31
+ @data.each do |k, v|
32
+ record.send("#{k}=", v)
33
+ end
34
+ raise "Error Saving: #{record.inspect}" unless record.save(false)
35
+ puts " - #{@model_class} #{condition_hash.inspect}"
36
+ record
37
+ end
38
+
39
+ def method_missing(method_name, *args) #:nodoc:
40
+ if args.size == 1 and (match = method_name.to_s.match(/(.*)=$/))
41
+ self.class.class_eval "def #{method_name} arg; @data[:#{match[1]}] = arg; end"
42
+ send(method_name, args[0])
43
+ else
44
+ super
45
+ end
46
+ end
47
+
48
+ protected
49
+
50
+ def get
51
+ records = @model_class.find(:all, :conditions => condition_hash)
52
+ raise "Given constraints yielded multiple records." unless records.size < 2
53
+ if records.any?
54
+ return records.first
55
+ else
56
+ return @model_class.new
57
+ end
58
+ end
59
+
60
+ def condition_hash
61
+ @constraints.inject({}) {|a,c| a[c] = @data[c]; a }
62
+ end
63
+ end
64
+ end
65
+
66
+
67
+ class ActiveRecord::Base
68
+ # Creates a single record of seed data for use
69
+ # with the db:seed rake task.
70
+ #
71
+ # === Parameters
72
+ # constraints :: Immutable reference attributes. Defaults to :id
73
+ def self.seed(*constraints, &block)
74
+ SeedFu::Seeder.plant(self, *constraints, &block)
75
+ end
76
+
77
+ def self.seed_many(*constraints)
78
+ seeds = constraints.pop
79
+ seeds.each do |seed_data|
80
+ seed(*constraints) do |s|
81
+ seed_data.each_pair do |k,v|
82
+ s.send "#{k}=", v
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,3 @@
1
+ require File.join( File.dirname(__FILE__), 'writer', 'abstract' )
2
+ require File.join( File.dirname(__FILE__), 'writer', 'seed' )
3
+ require File.join( File.dirname(__FILE__), 'writer', 'seed_many' )
@@ -0,0 +1,58 @@
1
+ module SeedFu
2
+
3
+ module Writer
4
+
5
+ class Abstract
6
+ attr_accessor :seed_handle, :config, :number_of_seeds
7
+
8
+ def initialize(options={})
9
+ self.config = options
10
+ self.number_of_seeds = 0
11
+
12
+ self.seed_handle = File.new(self.config[:seed_file], 'w')
13
+
14
+ write_header
15
+ end
16
+
17
+ def header
18
+ <<-END
19
+ # DO NOT MODIFY THIS FILE, it was auto-generated.
20
+ #
21
+ # Date: #{DateTime.now}
22
+ # Using #{self.class} to seed #{config[:seed_model]}
23
+ # Written with the command:
24
+ #
25
+ # #{$0} #{$*.join}
26
+ #
27
+ END
28
+ end
29
+
30
+ def footer
31
+ <<-END
32
+ # End auto-generated file.
33
+ END
34
+ end
35
+
36
+ def add_seed(hash)
37
+ $stdout.puts "Added #{hash.inspect}" unless config[:quiet]
38
+ self.number_of_seeds += 1
39
+ end
40
+
41
+ def write_header
42
+ seed_handle.syswrite header
43
+ end
44
+
45
+ def write_footer
46
+ seed_handle.syswrite footer
47
+ end
48
+
49
+ def finish
50
+ write_footer
51
+ seed_handle.close
52
+ end
53
+
54
+ end
55
+
56
+ end
57
+
58
+ end
@@ -0,0 +1,26 @@
1
+ module SeedFu
2
+
3
+ module Writer
4
+
5
+ class Seed < Abstract
6
+
7
+ # This method uses the :seed_by set earlier.
8
+ #
9
+ def add_seed(hash, seed_by=nil)
10
+ seed_by ||= config[:seed_by]
11
+ seed_handle.syswrite( <<-END
12
+ #{config[:seed_model]}.seed(#{seed_by.collect{|s| ":#{s}"}.join(',')}) { |s|
13
+ #{hash.collect{|k,v| " s.#{k} = '#{v.to_s.gsub("'", "\'")}'\n"}.join}}
14
+ END
15
+ )
16
+ super(hash)
17
+ if chunk_this_seed?
18
+ seed_handle.syswrite "# BREAK EVAL\n"
19
+ end
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+
26
+ end
@@ -0,0 +1,52 @@
1
+ module SeedFu
2
+
3
+ module Writer
4
+
5
+ class SeedMany < Abstract
6
+
7
+ def seed_many_header
8
+ "#{config[:seed_model]}.seed_many(#{config[:seed_by].collect{|s| ":#{s}"}.join(',')},["
9
+ end
10
+
11
+ def seed_many_footer
12
+ "\n])\n"
13
+ end
14
+
15
+ # Chunk in groups of 100 for performance
16
+ #
17
+ def chunk_this_seed?
18
+ 0 == (self.number_of_seeds % (config[:chunk_size] || 100))
19
+ end
20
+
21
+ def add_seed(hash)
22
+ seed_handle.syswrite( (<<-END
23
+ #{',' unless self.number_of_seeds == 0 or chunk_this_seed?}
24
+ { #{hash.collect{|k,v| ":#{k} => '#{v.to_s.gsub("'", "\'")}'"}.join(', ')} }
25
+ END
26
+ ).chomp )
27
+ super(hash)
28
+
29
+ if chunk_this_seed?
30
+ seed_handle.syswrite(
31
+ self.seed_many_footer +
32
+ "# BREAK EVAL\n" +
33
+ self.seed_many_header
34
+ )
35
+ end
36
+ end
37
+
38
+ def write_header
39
+ super
40
+ seed_handle.syswrite self.seed_many_header
41
+ end
42
+
43
+ def write_footer
44
+ seed_handle.syswrite self.seed_many_footer
45
+ super
46
+ end
47
+
48
+ end
49
+
50
+ end
51
+
52
+ end
@@ -0,0 +1 @@
1
+ require 'seed-fu'
@@ -0,0 +1,8 @@
1
+ ActiveRecord::Schema.define :version => 0 do
2
+ create_table :seeded_models, :force => true do |t|
3
+ t.column :login, :string
4
+ t.column :first_name, :string
5
+ t.column :last_name, :string
6
+ t.column :title, :string
7
+ end
8
+ end
@@ -0,0 +1,73 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ load(File.dirname(__FILE__) + '/schema.rb')
4
+
5
+ describe SeedFu::Seeder do
6
+ it "should create a model if one doesn't exist" do
7
+ SeededModel.seed(:id) do |s|
8
+ s.id = 1
9
+ s.login = "bob"
10
+ s.first_name = "Bob"
11
+ s.last_name = "Bobson"
12
+ s.title = "Peon"
13
+ end
14
+
15
+ bob = SeededModel.find_by_id(1)
16
+ bob.first_name.should == "Bob"
17
+ bob.last_name.should == "Bobson"
18
+ end
19
+
20
+ it "should be able to handle multiple constraints" do
21
+ SeededModel.seed(:title, :login) do |s|
22
+ s.login = "bob"
23
+ s.title = "Peon"
24
+ s.first_name = "Bob"
25
+ end
26
+
27
+ SeededModel.count.should == 1
28
+
29
+ SeededModel.seed(:title, :login) do |s|
30
+ s.login = "frank"
31
+ s.title = "Peon"
32
+ s.first_name = "Frank"
33
+ end
34
+
35
+ SeededModel.count.should == 2
36
+
37
+ SeededModel.find_by_login("bob").first_name.should == "Bob"
38
+ SeededModel.seed(:title, :login) do |s|
39
+ s.login = "bob"
40
+ s.title = "Peon"
41
+ s.first_name = "Steve"
42
+ end
43
+ SeededModel.find_by_login("bob").first_name.should == "Steve"
44
+ end
45
+
46
+ it "should be able to create models from an array of seed attributes" do
47
+ SeededModel.seed_many(:title, :login, [
48
+ {:login => "bob", :title => "Peon", :first_name => "Steve"},
49
+ {:login => "frank", :title => "Peasant", :first_name => "Francis"},
50
+ {:login => "harry", :title => "Noble", :first_name => "Harry"}
51
+ ])
52
+
53
+ SeededModel.find_by_login("bob").first_name.should == "Steve"
54
+ SeededModel.find_by_login("frank").first_name.should == "Francis"
55
+ SeededModel.find_by_login("harry").first_name.should == "Harry"
56
+ end
57
+
58
+ #it "should raise an error if constraints are not unique" do
59
+ # SeededModel.create(:login => "bob", :first_name => "Bob", :title => "Peon")
60
+ # SeededModel.create(:login => "bob", :first_name => "Robert", :title => "Manager")
61
+ #
62
+ # SeededModel.seed(:login) do |s|
63
+ # s.login = "bob"
64
+ # s.title = "Overlord"
65
+ # end
66
+ #end
67
+
68
+ it "should default to an id constraint"
69
+ it "should update, not create, if constraints are met"
70
+ it "should require that all constraints are defined"
71
+ it "should raise an error if validation fails"
72
+ it "should retain fields that aren't specifically altered in the seeding"
73
+ end
@@ -0,0 +1,8 @@
1
+ require File.dirname(__FILE__) + '/../../../../spec/spec_helper'
2
+
3
+ plugin_spec_dir = File.dirname(__FILE__)
4
+ ActiveRecord::Base.logger = Logger.new(plugin_spec_dir + "/debug.log")
5
+
6
+ class SeededModel < ActiveRecord::Base
7
+ validates_presence_of :title
8
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: seed-fu
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.2.1
5
+ platform: ruby
6
+ authors:
7
+ - Michael Bleigh
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-23 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rails
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "2.1"
24
+ version:
25
+ description: Seed Fu is an attempt to once and for all solve the problem of inserting and maintaining seed data in a database. It uses a variety of techniques gathered from various places around the web and combines them to create what is hopefully the most robust seed data system around.
26
+ email: michael@intridea.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - README.rdoc
33
+ files:
34
+ - README.rdoc
35
+ - Rakefile
36
+ - VERSION
37
+ - lib/seed-fu.rb
38
+ - lib/seed-fu/writer.rb
39
+ - lib/seed-fu/writer/abstract.rb
40
+ - lib/seed-fu/writer/seed.rb
41
+ - lib/seed-fu/writer/seed_many.rb
42
+ - rails/init.rb
43
+ - spec/schema.rb
44
+ - spec/seed_fu_spec.rb
45
+ - spec/spec_helper.rb
46
+ has_rdoc: true
47
+ homepage: http://github.com/mblegih/seed-fu
48
+ licenses: []
49
+
50
+ post_install_message:
51
+ rdoc_options:
52
+ - --charset=UTF-8
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ requirements: []
68
+
69
+ rubyforge_project:
70
+ rubygems_version: 1.3.5
71
+ signing_key:
72
+ specification_version: 3
73
+ summary: Allows easier database seeding of tables in Rails.
74
+ test_files:
75
+ - spec/schema.rb
76
+ - spec/seed_fu_spec.rb
77
+ - spec/spec_helper.rb