├── Gemfile ├── .travis.yml ├── lib ├── named_seeds │ ├── databases.rake │ ├── version.rb │ ├── uuid.rb │ ├── dsl.rb │ ├── rails.rb │ ├── railtie.rb │ └── identity.rb └── named_seeds.rb ├── .gitignore ├── Appraisals ├── test ├── cases │ ├── dsl_test.rb │ ├── identify_test.rb │ └── named_seeds_test.rb ├── test_helper.rb └── support │ └── active_record.rb ├── Rakefile ├── TODO.md ├── LICENSE ├── named_seeds.gemspec ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md └── README.md /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gemspec 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | cache: bundler 3 | rvm: 4 | - 2.2.5 5 | install: 6 | - bundle install 7 | - bundle exec appraisal install 8 | script: 9 | - bundle exec appraisal rake test 10 | -------------------------------------------------------------------------------- /lib/named_seeds/databases.rake: -------------------------------------------------------------------------------- 1 | namespace :named_seeds do 2 | 3 | task :setup => :environment do 4 | NamedSeeds::Railtie.db_setup 5 | end 6 | 7 | end 8 | 9 | Rake::Task['db:setup'].enhance { Rake::Task['named_seeds:setup'].invoke } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .ruby-version 6 | .yardoc 7 | Gemfile.lock 8 | InstalledFiles 9 | _yardoc 10 | coverage 11 | gemfiles 12 | doc/ 13 | lib/bundler/man 14 | pkg 15 | rdoc 16 | spec/reports 17 | test/tmp 18 | test/version_tmp 19 | tmp 20 | gemfiles/* 21 | -------------------------------------------------------------------------------- /Appraisals: -------------------------------------------------------------------------------- 1 | 2 | appraise 'rails40' do 3 | gem 'rails', '~> 4.0.0' 4 | end 5 | 6 | appraise 'rails41' do 7 | gem 'rails', '~> 4.1.0' 8 | end 9 | 10 | appraise 'rails42' do 11 | gem 'rails', '~> 4.2.0' 12 | end 13 | 14 | appraise 'rails50' do 15 | gem 'rails', '~> 5.0.0' 16 | end 17 | -------------------------------------------------------------------------------- /test/cases/dsl_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class DslTest < NamedSeeds::TestCase 4 | 5 | module T ; extend NamedSeeds::DSL ; end 6 | 7 | def test_includes_all_methods 8 | assert_equal 207281424, T.identify(:ruby) 9 | assert_equal 207281424, T.id(:ruby) 10 | end 11 | 12 | 13 | end 14 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | require "bundler/gem_tasks" 3 | require 'rake/testtask' 4 | 5 | desc 'Test the NamedSeeds gem.' 6 | Rake::TestTask.new do |t| 7 | t.libs = ['lib','test'] 8 | t.test_files = Dir.glob("test/**/*_test.rb").sort 9 | t.verbose = true 10 | end 11 | 12 | task :default do 13 | exec 'appraisal update' 14 | exec 'appraisal rake test' 15 | end 16 | -------------------------------------------------------------------------------- /lib/named_seeds/version.rb: -------------------------------------------------------------------------------- 1 | module NamedSeeds 2 | 3 | module VERSION 4 | MAJOR = 2 5 | MINOR = 2 6 | TINY = 1 7 | PRE = nil 8 | STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") 9 | end 10 | 11 | def self.version 12 | Gem::Version.new VERSION::STRING 13 | end 14 | 15 | def self.version_string 16 | VERSION::STRING 17 | end 18 | 19 | end 20 | -------------------------------------------------------------------------------- /lib/named_seeds.rb: -------------------------------------------------------------------------------- 1 | require 'rails' 2 | require 'active_record' 3 | require 'named_seeds/version' 4 | require 'named_seeds/railtie' 5 | require 'named_seeds/uuid' 6 | require 'named_seeds/dsl' 7 | require 'named_seeds/identity' 8 | require 'named_seeds/rails' 9 | 10 | module NamedSeeds 11 | 12 | extend DSL 13 | 14 | def self.load_seed 15 | Railtie.load_seed 16 | end 17 | 18 | def self.reset_cache 19 | Identity.reset_cache 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /lib/named_seeds/uuid.rb: -------------------------------------------------------------------------------- 1 | require 'securerandom' 2 | 3 | module NamedSeeds 4 | 5 | # Copy of ActiveSupport's Digest::UUID extension for v5 UUIDs. 6 | # Needed to maintain backward compatibility with Rails 4.0 and 4.1. 7 | # 8 | def self.uuid_v5(name) 9 | hash = Digest::SHA1.new 10 | hash.update("k\xA7\xB8\x12\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8") 11 | hash.update(name.to_s) 12 | ary = hash.digest.unpack('NnnnnN') 13 | ary[2] = (ary[2] & 0x0FFF) | (5 << 12) 14 | ary[3] = (ary[3] & 0x3FFF) | 0x8000 15 | "%08x-%04x-%04x-%04x-%04x%08x" % ary 16 | end 17 | 18 | end 19 | -------------------------------------------------------------------------------- /test/cases/identify_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class IdentifyTest < NamedSeeds::TestCase 4 | 5 | def test_works_on_strings 6 | assert_equal id('foo'), id('foo') 7 | assert_not_equal id('FOO'), id('foo') 8 | end 9 | 10 | def test_works_on_symbols 11 | assert_equal id('foo'), id(:foo) 12 | end 13 | 14 | def test_identifies_consistently 15 | assert_equal 207281424, id(:ruby) 16 | assert_equal 1066363776, id(:sapphire_2) 17 | end 18 | 19 | def test_can_generate_uuid_identities 20 | assert_match '4f156606-8cb3-509e-a177-956ca0a22015', id(:ken, :uuid) 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /lib/named_seeds/dsl.rb: -------------------------------------------------------------------------------- 1 | require 'zlib' 2 | 3 | module NamedSeeds 4 | module DSL 5 | 6 | MAX_ID = 2 ** 30 - 1 7 | 8 | # Copy of ActiveRecord::Fixtures.identify method. 9 | # Returns a consistent, platform-independent identifier for +label+. 10 | # Integer identifiers are values less than 2^30. 11 | # UUIDs are RFC 4122 version 5 SHA-1 hashes. 12 | # 13 | def identify(label, column_type = :integer) 14 | if column_type == :uuid 15 | NamedSeeds.uuid_v5(label) 16 | else 17 | Zlib.crc32(label.to_s) % MAX_ID 18 | end 19 | end 20 | alias_method :id, :identify 21 | 22 | end 23 | end 24 | 25 | -------------------------------------------------------------------------------- /lib/named_seeds/rails.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/concern' 2 | require 'active_support/test_case' 3 | 4 | module NamedSeeds 5 | module TestHelper 6 | 7 | extend ActiveSupport::Concern 8 | 9 | def after_teardown 10 | super 11 | NamedSeeds.reset_cache 12 | end 13 | 14 | module ClassMethods 15 | 16 | def named_seeds(*names) 17 | options = names.extract_options! 18 | if names.many? 19 | names.each do |name| 20 | define_method(name) do |*identities| 21 | Identity.named(name).find(*identities) 22 | end 23 | end 24 | else 25 | name = names.first 26 | define_method(name) do |*identities| 27 | Identity.named(name, options).find(*identities) 28 | end 29 | end 30 | end 31 | 32 | end 33 | 34 | end 35 | end 36 | 37 | ActiveSupport::TestCase.send :include, NamedSeeds::TestHelper 38 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | 2 | ## Get RSpec Working 3 | 4 | https://semaphoreci.com/community/tutorials/getting-started-with-rspec 5 | 6 | 7 | ## Prepare for v2 8 | 9 | http://chriskottom.com/blog/2014/11/fixing-fixtures/ 10 | 11 | 12 | ## Alternative Solutions 13 | 14 | There are no absolutes, especially when it comes to software. That said, we think NamedSeeds is the best way to use fixtures. It encourages... (good factoies) 15 | 16 | Everyone loves their own brand :poop: 17 | 18 | 19 | * Seedbank gives your Rails seed data a little structure. 20 | https://github.com/james2m/seedbank 21 | 22 | 23 | * Relational seeding for Rails apps 24 | https://github.com/vigetlabs/sprig 25 | * Rails 4 task to dump (parts) of your database to db/seeds.rb 26 | https://github.com/rroblak/seed_dump 27 | * Allows composing fixture sets 28 | https://github.com/optoro/composable_fixtures 29 | * Advanced seed data handling for Rails, combining the best practices of several methods together. 30 | https://github.com/mbleigh/seed-fu 31 | * Auto generate factories from data 32 | https://github.com/markburns/to_factory 33 | 34 | 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Ken Collins 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /named_seeds.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "named_seeds/version" 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = "named_seeds" 7 | spec.version = NamedSeeds.version_string 8 | spec.authors = ["Ken Collins"] 9 | spec.email = ["ken@metaskills.net"] 10 | spec.summary = %q|Replace ActiveRecord::Fixtures With #{your_factory_lib}.| 11 | spec.description = %q|Make you tests fast by augmenting them with transactional fixtures powered by your favorite factory library!| 12 | spec.homepage = "http://github.com/metaskills/named_seeds" 13 | spec.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 14 | spec.files = `git ls-files`.split("\n") 15 | spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 16 | spec.require_paths = ["lib"] 17 | spec.add_runtime_dependency 'rails', '>= 4.0' 18 | spec.add_development_dependency 'appraisal' 19 | spec.add_development_dependency 'pry' 20 | spec.add_development_dependency 'rake' 21 | spec.add_development_dependency 'sqlite3' 22 | end 23 | -------------------------------------------------------------------------------- /lib/named_seeds/railtie.rb: -------------------------------------------------------------------------------- 1 | require 'active_record/tasks/database_tasks' 2 | 3 | module NamedSeeds 4 | class Railtie < Rails::Railtie 5 | 6 | config.named_seeds = ActiveSupport::OrderedOptions.new 7 | config.named_seeds.load_app_seed_file = true 8 | config.named_seeds.custom_seed_file = nil 9 | config.named_seeds.engines_with_load_seed = [] 10 | 11 | config.before_initialize do |app| 12 | ActiveRecord::Tasks::DatabaseTasks.seed_loader = NamedSeeds::Railtie 13 | end 14 | 15 | rake_tasks do 16 | load 'named_seeds/databases.rake' 17 | end 18 | 19 | def load_seed 20 | load_app_seed_file 21 | load_custom_seed_file 22 | engines_load_seed 23 | end 24 | 25 | def db_setup 26 | load_custom_seed_file 27 | engines_load_seed 28 | end 29 | 30 | 31 | protected 32 | 33 | def load_app_seed_file 34 | Rails.application.load_seed if config.named_seeds.load_app_seed_file 35 | end 36 | 37 | def load_custom_seed_file 38 | return unless config.named_seeds.custom_seed_file 39 | custom_seed_file = Rails.root.join(config.named_seeds.custom_seed_file) 40 | load(custom_seed_file) if File.exists?(custom_seed_file) 41 | end 42 | 43 | def engines_load_seed 44 | config.named_seeds.engines_with_load_seed.each { |engine| engine.load_seed } 45 | end 46 | 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # Always keep a CHANGELOG. See: http://keepachangelog.com 3 | 4 | ## v2.2.1 - 2016-12-22 5 | 6 | * Fix `Identity.named` to allow options to be optional. 7 | 8 | 9 | ## v2.2.0 - 2016-12-14 10 | 11 | * Allow `named_seeds` to take many names. 12 | 13 | 14 | ## v2.1.0 - 2015-10-04 15 | 16 | * Support Rails 5 edge. 17 | 18 | 19 | ## v2.0.1 - 2015-03-29 20 | 21 | * Added: `custom_seed_file` config. Use this to set a file other than `db/seeds.rb` for loading. 22 | * Added: `load_app_seed_file` config which defaults to true. Use to disable `db/seeds.rb` when calling `NamedSeeds.load_seed`, typically for test env. 23 | 24 | 25 | ## v2.0.0 - 2015-03-25 26 | 27 | * New version tested for Rails v4.0 to v4.2. 28 | 29 | 30 | ## v1.1.0 - 2014-11-27 31 | 32 | * Many changes to support new Rails 4.2 sync test schema strategy. 33 | * The test:prepare Task Might Be Useless Now? - http://git.io/mu2F2Q 34 | * Bring back db:test:prepare - http://git.io/VKEwhg 35 | 36 | 37 | ## v1.0.4 38 | 39 | * Make db:development:seed automatic based on Rails db:setup convention. 40 | 41 | 42 | ## v1.0.3 43 | 44 | * Slacker rails dep version. 3.1 or up. 45 | 46 | 47 | ## v1.0.2 48 | 49 | * Added a db:development:seed rake task. 50 | * Update docs a bit more. 51 | 52 | 53 | ## v1.0.1 54 | 55 | * Do nothing unless there is a seed file. 56 | 57 | 58 | ## v1.0.0 - 2012-03-26 59 | 60 | Initial release 61 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler' ; Bundler.require :development, :test 3 | require 'named_seeds' 4 | require 'support/active_record' 5 | require 'active_support/test_case' 6 | require 'active_support/testing/autorun' 7 | 8 | ActiveSupport.test_order = :random if ActiveSupport.respond_to?(:test_order) 9 | 10 | module NamedSeeds 11 | class TestCase < ActiveSupport::TestCase 12 | 13 | class << self 14 | 15 | def id(*args) 16 | NamedSeeds.identify(*args) 17 | end 18 | 19 | def create_user(id, attributes={}) 20 | ::User.create!(attributes) { |x| x.id = id } 21 | end 22 | 23 | def create_state(id, attributes={}) 24 | ::State.create!(attributes) { |x| x.abbr = id } 25 | end 26 | 27 | def create_enterprise_object(id, attributes={}) 28 | ::EnterpriseObject.create!(attributes) { |x| x.id = id } 29 | end 30 | 31 | end 32 | 33 | @@ken = create_user 155397742, name: 'Ken Collins', email: 'ken@metaskills.net' 34 | @@john = create_user 830138774, name: 'John Hall', email: 'john.hall@example.com' 35 | 36 | @@virginia = create_state 'VA', name: 'Virginia' 37 | @@washington = create_state 'WA', name: 'Washington' 38 | 39 | @@enterprising_ken = create_enterprise_object '4f156606-8cb3-509e-a177-956ca0a22015', name: 'Ken Collins' unless TESTING_RAILS_40 40 | 41 | 42 | private 43 | 44 | def id(*args) ; self.class.id(*args) ; end 45 | 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /test/support/active_record.rb: -------------------------------------------------------------------------------- 1 | require 'active_record/base' 2 | 3 | TESTING_RAILS_40 = Gem::Dependency.new('rails', '~> 4.0.0') =~ Gem::Dependency.new('rails', Rails::VERSION::STRING) 4 | TESTING_RAILS_41 = Gem::Dependency.new('rails', '~> 4.1.0') =~ Gem::Dependency.new('rails', Rails::VERSION::STRING) 5 | TESTING_RAILS_42 = Gem::Dependency.new('rails', '~> 4.2.0') =~ Gem::Dependency.new('rails', Rails::VERSION::STRING) 6 | 7 | ActiveRecord::Base.logger = nil 8 | ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: ':memory:' 9 | 10 | ActiveRecord::Base.class_eval do 11 | connection.instance_eval do 12 | 13 | create_table :users, force: true do |t| 14 | t.string :name, :email 15 | end 16 | 17 | create_table :posts, id: false, force: true do |t| 18 | t.primary_key :id, :uuid, default: nil 19 | t.string :title, :body 20 | t.references :user 21 | end 22 | 23 | create_table :states, id: false, force: true do |t| 24 | t.primary_key :abbr, :string, default: nil 25 | t.string :name 26 | end 27 | 28 | create_table :enterprise_objects, id: :uuid, primary_key: :id, force: true do |t| 29 | t.string :name 30 | end unless TESTING_RAILS_40 31 | 32 | end 33 | end 34 | 35 | class User < ActiveRecord::Base 36 | has_many :posts 37 | end 38 | 39 | class Post < ActiveRecord::Base 40 | end 41 | 42 | class State < ActiveRecord::Base 43 | self.primary_key = :abbr 44 | end 45 | 46 | class EnterpriseObject < ActiveRecord::Base 47 | self.primary_key = :id 48 | end 49 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, we pledge to respect all 4 | people who contribute through reporting issues, posting feature requests, 5 | updating documentation, submitting pull requests or patches, and other 6 | activities. 7 | 8 | We are committed to making participation in this project a harassment-free 9 | experience for everyone, regardless of level of experience, gender, gender 10 | identity and expression, sexual orientation, disability, personal appearance, 11 | body size, race, ethnicity, age, or religion. 12 | 13 | Examples of unacceptable behavior by participants include the use of sexual 14 | language or imagery, derogatory comments or personal attacks, trolling, public 15 | or private harassment, insults, or other unprofessional conduct. 16 | 17 | Project maintainers have the right and responsibility to remove, edit, or 18 | reject comments, commits, code, wiki edits, issues, and other contributions 19 | that are not aligned to this Code of Conduct. Project maintainers who do not 20 | follow the Code of Conduct may be removed from the project team. 21 | 22 | This code of conduct applies both within project spaces and in public spaces 23 | when an individual is representing the project or its community. 24 | 25 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 26 | reported by opening an issue or contacting one or more of the project 27 | maintainers. 28 | 29 | This Code of Conduct is adapted from the Contributor Covenant 30 | (http://contributor-covenant.org), version 1.1.0, available at 31 | http://contributor-covenant.org/version/1/1/0/ 32 | -------------------------------------------------------------------------------- /lib/named_seeds/identity.rb: -------------------------------------------------------------------------------- 1 | require 'active_record/fixtures' 2 | 3 | module NamedSeeds 4 | 5 | class FixtureClassNotFound < ActiveRecord::FixtureClassNotFound 6 | end 7 | 8 | class Identity 9 | 10 | @@all_cached = Hash.new 11 | 12 | class << self 13 | 14 | def reset_cache 15 | @@all_cached.clear 16 | end 17 | 18 | def named(name, options={}) 19 | @@all_cached[name] ||= new(name, options) 20 | end 21 | 22 | end 23 | 24 | def initialize(name, options={}) 25 | @name = name 26 | @options = options 27 | @fixtures = {} 28 | end 29 | 30 | def find(*identities) 31 | fixtures = identities.map { |identity| model_find(identity) } 32 | fixtures.many? ? fixtures : fixtures.first 33 | end 34 | 35 | def identities 36 | @options[:identities] || @options[:ids] 37 | end 38 | 39 | 40 | private 41 | 42 | def model_find(identity) 43 | @fixtures[identity.to_s] ||= model_class.unscoped { model_class.find model_id(identity) } 44 | end 45 | 46 | def model_class 47 | return @model_class if defined?(@model_class) 48 | @model_class = (@options[:class] || @options[:klass]) || @name.to_s.classify.safe_constantize 49 | raise FixtureClassNotFound, "No class found for named_seed #{@name.inspect}. Use :class option." unless @model_class 50 | @model_class 51 | end 52 | 53 | def model_id(identity) 54 | case identities 55 | when NilClass then NamedSeeds.identify(identity, model_primary_key_type) 56 | when Hash then identities[identity] 57 | when :natural then identity 58 | end 59 | end 60 | 61 | def model_primary_key_type 62 | type = model_class.columns_hash[model_class.primary_key].type 63 | sql_type = model_class.columns_hash[model_class.primary_key].sql_type 64 | return :uuid if ['uuid', 'guid', 'uniqueidentifier'].include?(sql_type) 65 | type ? type.to_sym : :id 66 | end 67 | 68 | end 69 | 70 | end 71 | -------------------------------------------------------------------------------- /test/cases/named_seeds_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class NamedSeedsTest < NamedSeeds::TestCase 4 | 5 | named_seeds :users 6 | named_seeds :posts 7 | named_seeds :states, identities: {virginia: 'VA', washington: 'WA'} 8 | named_seeds :enterprise_objects 9 | 10 | named_seeds :natural_id_states, identities: :natural, class: State 11 | named_seeds :classy_users, class: User 12 | named_seeds :no_model_class 13 | 14 | 15 | def test_generated_method_that_can_find_a_record 16 | assert_equal @@ken, users(:ken) 17 | end 18 | 19 | def test_not_found_is_just_plain_active_record_exceptions 20 | assert_raise(ActiveRecord::RecordNotFound) { users(:not_found) } 21 | end 22 | 23 | def test_can_pass_multiple_names_to_fixture_accessor 24 | assert_equal [@@ken, @@john], users(:ken, :john) 25 | assert_equal [@@virginia, @@washington], states(:virginia, :washington) 26 | assert_equal [@@virginia, @@washington], natural_id_states('VA', 'WA') 27 | end 28 | 29 | def test_can_find_string_primary_keys_using_identities_hash_option 30 | assert_equal @@virginia, states(:virginia) 31 | end 32 | 33 | def test_can_find_string_primary_keys_using_identities_natural_option 34 | assert_equal @@virginia, natural_id_states('VA') 35 | end 36 | 37 | def test_caches_fixture_lookups 38 | assert_equal users(:ken).object_id, users(:ken).object_id 39 | end 40 | 41 | def test_can_reset_the_fixture_lookup_cache 42 | ken1 = users(:ken) 43 | NamedSeeds.reset_cache 44 | ken2 = users(:ken) 45 | assert_not_equal ken1.object_id, ken2.object_id 46 | end 47 | 48 | def test_can_define_a_bad_named_seed_with_no_model_and_lazily_see_an_exception 49 | assert_raise(NamedSeeds::FixtureClassNotFound) { no_model_class(:name) } 50 | end 51 | 52 | def test_named_seeds_fixtur_class_not_found_subclasses_activerecords 53 | assert NamedSeeds::FixtureClassNotFound < ActiveRecord::FixtureClassNotFound 54 | end 55 | 56 | def test_can_find_named_seed_with_uuid_primary_key 57 | assert_equal @@enterprising_ken, enterprise_objects(:ken) 58 | end unless TESTING_RAILS_40 59 | 60 | end 61 | 62 | module SomeNamespace 63 | class User ; def self.find(id) ; self.new ; end ; end 64 | class NamespacedActiveSupportTest < NamedSeeds::TestCase 65 | 66 | named_seeds :users 67 | 68 | def test_generated_method_finds_proper_constant 69 | assert_equal @@ken, users(:ken) 70 | end 71 | 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | NamedSeeds - When it comes to fast tests... you reap what you sow! 3 | 4 |
5 | 6 | #### Make your tests fast by augmenting them with transactional fixtures powered by your favorite factory library! 7 | 8 | [![Gem Version](https://badge.fury.io/rb/named_seeds.png)](http://badge.fury.io/rb/named_seeds) 9 | [![Build Status](https://secure.travis-ci.org/metaskills/named_seeds.png)](http://travis-ci.org/metaskills/named_seeds) 10 | 11 | We all know that ActiveRecord's fixtures are hard to maintain, and more importantly, disconnected from the models that create your data. This disconnect can lead to invalid or incomplete representations of your objects as your application grows. But Rails did get something right. Fixtures combined with transactional tests are a huge performance win while providing [canned references among your team](http://martinfowler.com/bliki/ObjectMother.html) via helper methods that find fixtures using a unique name. The NamedSeeds gem aims to replace YAML fixtures by providing a slim identification layer to be used in conjunction with your factory library of choice. For example, [FactoryGirl](https://github.com/thoughtbot/factory_girl). 12 | 13 | The idea is to leverage your tests' existing factories to generate fixtures that will be populated before testing starts and to use a database transaction strategy around each test. In this way you have a curated set of personas that can be accessed via convenient helper methods like `users(:admin)` which in turn yields much faster test runs. **NamedSeeds fixtures also become your seed data for Rails' development environment.** This consistency between development and testing is a huge win for on-boarding new team members. Lastly, database fixtures, even those seeded by factories, are not a panacea and we recommend that you continue to use factories in your tests for edge cases when it makes sense to do so. 14 | 15 | NamedSeeds is best when your factories follow two key principals: 16 | 17 | * Leveraging your models for most business logic. 18 | * Creation of "valid garbage" with no/minimal args while allowing idempotency via explicit args. 19 | 20 | 21 | ## Installation 22 | 23 | Add the gem to your Rails' Gemfile in both the development and test group as shown below. This is needed since the NamedSeeds gem has integrations in both environments. 24 | 25 | ```ruby 26 | group :development, :test do 27 | gem 'named_seeds' 28 | end 29 | ``` 30 | 31 | 32 | ## Quick Start 33 | 34 | NamedSeeds only requires that you customize the Rails `db/seeds.rb` file. The contents of this file can be anything you want! We recommend using a factory library like [FactoryGirl](https://github.com/thoughtbot/factory_girl) or [Machinist](https://github.com/notahat/machinist). 35 | 36 | ```ruby 37 | require 'factory_girl' 38 | include FactoryGirl::Syntax::Methods 39 | FactoryGirl.find_definitions rescue false 40 | 41 | @bob = create :user, id: NamedSeeds.identify(:bob), email: 'bob@test.com' 42 | ``` 43 | 44 | In this example we have given our Bob user an explicit primary key using the identify method of NamedSeeds. This ensures we have a handle on Bob in our tests. For this happen, make the following changes to your Rails `test_helper.rb` file. 45 | 46 | ```ruby 47 | ENV["RAILS_ENV"] = "test" 48 | require File.expand_path('../../config/environment', __FILE__) 49 | require 'rails/test_help' 50 | NamedSeeds.load_seed 51 | 52 | class ActiveSupport::TestCase 53 | 54 | named_seeds :users 55 | 56 | end 57 | ``` 58 | 59 | Here we have added `NamedSeeds.load_seed` after our requires. This ensures our test database is properly seeded. We have also added a `named_seeds :users` declaration that creates a test helper method that can find any persona with a matching identity. The method name follows ActiveRecord model/table name conventions. So in this case we expect to find a `User` model. An example of what our unit test for Bob might look like with the new `users` fixture helper would be: 60 | 61 | ```ruby 62 | require 'test_helper' 63 | 64 | class UserTest < ActiveSupport::TestCase 65 | 66 | tests "should work" do 67 | user = users(:bob) 68 | assert_equal 'bob@test.com', user.email 69 | end 70 | 71 | end 72 | ``` 73 | 74 | This test is very contrived and is only meant to illustrate the `users(:bob)` test helper which closely mimics ActiveRecord's fixture helpers. 75 | 76 | 77 | ## Detailed Usage 78 | 79 | #### Development Benefits 80 | 81 | NamedSeeds hooks into the `db:setup` process. In this way, the same fixture story seeded in your test environment is the same in development. For example, if you are on-boarding a new developer and bootstrapping their environment. 82 | 83 | ```shell 84 | $ bundle 85 | $ rake db:create:all db:setup 86 | ``` 87 | 88 | Your local development database will now contain all fixtures created in db/seeds. As you fixtures grow, re-create your development environment with confidence. 89 | 90 | 91 | ```shell 92 | $ bundle 93 | $ rake db:drop:all db:create:all db:setup 94 | ``` 95 | 96 | #### The NamedSeeds::DSL 97 | 98 | If you do not like typing `NamedSeeds.identify(:persona)` over and over again in your seeds file, you can include the `NamedSeeds::DSL` at the top. This will give you direct scope to the `identify` method which is also succinctly aliased to just `id` if you want. 99 | 100 | ```ruby 101 | include NamedSeeds::DSL 102 | 103 | id(:ruby) # => 207281424 104 | id(:sapphire_2) # => 1066363776 105 | ``` 106 | 107 | #### UUID Identifiers 108 | 109 | By default identities boil down to a consistent integer whose values are less than 2^30. These are great for typical primary or foreign keys. Now that Rails supports UUID keys in both models and ActiveRecord fixtures, so does the NamedSeeds gem. The identity method can use an optional second parameter to denote the SQL type when seeding your database. 110 | 111 | ```ruby 112 | id(:ken, :uuid) # => '4f156606-8cb3-509e-a177-956ca0a22015' 113 | ``` 114 | 115 | So if our `User` model did use a UUID column type, our seed file might look like this. 116 | 117 | ```ruby 118 | @bob = create :user, id: NamedSeeds.identify(:bob), email: 'bob@test.com' 119 | ``` 120 | 121 | #### String Identifier 122 | 123 | If your model uses strings for primary keys or what is known as "natural" keys, then NamedSeeds can still work for you. First, create your seed data without the identity helper. For example, below we are seeding US states in a contrived lookup table. 124 | 125 | ```ruby 126 | # In db/seeds.rb file. 127 | create :state, id: 'VA', name: 'Virginia' 128 | create :state, id: 'WA', name: 'Washington' 129 | ``` 130 | 131 | Here the primary key of the `State` model is a string column. You have two options to generate test helpers for these objects. Note that none are technically needed since the identities are known and not random integers. 132 | 133 | ```ruby 134 | named_seeds :states, identities: {virginia: 'VA', washington: 'WA'} 135 | states(:virginia) # => # 136 | 137 | named_seeds :states, identities: :natural 138 | states('VA') # => # 139 | 140 | ``` 141 | 142 | By passing an identities hash to `named_seeds` you can customize the name of the key used in finders. The first line would generate a method that allows you to find states using the longer identities key values. This is great if your natural keys do not necessarily speak to the persona/object under test. If you wanted to use the natural key string values, you can just pass `:natural` to the identities option. 143 | 144 | #### Setting Fixture Classes 145 | 146 | If your test helper name can not infer the class name, just use the `:class` option when declaring seeds in your test helper. 147 | 148 | ```ruby 149 | named_seeds :users, class: Legacy::User 150 | ``` 151 | 152 | #### Moving From ActiveRecord Fixtures 153 | 154 | Unlike ActiveRecord fixtures, NamedSeeds does not support loading fixtures per test case. Since the entire fixture story is loaded before the test suite runs. NamedSeeds is much more akin to `fixtures(:all)` and the `named_seeds` method is more about creating test helper methods to access named fixtures. Lastly, NamedSeeds has no notion of instantiated fixtures. 155 | 156 | #### Using DatabaseCleaner 157 | 158 | Rails 4 (maybe starting in 4.1) now has a new test database synchronization strategy. No longer is your entire development schema cloned when you run your tests. This saves time when running tests but it also adds a potential gotcha when using db/seeds.rb with the NamedSeeds gem. We recommend using the DatabaseCleaner gem and cleaning your DB at the top of your seed file. 159 | 160 | ```ruby 161 | # In your Gemfile. 162 | group :development, :test do 163 | gem 'named_seeds' 164 | gem 'database_cleaner' 165 | end 166 | 167 | # Top of db/seeds.rb file. 168 | require 'database_cleaner' 169 | DatabaseCleaner.clean_with :truncation 170 | DatabaseCleaner.clean 171 | ``` 172 | 173 | 174 | ## Configurations 175 | 176 | All configurations are best done in a `config/initializers/named_seeds.rb` file using a defined check as shown below. All other examples below assume this convention. 177 | 178 | ```ruby 179 | if defined?(NamedSeeds) 180 | Rails.application.config.named_seeds 181 | end 182 | ``` 183 | 184 | #### Other Rails::Engine And Seed Loaders 185 | 186 | Rails::Engines are a great way to distribute shared functionality. Rails exposes a hook called `load_seed` that an engine can implement. These are great for seeding tables that an engine's models may need. You can tell NamedSeeds to use any object that responds to `load_seed` and each will be called after `db/seeds.rb` is loaded. For example: 187 | 188 | ```ruby 189 | config.named_seeds.engines_with_load_seed = [ 190 | GeoData::Engine, 191 | OurLookupTables 192 | ] 193 | ``` 194 | 195 | #### Custom Seed File 196 | 197 | By default, the NamedSeeds gem relies on Rails `db/seeds.rb` as your seed file. Some people use this file for setting up new production instances while others have code in this file that is something completely different. If this file is not safe for development/test then you should really re-examine your usage of `db/seeds.rb` since Rails loads this file on the `db:setup` task automatically. If you do not want to use this for your development/test seed data, then you can customize the location. However, NamedSeeds does this by running our own `named_seeds:setup` task after the Rails `db:setup` task. So the result is still somewhat the same for development but this does keep `db/seeds.rb` out of the test environment. 198 | 199 | ```ruby 200 | config.named_seeds.load_app_seed_file = false 201 | config.named_seeds.custom_seed_file = 'db/seeds_devtest.rb' 202 | ``` 203 | 204 | 205 | ## Versions 206 | 207 | The current master branch is for Rails v4.0.0 and up and. Please use our `1-0-stable` branch for Rails v3.x. 208 | 209 | 210 | ## Contributing 211 | 212 | We use the [Appraisal](https://github.com/thoughtbot/appraisal) gem from Thoughtbot to help us test different versions of Rails. The `rake appraisal test` command actually runs our test suite against all Rails versions in our `Appraisal` file. So after cloning the repo, running the following commands. 213 | 214 | ```shell 215 | $ bundle install 216 | $ bundle exec appraisal install 217 | $ bundle exec appraisal rake test 218 | ``` 219 | 220 | If you want to run the tests for a specific Rails version, use one of the appraisal names found in our `Appraisals` file. For example, the following will run our tests suite for Rails 4.1.x. 221 | 222 | ```shell 223 | $ bundle exec appraisal rails41 rake test 224 | ``` 225 | 226 | 227 | --------------------------------------------------------------------------------