sprig 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +131 -0
- data/Rakefile +5 -0
- data/lib/sprig.rb +29 -0
- data/lib/sprig/configuration.rb +16 -0
- data/lib/sprig/data.rb +6 -0
- data/lib/sprig/dependency.rb +45 -0
- data/lib/sprig/dependency_collection.rb +23 -0
- data/lib/sprig/dependency_sorter.rb +83 -0
- data/lib/sprig/directive.rb +37 -0
- data/lib/sprig/directive_list.rb +30 -0
- data/lib/sprig/helpers.rb +25 -0
- data/lib/sprig/parser.rb +9 -0
- data/lib/sprig/parser/base.rb +15 -0
- data/lib/sprig/parser/csv.rb +22 -0
- data/lib/sprig/parser/google_spreadsheet_json.rb +35 -0
- data/lib/sprig/parser/json.rb +9 -0
- data/lib/sprig/parser/yml.rb +9 -0
- data/lib/sprig/planter.rb +39 -0
- data/lib/sprig/seed.rb +9 -0
- data/lib/sprig/seed/attribute.rb +54 -0
- data/lib/sprig/seed/attribute_collection.rb +33 -0
- data/lib/sprig/seed/entry.rb +80 -0
- data/lib/sprig/seed/factory.rb +56 -0
- data/lib/sprig/seed/record.rb +35 -0
- data/lib/sprig/source.rb +143 -0
- data/lib/sprig/sprig_logger.rb +68 -0
- data/lib/sprig/sprig_record_store.rb +31 -0
- data/lib/sprig/version.rb +3 -0
- data/spec/db/activerecord.db +0 -0
- data/spec/feature/configurations_spec.rb +30 -0
- data/spec/fixtures/cassettes/google_spreadsheet_json_posts.yml +60 -0
- data/spec/fixtures/models/comment.rb +5 -0
- data/spec/fixtures/models/post.rb +2 -0
- data/spec/fixtures/seeds/staging/posts.yml +6 -0
- data/spec/fixtures/seeds/test/comments.yml +6 -0
- data/spec/fixtures/seeds/test/legacy_posts.yml +6 -0
- data/spec/fixtures/seeds/test/posts.csv +2 -0
- data/spec/fixtures/seeds/test/posts.json +1 -0
- data/spec/fixtures/seeds/test/posts.yml +6 -0
- data/spec/fixtures/seeds/test/posts_find_existing_by_multiple.yml +9 -0
- data/spec/fixtures/seeds/test/posts_find_existing_by_single.yml +8 -0
- data/spec/fixtures/seeds/test/posts_missing_dependency.yml +7 -0
- data/spec/lib/sprig/configuration_spec.rb +21 -0
- data/spec/lib/sprig/directive_list_spec.rb +24 -0
- data/spec/lib/sprig/directive_spec.rb +60 -0
- data/spec/spec_helper.rb +75 -0
- data/spec/sprig_spec.rb +205 -0
- metadata +211 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
module Sprig
|
2
|
+
module Helpers
|
3
|
+
def seed_directory
|
4
|
+
Sprig.configuration.directory
|
5
|
+
end
|
6
|
+
|
7
|
+
def sprig_environment
|
8
|
+
Rails.env #TODO: make customizable
|
9
|
+
end
|
10
|
+
|
11
|
+
def sprig(directive_definitions)
|
12
|
+
hopper = []
|
13
|
+
DirectiveList.new(directive_definitions).add_seeds_to_hopper(hopper)
|
14
|
+
Planter.new(hopper).sprig
|
15
|
+
end
|
16
|
+
|
17
|
+
def sprig_record(klass, seed_id)
|
18
|
+
SprigRecordStore.instance.get(klass, seed_id)
|
19
|
+
end
|
20
|
+
|
21
|
+
def sprig_file(relative_path)
|
22
|
+
File.new(seed_directory.join('files', relative_path))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/sprig/parser.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
module Sprig
|
4
|
+
module Parser
|
5
|
+
class Csv < Base
|
6
|
+
|
7
|
+
def parse
|
8
|
+
{ :records => records }
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def records
|
14
|
+
[].tap do |records|
|
15
|
+
CSV.foreach(data_io, headers: :first_row, skip_blanks: true) do |row|
|
16
|
+
records << row.to_hash
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Sprig
|
2
|
+
module Parser
|
3
|
+
class GoogleSpreadsheetJson < Base
|
4
|
+
|
5
|
+
def parse
|
6
|
+
{ :records => records }
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def records
|
12
|
+
@records ||= raw_records.map { |record| build_record(record) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def build_record(record)
|
16
|
+
hash = {}
|
17
|
+
|
18
|
+
record.keys.each do |key|
|
19
|
+
attr_name = key.tr('-', '_').scan(/gsx\$([a-z_]+$)/).flatten.first
|
20
|
+
hash[attr_name] = record[key]['$t'] if attr_name
|
21
|
+
end
|
22
|
+
|
23
|
+
hash
|
24
|
+
end
|
25
|
+
|
26
|
+
def raw_records
|
27
|
+
json['feed']['entry']
|
28
|
+
end
|
29
|
+
|
30
|
+
def json
|
31
|
+
@json ||= JSON.load(data_io)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Sprig
|
2
|
+
class Planter
|
3
|
+
def initialize(seeds)
|
4
|
+
@seeds = seeds.to_a
|
5
|
+
end
|
6
|
+
|
7
|
+
def sprig
|
8
|
+
dependency_sorted_seeds.each do |seed|
|
9
|
+
plant(seed)
|
10
|
+
end
|
11
|
+
|
12
|
+
logger.log_summary
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
attr_reader :seeds
|
18
|
+
|
19
|
+
def dependency_sorted_seeds
|
20
|
+
DependencySorter.new(seeds).sorted_items
|
21
|
+
end
|
22
|
+
|
23
|
+
def logger
|
24
|
+
@logger ||= SprigLogger.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def plant(seed)
|
28
|
+
logger.processing
|
29
|
+
seed.before_save
|
30
|
+
|
31
|
+
if seed.save_record
|
32
|
+
seed.save_to_store
|
33
|
+
logger.log_success(seed)
|
34
|
+
else
|
35
|
+
logger.log_error(seed)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/sprig/seed.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
module Sprig
|
2
|
+
module Seed
|
3
|
+
autoload :Attribute, 'sprig/seed/attribute'
|
4
|
+
autoload :AttributeCollection, 'sprig/seed/attribute_collection'
|
5
|
+
autoload :Entry, 'sprig/seed/entry'
|
6
|
+
autoload :Factory, 'sprig/seed/factory'
|
7
|
+
autoload :Record, 'sprig/seed/record'
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Sprig
|
2
|
+
module Seed
|
3
|
+
class Attribute
|
4
|
+
attr_reader :name, :raw_value, :value
|
5
|
+
|
6
|
+
def initialize(name, raw_value)
|
7
|
+
@name = name.to_s
|
8
|
+
@raw_value = raw_value
|
9
|
+
end
|
10
|
+
|
11
|
+
def dependencies
|
12
|
+
@dependencies ||= determine_dependencies.uniq
|
13
|
+
end
|
14
|
+
|
15
|
+
def value
|
16
|
+
compute_value if @value.nil?
|
17
|
+
|
18
|
+
@value
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def determine_dependencies
|
24
|
+
if computed_value?
|
25
|
+
matches = raw_value.scan(/(sprig_record\(([A-Z][^,]*), ([\d]*)\))+/)
|
26
|
+
matches.map {|match| Dependency.for(match[1], match[2]) }
|
27
|
+
else
|
28
|
+
[]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def string?
|
33
|
+
raw_value.is_a?(String)
|
34
|
+
end
|
35
|
+
|
36
|
+
def computed_value?
|
37
|
+
string? && raw_value =~ computed_value_regex
|
38
|
+
end
|
39
|
+
|
40
|
+
def computed_value_regex
|
41
|
+
/<%[=]?(.*)%>/
|
42
|
+
end
|
43
|
+
|
44
|
+
def compute_value
|
45
|
+
@value = if computed_value?
|
46
|
+
matches = computed_value_regex.match(raw_value)
|
47
|
+
eval(matches[1])
|
48
|
+
else
|
49
|
+
raw_value
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Sprig
|
2
|
+
module Seed
|
3
|
+
class AttributeCollection
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
delegate :each, :to => :attributes
|
7
|
+
|
8
|
+
def initialize(attrs_hash)
|
9
|
+
@attrs_hash = attrs_hash.to_hash
|
10
|
+
end
|
11
|
+
|
12
|
+
def find_by_name(name)
|
13
|
+
attributes.detect {|attribute| attribute.name == name.to_s } || attribute_not_found(name)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
attr_reader :attrs_hash
|
19
|
+
|
20
|
+
class AttributeNotFoundError < StandardError; end
|
21
|
+
|
22
|
+
def attributes
|
23
|
+
@attributes ||= attrs_hash.map do |name, value|
|
24
|
+
Attribute.new(name, value)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def attribute_not_found(name)
|
29
|
+
raise AttributeNotFoundError, "Attribute '#{name}' is not present."
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Sprig
|
2
|
+
module Seed
|
3
|
+
class Entry
|
4
|
+
def initialize(klass, attrs, options)
|
5
|
+
self.klass = klass
|
6
|
+
attrs = attrs.to_hash.with_indifferent_access
|
7
|
+
self.sprig_id = attrs.delete(:sprig_id) || SecureRandom.uuid
|
8
|
+
@attributes = AttributeCollection.new(attrs)
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
def dependencies
|
13
|
+
@dependencies ||= attributes.map do |attribute|
|
14
|
+
attribute.dependencies
|
15
|
+
end.flatten.uniq
|
16
|
+
end
|
17
|
+
|
18
|
+
def dependency_id
|
19
|
+
@dependency_id ||= Dependency.for(klass, sprig_id).id
|
20
|
+
end
|
21
|
+
|
22
|
+
def before_save
|
23
|
+
# TODO: make these filters take chains like rails before_filters
|
24
|
+
if options[:delete_existing_by]
|
25
|
+
klass.delete_all(options[:delete_existing_by] => attributes.find_by_name(options[:delete_existing_by]).value)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def save_record
|
30
|
+
record.save
|
31
|
+
end
|
32
|
+
|
33
|
+
def save_to_store
|
34
|
+
SprigRecordStore.instance.save(record.orm_record, sprig_id)
|
35
|
+
end
|
36
|
+
|
37
|
+
def success_log_text
|
38
|
+
"#{klass.name} with sprig_id #{sprig_id} successfully saved."
|
39
|
+
end
|
40
|
+
|
41
|
+
def error_log_text
|
42
|
+
"There was an error saving #{klass.name} with sprig_id #{sprig_id}."
|
43
|
+
end
|
44
|
+
|
45
|
+
def record
|
46
|
+
@record ||= new_or_existing_record
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
attr_reader :attributes, :klass, :options, :sprig_id
|
52
|
+
|
53
|
+
def klass=(klass)
|
54
|
+
raise ArgumentError, 'First argument must be a Class' unless klass.is_a?(Class)
|
55
|
+
|
56
|
+
@klass = klass
|
57
|
+
end
|
58
|
+
|
59
|
+
def sprig_id=(sprig_id)
|
60
|
+
@sprig_id = sprig_id.to_s
|
61
|
+
end
|
62
|
+
|
63
|
+
def new_or_existing_record
|
64
|
+
if options[:find_existing_by]
|
65
|
+
Record.new_or_existing(klass, attributes, find_existing_params)
|
66
|
+
else
|
67
|
+
Record.new(klass, attributes)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def find_existing_params
|
72
|
+
Array(options[:find_existing_by]).inject({}) do |hash, attribute_name|
|
73
|
+
hash.merge!(
|
74
|
+
{ attribute_name => attributes.find_by_name(attribute_name).value }
|
75
|
+
)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Sprig
|
2
|
+
module Seed
|
3
|
+
class Factory
|
4
|
+
def self.new_from_directive(directive)
|
5
|
+
raise ArgumentError, 'Must provide a Directive' unless directive.is_a? Directive
|
6
|
+
|
7
|
+
klass = directive.klass
|
8
|
+
datasource = directive.datasource
|
9
|
+
options = directive.options
|
10
|
+
|
11
|
+
new(klass, datasource, options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(klass, datasource, options)
|
15
|
+
self.klass = klass
|
16
|
+
self.datasource = datasource
|
17
|
+
self.initial_options = options
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_seeds_to_hopper(hopper)
|
21
|
+
datasource.records.each do |record_data|
|
22
|
+
hopper << Entry.new(klass, record_data, options)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :datasource, :initial_options, :klass
|
29
|
+
|
30
|
+
def klass=(klass)
|
31
|
+
raise ArgumentError, 'Must provide a Class as first argument' unless klass.is_a? Class
|
32
|
+
|
33
|
+
@klass = klass
|
34
|
+
end
|
35
|
+
|
36
|
+
def datasource=(datasource)
|
37
|
+
raise ArgumentError, 'Datasource must respond to #records and #options' unless datasource.respond_to?(:records) && datasource.respond_to?(:options)
|
38
|
+
|
39
|
+
@datasource = datasource
|
40
|
+
end
|
41
|
+
|
42
|
+
def initial_options=(initial_options)
|
43
|
+
initial_options ||= {}
|
44
|
+
@initial_options = initial_options.to_hash
|
45
|
+
end
|
46
|
+
|
47
|
+
def options
|
48
|
+
@options ||= datasource.options.merge(initial_options)
|
49
|
+
end
|
50
|
+
|
51
|
+
def data
|
52
|
+
@data ||= datasource.to_hash
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Sprig
|
2
|
+
module Seed
|
3
|
+
class Record
|
4
|
+
attr_reader :orm_record
|
5
|
+
|
6
|
+
delegate :errors, :to => :orm_record
|
7
|
+
|
8
|
+
def self.new_or_existing(klass, attributes, find_params)
|
9
|
+
orm_record = klass.where(find_params).first
|
10
|
+
new(klass, attributes, orm_record)
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(klass, attributes, orm_record = nil)
|
14
|
+
@klass = klass
|
15
|
+
@attributes = attributes
|
16
|
+
@orm_record = orm_record || klass.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def save
|
20
|
+
populate_attributes
|
21
|
+
orm_record.save
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :attributes, :klass
|
27
|
+
|
28
|
+
def populate_attributes
|
29
|
+
attributes.each do |attribute|
|
30
|
+
orm_record.send(:"#{attribute.name}=", attribute.value)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|