seeder 0.1.3 → 1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a5611b4210e0232e190e6a834a990a450a3f8a97
4
+ data.tar.gz: b4f69c31fe5f82988f4e8174ad6f9f5349ae07a5
5
+ SHA512:
6
+ metadata.gz: cb23ee0d4e02b6656024bad8cf34229d115e74dfe7553adccdb5424297aad92a4271c70e95e03601a4b60e7b59ac236ba466112efbf1a57ef7707300105ec92f
7
+ data.tar.gz: 39ce51f9890f2183e46d1845bb3a939a31c955dfbb4aee24b4803b3427ee870b3e2919c7382242337ceb4a6f7c215d5c65cf4bbe55a61bc6830075d8fa366da8
@@ -0,0 +1 @@
1
+ Gemfile.lock
@@ -0,0 +1,18 @@
1
+ sudo: false
2
+ cache: bundler
3
+ language: ruby
4
+ rvm:
5
+ - 1.9.3
6
+ - 2.0.0
7
+ - 2.1.2
8
+ - 2.2.3
9
+ env:
10
+ global:
11
+ - NOKOGIRI_USE_SYSTEM_LIBRARIES=true
12
+ matrix:
13
+ - AR=3.2.18
14
+ - AR=4.0.5
15
+ - AR=4.1.1
16
+ - AR=4.2.5
17
+ before_script:
18
+ - mysql -e 'create database seeder_test;'
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
4
+ # The AR environment variable lets you test against different
5
+ # versions of Rails. For example:
6
+ #
7
+ # AR=3.2.13 rm Gemfile.lock && bundle install && bundle exec rspec
8
+ # AR=4.0.0 rm Gemfile.lock && bundle install && bundle exec rspec
9
+ #
10
+ if ENV['AR']
11
+ gem 'activerecord', ENV['AR']
12
+ end
data/README.md CHANGED
@@ -1,49 +1,95 @@
1
- Description
2
- ===========
1
+ # seeder
3
2
 
4
- Seeder provides a way for your app to plant seed data in its database.
5
- It respects unique keys in your database, so that it does the equivalent of
6
- MySQL's "on duplicate key update" (but this is database agnostic)
3
+ Seeder provides a way for your app to plant seed data in its database. A
4
+ primary benefit of using this gem is that it gives you an easy way to manage
5
+ updating and deleting seed data, in addition to just creating it.
7
6
 
7
+ ## Install
8
8
 
9
- Install
10
- =======
9
+ ```
10
+ gem install seeder
11
+ ```
12
+
13
+ ## Motivation
14
+
15
+ Seed data refers to data stored in your database that is not generated
16
+ dynamically through your application. You can generate your seed data via
17
+ `rake db:seed`, which runs the contents of your `db/seeds.rb` file.
18
+
19
+ Generating seed data is easy enough. But as your application grows and
20
+ changes, you may find that you need to modify existing seed data.
21
+
22
+ You could run migrations to modify seed data, but you would need to make sure
23
+ to adjust the seeds.rb file in a consistent way. It is very easy to forget to
24
+ do this, or to make a mistake along the way.
25
+
26
+ Seeder allows you to manage all of your seeds via the seeds.rb file, without
27
+ needing to use migrations when you have to change existing seed data.
28
+
29
+ You provide a set of attributes associated with your seed data for a given
30
+ model. You also specify which attributes of the model should be treated as
31
+ "identifying attributes" - that is, which attributes should be used to
32
+ determine if an existing database row needs to be updated, or if a new row
33
+ needs to be added. Seeder then synchronizes the contents of the database table
34
+ to the attributes it is given.
35
+
36
+ ## Usage
37
+
38
+ Suppose you have a `DataField` model, with attributes `data_type`, `name`, and
39
+ `description`.
11
40
 
12
- $ gem install seeder
41
+ To seed your database with a couple data fields, you would just include the
42
+ following in your seeds.rb file:
13
43
 
14
- Usage
15
- =====
44
+ ```ruby
45
+ seeds = [
46
+ { data_type: "Oil", name: "btu", description: "Oil usage" },
47
+ { data_type: "Water", name: "gallons", description: "Water usage" }
48
+ ]
49
+
50
+ Seeder.create(seeds, [:data_type, :name], DataField)
51
+ ```
52
+
53
+ In the example above, the `data_type` and `name` attributes will be used to
54
+ identify records that already exist in the database. The first time you seed
55
+ data, there are no existing records so two new records will be created. If you
56
+ re-seed the database, Seeder will not do anything, since the records already
57
+ exist in the database, and no attributes have changed.
16
58
 
17
- Suppose you have a `User` model
59
+ Suppose the database has already been seeded, and you decide to change the
60
+ description of the Oil btu data field. All you need to do is update the
61
+ seeds.rb file and rerun the `db:seed` Rake task. Seeder will know to update the
62
+ _existing_ database record associated with Oil btu:
18
63
 
19
- And suppose that user model has fields "name", "age", "address", "gender"
64
+ ```ruby
65
+ seeds = [
66
+ { data_type: "Oil", name: "btu", description: "Fuel oil usage" },
67
+ { data_type: "Water", name: "gallons", description: "Water usage" }
68
+ ]
20
69
 
21
- Finally, suppose you've set up your database to have a unique key on the fields
22
- "name" and "address" (so two people can have the same name or live at the
23
- same address, but not both)
70
+ Seeder.create(seeds, [:data_type, :name], DataField)
71
+ ```
24
72
 
25
- If you wanted to seed your data with a couple users you could do the following:
73
+ If you then want to add an Oil therms DataField record to the db, you could
74
+ just add a line to your seeds.rb file:
26
75
 
27
76
  ```ruby
28
- seed_users = [
29
- {
30
- :name => "J'onn J'onzz",
31
- :age => 94,
32
- :address => 'Mars',
33
- :gender => 'Male'
34
- },
35
- {
36
- :name => "Barbara Gordon",
37
- :age => 35,
38
- :address => '14 Gotham Heights',
39
- :gender => 'Female'
40
- }
77
+ seeds = [
78
+ { data_type: "Oil", name: "btu", description: "Fuel oil usage (btu)" },
79
+ { data_type: "Oil", name: "therms", description: "Fuel oil usage (therms)" },
80
+ { data_type: "Water", name: "gallons", description: "Water usage" }
41
81
  ]
42
82
 
43
- SeedData.create {'User' => seed_users}, [:name, :address], User
83
+ Seeder.create(seeds, [:data_type, :name], DataField)
44
84
  ```
45
85
 
46
- License
47
- =======
86
+ The above code would adjust the description of the existing Oil btu database
87
+ row, and create a new one for Oil therms.
88
+
89
+ Now let's say you changed your mind and decided that you don't need an
90
+ Oil therms entry in the database. You could just delete the corresponding row
91
+ from seeds.rb, and Seeder will know to delete that entry from the database.
92
+
93
+ ## License
48
94
 
49
95
  See LICENSE.txt
data/Rakefile CHANGED
@@ -1,36 +1,3 @@
1
- require 'rake'
2
- require 'fileutils'
3
-
4
- def gemspec_name
5
- @gemspec_name ||= Dir['*.gemspec'][0]
6
- end
7
-
8
- def gemspec
9
- @gemspec ||= eval(File.read(gemspec_name), binding, gemspec_name)
10
- end
11
-
12
- desc "Build the gem"
13
- task :gem=>:gemspec do
14
- sh "gem build #{gemspec_name}"
15
- FileUtils.mkdir_p 'pkg'
16
- FileUtils.mv "#{gemspec.name}-#{gemspec.version}.gem", 'pkg'
17
- end
18
-
19
- desc "Install the gem locally"
20
- task :install => :gem do
21
- sh %{gem install pkg/#{gemspec.name}-#{gemspec.version}}
22
- end
23
-
24
- desc "Generate the gemspec"
25
- task :generate do
26
- puts gemspec.to_ruby
27
- end
28
-
29
- desc "Validate the gemspec"
30
- task :gemspec do
31
- gemspec.validate
32
- end
33
-
34
1
  desc 'Run tests'
35
2
  task :test do |t|
36
3
  sh 'rspec spec'
@@ -1,78 +1,66 @@
1
1
  class Seeder
2
+ attr_reader :keys, :data, :model
2
3
 
3
- class << self
4
- QUOTING = {
5
- "ActiveRecord::ConnectionAdapters::MysqlAdapter" => "`",
6
- "ActiveRecord::ConnectionAdapters::Mysql2Adapter" => "`"
7
- }
4
+ def self.create(data, keys, model)
5
+ new(data, keys, model).create
6
+ end
8
7
 
9
- def existing_data_keys(ar_model)
10
- keys = @primary_keys[ar_model.name]
11
- ar_model.all.map{|item| item.attributes.symbolize_keys.values_at(*keys) }
12
- end
8
+ def initialize(data, keys, model)
9
+ @keys = keys.map(&:to_sym)
10
+ @data = data.map(&:symbolize_keys)
11
+ @model = model
12
+ end
13
13
 
14
- def seed_data_keys(ar_model)
15
- keys = @primary_keys[ar_model.name]
16
- @data[ar_model.name].collect{|attributes| attributes.values_at(*keys) }
14
+ def create
15
+ model.transaction do
16
+ delete_outdated_records
17
+ update_existing_records
18
+ create_new_records
17
19
  end
20
+ end
18
21
 
19
- def items_to_delete(ar_model)
20
- existing_data_keys(ar_model) - seed_data_keys(ar_model)
21
- end
22
+ def delete_outdated_records
23
+ return unless records_to_delete.present?
24
+ model.where(id: records_to_delete).delete_all
25
+ end
22
26
 
23
- def value_to_sql(value)
24
- if value.nil? then 'NULL'
25
- elsif value.is_a?(String) then "'#{value}'"
26
- elsif value == false then 0
27
- elsif value == true then 1
28
- else value
29
- end
30
- end
27
+ def update_existing_records
28
+ existing_records_keys_hash.each do |record_keys, record|
29
+ attributes = data_keys_hash[record_keys]
30
+ next unless attributes
31
31
 
32
- def quote(value)
33
- q = QUOTING[ActiveRecord::Base.connection.class.to_s] || '"'
34
- "#{q}#{value}#{q}"
32
+ record.attributes = attributes
33
+ record.save! if record.changed?
35
34
  end
35
+ end
36
36
 
37
- def add_new_or_changed_data(ar_model)
38
- keys = @primary_keys[ar_model.name]
39
- find_method = "find_by_#{keys.join('_and_')}"
40
-
41
- @data[ar_model.name].collect do |attributes|
42
- relevant_keys = attributes.keys & ar_model.column_names.map(&:to_sym)
43
- existing = ar_model.send(find_method, *attributes.values_at(*keys))
44
- values = attributes.values_at(*relevant_keys)
37
+ def create_new_records
38
+ new_keys = data_keys_hash.keys - existing_records_keys_hash.keys
45
39
 
46
- if existing
47
- updates = attributes.slice(*relevant_keys).map { |k,v| "#{quote(k)} = #{value_to_sql(v)}" }.join(", ")
48
- conditions = keys.map { |k| "#{quote(k)} = #{value_to_sql(existing.send(k))}" }.join(" AND ")
49
- %(UPDATE #{ar_model.table_name} SET #{updates} WHERE #{conditions})
50
- else
51
- columns = relevant_keys.map { |k| quote(k) }.join(", ")
52
- inserts = attributes.slice(*relevant_keys).values.map { |v| value_to_sql(v) }.join(", ")
53
- %(INSERT INTO #{ar_model.table_name} (#{columns}) VALUES (#{inserts}))
54
- end
55
- end.compact
40
+ data_keys_hash.values_at(*new_keys).each do |attributes|
41
+ model.create!(attributes)
56
42
  end
43
+ end
57
44
 
58
- def delete_outdated_data(ar_model)
59
- keys = @primary_keys[ar_model.name]
60
- items_to_delete(ar_model).collect do |field_to_delete|
61
- conditions = field_to_delete.to_enum.with_index.collect{|value, index| "#{keys[index]} = '#{value}'"}.join(' AND ')
62
- %{ DELETE FROM #{ar_model.table_name} WHERE #{conditions} }
63
- end.compact
45
+ private
46
+
47
+ def data_keys_hash
48
+ @data_keys_hash ||= data.inject({}) do |hash, attributes|
49
+ hash.merge!(attributes.values_at(*keys) => attributes)
64
50
  end
51
+ end
65
52
 
66
- def create(data, keys, models)
67
- @data, @primary_keys = data, keys
68
- models.each {|model|
69
- delete_outdated_data(model).each{|query| sql(query)}
70
- add_new_or_changed_data(model).each{|query| sql(query)}
71
- }
53
+ def existing_records_keys_hash
54
+ @existing_records_keys_hash ||= model.all.inject({}) do |hash, record|
55
+ record_keys = keys.map { |key| record.public_send(key) }
56
+ hash.merge!(record_keys => record)
72
57
  end
58
+ end
73
59
 
74
- def sql(sql_command)
75
- ActiveRecord::Base.connection.execute(sql_command)
60
+ def records_to_delete
61
+ @records_to_delete ||= begin
62
+ keys_to_delete = existing_records_keys_hash.keys - data_keys_hash.keys
63
+ existing_records_keys_hash.values_at(*keys_to_delete)
76
64
  end
77
65
  end
78
66
  end
@@ -1,3 +1,3 @@
1
1
  class Seeder
2
- VERSION = '0.1.3'
2
+ VERSION = '1.0'
3
3
  end
@@ -3,16 +3,20 @@ require 'rubygems' unless defined? Gem
3
3
  require File.dirname(__FILE__) + "/lib/seeder/version"
4
4
 
5
5
  Gem::Specification.new do |s|
6
- s.name = "seeder"
7
- s.version = Seeder::VERSION
8
- s.authors = ["Barun Singh", "Gabriel Horner"]
9
- s.email = "bsingh@wegowise.com"
10
- s.homepage = "http://github.com/wegowise/seeder"
11
- s.summary = "Seed your data"
12
- s.description = "Keep your app's seed data in one file and update it easily, while respecting key constraints"
6
+ s.name = "seeder"
7
+ s.version = Seeder::VERSION
8
+ s.authors = ["Barun Singh"]
9
+ s.email = "bsingh@wegowise.com"
10
+ s.homepage = "http://github.com/wegowise/seeder"
11
+ s.summary = "Manage seed data for your Rails app"
12
+ s.description = "Keep your app's seed data in one file and update it easily"
13
13
  s.required_rubygems_version = ">= 1.3.6"
14
- s.files = Dir.glob(%w[{lib,spec}/**/*.rb [A-Z]*.{txt,rdoc,md} *.gemspec]) + %w{Rakefile}
14
+ s.files = `git ls-files`.split("\n")
15
15
  s.extra_rdoc_files = ["README.md", "LICENSE.txt"]
16
16
  s.license = 'MIT'
17
- s.add_development_dependency 'rspec', '~> 2.6.0'
17
+
18
+ s.add_dependency('activerecord', '>= 3.2', '< 5.0')
19
+
20
+ s.add_development_dependency('mysql2', '~> 0.3.10')
21
+ s.add_development_dependency('rspec-rails', '~> 3.0')
18
22
  end
@@ -0,0 +1,108 @@
1
+ require 'active_record'
2
+ require 'support/setup'
3
+ require 'seeder'
4
+
5
+ describe Seeder do
6
+ before(:each) { Grade.delete_all }
7
+
8
+ let(:seeder) do
9
+ Seeder.new(
10
+ [{ 'student_id' => 1, 'course_id' => 1, 'grade' => 90 },
11
+ { 'student_id' => 1, 'course_id' => 2, 'grade' => 80 }],
12
+ %w[student_id course_id],
13
+ Grade
14
+ )
15
+ end
16
+
17
+ describe '.create' do
18
+ it 'should call create on a new instance' do
19
+ seeder = double
20
+ expect(Seeder).to receive(:new).with(:data, :keys, :model)
21
+ .and_return(seeder)
22
+ expect(seeder).to receive(:create)
23
+
24
+ Seeder.create(:data, :keys, :model)
25
+ end
26
+ end
27
+
28
+ describe '#new' do
29
+ specify { expect(seeder.model).to eq Grade }
30
+ specify { expect(seeder.keys).to eq [:student_id, :course_id] }
31
+ specify do
32
+ expect(seeder.data).to eq(
33
+ [{ student_id: 1, course_id: 1, grade: 90 },
34
+ { student_id: 1, course_id: 2, grade: 80 }]
35
+ )
36
+ end
37
+ end
38
+
39
+ describe '#delete_outdated_records' do
40
+ before do
41
+ Grade.create!(student_id: 1, course_id: 3)
42
+ Grade.create!(student_id: 1, course_id: 1)
43
+ end
44
+
45
+ it 'should delete outdated records' do
46
+ expect { seeder.delete_outdated_records }.to change { Grade.count }.to(1)
47
+ expect(Grade.first.course_id).to eq 1
48
+ end
49
+ end
50
+
51
+ describe '#update_existing_records' do
52
+ let!(:existing_grade) { Grade.create!(student_id: 1, course_id: 1) }
53
+
54
+ it 'should delete outdated records' do
55
+ expect { seeder.update_existing_records }
56
+ .to change { existing_grade.reload.grade }.to(90)
57
+ end
58
+ end
59
+
60
+ describe '#create_new_records' do
61
+ let!(:existing_grade) { Grade.create!(student_id: 1, course_id: 1) }
62
+
63
+ it 'should create new records when there are no existing records with
64
+ matching keys' do
65
+ expect { seeder.create_new_records }.to change { Grade.count }.to(2)
66
+ expect(Grade.last.course_id).to eq 2
67
+ end
68
+ end
69
+
70
+ describe '#create' do
71
+ it 'calls the delete, update and create methods in order' do
72
+ expect(seeder).to receive(:delete_outdated_records).ordered
73
+ expect(seeder).to receive(:update_existing_records).ordered
74
+ expect(seeder).to receive(:create_new_records).ordered
75
+ seeder.create
76
+ end
77
+
78
+ it 'aborts when an exception is raised' do
79
+ expect(seeder).to receive(:create_new_records).and_raise
80
+ initial_attributes = Grade.all.map(&:attributes)
81
+
82
+ expect { seeder.create }.to raise_error
83
+
84
+ expect(Grade.all.map(&:attributes)).to eq(initial_attributes)
85
+ end
86
+
87
+ it 'produces the appropriate results' do
88
+ grade1 = Grade.create!(student_id: 1, course_id: 3)
89
+ grade2 = Grade.create!(student_id: 1, course_id: 1)
90
+
91
+ seeder.create
92
+
93
+ expect(Grade.count).to eq(2)
94
+ expect(Grade.exists?(grade1)).to eq(false)
95
+
96
+ grade2.reload
97
+ expect(grade2.student_id).to eq(1)
98
+ expect(grade2.course_id).to eq(1)
99
+ expect(grade2.grade).to eq(90)
100
+
101
+ grade3 = Grade.last
102
+ expect(grade3.student_id).to eq(1)
103
+ expect(grade3.course_id).to eq(2)
104
+ expect(grade3.grade).to eq(80)
105
+ end
106
+ end
107
+
108
+ end
@@ -0,0 +1,20 @@
1
+ ActiveRecord::Base.establish_connection({
2
+ adapter: 'mysql2',
3
+ username: 'travis',
4
+ database: 'seeder_test'
5
+ })
6
+
7
+ ActiveRecord::Base.connection.tables.each do |table|
8
+ ActiveRecord::Base.connection.drop_table table
9
+ end
10
+
11
+ ActiveRecord::Schema.define do
12
+ create_table :grades do |t|
13
+ t.integer :student_id
14
+ t.integer :course_id
15
+ t.integer :grade
16
+ end
17
+ end
18
+
19
+ class Grade < ActiveRecord::Base
20
+ end
metadata CHANGED
@@ -1,30 +1,64 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: seeder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
5
- prerelease:
4
+ version: '1.0'
6
5
  platform: ruby
7
6
  authors:
8
7
  - Barun Singh
9
- - Gabriel Horner
10
8
  autorequire:
11
9
  bindir: bin
12
10
  cert_chain: []
13
- date: 2011-09-20 00:00:00.000000000Z
11
+ date: 2016-01-21 00:00:00.000000000 Z
14
12
  dependencies:
15
13
  - !ruby/object:Gem::Dependency
16
- name: rspec
17
- requirement: &2165419640 !ruby/object:Gem::Requirement
18
- none: false
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
19
16
  requirements:
20
- - - ~>
17
+ - - ">="
21
18
  - !ruby/object:Gem::Version
22
- version: 2.6.0
19
+ version: '3.2'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '5.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '3.2'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '5.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: mysql2
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 0.3.10
23
40
  type: :development
24
41
  prerelease: false
25
- version_requirements: *2165419640
26
- description: Keep your app's seed data in one file and update it easily, while respecting
27
- key constraints
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: 0.3.10
47
+ - !ruby/object:Gem::Dependency
48
+ name: rspec-rails
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '3.0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '3.0'
61
+ description: Keep your app's seed data in one file and update it easily
28
62
  email: bsingh@wegowise.com
29
63
  executables: []
30
64
  extensions: []
@@ -32,36 +66,40 @@ extra_rdoc_files:
32
66
  - README.md
33
67
  - LICENSE.txt
34
68
  files:
35
- - lib/seeder/version.rb
36
- - lib/seeder.rb
69
+ - ".gitignore"
70
+ - ".travis.yml"
37
71
  - CHANGELOG.txt
72
+ - Gemfile
38
73
  - LICENSE.txt
39
74
  - README.md
40
- - seeder.gemspec
41
75
  - Rakefile
76
+ - lib/seeder.rb
77
+ - lib/seeder/version.rb
78
+ - seeder.gemspec
79
+ - spec/seeder_spec.rb
80
+ - spec/support/setup.rb
42
81
  homepage: http://github.com/wegowise/seeder
43
82
  licenses:
44
83
  - MIT
84
+ metadata: {}
45
85
  post_install_message:
46
86
  rdoc_options: []
47
87
  require_paths:
48
88
  - lib
49
89
  required_ruby_version: !ruby/object:Gem::Requirement
50
- none: false
51
90
  requirements:
52
- - - ! '>='
91
+ - - ">="
53
92
  - !ruby/object:Gem::Version
54
93
  version: '0'
55
94
  required_rubygems_version: !ruby/object:Gem::Requirement
56
- none: false
57
95
  requirements:
58
- - - ! '>='
96
+ - - ">="
59
97
  - !ruby/object:Gem::Version
60
98
  version: 1.3.6
61
99
  requirements: []
62
100
  rubyforge_project:
63
- rubygems_version: 1.8.10
101
+ rubygems_version: 2.4.5.1
64
102
  signing_key:
65
- specification_version: 3
66
- summary: Seed your data
103
+ specification_version: 4
104
+ summary: Manage seed data for your Rails app
67
105
  test_files: []