seedlings 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format nested
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use --create 1.9.2@seedlings_gem
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in seedlings.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Matthew Wilson
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.
data/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # Seedlings
2
+
3
+ A little project to make seeding data easier to deal with across multiple ORMs.
4
+
5
+ **N.B.** This is early-release code that writes data to your database. While it is extracted from a live app, there may be cases where it blows up still :). Please test first. I don't want to hear about lost production data because you didn't try it in development first :).
6
+
7
+ ## Usage
8
+
9
+ gem "seedlings"
10
+
11
+ Install the gem.
12
+
13
+ There are two methods of note: `.plant` and `.plant_and_return`. They take 3 parameters:
14
+
15
+ * the Class to use (must support `ActiveModel`ish interface, like MongoMapper, Mongoid, ActiveRecord)
16
+ * options you want Seedlings to respect, of which there are two:
17
+ * `:constrain` which takes a column name or array of column names that will be used to find the records, and
18
+ * `:dont_update_existing`, which, if set to true, will cause Seedlings to skip updating existing records.
19
+ * the third parameter is that data you want seeded. `.plant` takes however many attribute hashes you give it. `.plant_and_return` takes only a single hash, but returns the resulting object.
20
+
21
+ Here's some examples:
22
+
23
+ ```ruby
24
+ Seedlings.plant(Widget, { :constrain => :name },
25
+ # your seed data goes here, as a bunch of hashes.
26
+ # Make sure you include values for any constraint columns you've specified!
27
+ { :name => "Gizwidget", :context => "science!" },
28
+ { :name => "Somoflange", :context => "80s cartoons" },
29
+ # ...
30
+ )
31
+ ```
32
+
33
+ If some of your seed data depends on other records, switch up to `Seedlings.plant_and_return`, which only takes one attribute hash but returns the object so you can use it in other hashes later, e.g.
34
+
35
+ ```ruby
36
+ parent_thing = Seedlings.plant_and_return(Thing, {}, { :name => "I'm a Parent!" })
37
+
38
+ Seedlings.plant(ChildThing, {},
39
+ { :name => "Child Bit", :context => "docco", :parent => parent_thing },
40
+ # ...
41
+ )
42
+ ```
43
+
44
+ ### Rails? Rails.
45
+
46
+ "Integration" with rails is as simple as sticking the gem in your `Gemfile` and calling `Seedlings.plant(...)` in `db/seeds.rb`. Then you use rake per normal: `rake db:seed`. Yep.
47
+
48
+ ## Of Note
49
+
50
+ This is early code, ripped out from an upcoming project. It probably has some rough edges.
51
+
52
+ TODO:
53
+
54
+ * more and better integration tests
55
+ * a logging system that isn't `puts`-based :)
56
+ * rake tasks
57
+ * more options, etc, as desired
58
+
59
+ ## Note on Patches/Pull Requests
60
+
61
+ * Fork the project.
62
+ * Make your feature addition or bug fix.
63
+ * Add tests for it. This is important so I don't break it in a
64
+ future version unintentionally.
65
+ * Commit, do not mess with rakefile, version, or history.
66
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
67
+ * Send me a pull request. Bonus points for topic branches.
68
+
69
+ ## Copyright
70
+
71
+ Copyright (c) 2011 Matt Wilson. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,3 @@
1
+ class Seedlings
2
+ VERSION = "0.0.1"
3
+ end
data/lib/seedlings.rb ADDED
@@ -0,0 +1,91 @@
1
+ require "seedlings/version"
2
+
3
+ # Seedlings.rb
4
+ #
5
+ # A Seeding system that handles all the annoying corner-cases for you.
6
+ class Seedlings
7
+ attr_accessor :klass, :options, :data
8
+
9
+ def self.plant klass, options, *data
10
+ thang = new(klass, options, data)
11
+ thang.plant!
12
+ end
13
+
14
+ def self.plant_and_return klass, options, data
15
+ seedling = new(klass, options, [data])
16
+ seedling.plant_and_return data
17
+ end
18
+
19
+ def initialize klass, options, data
20
+ self.klass = klass
21
+ self.options = options
22
+ self.data = data
23
+ adapt!
24
+ end
25
+
26
+ #
27
+ # determine what manner of class we've been asked to seed and load the appropriate adapter.
28
+ # Yes, I know it only does one thing now.
29
+ # N.B. I'm open to more efficient suggestions here.
30
+ def adapt!
31
+ extend Seedlings::ActiveModel
32
+ end
33
+
34
+ #
35
+ # Get this planting party started
36
+ def plant!
37
+ data.each do |the_data|
38
+ find_and_update_model_with the_data unless the_data.nil?
39
+ end
40
+ end
41
+
42
+ def plant_and_return the_data
43
+ find_and_update_model_with the_data unless the_data.nil?
44
+ end
45
+
46
+ #
47
+ # The base plugin for Seedlings: ActiveModel.
48
+ # Use this if the class to be seeded has an AREL-like query interface
49
+ module ActiveModel
50
+
51
+ def constraints_for the_data
52
+ constraints = []
53
+ constraint_columns = options[:constrain] ? [options[:constrain]].flatten : [:id]
54
+ unless the_data.keys & constraint_columns == constraint_columns
55
+ puts "Couldn't find necessary constraints #{constraint_columns.inspect} in #{the_data.inspect}, skipping"
56
+ return constraints
57
+ end
58
+ constraint_columns.each do |key|
59
+ constraints << { key => the_data[key] } if the_data[key]
60
+ end
61
+ constraints
62
+ end
63
+
64
+ def find_model_with the_data
65
+ finder = constraints_for(the_data).inject(klass) { |k, constraint| k.where(constraint) }
66
+ finder.first
67
+ end
68
+
69
+ def find_and_update_model_with the_data
70
+ object = find_model_with the_data
71
+ if object
72
+ puts "FOUND: #{object.inspect}"
73
+ if options[:dont_update_existing]
74
+ puts " * skipping update as per options[:dont_update_existing]"
75
+ else
76
+ puts " * updating with #{the_data}"
77
+ object.update_attributes(the_data)
78
+ end
79
+ else
80
+ puts "NEW OBJECT: #{klass}.new(#{the_data.inspect})"
81
+ object = klass.new(the_data)
82
+ object.save!
83
+ end
84
+ puts
85
+ object
86
+ end
87
+
88
+ end
89
+
90
+ end
91
+
data/seedlings.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "seedlings/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "seedlings"
7
+ s.version = Seedlings::VERSION
8
+ s.authors = ["Matt Wilson"]
9
+ s.email = ["mhw@hypomodern.com"]
10
+ s.homepage = ""
11
+ s.summary = "Simple seed data management"
12
+ s.description = "An ActiveModel (MongoMapper/Mongoid/ActiveRecord 3+) compliant seed data handler for any ruby project."
13
+
14
+ s.rubyforge_project = "seedlings"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_development_dependency("rspec")
22
+ s.add_development_dependency("mongo_mapper")
23
+ s.add_development_dependency("mongoid")
24
+ s.add_development_dependency("bson_ext")
25
+ s.add_development_dependency("activerecord")
26
+ s.add_development_dependency("sqlite3")
27
+ end
data/spec/README.md ADDED
@@ -0,0 +1,5 @@
1
+ ## Running Specs
2
+
3
+ By default, we run the specs with mongomapper. To run them with mongoid or sqlite, use: `DB=mongoid rspec spec`. Make sure you have the requisite table set up for the SQL-based test.
4
+
5
+ Taking our cues from seed-fu, I've placed the connection and model setup stuff out in `spec/connections/*`.
@@ -0,0 +1,20 @@
1
+ require 'mongoid'
2
+
3
+ Mongoid.configure do |config|
4
+ config.master = Mongo::Connection.new.db("seedlings_gem_test")
5
+ end
6
+
7
+ INVALID_DOCUMENT_ERROR = Mongoid::Errors::Validations
8
+
9
+ class Widget
10
+ include Mongoid::Document
11
+
12
+ CONTEXTS = %w(hero columns)
13
+
14
+ field :name, type: String
15
+ field :context, type: String
16
+
17
+ validates :name, :presence => true, :uniqueness => true
18
+ validates :context, :inclusion => CONTEXTS
19
+
20
+ end
@@ -0,0 +1,20 @@
1
+ require 'mongo_mapper'
2
+
3
+ MongoMapper.connection = Mongo::Connection.new(ENV["MONGO_HOST"] || 'localhost')
4
+ MongoMapper.database = "seedlings_gem_test"
5
+
6
+ INVALID_DOCUMENT_ERROR = MongoMapper::DocumentNotValid
7
+
8
+ class Widget
9
+ include MongoMapper::Document
10
+
11
+ CONTEXTS = %w(hero columns)
12
+
13
+ key :name, String
14
+ key :context, String
15
+
16
+ validates :name, :presence => true, :uniqueness => true
17
+ validates :context, :inclusion => CONTEXTS
18
+
19
+ end
20
+
@@ -0,0 +1,24 @@
1
+ require 'active_record'
2
+
3
+ ActiveRecord::Base.establish_connection(
4
+ :adapter => "sqlite3",
5
+ :database => File.dirname(__FILE__) + "/test.sqlite3"
6
+ )
7
+
8
+ INVALID_DOCUMENT_ERROR = ActiveRecord::RecordInvalid
9
+
10
+ ActiveRecord::Schema.define :version => 0 do
11
+ create_table :widgets, :force => true do |t|
12
+ t.column :name, :string
13
+ t.column :context, :string
14
+ end
15
+ end
16
+
17
+ class Widget < ActiveRecord::Base
18
+
19
+ CONTEXTS = %w(hero columns)
20
+
21
+ validates :name, :presence => true, :uniqueness => true
22
+ validates :context, :inclusion => CONTEXTS
23
+
24
+ end
Binary file
@@ -0,0 +1,154 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Seedlings do
4
+ let(:seeds) { Seedlings.new(Widget, { :constrain => :name }, []) }
5
+
6
+ describe ".plant" do
7
+ it "accepts the class to be seeded, options, and the seed data itself" do
8
+ lambda {
9
+ Seedlings.plant(Widget, { :constrain => :name })
10
+ }.should_not raise_error
11
+ end
12
+ it "runs the seeding operation" do
13
+ Widget.all.should == []
14
+ Seedlings.plant(Widget, { :constrain => :name },
15
+ { :name => "Basic text elements", :context => "hero" },
16
+ { :name => "Social media", :context => "hero" },
17
+ { :name => "Networks", :context => "hero" }
18
+ )
19
+ Widget.all.map { |wg| wg.name }.sort.should == ["Basic text elements", "Networks", "Social media"]
20
+ end
21
+ end
22
+
23
+ describe ".plant_and_return" do
24
+ let(:the_attrs) { { :name => "Basic text elements", :context => "hero" } }
25
+
26
+ it "seeds and returns an object" do
27
+ obj = Seedlings.plant_and_return(Widget, { :constrain => :name }, the_attrs)
28
+ obj.should_not be_nil
29
+ obj.name.should == the_attrs[:name]
30
+ obj.context.should == the_attrs[:context]
31
+ end
32
+ it "doesn't create duplicates" do
33
+ alt = Widget.create(the_attrs)
34
+ neu = Seedlings.plant_and_return(Widget, { :constrain => :name }, the_attrs)
35
+
36
+ neu.id.should == alt.id
37
+ Widget.all.map { |wg| wg.id }.should == [alt.id]
38
+ end
39
+ end
40
+
41
+ describe "#initialize" do
42
+ it "stashes the class, options, and data as attributes" do
43
+ seeds.klass.should == Widget
44
+ seeds.options.should == { :constrain => :name }
45
+ seeds.data.should == []
46
+ end
47
+ end
48
+
49
+ describe "#adapt!" do
50
+ it "extends the object with an appropriate adapter" do
51
+ seeds.should_receive(:extend).with(Seedlings::ActiveModel)
52
+ seeds.send(:adapt!)
53
+ end
54
+ describe "an adapter" do
55
+ it "implements #find_model_with" do
56
+ seeds.should respond_to(:find_model_with)
57
+ end
58
+ it "implements #find_and_update_model_with" do
59
+ seeds.should respond_to(:find_and_update_model_with)
60
+ end
61
+ it "implements #constraints_for" do
62
+ seeds.should respond_to(:constraints_for)
63
+ end
64
+ end
65
+ end
66
+
67
+ describe "#plant!" do
68
+ it "plants a bunch of seedlings" do
69
+ Widget.all.should == []
70
+ seeds = Seedlings.new(Widget, { :constrain => :name }, [
71
+ { :name => "Basic text elements", :context => "hero" },
72
+ { :name => "Social media", :context => "hero" },
73
+ { :name => "Networks", :context => "hero" }
74
+ ])
75
+ seeds.plant!
76
+ Widget.all.map { |wg| wg.name }.sort.should == ["Basic text elements", "Networks", "Social media"]
77
+ end
78
+ end
79
+
80
+ end
81
+
82
+ describe Seedlings::ActiveModel do
83
+ let(:seeds) { Seedlings.new(Widget, { :constrain => :name }, []) }
84
+
85
+ describe "#constraints_for" do
86
+ # hey, it could happen
87
+ context "with no specified constraints" do
88
+ before do
89
+ seeds.options[:constrain] = nil
90
+ end
91
+ it "returns an empty array if given no data" do
92
+ seeds.constraints_for({}).should == []
93
+ end
94
+ it "returns only one constraint: { :id => data[:id] } if given that data" do
95
+ seeds.constraints_for({:id => 1}).should == [{ :id => 1 }]
96
+ end
97
+ end
98
+ context "with one constraint specified" do
99
+ it "returns an empty array if the data doesn't include the necessary constraint column" do
100
+ seeds.constraints_for({:bob => "baz"}).should == []
101
+ end
102
+ it "returns an array with the correct constraint otherwise" do
103
+ seeds.constraints_for({:name => "bar"}).should == [{:name => "bar"}]
104
+ end
105
+ end
106
+ context "with more than one constraint" do
107
+ before do
108
+ seeds.options[:constrain] = [:name, :id]
109
+ end
110
+ it "returns an empty array if it cannot locate sufficient data" do
111
+ seeds.constraints_for({:foo => "bar"}).should == []
112
+ end
113
+ it "returns correctly formatted constraints otherwise" do
114
+ seeds.constraints_for({:name => "bar", :id => 7}).should == [{:name=>"bar"}, {:id=>7}]
115
+ end
116
+ end
117
+ end
118
+
119
+ describe "#find_and_update_model_with" do
120
+ let(:the_attrs) { { :name => "Basic text elements", :context => "hero" } }
121
+ let(:seedlings) { Seedlings.new(Widget, { :constrain => :name }, [] ) }
122
+
123
+ context "when the seedling already exists" do
124
+ before do
125
+ @alt = Widget.create(the_attrs.merge(:context => "columns"))
126
+ end
127
+ it "updates the attributes of the object to match the seed data by default" do
128
+ @alt.context.should == "columns"
129
+ seedlings.find_and_update_model_with the_attrs
130
+ Widget.where(name: "Basic text elements").first.context.should == "hero"
131
+ end
132
+ it "skips the update if you've given it the :dont_update_existing option" do
133
+ seeds = Seedlings.new(Widget, { :constrain => :name, :dont_update_existing => true }, the_attrs )
134
+ seeds.find_and_update_model_with the_attrs
135
+ Widget.where(name: "Basic text elements").first.context.should == "columns"
136
+ end
137
+ end
138
+ context "when the seedling doesn't exist" do
139
+ it "creates a new record" do
140
+ Widget.all.should == []
141
+ obj = seedlings.find_and_update_model_with( the_attrs )
142
+ obj.should be_valid
143
+ Widget.all.map { |wg| wg.id }.should == [obj.id]
144
+ end
145
+ it "blows up, halting everything, on error" do
146
+ lambda {
147
+ seedlings.find_and_update_model_with( the_attrs.merge(:context => "INVALID VALUE AHOY") )
148
+ }.should raise_error(INVALID_DOCUMENT_ERROR)
149
+ end
150
+ end
151
+
152
+ end
153
+
154
+ end
@@ -0,0 +1,26 @@
1
+ require 'bundler'
2
+ begin
3
+ Bundler.setup
4
+ rescue Bundler::BundlerError => e
5
+ $stderr.puts e.message
6
+ $stderr.puts "Run `bundle install` to install missing gems"
7
+ exit e.status_code
8
+ end
9
+
10
+ require 'rspec'
11
+ require 'seedlings'
12
+
13
+ our_db = ENV["DB"] || 'mongomapper'
14
+ puts "Running tests with #{our_db}."
15
+ require File.dirname(__FILE__) + "/connections/#{our_db}.rb"
16
+
17
+ # Requires supporting files with custom matchers and macros, etc,
18
+ # in ./support/ and its subdirectories.
19
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
20
+
21
+ RSpec.configure do |config|
22
+ # clean the database before each test. Might need to switch up to DatabaseCleaner.
23
+ config.before do
24
+ Widget.delete_all
25
+ end
26
+ end
metadata ADDED
@@ -0,0 +1,142 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: seedlings
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Matt Wilson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-09-21 00:00:00 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rspec
17
+ prerelease: false
18
+ requirement: &id001 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ type: :development
25
+ version_requirements: *id001
26
+ - !ruby/object:Gem::Dependency
27
+ name: mongo_mapper
28
+ prerelease: false
29
+ requirement: &id002 !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: "0"
35
+ type: :development
36
+ version_requirements: *id002
37
+ - !ruby/object:Gem::Dependency
38
+ name: mongoid
39
+ prerelease: false
40
+ requirement: &id003 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: "0"
46
+ type: :development
47
+ version_requirements: *id003
48
+ - !ruby/object:Gem::Dependency
49
+ name: bson_ext
50
+ prerelease: false
51
+ requirement: &id004 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ type: :development
58
+ version_requirements: *id004
59
+ - !ruby/object:Gem::Dependency
60
+ name: activerecord
61
+ prerelease: false
62
+ requirement: &id005 !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ type: :development
69
+ version_requirements: *id005
70
+ - !ruby/object:Gem::Dependency
71
+ name: sqlite3
72
+ prerelease: false
73
+ requirement: &id006 !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: "0"
79
+ type: :development
80
+ version_requirements: *id006
81
+ description: An ActiveModel (MongoMapper/Mongoid/ActiveRecord 3+) compliant seed data handler for any ruby project.
82
+ email:
83
+ - mhw@hypomodern.com
84
+ executables: []
85
+
86
+ extensions: []
87
+
88
+ extra_rdoc_files: []
89
+
90
+ files:
91
+ - .gitignore
92
+ - .rspec
93
+ - .rvmrc
94
+ - Gemfile
95
+ - LICENSE
96
+ - README.md
97
+ - Rakefile
98
+ - lib/seedlings.rb
99
+ - lib/seedlings/version.rb
100
+ - seedlings.gemspec
101
+ - spec/README.md
102
+ - spec/connections/mongoid.rb
103
+ - spec/connections/mongomapper.rb
104
+ - spec/connections/sqlite.rb
105
+ - spec/connections/test.sqlite3
106
+ - spec/lib/seedlings_spec.rb
107
+ - spec/spec_helper.rb
108
+ homepage: ""
109
+ licenses: []
110
+
111
+ post_install_message:
112
+ rdoc_options: []
113
+
114
+ require_paths:
115
+ - lib
116
+ required_ruby_version: !ruby/object:Gem::Requirement
117
+ none: false
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: "0"
122
+ required_rubygems_version: !ruby/object:Gem::Requirement
123
+ none: false
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: "0"
128
+ requirements: []
129
+
130
+ rubyforge_project: seedlings
131
+ rubygems_version: 1.8.10
132
+ signing_key:
133
+ specification_version: 3
134
+ summary: Simple seed data management
135
+ test_files:
136
+ - spec/README.md
137
+ - spec/connections/mongoid.rb
138
+ - spec/connections/mongomapper.rb
139
+ - spec/connections/sqlite.rb
140
+ - spec/connections/test.sqlite3
141
+ - spec/lib/seedlings_spec.rb
142
+ - spec/spec_helper.rb