├── .gitignore ├── LICENSE ├── README.markdown ├── Rakefile ├── extras └── samples.rake ├── lib └── spawn.rb ├── rails └── init.rb ├── spawn.gemspec ├── spawn.gemspec.erb └── test ├── active_record_test.rb ├── all_test.rb ├── ohm_test.rb └── sequel_test.rb /.gitignore: -------------------------------------------------------------------------------- 1 | pkg 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008 Michel Martens and Damian Janowski 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.markdown: -------------------------------------------------------------------------------- 1 | Spawn 2 | ======= 3 | 4 | A ridiculously simple fixtures replacement for your web framework of 5 | choice. 6 | 7 | Description 8 | ----------- 9 | 10 | Spawn is a very small library that can effectively replace fixtures or 11 | any other huge library for the same task. 12 | 13 | Usage 14 | ----- 15 | 16 | In the examples below we are using [Faker](http://faker.rubyforge.org/) 17 | to generate random data, but you can use any method. 18 | 19 | Note that the object the spawner block yields is not an instance of 20 | User, but an OpenStruct that records the attributes passed and will in 21 | turn relay that information when the User is instantiated. 22 | 23 | 24 | With [ActiveRecord](http://api.rubyonrails.org/classes/ActiveRecord/Base.html): 25 | 26 | class User < ActiveRecord::Base 27 | spawner do |user| 28 | user.name = Faker::Name.name 29 | user.email = Faker::Internet.email 30 | end 31 | end 32 | 33 | With [Sequel](http://sequel.rubyforge.org/): 34 | 35 | class User < Sequel::Model 36 | extend Spawn 37 | 38 | spawner do |user| 39 | user.name = Faker::Name.name 40 | user.email = Faker::Internet.email 41 | end 42 | end 43 | 44 | With [Ohm](http://ohm.keyvalue.org): 45 | 46 | class User < Ohm::Model 47 | extend Spawn 48 | 49 | attribute :name 50 | attribute :email 51 | 52 | spawner do |user| 53 | user.name = Faker::Name.name 54 | user.email = Faker::Internet.email 55 | end 56 | end 57 | 58 | If you don't want to pollute your class definition, you 59 | can of course use it from outside: 60 | 61 | User.spawner do |user| 62 | user.name = Faker::Name.name 63 | user.email = Faker::Internet.email 64 | end 65 | 66 | Then, in your test or in any other place: 67 | 68 | @user = User.spawn 69 | 70 | Or, if you need something special: 71 | 72 | @user = User.spawn :name => "Michel Martens" 73 | 74 | This sends to User.new all the attributes defined in the spawner block, along with 75 | the hash of attributes passed when spawning. 76 | 77 | If you want a reference to the model before the validity is checked, you can pass 78 | a block: 79 | 80 | @user = User.spawn { |u| u.name = "Michel Martens" } 81 | 82 | 83 | Conditional evaluation 84 | ---------------------- 85 | 86 | Spawn will execute the right hand side of your assignment even if you 87 | provide a value for some particular key. Consider the following example: 88 | 89 | User.spawner do |user| 90 | user.profile = Profile.spawn # Profile.spawn is executed even if you provide a value for :profile. 91 | end 92 | 93 | User.spawn(:profile => Profile.first) 94 | 95 | Here, you will be creating an extra instance of `Profile`, because when 96 | the block is evaluated it calls `Profile.spawn`. If the right hand side 97 | of your assignment is costly or has side effects, you may want to avoid 98 | this behavior by using `||=`: 99 | 100 | User.spawner do |user| 101 | user.profile ||= Profile.spawn 102 | end 103 | 104 | Then, if you pass a `:profile`: 105 | 106 | User.spawn(:profile => Profile.first) 107 | 108 | You can verify that `Profile.spawn` is never called. Although this 109 | may sound evident, it can bite you if you rely on the RHS not executing 110 | (e.g. if you're using Spawn to populate fake data into a database and 111 | you want to control how many instances you create). 112 | 113 | Installation 114 | ------------ 115 | 116 | $ sudo gem install spawn 117 | 118 | ### Contributors 119 | 120 | Thanks to [Foca](http://github.com/foca), 121 | [Pedro](http://github.com/peterpunk) and 122 | [Diego](http://github.com/oboxodo) for the suggestions and improvements 123 | to this tiny library. 124 | 125 | License 126 | ------- 127 | 128 | Copyright (c) 2009 Michel Martens and Damian Janowski 129 | 130 | Permission is hereby granted, free of charge, to any person 131 | obtaining a copy of this software and associated documentation 132 | files (the "Software"), to deal in the Software without 133 | restriction, including without limitation the rights to use, 134 | copy, modify, merge, publish, distribute, sublicense, and/or sell 135 | copies of the Software, and to permit persons to whom the 136 | Software is furnished to do so, subject to the following 137 | conditions: 138 | 139 | The above copyright notice and this permission notice shall be 140 | included in all copies or substantial portions of the Software. 141 | 142 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 143 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 144 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 145 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 146 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 147 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 148 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 149 | OTHER DEALINGS IN THE SOFTWARE. 150 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/testtask' 2 | 3 | task :default => :test 4 | 5 | Rake::TestTask.new(:test) do |t| 6 | t.pattern = 'test/**/*_test.rb' 7 | t.verbose = false 8 | end 9 | -------------------------------------------------------------------------------- /extras/samples.rake: -------------------------------------------------------------------------------- 1 | # Use this task to generate sample records 2 | # for your database. This code has to be 3 | # customized for each application. 4 | # 5 | # This task is not installed by default. It's 6 | # provided here so you can use it if you find 7 | # it convenient. To use it, move it to the 8 | # lib/tasks directory in your rails install. 9 | namespace(:db) do 10 | namespace(:samples) do 11 | desc "Populate the database with sample records." 12 | task :load => :environment do 13 | 14 | # Default user 15 | user = User.spawn :login => 'admin' 16 | 17 | # Default blog 18 | blog = Blog.spawn :title => 'Foo Blog' 19 | 20 | # Blog posts 21 | 20.times do 22 | post = Post.spawn :user => user 23 | 24 | # Post comments 25 | 5.times do 26 | Comment.spawn :post => post 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/spawn.rb: -------------------------------------------------------------------------------- 1 | require "ostruct" 2 | 3 | module Spawn 4 | 5 | class Invalid < ArgumentError; end 6 | 7 | def spawner &default 8 | @@spawn ||= Hash.new 9 | @@spawn[self] = default 10 | end 11 | 12 | def spawn params = {} 13 | 14 | # Grab default parameters from spawner block. 15 | @@spawn[self].call(attrs = OpenStruct.new(params)) 16 | 17 | # Initialize model 18 | model = new(attrs.send(:table).merge(params)) 19 | 20 | # Yield model for changes to be made before saving. 21 | yield(model) if block_given? 22 | 23 | # Raise an error if the model is invalid or couldn't be saved. 24 | model.valid? and model.save and model or raise(Invalid, model.errors.inspect) 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /rails/init.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../lib/spawn' 2 | 3 | ActiveRecord::Base.send(:extend, Spawn) 4 | -------------------------------------------------------------------------------- /spawn.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = 'spawn' 3 | s.version = '0.1.4' 4 | s.summary = %{Simple fixtures replacement for Sequel, ActiveRecord, Ohm and probably many other ORMs} 5 | s.description = %{Spawn is a very small library (just 14 lines of code) that can effectively replace fixtures or any other library for the same task.} 6 | s.authors = ["Michel Martens", "Damian Janowski"] 7 | s.email = ["michel@soveran.com", "djanowski@dimaion.com"] 8 | s.homepage = "http://github.com/soveran/spawn" 9 | s.files = ["lib/spawn.rb", "rails/init.rb", "README.markdown", "LICENSE", "Rakefile", "test/active_record_test.rb", "test/all_test.rb", "test/ohm_test.rb", "test/sequel_test.rb", "spawn.gemspec"] 10 | s.rubyforge_project = "spawner" 11 | end 12 | -------------------------------------------------------------------------------- /spawn.gemspec.erb: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = 'spawn' 3 | s.version = '0.1.4' 4 | s.summary = %{Simple fixtures replacement for Sequel, ActiveRecord, Ohm and probably many other ORMs} 5 | s.description = %{Spawn is a very small library (just 14 lines of code) that can effectively replace fixtures or any other library for the same task.} 6 | s.authors = ["Michel Martens", "Damian Janowski"] 7 | s.email = ["michel@soveran.com", "djanowski@dimaion.com"] 8 | s.homepage = "http://github.com/soveran/spawn" 9 | s.files = <%= Dir['lib/**/*.rb', 'rails/**/*.rb', 'README*', 'LICENSE', 'Rakefile', 'test/**/*.*', '*.gemspec'].inspect %> 10 | s.rubyforge_project = "spawner" 11 | end 12 | -------------------------------------------------------------------------------- /test/active_record_test.rb: -------------------------------------------------------------------------------- 1 | require "rubygems" 2 | require "active_record" 3 | require "contest" 4 | require File.dirname(__FILE__) + "/../lib/spawn" 5 | require "faker" 6 | 7 | ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:") 8 | ActiveRecord::Schema.define do 9 | create_table :active_record_users do |table| 10 | table.column :name, :string 11 | table.column :email, :string 12 | end 13 | end 14 | 15 | class ActiveRecordUser < ActiveRecord::Base 16 | extend Spawn 17 | 18 | validates_presence_of :name 19 | 20 | spawner do |user| 21 | user.email = Faker::Internet.email 22 | end 23 | end 24 | 25 | class TestSpawnWithActiveRecord < Test::Unit::TestCase 26 | context "spawned user" do 27 | setup do 28 | @user = ActiveRecordUser.spawn :name => "John" 29 | end 30 | 31 | should "have John as name" do 32 | assert_equal "John", @user.name 33 | end 34 | 35 | context "with invalid attributes" do 36 | should "raise an error" do 37 | assert_raise Spawn::Invalid do 38 | ActiveRecordUser.spawn 39 | end 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /test/all_test.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'contest' 3 | require File.dirname(__FILE__) + "/../lib/spawn" 4 | 5 | class Base 6 | attr_accessor :attributes 7 | 8 | def initialize(attrs = {}) 9 | @attributes = attrs 10 | end 11 | 12 | def self.create(attrs = {}) 13 | new(attrs) 14 | end 15 | 16 | def bar; attributes[:bar] end 17 | def baz; attributes[:baz] end 18 | 19 | def bar=(value); attributes[:bar] = value; end 20 | def baz=(value); attributes[:baz] = value; end 21 | 22 | def save 23 | self 24 | end 25 | 26 | def valid? 27 | true 28 | end 29 | 30 | extend Spawn 31 | end 32 | 33 | class Foo < Base 34 | class << self 35 | alias_method :create!, :create 36 | end 37 | 38 | spawner do |object| 39 | object.bar = 7 40 | object.baz = 8 41 | end 42 | end 43 | 44 | class Bar < Foo 45 | spawner do |bar| 46 | bar.bar = 9 47 | bar.baz = 10 48 | end 49 | end 50 | 51 | class Baz < Base 52 | spawner do |object| 53 | object.bar = 7 54 | object.baz = 8 55 | end 56 | end 57 | 58 | module FooBar 59 | class Baz < ::Base 60 | spawner do |baz| 61 | baz.bar = 1 62 | end 63 | end 64 | end 65 | 66 | module FooBaz 67 | class Baz < ::Base 68 | spawner do |baz| 69 | baz.bar = 2 70 | end 71 | end 72 | end 73 | 74 | class Qux < Base 75 | def self.name; "Qux"; end 76 | 77 | spawner do |barz| 78 | barz.bar ||= raise "Should have received :bar" 79 | end 80 | end 81 | 82 | class TestFoo < Test::Unit::TestCase 83 | should "have a name class method" do 84 | assert Foo.respond_to?(:name) 85 | assert_equal "Foo", Foo.name 86 | end 87 | 88 | context "with attributes :bar and :baz" do 89 | context "when sent a hash on initialization" do 90 | should "set the attributes to the passed values" do 91 | foo = Foo.new :bar => 1, :baz => 2 92 | assert_equal 1, foo.bar 93 | assert_equal 2, foo.baz 94 | end 95 | 96 | should "pass the values to the block" do 97 | assert_raise(RuntimeError) do 98 | Qux.spawn 99 | end 100 | 101 | assert_equal "Qux", Qux.spawn(:bar => "Qux").bar 102 | end 103 | end 104 | end 105 | 106 | context "that implements Spawn" do 107 | should "be kind of Spawn" do 108 | assert Foo.kind_of?(Spawn) 109 | end 110 | 111 | context "when instantiated with spawn" do 112 | context "without parameters" do 113 | should "initialize the instance with the defined block" do 114 | foo = Foo.spawn 115 | assert_equal 7, foo.bar 116 | assert_equal 8, foo.baz 117 | end 118 | end 119 | 120 | context "with a hash supplied" do 121 | should "override the default values" do 122 | foo = Foo.spawn :bar => 1 123 | assert_equal 1, foo.bar 124 | assert_equal 8, foo.baz 125 | end 126 | end 127 | 128 | context "with a block supplied" do 129 | should "override the default values with single assignments" do 130 | foo = Foo.spawn(:bar => 1) do |f| 131 | f.baz = 2 132 | end 133 | assert_equal 1, foo.bar 134 | assert_equal 2, foo.baz 135 | end 136 | end 137 | 138 | context "and a class Bar" do 139 | context "that also implements Spawn" do 140 | should "be kind of Spawn" do 141 | assert Bar.kind_of?(Spawn) 142 | end 143 | 144 | context "when sent :name" do 145 | should "return 'Bar'" do 146 | assert_equal "Bar", Bar.name 147 | end 148 | end 149 | 150 | context "when sent :spawn" do 151 | should "return an instance of Bar" do 152 | assert Bar.spawn.kind_of?(Bar) 153 | end 154 | 155 | should "not interfere with Foo.spawn" do 156 | assert_equal 7, Foo.spawn.bar 157 | assert_equal 9, Bar.spawn.bar 158 | end 159 | end 160 | end 161 | end 162 | end 163 | 164 | context "and it doesn't understand #create! (only #create)" do 165 | should "work anyway" do 166 | baz = Baz.spawn :bar => 1 167 | assert_equal 1, baz.bar 168 | end 169 | end 170 | end 171 | 172 | should "respect namespaces" do 173 | assert_equal 1, FooBar::Baz.spawn.bar 174 | assert_equal 2, FooBaz::Baz.spawn.bar 175 | end 176 | end 177 | -------------------------------------------------------------------------------- /test/ohm_test.rb: -------------------------------------------------------------------------------- 1 | require "rubygems" 2 | require "ohm" 3 | require "contest" 4 | require File.dirname(__FILE__) + "/../lib/spawn" 5 | 6 | Ohm.connect 7 | 8 | class OhmUser < Ohm::Model 9 | extend Spawn 10 | 11 | attribute :name 12 | attribute :email 13 | 14 | def validate 15 | assert_present :name 16 | end 17 | 18 | spawner do |user| 19 | user.email = "albert@example.com" 20 | end 21 | end 22 | 23 | class TestSpawnWithOhm < Test::Unit::TestCase 24 | setup do 25 | @user = OhmUser.spawn :name => "John" 26 | end 27 | 28 | context "spawned user" do 29 | should "have John as name" do 30 | assert_equal "John", @user.name 31 | end 32 | 33 | context "with invalid attributes" do 34 | should "raise an error" do 35 | assert_raise Spawn::Invalid do 36 | OhmUser.spawn 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /test/sequel_test.rb: -------------------------------------------------------------------------------- 1 | require "rubygems" 2 | require "sequel" 3 | require "contest" 4 | require File.dirname(__FILE__) + "/../lib/spawn" 5 | require "faker" 6 | 7 | DB = Sequel.sqlite 8 | DB << "CREATE TABLE sequel_users (name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL)" 9 | 10 | class SequelUser < Sequel::Model 11 | extend Spawn 12 | 13 | def validate 14 | errors.add(:name, "Not present") if name.nil? or name.empty? 15 | end 16 | 17 | spawner do |user| 18 | user.email = Faker::Internet.email 19 | end 20 | end 21 | 22 | class TestSpawnWithSequel < Test::Unit::TestCase 23 | setup do 24 | @user = SequelUser.spawn :name => "John" 25 | end 26 | 27 | context "spawned user" do 28 | should "have John as name" do 29 | assert_equal "John", @user.name 30 | end 31 | 32 | context "with invalid attributes" do 33 | should "raise an error" do 34 | assert_raise Spawn::Invalid do 35 | SequelUser.spawn 36 | end 37 | end 38 | end 39 | end 40 | end 41 | --------------------------------------------------------------------------------