sprig 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|