├── .gitignore ├── .yardopts ├── CHANGELOG.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── lib ├── seed-fu.rb ├── seed-fu │ ├── active_record_extension.rb │ ├── block_hash.rb │ ├── capistrano.rb │ ├── capistrano3.rb │ ├── railtie.rb │ ├── runner.rb │ ├── seeder.rb │ ├── version.rb │ └── writer.rb └── tasks │ ├── seed_fu.rake │ └── seed_fu_capistrano3.rake ├── seed-fu.gemspec └── spec ├── .gitignore ├── README ├── connections ├── mysql.rb ├── postgresql.rb └── sqlite3.rb ├── fixtures ├── seeded_models.rb ├── seeded_models_2.rb └── seeded_models_3.rb.gz ├── runner_spec.rb ├── seeder_spec.rb ├── spec_helper.rb └── writer_spec.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.gem 3 | .DS_Store 4 | /pkg 5 | Gemfile.lock 6 | .bundle 7 | .yardoc 8 | doc -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | -m markdown 2 | --no-private 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Version 2.3.8 2 | ------------- 3 | 4 | Bug fixes: 5 | 6 | * Restored support for PostgreSQL with Rails versions before 5.0.0 broken in Seed Fu 2.3.7. 7 | 8 | Version 2.3.7 9 | ------------- 10 | 11 | Features: 12 | 13 | * Postgresql >= 10.0 support 14 | 15 | Version 2.3.6 16 | ------------- 17 | 18 | Features: 19 | 20 | * Rails 5.0 support 21 | 22 | Version 2.3.5 23 | ------------- 24 | 25 | Features: 26 | 27 | * Rails 4.2 support 28 | 29 | Version 2.3.3 30 | ------------- 31 | 32 | Features: 33 | 34 | * Capistrano v3 support (by @shishi) 35 | 36 | Version 2.3.2 37 | ------------- 38 | 39 | Features: 40 | 41 | * Documentation improvements (by @george, @kenips, @joshuapinter) 42 | * Fix documentation of seed_once method (by weedySeaDragon) 43 | * Allow to seed data with an id < 1 (by @SamSaffron, @aserafin) 44 | * Seeds work on postgresql when there is no primary key or if primary key has no sequence assigned (by @aserafin) 45 | 46 | Version 2.3.1 47 | ------------- 48 | 49 | Features: 50 | 51 | * Rails 4.1 support added. 52 | * Capistrano task included. (by @linjunpop) 53 | 54 | Version 2.3.0 55 | ------------- 56 | 57 | Features: 58 | 59 | * Rails 4.0.X support added. (by @tkhr, @DanielWright) 60 | 61 | Version 2.2.0 62 | ------------- 63 | 64 | * Rails 3.2 support 65 | 66 | Version 2.1.0 67 | ------------- 68 | 69 | Features: 70 | 71 | * Deprecations removed 72 | 73 | * Rails 3.1 support added, Rails 3.0 support removed (please use 2.0.X line with 3.0) 74 | 75 | Version 2.0.1 76 | ------------- 77 | 78 | Bug fixes: 79 | 80 | * Update the primary key sequence in PostgreSQL tables after seeding data. This ensures that id conflicts do not occur when records are subsequently added to the table. 81 | 82 | * Raise ActiveRecord::RecordNotSaved if any of the saves fail (but they won't fail due to validation since saves are done without validation, so this guards against callbacks failing etc.) 83 | 84 | Version 2.0.0 85 | ------------- 86 | 87 | Features: 88 | 89 | * Depends only on Active Record, not the whole of Rails 90 | 91 | * The `Model.seed_many` syntax is now supported by `Model.seed`, and `Model.seed_many` is deprecated 92 | 93 | * `Model.seed` supports adding multiple records without an explicit array argument. I.e. the following are equivalent: 94 | 95 | Model.seed([ 96 | { :name => "Jon" }, 97 | { :name => "Emily" } 98 | ]) 99 | 100 | Model.seed( 101 | { :name => "Jon" }, 102 | { :name => "Emily } 103 | ) 104 | 105 | * A side-effect of the above is another option for single seeds: 106 | 107 | Model.seed(:name => "Jon") 108 | 109 | * The `SEED` option to `rake db:seed_fu` is deprecated, and replaced by `FILTER` which works the same way. 110 | 111 | * Added `SeedFu.quiet` boolean option, set to `true` if you don't want any output from Seed Fu. 112 | 113 | * Added `SeedFu.fixture_paths`. Set to an array of paths to look for seed files in. Defaults to `["db/fixtures"]` in general, or `["#{Rails.root}/db/fixtures", "#{Rails.root}/db/fixtures/#{Rails.env}"]` when Seed Fu is installed as a Rails plugin. 114 | 115 | * Added `SeedFu.seed` method which is basically a method equivalent of running `rake db:seed_fu` (the rake task now just basically called `SeedFu.seed`) 116 | 117 | * Simplified and changed the `SeedFu::Writer` API, see docs for details 118 | 119 | Bug fixes: 120 | 121 | * Fix Rails 3 deprecation warnings and make seed-fu fully compatible with being installed as a gem 122 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | gem 'rails', ">= 3.1", "< 5.1" 3 | gemspec 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2010 Michael Bleigh 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Seed Fu 2 | ======= 3 | 4 | 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. 5 | 6 | Warning: API Changes 7 | -------------------- 8 | 9 | Version 2.0.0 of Seed Fu introduced API changes. `Seed::Writer` was been completely overhauled and will require you to update your scripts. Some other deprecations were introduced, and support is fully removed in version 2.1.0. Please see the [CHANGELOG](CHANGELOG.md) for details. 10 | 11 | The API documentation is available in full at [http://rubydoc.info/github/mbleigh/seed-fu/master/frames](http://rubydoc.info/github/mbleigh/seed-fu/master/frames). 12 | 13 | Basic Example 14 | ------------- 15 | 16 | ### In `db/fixtures/users.rb` 17 | 18 | User.seed do |s| 19 | s.id = 1 20 | s.login = "jon" 21 | s.email = "jon@example.com" 22 | s.name = "Jon" 23 | end 24 | 25 | User.seed do |s| 26 | s.id = 2 27 | s.login = "emily" 28 | s.email = "emily@example.com" 29 | s.name = "Emily" 30 | end 31 | 32 | ### To load the data: 33 | 34 | $ rake db:seed_fu 35 | == Seed from /path/to/app/db/fixtures/users.rb 36 | - User {:id=>1, :login=>"jon", :email=>"jon@example.com", :name=>"Jon"} 37 | - User {:id=>2, :login=>"emily", :email=>"emily@example.com", :name=>"Emily"} 38 | 39 | Installation 40 | ------------ 41 | 42 | ### Rails 3.1, 3.2, 4.0, 4.1, 4.2, 5.0 43 | 44 | Just add `gem 'seed-fu', '~> 2.3'` to your `Gemfile` 45 | 46 | Seed Fu depends on Active Record, but doesn't have to be used with a full Rails app. Simply load and require the `seed-fu` gem and you're set. 47 | 48 | ### Rails 3.0 49 | 50 | The current version is not backwards compatible with Rails 3.0. Please use `gem 'seed-fu', '~> 2.0.0'`. 51 | 52 | ### Rails 2.3 53 | 54 | The current version is not backwards compatible with Rails 2.3. Please use `gem 'seed-fu', '~> 1.2.0'`. 55 | 56 | Constraints 57 | ----------- 58 | 59 | Constraints are used to identify seeds, so that they can be updated if necessary. For example: 60 | 61 | Point.seed(:x, :y) do |s| 62 | s.x = 4 63 | s.y = 7 64 | s.name = "Home" 65 | end 66 | 67 | The first time this seed is loaded, a `Point` record will be created. Now suppose the name is changed: 68 | 69 | Point.seed(:x, :y) do |s| 70 | s.x = 4 71 | s.y = 7 72 | s.name = "Work" 73 | end 74 | 75 | When this is run, Seed Fu will look for a `Point` based on the `:x` and `:y` constraints provided. It will see that a matching `Point` already exists and so update its attributes rather than create a new record. 76 | 77 | If you do not want seeds to be updated after they have been created, use `seed_once`: 78 | 79 | Point.seed_once(:x, :y) do |s| 80 | s.x = 4 81 | s.y = 7 82 | s.name = "Home" 83 | end 84 | 85 | The default constraint just checks the `id` of the record. 86 | 87 | Where to put seed files 88 | ----------------------- 89 | 90 | By default, seed files are looked for in the following locations: 91 | 92 | * `#{Rails.root}/db/fixtures` and `#{Rails.root}/db/fixtures/#{Rails.env}` in a Rails app 93 | * `./db/fixtures` when loaded without Rails 94 | 95 | You can change these defaults by modifying the `SeedFu.fixture_paths` array. 96 | 97 | Seed files can be named whatever you like, and are loaded in alphabetical order. 98 | 99 | Terser syntax 100 | ------------- 101 | 102 | When loading lots of records, the above block-based syntax can be quite verbose. You can use the following instead: 103 | 104 | User.seed(:id, 105 | { :id => 1, :login => "jon", :email => "jon@example.com", :name => "Jon" }, 106 | { :id => 2, :login => "emily", :email => "emily@example.com", :name => "Emily" } 107 | ) 108 | 109 | Rake task 110 | --------- 111 | 112 | Seed files can be run automatically using `rake db:seed_fu`. There are two options which you can pass: 113 | 114 | * `rake db:seed_fu FIXTURE_PATH=path/to/fixtures` -- Where to find the fixtures 115 | * `rake db:seed_fu FILTER=users,articles` -- Only run seed files with a filename matching the `FILTER` 116 | 117 | You can also do a similar thing in your code by calling `SeedFu.seed(fixture_paths, filter)`. 118 | 119 | Disable output 120 | -------------- 121 | 122 | To disable output from Seed Fu, set `SeedFu.quiet = true`. 123 | 124 | Handling large seed files 125 | ------------------------- 126 | 127 | Seed files can be huge. To handle large files (over a million rows), try these tricks: 128 | 129 | * Gzip your fixtures. Seed Fu will read .rb.gz files happily. `gzip -9` gives the best compression, and with Seed Fu's repetitive syntax, a 160M file can shrink to 16M. 130 | * Add lines reading `# BREAK EVAL` in your big fixtures, and Seed Fu will avoid loading the whole file into memory. If you use `SeedFu::Writer`, these breaks are built into your generated fixtures. 131 | * Load a single fixture at a time with the `FILTER` environment variable 132 | * If you don't need Seed Fu's ability to update seed with new data, then you may find that [activerecord-import](https://github.com/zdennis/activerecord-import) is faster 133 | 134 | Generating seed files 135 | --------------------- 136 | 137 | If you need to programmatically generate seed files, for example to convert a CSV file into a seed file, then you can use [`SeedFu::Writer`](lib/seed-fu/writer.rb). 138 | 139 | Capistrano deployment 140 | --------------------- 141 | 142 | SeedFu has included Capistrano [deploy script](lib/seed-fu/capistrano.rb), you just need require that 143 | in `config/deploy.rb`: 144 | 145 | ```ruby 146 | require 'seed-fu/capistrano' 147 | 148 | # Trigger the task after update_code 149 | after 'deploy:update_code', 'db:seed_fu' 150 | ``` 151 | 152 | If you use Capistrano3, you should require another file. 153 | 154 | ```ruby 155 | require 'seed-fu/capistrano3' 156 | 157 | # Trigger the task before publishing 158 | before 'deploy:publishing', 'db:seed_fu' 159 | ``` 160 | 161 | Bugs / Feature requests 162 | ----------------------- 163 | 164 | Please report them on [Github Issues](https://github.com/mbleigh/seed-fu/issues). 165 | 166 | Contributors 167 | ------------ 168 | 169 | * [Michael Bleigh](http://www.mbleigh.com/) is the original author 170 | * [Jon Leighton](http://jonathanleighton.com/) is the current maintainer 171 | * Thanks to [Matthew Beale](https://github.com/mixonic) for his great work in adding the writer, making it faster and better. 172 | 173 | Copyright © 2008-2010 Michael Bleigh, released under the MIT license 174 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rake/testtask" 3 | require 'rspec/core/rake_task' 4 | 5 | RSpec::Core::RakeTask.new :spec do |spec| 6 | spec.pattern = 'spec/**/*_spec.rb' 7 | end 8 | 9 | -------------------------------------------------------------------------------- /lib/seed-fu.rb: -------------------------------------------------------------------------------- 1 | require 'active_record' 2 | require 'active_support/core_ext/module/attribute_accessors' 3 | require 'seed-fu/railtie' if defined?(Rails) && Rails.version >= "3" 4 | 5 | module SeedFu 6 | autoload :VERSION, 'seed-fu/version' 7 | autoload :Seeder, 'seed-fu/seeder' 8 | autoload :ActiveRecordExtension, 'seed-fu/active_record_extension' 9 | autoload :BlockHash, 'seed-fu/block_hash' 10 | autoload :Runner, 'seed-fu/runner' 11 | autoload :Writer, 'seed-fu/writer' 12 | 13 | mattr_accessor :quiet 14 | 15 | # Set `SeedFu.quiet = true` to silence all output 16 | @@quiet = false 17 | 18 | mattr_accessor :fixture_paths 19 | 20 | # Set this to be an array of paths to directories containing your seed files. If used as a Rails 21 | # plugin, SeedFu will set to to contain `Rails.root/db/fixtures` and 22 | # `Rails.root/db/fixtures/Rails.env` 23 | @@fixture_paths = ['db/fixtures'] 24 | 25 | # Load seed data from files 26 | # @param [Array] fixture_paths The paths to look for seed files in 27 | # @param [Regexp] filter If given, only filenames matching this expression will be loaded 28 | def self.seed(fixture_paths = SeedFu.fixture_paths, filter = nil) 29 | Runner.new(fixture_paths, filter).run 30 | end 31 | end 32 | 33 | # @public 34 | class ActiveRecord::Base 35 | extend SeedFu::ActiveRecordExtension 36 | end 37 | -------------------------------------------------------------------------------- /lib/seed-fu/active_record_extension.rb: -------------------------------------------------------------------------------- 1 | module SeedFu 2 | module ActiveRecordExtension 3 | # Load some seed data. There are two ways to do this. 4 | # 5 | # Verbose syntax 6 | # -------------- 7 | # 8 | # This will seed a single record. The `:id` parameter ensures that if a record already exists 9 | # in the database with the same id, then it will be updated with the name and age, rather 10 | # than created from scratch. 11 | # 12 | # Person.seed(:id) do |s| 13 | # s.id = 1 14 | # s.name = "Jon" 15 | # s.age = 21 16 | # end 17 | # 18 | # Note that `:id` is the default attribute used to identify a seed, so it need not be 19 | # specified. 20 | # 21 | # Terse syntax 22 | # ------------ 23 | # 24 | # This is a more succinct way to load multiple records. Note that both `:x` and `:y` are being 25 | # used to identify a seed here. 26 | # 27 | # Point.seed(:x, :y, 28 | # { :x => 3, :y => 10, :name => "Home" }, 29 | # { :x => 5, :y => 9, :name => "Office" } 30 | # ) 31 | def seed(*args, &block) 32 | SeedFu::Seeder.new(self, *parse_seed_fu_args(args, block)).seed 33 | end 34 | 35 | # Has the same syntax as {#seed}, but if a record already exists with the same values for 36 | # constraining attributes, it will not be updated. 37 | # 38 | # @example 39 | # Person.seed(:id, :id => 1, :name => "Jon") # => Record created 40 | # Person.seed(:id, :id => 1, :name => "Bob") # => Name changed 41 | # Person.seed_once(:id, :id => 1, :name => "Harry") # => Name *not* changed 42 | def seed_once(*args, &block) 43 | constraints, data = parse_seed_fu_args(args, block) 44 | SeedFu::Seeder.new(self, constraints, data, :insert_only => true).seed 45 | end 46 | 47 | private 48 | 49 | def parse_seed_fu_args(args, block) 50 | if block.nil? 51 | if args.last.is_a?(Array) 52 | # Last arg is an array of data, so assume the rest of the args are constraints 53 | data = args.pop 54 | [args, data] 55 | else 56 | # Partition the args, assuming the first hash is the start of the data 57 | args.partition { |arg| !arg.is_a?(Hash) } 58 | end 59 | else 60 | # We have a block, so assume the args are all constraints 61 | [args, [SeedFu::BlockHash.new(block).to_hash]] 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /lib/seed-fu/block_hash.rb: -------------------------------------------------------------------------------- 1 | module SeedFu 2 | # @private 3 | class BlockHash 4 | def initialize(proc) 5 | @hash = {} 6 | proc.call(self) 7 | end 8 | 9 | def to_hash 10 | @hash 11 | end 12 | 13 | def method_missing(method_name, *args, &block) 14 | if method_name.to_s =~ /^(.*)=$/ && args.length == 1 && block.nil? 15 | @hash[$1] = args.first 16 | else 17 | super 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/seed-fu/capistrano.rb: -------------------------------------------------------------------------------- 1 | Capistrano::Configuration.instance.load do 2 | namespace :db do 3 | desc "Load seed data into database" 4 | task :seed_fu, :roles => :db, :only => { :primary => true } do 5 | run "cd #{release_path} && bundle exec rake RAILS_ENV=#{rails_env} db:seed_fu" 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/seed-fu/capistrano3.rb: -------------------------------------------------------------------------------- 1 | load File.expand_path('../../tasks/seed_fu_capistrano3.rake', __FILE__) 2 | -------------------------------------------------------------------------------- /lib/seed-fu/railtie.rb: -------------------------------------------------------------------------------- 1 | module SeedFu 2 | class Railtie < Rails::Railtie 3 | rake_tasks do 4 | load "tasks/seed_fu.rake" 5 | end 6 | 7 | initializer 'seed_fu.set_fixture_paths' do 8 | SeedFu.fixture_paths = [ 9 | Rails.root.join('db/fixtures').to_s, 10 | Rails.root.join('db/fixtures/' + Rails.env).to_s 11 | ] 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/seed-fu/runner.rb: -------------------------------------------------------------------------------- 1 | require 'zlib' 2 | require 'active_support/core_ext/array/wrap' 3 | 4 | module SeedFu 5 | # Runs seed files. 6 | # 7 | # It is not recommended to use this class directly. Instead, use {SeedFu.seed SeedFu.seed}, which creates 8 | # an instead of {Runner} and calls {#run #run}. 9 | # 10 | # @see SeedFu.seed SeedFu.seed 11 | class Runner 12 | # @param [Array] fixture_paths The paths where fixtures are located. Will use 13 | # `SeedFu.fixture_paths` if {nil}. If the argument is not an array, it will be wrapped by one. 14 | # @param [Regexp] filter If given, only seed files with a file name matching this pattern will 15 | # be used 16 | def initialize(fixture_paths = nil, filter = nil) 17 | @fixture_paths = Array.wrap(fixture_paths || SeedFu.fixture_paths) 18 | @filter = filter 19 | end 20 | 21 | # Run the seed files. 22 | def run 23 | puts "\n== Filtering seed files against regexp: #{@filter.inspect}" if @filter && !SeedFu.quiet 24 | 25 | filenames.each do |filename| 26 | run_file(filename) 27 | end 28 | end 29 | 30 | private 31 | 32 | def run_file(filename) 33 | puts "\n== Seed from #{filename}" unless SeedFu.quiet 34 | 35 | ActiveRecord::Base.transaction do 36 | open(filename) do |file| 37 | chunked_ruby = '' 38 | file.each_line do |line| 39 | if line == "# BREAK EVAL\n" 40 | eval(chunked_ruby) 41 | chunked_ruby = '' 42 | else 43 | chunked_ruby << line 44 | end 45 | end 46 | eval(chunked_ruby) unless chunked_ruby == '' 47 | end 48 | end 49 | end 50 | 51 | def open(filename) 52 | if filename[-3..-1] == '.gz' 53 | Zlib::GzipReader.open(filename) do |file| 54 | yield file 55 | end 56 | else 57 | File.open(filename) do |file| 58 | yield file 59 | end 60 | end 61 | end 62 | 63 | def filenames 64 | filenames = [] 65 | @fixture_paths.each do |path| 66 | filenames += (Dir[File.join(path, '*.rb')] + Dir[File.join(path, '*.rb.gz')]).sort 67 | end 68 | filenames.uniq! 69 | filenames = filenames.find_all { |filename| filename =~ @filter } if @filter 70 | filenames 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /lib/seed-fu/seeder.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/core_ext/hash/keys' 2 | 3 | module SeedFu 4 | # Creates or updates seed records with data. 5 | # 6 | # It is not recommended to use this class directly. Instead, use `Model.seed`, and `Model.seed_once`, 7 | # where `Model` is your Active Record model. 8 | # 9 | # @see ActiveRecordExtension 10 | class Seeder 11 | # @param [ActiveRecord::Base] model_class The model to be seeded 12 | # @param [Array] constraints A list of attributes which identify a particular seed. If 13 | # a record with these attributes already exists then it will be updated rather than created. 14 | # @param [Array] data Each item in this array is a hash containing attributes for a 15 | # particular record. 16 | # @param [Hash] options 17 | # @option options [Boolean] :quiet (SeedFu.quiet) If true, output will be silenced 18 | # @option options [Boolean] :insert_only (false) If true then existing records which match the 19 | # constraints will not be updated, even if the seed data has changed 20 | def initialize(model_class, constraints, data, options = {}) 21 | @model_class = model_class 22 | @constraints = constraints.to_a.empty? ? [:id] : constraints 23 | @data = data.to_a || [] 24 | @options = options.symbolize_keys 25 | 26 | @options[:quiet] ||= SeedFu.quiet 27 | 28 | validate_constraints! 29 | validate_data! 30 | end 31 | 32 | # Insert/update the records as appropriate. Validation is skipped while saving. 33 | # @return [Array] The records which have been seeded 34 | def seed 35 | records = @model_class.transaction do 36 | @data.map { |record_data| seed_record(record_data.symbolize_keys) } 37 | end 38 | update_id_sequence 39 | records 40 | end 41 | 42 | private 43 | 44 | def validate_constraints! 45 | unknown_columns = @constraints.map(&:to_s) - @model_class.column_names 46 | unless unknown_columns.empty? 47 | raise(ArgumentError, 48 | "Your seed constraints contained unknown columns: #{column_list(unknown_columns)}. " + 49 | "Valid columns are: #{column_list(@model_class.column_names)}.") 50 | end 51 | end 52 | 53 | def validate_data! 54 | raise ArgumentError, "Seed data missing" if @data.empty? 55 | end 56 | 57 | def column_list(columns) 58 | '`' + columns.join("`, `") + '`' 59 | end 60 | 61 | def seed_record(data) 62 | record = find_or_initialize_record(data) 63 | return if @options[:insert_only] && !record.new_record? 64 | 65 | puts " - #{@model_class} #{data.inspect}" unless @options[:quiet] 66 | 67 | # Rails 3 or Rails 4 + rails/protected_attributes 68 | if record.class.respond_to?(:protected_attributes) && record.class.respond_to?(:accessible_attributes) 69 | record.assign_attributes(data, :without_protection => true) 70 | # Rails 4 without rails/protected_attributes 71 | else 72 | record.assign_attributes(data) 73 | end 74 | record.save(:validate => false) || raise(ActiveRecord::RecordNotSaved, 'Record not saved!') 75 | record 76 | end 77 | 78 | def find_or_initialize_record(data) 79 | @model_class.where(constraint_conditions(data)).take || 80 | @model_class.new 81 | end 82 | 83 | def constraint_conditions(data) 84 | Hash[@constraints.map { |c| [c, data[c.to_sym]] }] 85 | end 86 | 87 | def update_id_sequence 88 | if @model_class.connection.adapter_name == "PostgreSQL" or @model_class.connection.adapter_name == "PostGIS" 89 | return if @model_class.primary_key.nil? || @model_class.sequence_name.nil? 90 | 91 | quoted_id = @model_class.connection.quote_column_name(@model_class.primary_key) 92 | sequence = @model_class.sequence_name 93 | 94 | # TODO postgresql_version was made public in Rails 5.0.0, remove #send when support for earlier versions are dropped 95 | if @model_class.connection.send(:postgresql_version) >= 100000 96 | sql =<<-EOS 97 | SELECT setval('#{sequence}', (SELECT GREATEST(MAX(#{quoted_id})+(SELECT seqincrement FROM pg_sequence WHERE seqrelid = '#{sequence}'::regclass), (SELECT seqmin FROM pg_sequence WHERE seqrelid = '#{sequence}'::regclass)) FROM #{@model_class.quoted_table_name}), false) 98 | EOS 99 | else 100 | sql =<<-EOS 101 | SELECT setval('#{sequence}', (SELECT GREATEST(MAX(#{quoted_id})+(SELECT increment_by FROM #{sequence}), (SELECT min_value FROM #{sequence})) FROM #{@model_class.quoted_table_name}), false) 102 | EOS 103 | end 104 | 105 | @model_class.connection.execute sql 106 | end 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /lib/seed-fu/version.rb: -------------------------------------------------------------------------------- 1 | module SeedFu 2 | # The current version of Seed Fu 3 | VERSION = '2.3.9' 4 | end 5 | -------------------------------------------------------------------------------- /lib/seed-fu/writer.rb: -------------------------------------------------------------------------------- 1 | module SeedFu 2 | # {Writer} is used to programmatically generated seed files. For example, you might want to write 3 | # a script which converts data in a CSV file to a valid Seed Fu seed file, which can then be 4 | # imported. 5 | # 6 | # @example Basic usage 7 | # SeedFu::Writer.write('path/to/file.rb', :class_name => 'Person', :constraints => [:first_name, :last_name]) do |writer| 8 | # writer.add(:first_name => 'Jon', :last_name => 'Smith', :age => 21) 9 | # writer.add(:first_name => 'Emily', :last_name => 'McDonald', :age => 24) 10 | # end 11 | # 12 | # # Writes the following to the file: 13 | # # 14 | # # Person.seed(:first_name, :last_name, 15 | # # {:first_name=>"Jon", :last_name=>"Smith", :age=>21}, 16 | # # {:first_name=>"Emily", :last_name=>"McDonald", :age=>24} 17 | # # ) 18 | class Writer 19 | cattr_accessor :default_options 20 | @@default_options = { 21 | :chunk_size => 100, 22 | :constraints => [:id], 23 | :seed_type => :seed 24 | } 25 | 26 | # @param [Hash] options 27 | # @option options [String] :class_name *Required* The name of the Active Record model to 28 | # generate seeds for 29 | # @option options [Fixnum] :chunk_size (100) The number of seeds to write before generating a 30 | # `# BREAK EVAL` line. (Chunking reduces memory usage when loading seeds.) 31 | # @option options [:seed, :seed_once] :seed_type (:seed) The method to use when generating 32 | # seeds. See {ActiveRecordExtension} for details. 33 | # @option options [Array] :constraints ([:id]) The constraining attributes for the seeds 34 | def initialize(options = {}) 35 | @options = self.class.default_options.merge(options) 36 | raise ArgumentError, "missing option :class_name" unless @options[:class_name] 37 | end 38 | 39 | # Creates a new instance of {Writer} with the `options`, and then calls {#write} with the 40 | # `io_or_filename` and `block` 41 | def self.write(io_or_filename, options = {}, &block) 42 | new(options).write(io_or_filename, &block) 43 | end 44 | 45 | # Writes the necessary headers and footers, and yields to a block within which the actual 46 | # seed data should be writting using the `#<<` method. 47 | # 48 | # @param [IO] io_or_filename The IO to which writes will be made. (If an `IO` is given, it is 49 | # your responsibility to close it after writing.) 50 | # @param [String] io_or_filename The filename of a file to make writes to. (Will be opened and 51 | # closed automatically.) 52 | # @yield [self] make calls to `#<<` within the block 53 | def write(io_or_filename, &block) 54 | raise ArgumentError, "missing block" unless block_given? 55 | 56 | if io_or_filename.respond_to?(:write) 57 | write_to_io(io_or_filename, &block) 58 | else 59 | File.open(io_or_filename, 'w') do |file| 60 | write_to_io(file, &block) 61 | end 62 | end 63 | end 64 | 65 | # Add a seed. Must be called within a block passed to {#write}. 66 | # @param [Hash] seed The attributes for the seed 67 | def <<(seed) 68 | raise "You must add seeds inside a SeedFu::Writer#write block" unless @io 69 | 70 | buffer = '' 71 | 72 | if chunk_this_seed? 73 | buffer << seed_footer 74 | buffer << "# BREAK EVAL\n" 75 | buffer << seed_header 76 | end 77 | 78 | buffer << ",\n" 79 | buffer << ' ' + seed.inspect 80 | 81 | @io.write(buffer) 82 | 83 | @count += 1 84 | end 85 | alias_method :add, :<< 86 | 87 | private 88 | 89 | def write_to_io(io) 90 | @io, @count = io, 0 91 | @io.write(file_header) 92 | @io.write(seed_header) 93 | yield(self) 94 | @io.write(seed_footer) 95 | @io.write(file_footer) 96 | ensure 97 | @io, @count = nil, nil 98 | end 99 | 100 | def file_header 101 | <<-END 102 | # DO NOT MODIFY THIS FILE, it was auto-generated. 103 | # 104 | # Date: #{Time.now} 105 | # Seeding #{@options[:class_name]} 106 | # Written with the command: 107 | # 108 | # #{$0} #{$*.join} 109 | # 110 | END 111 | end 112 | 113 | def file_footer 114 | <<-END 115 | # End auto-generated file. 116 | END 117 | end 118 | 119 | def seed_header 120 | constraints = @options[:constraints] && @options[:constraints].map(&:inspect).join(', ') 121 | "#{@options[:class_name]}.#{@options[:seed_type]}(#{constraints}" 122 | end 123 | 124 | def seed_footer 125 | "\n)\n" 126 | end 127 | 128 | def chunk_this_seed? 129 | @count != 0 && (@count % @options[:chunk_size]) == 0 130 | end 131 | end 132 | end 133 | -------------------------------------------------------------------------------- /lib/tasks/seed_fu.rake: -------------------------------------------------------------------------------- 1 | require 'seed-fu' 2 | 3 | namespace :db do 4 | desc <<-EOS 5 | Loads seed data for the current environment. It will look for 6 | ruby seed files in /db/fixtures/ and 7 | /db/fixtures//. 8 | 9 | By default it will load any ruby files found. You can filter the files 10 | loaded by passing in the FILTER environment variable with a comma-delimited 11 | list of patterns to include. Any files not matching the pattern will 12 | not be loaded. 13 | 14 | You can also change the directory where seed files are looked for 15 | with the FIXTURE_PATH environment variable. 16 | 17 | Examples: 18 | # default, to load all seed files for the current environment 19 | rake db:seed_fu 20 | 21 | # to load seed files matching orders or customers 22 | rake db:seed_fu FILTER=orders,customers 23 | 24 | # to load files from RAILS_ROOT/features/fixtures 25 | rake db:seed_fu FIXTURE_PATH=features/fixtures 26 | EOS 27 | task :seed_fu => :environment do 28 | if ENV["FILTER"] 29 | filter = /#{ENV["FILTER"].gsub(/,/, "|")}/ 30 | end 31 | 32 | if ENV["FIXTURE_PATH"] 33 | fixture_paths = [ENV["FIXTURE_PATH"], ENV["FIXTURE_PATH"] + '/' + Rails.env] 34 | end 35 | 36 | SeedFu.seed(fixture_paths, filter) 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/tasks/seed_fu_capistrano3.rake: -------------------------------------------------------------------------------- 1 | namespace :db do 2 | desc 'Load seed data into database' 3 | task :seed_fu do 4 | on roles(:db) do 5 | within release_path do 6 | with rails_env: fetch(:rails_env) do 7 | execute :bundle, :exec, :rake, 'db:seed_fu' 8 | end 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /seed-fu.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib/', __FILE__) 3 | $:.unshift lib unless $:.include?(lib) 4 | 5 | require 'seed-fu/version' 6 | 7 | Gem::Specification.new do |s| 8 | s.name = "seed-fu" 9 | s.version = SeedFu::VERSION 10 | s.platform = Gem::Platform::RUBY 11 | s.licenses = ['MIT'] 12 | s.authors = ["Michael Bleigh", "Jon Leighton"] 13 | s.email = ["michael@intridea.com", "j@jonathanleighton.com"] 14 | s.homepage = "http://github.com/mbleigh/seed-fu" 15 | s.summary = "Easily manage seed data in your Active Record application" 16 | s.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." 17 | 18 | s.add_dependency "activerecord", [">= 3.1"] 19 | s.add_dependency "activesupport", [">= 3.1"] 20 | 21 | s.add_development_dependency "rspec", "~> 2.0" 22 | s.add_development_dependency "pg", '~> 0' 23 | s.add_development_dependency "mysql2", '~> 0' 24 | s.add_development_dependency "sqlite3", '~> 0' 25 | 26 | s.files = Dir.glob("{lib}/**/*") + %w(LICENSE README.md CHANGELOG.md) 27 | s.require_path = 'lib' 28 | end 29 | -------------------------------------------------------------------------------- /spec/.gitignore: -------------------------------------------------------------------------------- 1 | debug.log 2 | test.sqlite3 3 | seeded_models.rb 4 | -------------------------------------------------------------------------------- /spec/README: -------------------------------------------------------------------------------- 1 | To run the specs: 2 | 3 | gem install bundler # If not already installed 4 | bundle install # Install the dependencies 5 | rspec spec/ # Run the specs 6 | 7 | By default an sqlite3 database is used. 8 | 9 | To test others: 10 | 11 | DB=mysql rspec spec/ 12 | DB=postgresql rspec spec/ 13 | 14 | In each of these cases, you'll need to create a database named "spec_fu_test". 15 | 16 | The connection paramaters for each of these are specified in spec/connections/, which you can edit if necessary (for example to change the username/password). 17 | -------------------------------------------------------------------------------- /spec/connections/mysql.rb: -------------------------------------------------------------------------------- 1 | ActiveRecord::Base.establish_connection( 2 | :adapter => "mysql2", 3 | :database => "seed_fu_test", 4 | :username => "root" 5 | ) 6 | -------------------------------------------------------------------------------- /spec/connections/postgresql.rb: -------------------------------------------------------------------------------- 1 | ActiveRecord::Base.establish_connection( 2 | :adapter => "postgresql", 3 | :database => "seed_fu_test", 4 | :username => "postgres" 5 | ) 6 | -------------------------------------------------------------------------------- /spec/connections/sqlite3.rb: -------------------------------------------------------------------------------- 1 | ActiveRecord::Base.establish_connection( 2 | :adapter => "sqlite3", 3 | :database => File.dirname(__FILE__) + "/test.sqlite3" 4 | ) 5 | -------------------------------------------------------------------------------- /spec/fixtures/seeded_models.rb: -------------------------------------------------------------------------------- 1 | SeededModel.seed do |s| 2 | s.id = 1 3 | s.title = "Foo" 4 | end 5 | -------------------------------------------------------------------------------- /spec/fixtures/seeded_models_2.rb: -------------------------------------------------------------------------------- 1 | SeededModel.seed do |s| 2 | s.id = 2 3 | s.title = "Bar" 4 | end 5 | -------------------------------------------------------------------------------- /spec/fixtures/seeded_models_3.rb.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbleigh/seed-fu/34c054c914858c3d7685f83d16dea5c0e2114561/spec/fixtures/seeded_models_3.rb.gz -------------------------------------------------------------------------------- /spec/runner_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe SeedFu::Runner do 4 | it "should seed data from Ruby and gzipped Ruby files in the given fixtures directory" do 5 | SeedFu.seed(File.dirname(__FILE__) + '/fixtures') 6 | 7 | SeededModel.find(1).title.should == "Foo" 8 | SeededModel.find(2).title.should == "Bar" 9 | SeededModel.find(3).title.should == "Baz" 10 | end 11 | 12 | it "should seed only the data which matches the filter, if one is given" do 13 | SeedFu.seed(File.dirname(__FILE__) + '/fixtures', /_2/) 14 | 15 | SeededModel.count.should == 1 16 | SeededModel.find(2).title.should == "Bar" 17 | end 18 | 19 | it "should use the SpecFu.fixtures_path variable to determine where fixtures are" do 20 | SeedFu.fixture_paths = [File.dirname(__FILE__) + '/fixtures'] 21 | SeedFu.seed 22 | SeededModel.count.should == 3 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/seeder_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe SeedFu::Seeder do 4 | 5 | it "should work with negative seeds" do 6 | SeededModel.seed(:id) do |s| 7 | s.id = 10 8 | s.login = "bob2" 9 | s.first_name = "Bob2" 10 | s.last_name = "Bobson2" 11 | s.title = "Peaon2" 12 | end 13 | 14 | SeededModel.seed(:id) do |s| 15 | s.id = -2 16 | s.login = "bob" 17 | s.first_name = "Bob" 18 | s.last_name = "Bobson" 19 | s.title = "Peon" 20 | end 21 | 22 | bob = SeededModel.find_by_id(-2) 23 | bob.first_name.should == "Bob" 24 | bob.last_name.should == "Bobson" 25 | 26 | if ENV['DB'] == 'postgresql' 27 | next_id = SeededModel.connection.execute("select nextval('seeded_models_id_seq')") 28 | next_id[0]['nextval'].to_i.should == 11 29 | end 30 | end 31 | 32 | it "should create a model if one doesn't exist" do 33 | SeededModel.seed(:id) do |s| 34 | s.id = 5 35 | s.login = "bob" 36 | s.first_name = "Bob" 37 | s.last_name = "Bobson" 38 | s.title = "Peon" 39 | end 40 | 41 | bob = SeededModel.find_by_id(5) 42 | bob.first_name.should == "Bob" 43 | bob.last_name.should == "Bobson" 44 | end 45 | 46 | it "should be able to handle multiple constraints" do 47 | SeededModel.seed(:title, :login) do |s| 48 | s.login = "bob" 49 | s.title = "Peon" 50 | s.first_name = "Bob" 51 | end 52 | 53 | SeededModel.count.should == 1 54 | 55 | SeededModel.seed(:title, :login) do |s| 56 | s.login = "frank" 57 | s.title = "Peon" 58 | s.first_name = "Frank" 59 | end 60 | 61 | SeededModel.count.should == 2 62 | 63 | SeededModel.find_by_login("bob").first_name.should == "Bob" 64 | SeededModel.seed(:title, :login) do |s| 65 | s.login = "bob" 66 | s.title = "Peon" 67 | s.first_name = "Steve" 68 | end 69 | SeededModel.find_by_login("bob").first_name.should == "Steve" 70 | end 71 | 72 | it "should be able to create models from an array of seed attributes" do 73 | SeededModel.seed(:title, :login, [ 74 | {:login => "bob", :title => "Peon", :first_name => "Steve"}, 75 | {:login => "frank", :title => "Peasant", :first_name => "Francis"}, 76 | {:login => "harry", :title => "Noble", :first_name => "Harry"} 77 | ]) 78 | 79 | SeededModel.find_by_login("bob").first_name.should == "Steve" 80 | SeededModel.find_by_login("frank").first_name.should == "Francis" 81 | SeededModel.find_by_login("harry").first_name.should == "Harry" 82 | end 83 | 84 | it "should be able to create models from a list of seed attribute hashes at the end of the args" do 85 | SeededModel.seed(:title, :login, 86 | {:login => "bob", :title => "Peon", :first_name => "Steve"}, 87 | {:login => "frank", :title => "Peasant", :first_name => "Francis"}, 88 | {:login => "harry", :title => "Noble", :first_name => "Harry"} 89 | ) 90 | 91 | SeededModel.find_by_login("bob").first_name.should == "Steve" 92 | SeededModel.find_by_login("frank").first_name.should == "Francis" 93 | SeededModel.find_by_login("harry").first_name.should == "Harry" 94 | end 95 | 96 | it "should update, not create, if constraints are met" do 97 | SeededModel.seed(:id) do |s| 98 | s.id = 1 99 | s.login = "bob" 100 | s.first_name = "Bob" 101 | s.last_name = "Bobson" 102 | s.title = "Peon" 103 | end 104 | 105 | SeededModel.seed(:id) do |s| 106 | s.id = 1 107 | s.login = "bob" 108 | s.first_name = "Robert" 109 | s.last_name = "Bobson" 110 | s.title = "Peon" 111 | end 112 | 113 | bob = SeededModel.find_by_id(1) 114 | bob.first_name.should == "Robert" 115 | bob.last_name.should == "Bobson" 116 | end 117 | 118 | it "should create but not update with seed_once" do 119 | SeededModel.seed_once(:id) do |s| 120 | s.id = 1 121 | s.login = "bob" 122 | s.first_name = "Bob" 123 | s.last_name = "Bobson" 124 | s.title = "Peon" 125 | end 126 | 127 | SeededModel.seed_once(:id) do |s| 128 | s.id = 1 129 | s.login = "bob" 130 | s.first_name = "Robert" 131 | s.last_name = "Bobson" 132 | s.title = "Peon" 133 | end 134 | 135 | bob = SeededModel.find_by_id(1) 136 | bob.first_name.should == "Bob" 137 | bob.last_name.should == "Bobson" 138 | end 139 | 140 | it "should default to an id constraint" do 141 | SeededModel.seed(:title => "Bla", :id => 1) 142 | SeededModel.seed(:title => "Foo", :id => 1) 143 | 144 | SeededModel.find(1).title.should == "Foo" 145 | end 146 | 147 | it "should require that all constraints are defined" do 148 | expect { SeededModel.seed(:doesnt_exist, :title => "Bla") }.to raise_error(ArgumentError) 149 | end 150 | 151 | it "should not perform validation" do 152 | expect { SeededModel.seed(:id => 1) }.not_to raise_error() 153 | end 154 | 155 | if ENV["DB"] == "postgresql" 156 | it "should update the primary key sequence after a records have been seeded" do 157 | id = SeededModel.connection.select_value("SELECT currval('seeded_models_id_seq')").to_i + 1 158 | SeededModel.seed(:title => "Foo", :id => id) 159 | 160 | expect { SeededModel.create!(:title => "Bla") }.not_to raise_error 161 | end 162 | 163 | it "should not raise error when there is no primary key specified" do 164 | expect { SeededModelNoPrimaryKey.seed(:id => "Id") }.not_to raise_error 165 | end 166 | 167 | it "should not raise error when there is primary key without sequence" do 168 | expect { SeededModelNoSequence.seed(:id => "Id") }.not_to raise_error 169 | end 170 | end 171 | 172 | it "should raise an ActiveRecord::RecordNotSaved exception if any records fail to save" do 173 | expect { SeededModel.seed(:fail_to_save => true, :title => "Foo") }.to raise_error(ActiveRecord::RecordNotSaved) 174 | end 175 | end 176 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler/setup' 3 | require 'seed-fu' 4 | require 'logger' 5 | 6 | SeedFu.quiet = true 7 | 8 | ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/../debug.log") 9 | 10 | ENV["DB"] ||= 'sqlite3' 11 | puts "Using #{ENV["DB"]} to run the tests." 12 | require File.dirname(__FILE__) + "/connections/#{ENV["DB"]}.rb" 13 | 14 | ActiveRecord::Schema.define :version => 0 do 15 | create_table :seeded_models, :force => true do |t| 16 | t.column :login, :string 17 | t.column :first_name, :string 18 | t.column :last_name, :string 19 | t.column :title, :string 20 | end 21 | 22 | create_table :seeded_model_no_primary_keys, :id => false, :force => true do |t| 23 | t.column :id, :string 24 | end 25 | 26 | create_table :seeded_model_no_sequences, :id => false, :force => true do |t| 27 | t.column :id, :string 28 | end 29 | 30 | execute("ALTER TABLE seeded_model_no_sequences ADD PRIMARY KEY (id)") if ENV['DB'] == 'postgresql' 31 | end 32 | 33 | class SeededModel < ActiveRecord::Base 34 | validates_presence_of :title 35 | attr_protected :first_name if self.respond_to?(:protected_attributes) 36 | attr_accessor :fail_to_save 37 | 38 | before_save { false if fail_to_save } 39 | end 40 | 41 | class SeededModelNoPrimaryKey < ActiveRecord::Base 42 | 43 | end 44 | 45 | class SeededModelNoSequence < ActiveRecord::Base 46 | 47 | end 48 | 49 | RSpec.configure do |config| 50 | config.before do 51 | SeededModel.delete_all 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /spec/writer_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe SeedFu::Writer do 4 | before do 5 | @file_name = File.dirname(__FILE__) + '/seeded_models.rb' 6 | end 7 | 8 | after do 9 | FileUtils.rm(@file_name) 10 | end 11 | 12 | it "should successfully write some seeds out to a file and then import them back in" do 13 | SeedFu::Writer.write(@file_name, :class_name => 'SeededModel') do |writer| 14 | writer << { :id => 1, :title => "Mr" } 15 | writer << { :id => 2, :title => "Dr" } 16 | end 17 | load @file_name 18 | 19 | SeededModel.find(1).title.should == "Mr" 20 | SeededModel.find(2).title.should == "Dr" 21 | end 22 | 23 | it "should support chunking" do 24 | SeedFu::Writer.write(@file_name, :class_name => 'SeededModel', :chunk_size => 2) do |writer| 25 | writer << { :id => 1, :title => "Mr" } 26 | writer << { :id => 2, :title => "Dr" } 27 | writer << { :id => 3, :title => "Dr" } 28 | end 29 | load @file_name 30 | 31 | SeededModel.count.should == 3 32 | File.read(@file_name).should include("# BREAK EVAL\n") 33 | end 34 | 35 | it "should support specifying the output to use 'seed_once' rather than 'seed'" do 36 | SeededModel.seed(:id => 1, :title => "Dr") 37 | 38 | SeedFu::Writer.write(@file_name, :class_name => 'SeededModel', :seed_type => :seed_once) do |writer| 39 | writer << { :id => 1, :title => "Mr" } 40 | end 41 | load @file_name 42 | 43 | SeededModel.find(1).title.should == "Dr" 44 | end 45 | end 46 | --------------------------------------------------------------------------------