data_seeder 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +256 -0
  3. data/Rakefile +34 -0
  4. data/app/models/data_seeder/seed_file.rb +34 -0
  5. data/db/migrate/20150306195118_create_data_seeder_seed_files.rb +9 -0
  6. data/lib/data_seeder.rb +68 -0
  7. data/lib/data_seeder/config.rb +41 -0
  8. data/lib/data_seeder/engine.rb +5 -0
  9. data/lib/data_seeder/loader.rb +122 -0
  10. data/lib/data_seeder/loader/csv.rb +15 -0
  11. data/lib/data_seeder/loader/json.rb +20 -0
  12. data/lib/data_seeder/loader/txt.rb +23 -0
  13. data/lib/data_seeder/loader/yaml.rb +23 -0
  14. data/lib/data_seeder/logger.rb +15 -0
  15. data/lib/data_seeder/version.rb +3 -0
  16. data/test/dummy/Rakefile +6 -0
  17. data/test/dummy/app/models/app.rb +3 -0
  18. data/test/dummy/app/models/app_error.rb +3 -0
  19. data/test/dummy/app/models/app_error_data_seeder.rb +52 -0
  20. data/test/dummy/app/models/country.rb +14 -0
  21. data/test/dummy/app/models/state.rb +2 -0
  22. data/test/dummy/bin/bundle +3 -0
  23. data/test/dummy/bin/rails +4 -0
  24. data/test/dummy/bin/rake +4 -0
  25. data/test/dummy/config.ru +4 -0
  26. data/test/dummy/config/application.rb +26 -0
  27. data/test/dummy/config/boot.rb +5 -0
  28. data/test/dummy/config/database.yml +12 -0
  29. data/test/dummy/config/environment.rb +5 -0
  30. data/test/dummy/config/environments/development.rb +37 -0
  31. data/test/dummy/config/environments/test.rb +42 -0
  32. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  33. data/test/dummy/config/initializers/inflections.rb +16 -0
  34. data/test/dummy/db/migrate/20150313022149_create_countries.rb +8 -0
  35. data/test/dummy/db/migrate/20150313022228_create_states.rb +8 -0
  36. data/test/dummy/db/migrate/20150313172634_create_apps.rb +7 -0
  37. data/test/dummy/db/migrate/20150313172719_create_app_errors.rb +10 -0
  38. data/test/dummy/db/schema.rb +45 -0
  39. data/test/dummy/db/seed.test/bar.err +3 -0
  40. data/test/dummy/db/seed.test/countries.txt +249 -0
  41. data/test/dummy/db/seed.test/foo.err +3 -0
  42. data/test/dummy/db/seed.test/states.csv +51 -0
  43. data/test/dummy/db/seed.test/states.json +153 -0
  44. data/test/dummy/db/seed.test/states.txt +51 -0
  45. data/test/dummy/db/seed.test/states.yml +101 -0
  46. data/test/dummy/db/seed.test/zulu.err +2 -0
  47. data/test/dummy/db/test.sqlite3 +0 -0
  48. data/test/dummy/log/development.log +39 -0
  49. data/test/dummy/log/test.log +68768 -0
  50. data/test/models/data_seeder_test.rb +147 -0
  51. data/test/test_helper.rb +12 -0
  52. metadata +159 -0
@@ -0,0 +1,5 @@
1
+ module DataSeeder
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace DataSeeder
4
+ end
5
+ end
@@ -0,0 +1,122 @@
1
+ module DataSeeder
2
+ module Loader
3
+ attr_accessor :file_config, :key_attribute
4
+ attr_reader :path, :path_minus_ext
5
+
6
+ def initialize(options={})
7
+ @only = options[:only]
8
+ @except = options[:except]
9
+ if options.has_key?(:purge)
10
+ @purge = options[:purge]
11
+ else
12
+ @purge = true
13
+ end
14
+ @old_keys = []
15
+ end
16
+
17
+ def config
18
+ DataSeeder.config
19
+ end
20
+
21
+ def logger
22
+ DataSeeder.logger
23
+ end
24
+
25
+ def klass
26
+ # This should always translate to a class except for custom loaders
27
+ @path_minus_ext.classify.constantize rescue nil
28
+ end
29
+
30
+ def process(path)
31
+ @path = path
32
+ dot_index = @path.rindex('.')
33
+ @path_minus_ext = @path[0, dot_index]
34
+ @file_config = {}
35
+ File.open(@path, 'r') do |fin|
36
+ load_file_config(fin)
37
+ setup
38
+ load(fin)
39
+ teardown
40
+ end
41
+ call_file_method(:teardown)
42
+ end
43
+
44
+ def setup
45
+ @key_attribute = self.file_config[:key_attribute] || :id
46
+ @old_keys = self.klass.all.pluck(@key_attribute).map(&:to_s) if @purge
47
+ logger.info { "Loading #{@path}" }
48
+ call_file_method(:setup)
49
+ end
50
+
51
+ def teardown
52
+ @old_keys.each do |key|
53
+ if model = self.klass.find_by(@key_attribute => key)
54
+ logger.info { " Destroying #{model_info(model)}"}
55
+ model.destroy
56
+ end
57
+ end
58
+ end
59
+
60
+ # The information displayed when creating, updating, or destroying a model.
61
+ # The changes argument will be the model.changes on an update.
62
+ def model_info(model, changes=nil)
63
+ if changes
64
+ attr = @file_config[:update_display_method] || @key_attribute
65
+ "#{model.send(attr)}: #{changes.inspect}"
66
+ else
67
+ model.inspect
68
+ end
69
+ end
70
+
71
+ def load_file_config(fin)
72
+ config_line = fin.readline
73
+ if match = config_line.match(/^\s*#\s*config:(.*)/)
74
+ @file_config = eval(match[1])
75
+ else
76
+ fin.seek(0)
77
+ if self.klass && self.klass.respond_to?(:data_seeder_config)
78
+ @file_config = self.klass.data_seeder_config
79
+ end
80
+ end
81
+ end
82
+
83
+ def load(fin)
84
+ throw 'Must override load'
85
+ end
86
+
87
+ def save(attr)
88
+ key = attr[@key_attribute.to_s] || attr[@key_attribute.to_sym]
89
+ raise "No #{@key_attribute} in #{attr.inspect}" unless key
90
+ @old_keys.delete(key.to_s)
91
+ model = self.klass.find_or_initialize_by(@key_attribute => key)
92
+ model.attributes = attr
93
+ save_model(model)
94
+ end
95
+
96
+ def save_model(model)
97
+ if model.new_record?
98
+ logger.info { " Saving #{model_info(model)}" }
99
+ else
100
+ changes = model.changes
101
+ return if changes.empty?
102
+ logger.info { " Updating #{model_info(model, changes)}" }
103
+ end
104
+ model.save!
105
+ end
106
+
107
+ def call_file_method(name, *args)
108
+ if method = @file_config[name]
109
+ return method.call(*args)
110
+ else
111
+ class_method = "data_seeder_#{name}"
112
+ return self.klass.send(class_method, *args) if @klass.respond_to?(class_method)
113
+ end
114
+ return nil
115
+ end
116
+ end
117
+ end
118
+
119
+ require 'data_seeder/loader/csv'
120
+ require 'data_seeder/loader/json'
121
+ require 'data_seeder/loader/yaml'
122
+ require 'data_seeder/loader/txt'
@@ -0,0 +1,15 @@
1
+ require 'csv'
2
+
3
+ module DataSeeder
4
+ module Loader
5
+ class CSV
6
+ include Loader
7
+
8
+ def load(io)
9
+ ::CSV.foreach(io, headers: true) do |row|
10
+ save(row.to_hash)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,20 @@
1
+ require 'json'
2
+
3
+ module DataSeeder
4
+ module Loader
5
+ class JSON
6
+ include Loader
7
+ def load(io)
8
+ json = ::JSON.parse(io.read)
9
+ if json.kind_of?(Hash)
10
+ json.each do |key, attr|
11
+ attr[self.key_attribute] = key if self.key_attribute
12
+ save(attr)
13
+ end
14
+ else
15
+ Array(json).each { |attr| save(attr) }
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,23 @@
1
+ module DataSeeder
2
+ module Loader
3
+ class Txt
4
+ include Loader
5
+
6
+ def load(io)
7
+ if method = self.file_config[:line]
8
+ io.each_line do |line|
9
+ next if line.blank? || line.match(/^\s*#/)
10
+ save(method.call(line))
11
+ end
12
+ elsif self.klass.respond_to?(:data_seeder_line)
13
+ io.each_line do |line|
14
+ next if line.blank? || line.match(/^\s*#/)
15
+ save(self.klass.send(:data_seeder_line, line))
16
+ end
17
+ else
18
+ raise "No line method defined for #{self.klass.name}"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ require 'yaml'
2
+
3
+ module DataSeeder
4
+ module Loader
5
+ class YAML
6
+ include Loader
7
+
8
+ def load(io)
9
+ yaml = ::YAML.load(io.read)
10
+ if yaml.kind_of?(Hash)
11
+ yaml.each do |key, attr|
12
+ attr[self.key_attribute] = key if self.key_attribute
13
+ save(attr)
14
+ end
15
+ elsif yaml.kind_of?(Array)
16
+ yaml.each { |attr| save(attr) }
17
+ else
18
+ raise "Don't know how to interpret #{self.path}"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ module DataSeeder
2
+ class Logger < ::Logger
3
+ attr_accessor :verbose
4
+
5
+ def initialize
6
+ super($stdout)
7
+ @verbose = true
8
+ self.formatter = ->(severity, datetime, progname, msg) { "#{msg}\n" }
9
+ end
10
+
11
+ def info(arg='', &block)
12
+ super if @verbose
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ module DataSeeder
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,6 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require File.expand_path('../config/application', __FILE__)
5
+
6
+ Rails.application.load_tasks
@@ -0,0 +1,3 @@
1
+ class App < ActiveRecord::Base
2
+ has_many :app_errors, inverse_of: :app
3
+ end
@@ -0,0 +1,3 @@
1
+ class AppError < ActiveRecord::Base
2
+ belongs_to :app, inverse_of: :app_errors
3
+ end
@@ -0,0 +1,52 @@
1
+ require 'data_seeder'
2
+
3
+ class AppErrorDataSeeder
4
+ include ::DataSeeder::Loader
5
+
6
+ def setup
7
+ @app = App.find_or_initialize_by(name: self.path_minus_ext)
8
+ @existing_errors = {}
9
+ if @app.new_record?
10
+ logger.info "Loading errors for new App: #{@app.name}"
11
+ @app.save!
12
+ else
13
+ logger.info "Loading errors for existing App: #{@app.name}"
14
+ @app.app_errors.each do |app_error|
15
+ @existing_errors[app_error.code] = app_error
16
+ end
17
+ end
18
+ end
19
+
20
+ def teardown
21
+ unless @existing_errors.empty?
22
+ logger.info { " The following are begin removed:" }
23
+ @existing_errors.each do |code, app_error|
24
+ logger.info " #{code}: #{app_error.message}"
25
+ app_error.destroy
26
+ end
27
+ end
28
+ end
29
+
30
+ def load(io)
31
+ io.each_line do |line|
32
+ line.strip!
33
+ next if line.blank? || line[0] == ?#
34
+ space_i = line.index(' ')
35
+ raise "Invalid line: #{line}" unless space_i
36
+ code = line[0,space_i].strip
37
+ message = line[space_i+1..-1].strip
38
+ app_error = @existing_errors[code]
39
+ if app_error
40
+ @existing_errors.delete(code)
41
+ app_error.message = message
42
+ unless app_error.changes.empty?
43
+ logger.info { " Changing #{code}: #{app_error.changes}" }
44
+ app_error.save!
45
+ end
46
+ else
47
+ logger.info { " Creating #{code}: #{message}" }
48
+ @app.app_errors.create!(code: code, message: message)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,14 @@
1
+ class Country < ActiveRecord::Base
2
+ def self.data_seeder_line(line)
3
+ {
4
+ code: line[0,2],
5
+ name: line[3...-1],
6
+ }
7
+ end
8
+
9
+ def self.data_seeder_config
10
+ {
11
+ key_attribute: :code
12
+ }
13
+ end
14
+ end
@@ -0,0 +1,2 @@
1
+ class State < ActiveRecord::Base
2
+ end
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3
+ load Gem.bin_path('bundler', 'bundle')
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
3
+ require_relative '../config/boot'
4
+ require 'rails/commands'
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative '../config/boot'
3
+ require 'rake'
4
+ Rake.application.run
@@ -0,0 +1,4 @@
1
+ # This file is used by Rack-based servers to start the application.
2
+
3
+ require ::File.expand_path('../config/environment', __FILE__)
4
+ run Rails.application
@@ -0,0 +1,26 @@
1
+ require File.expand_path('../boot', __FILE__)
2
+
3
+ require 'rails/all'
4
+
5
+ Bundler.require(*Rails.groups)
6
+ require "data_seeder"
7
+
8
+ module Dummy
9
+ class Application < Rails::Application
10
+ # Settings in config/environments/* take precedence over those specified here.
11
+ # Application configuration should go into files in config/initializers
12
+ # -- all .rb files in that directory are automatically loaded.
13
+
14
+ # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
15
+ # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
16
+ # config.time_zone = 'Central Time (US & Canada)'
17
+
18
+ # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
19
+ # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
20
+ # config.i18n.default_locale = :de
21
+
22
+ config.generators do |g|
23
+ g.test_framework :mini_test, :spec => true, :fixture => false
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,5 @@
1
+ # Set up gems listed in the Gemfile.
2
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__)
3
+
4
+ require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
5
+ $LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__)
@@ -0,0 +1,12 @@
1
+ default: &default
2
+ adapter: sqlite3
3
+ pool: 5
4
+ timeout: 5000
5
+
6
+ development:
7
+ <<: *default
8
+ database: db/development.sqlite3
9
+
10
+ test:
11
+ <<: *default
12
+ database: db/test.sqlite3
@@ -0,0 +1,5 @@
1
+ # Load the Rails application.
2
+ require File.expand_path('../application', __FILE__)
3
+
4
+ # Initialize the Rails application.
5
+ Rails.application.initialize!
@@ -0,0 +1,37 @@
1
+ Rails.application.configure do
2
+ # Settings specified here will take precedence over those in config/application.rb.
3
+
4
+ # In the development environment your application's code is reloaded on
5
+ # every request. This slows down response time but is perfect for development
6
+ # since you don't have to restart the web server when you make code changes.
7
+ config.cache_classes = false
8
+
9
+ # Do not eager load code on boot.
10
+ config.eager_load = false
11
+
12
+ # Show full error reports and disable caching.
13
+ config.consider_all_requests_local = true
14
+ config.action_controller.perform_caching = false
15
+
16
+ # Don't care if the mailer can't send.
17
+ config.action_mailer.raise_delivery_errors = false
18
+
19
+ # Print deprecation notices to the Rails logger.
20
+ config.active_support.deprecation = :log
21
+
22
+ # Raise an error on page load if there are pending migrations.
23
+ config.active_record.migration_error = :page_load
24
+
25
+ # Debug mode disables concatenation and preprocessing of assets.
26
+ # This option may cause significant delays in view rendering with a large
27
+ # number of complex assets.
28
+ config.assets.debug = true
29
+
30
+ # Adds additional error checking when serving assets at runtime.
31
+ # Checks for improperly declared sprockets dependencies.
32
+ # Raises helpful error messages.
33
+ config.assets.raise_runtime_errors = true
34
+
35
+ # Raises error for missing translations
36
+ # config.action_view.raise_on_missing_translations = true
37
+ end