seed-fu 1.2.1

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