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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 391a106a3a06f570e669016d9625f92c62feb152
|
4
|
+
data.tar.gz: d8ffa472a8d094540db22d9016eff0ef685c8755
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 489f198c7432cab1d2474eec6939e1e67f4ef9ebdb43f3faa2c93fe96263b43797dd8372c360a8f65a7d23c724a4b806ea44335cc3923b45c16222a17edacfde
|
7
|
+
data.tar.gz: a15a56f4af8df7492fa7ac2212223c59314163620d28e0301dbbe55f66418dabebd2139e78fcf9b289566c62dc8ab9c0fc58afc7be9d2cdb07125a445a49de62
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2013 Viget
|
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,131 @@
|
|
1
|
+
#Sprig
|
2
|
+
|
3
|
+
[![Code Climate](https://codeclimate.com/github/vigetlabs/sprig.png)](https://codeclimate.com/github/vigetlabs/sprig) [![Build Status](https://travis-ci.org/vigetlabs/sprig.png?branch=master)](https://travis-ci.org/vigetlabs/sprig)
|
4
|
+
|
5
|
+
Seed Rails application by convention, not configuration.
|
6
|
+
|
7
|
+
Provides support for common files types: *csv*, *yaml*, and *json*. Extensible for the rest!
|
8
|
+
|
9
|
+
|
10
|
+
##The Sprig Directive
|
11
|
+
|
12
|
+
Within your seed file, you can use the `sprig` directive to initiate Sprig's dark magicks. A simple directive might look like this.
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
# seeds.rb
|
16
|
+
|
17
|
+
include Sprig::Helpers
|
18
|
+
|
19
|
+
sprig [User, Post, Comment]
|
20
|
+
```
|
21
|
+
|
22
|
+
This directive tells Sprig to go find your datafiles for the `User`, `Post`, and `Comment` seed resources, build records from the data entries, and insert them into the database. Sprig will automatically detect known datafile types like `.yml`, `.json`, or `.csv` within your environment-specific seed directory.
|
23
|
+
|
24
|
+
|
25
|
+
##Environment
|
26
|
+
|
27
|
+
Seed files are unique to the environment in which your Rails application is running. Within `db/seeds` create an environment-specific directory (i.e. `/development` for your 'development' environment).
|
28
|
+
|
29
|
+
Todo: [Support for shared seed files]
|
30
|
+
|
31
|
+
|
32
|
+
##Seed files
|
33
|
+
|
34
|
+
Hang your seed definitions on a `records` key for *yaml* and *json* files.
|
35
|
+
|
36
|
+
Examples:
|
37
|
+
|
38
|
+
```yaml
|
39
|
+
# users.yml
|
40
|
+
|
41
|
+
records:
|
42
|
+
- sprig_id: 1
|
43
|
+
first_name: 'Lawson'
|
44
|
+
last_name: 'Kurtz'
|
45
|
+
username: 'lawdawg'
|
46
|
+
- sprig_id: 2
|
47
|
+
first_name: 'Ryan'
|
48
|
+
last_name: 'Foster'
|
49
|
+
username: 'mc_rubs'
|
50
|
+
```
|
51
|
+
|
52
|
+
```json
|
53
|
+
// posts.json
|
54
|
+
|
55
|
+
{
|
56
|
+
"records":[
|
57
|
+
{
|
58
|
+
"sprig_id":1,
|
59
|
+
"title":"Json title",
|
60
|
+
"content":"Json content"
|
61
|
+
},
|
62
|
+
{
|
63
|
+
"sprig_id":2,
|
64
|
+
"title":"Headline",
|
65
|
+
"content":"Words about things"
|
66
|
+
}
|
67
|
+
]
|
68
|
+
}
|
69
|
+
```
|
70
|
+
|
71
|
+
Each seed record needs a `sprig_id` defined that must be *unique across all seed files per class*. It can be an integer, string, whatever you prefer; as long as it is unique, Sprig can sort your seeds for insertion and detect any cyclic relationships.
|
72
|
+
|
73
|
+
Create relationships between seed records with the `sprig_record` helper:
|
74
|
+
|
75
|
+
```yaml
|
76
|
+
# comments.yml
|
77
|
+
|
78
|
+
records:
|
79
|
+
- sprig_id: 1
|
80
|
+
post_id: "<%= sprig_record(Post, 1).id %>"
|
81
|
+
body: "Yaml Comment body"
|
82
|
+
```
|
83
|
+
|
84
|
+
### Special Options
|
85
|
+
|
86
|
+
These are provided in a `options:` key for *yaml* and *json* files.
|
87
|
+
|
88
|
+
#### find_existing_by:
|
89
|
+
|
90
|
+
Rather than starting from a blank database, you can optionally choose to find existing records and update them with seed data.
|
91
|
+
|
92
|
+
The passed in attribute or array of attributes will be used for finding existing records during the sprigging process.
|
93
|
+
|
94
|
+
Example:
|
95
|
+
|
96
|
+
```yaml
|
97
|
+
# posts.yml
|
98
|
+
|
99
|
+
options:
|
100
|
+
find_existing_by: ['title', 'user_id']
|
101
|
+
```
|
102
|
+
|
103
|
+
##Custom Sources and Parsers
|
104
|
+
|
105
|
+
If all your data is in `.wat` files, fear not. You can tell Sprig where to look for your data, and point it toward a custom parser class for turning your data into records. The example below tells Sprig to read `User` seed data from a Google Spreadsheet, and parse it accordingly.
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
fanciness = {
|
109
|
+
:class => User,
|
110
|
+
:source => open('https://spreadsheets.google.com/feeds/list/somerandomtoken/1/public/values?alt=json'),
|
111
|
+
:parser => Sprig::Data::Parser::GoogleSpreadsheetJson
|
112
|
+
}
|
113
|
+
|
114
|
+
sprig [
|
115
|
+
fanciness,
|
116
|
+
Post,
|
117
|
+
Comment
|
118
|
+
]
|
119
|
+
```
|
120
|
+
|
121
|
+
##Configuration
|
122
|
+
|
123
|
+
When Sprig conventions don't suit, just add a configuration block to your seed file.
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
Sprig.configure do |c|
|
127
|
+
c.directory = 'seed_files'
|
128
|
+
end
|
129
|
+
```
|
130
|
+
|
131
|
+
This project rocks and uses MIT-LICENSE.
|
data/Rakefile
ADDED
data/lib/sprig.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
module Sprig
|
2
|
+
autoload :Configuration, 'sprig/configuration'
|
3
|
+
autoload :Planter, 'sprig/planter'
|
4
|
+
autoload :Dependency, 'sprig/dependency'
|
5
|
+
autoload :DependencyCollection, 'sprig/dependency_collection'
|
6
|
+
autoload :DependencySorter, 'sprig/dependency_sorter'
|
7
|
+
autoload :Directive, 'sprig/directive'
|
8
|
+
autoload :DirectiveList, 'sprig/directive_list'
|
9
|
+
autoload :Source, 'sprig/source'
|
10
|
+
autoload :Parser, 'sprig/parser'
|
11
|
+
autoload :Helpers, 'sprig/helpers'
|
12
|
+
autoload :Planter, 'sprig/planter'
|
13
|
+
autoload :SprigLogger, 'sprig/sprig_logger'
|
14
|
+
autoload :SprigRecordStore, 'sprig/sprig_record_store'
|
15
|
+
autoload :Data, 'sprig/data'
|
16
|
+
autoload :Seed, 'sprig/seed'
|
17
|
+
|
18
|
+
def self.configuration
|
19
|
+
@@configuration ||= Sprig::Configuration.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.configure
|
23
|
+
yield configuration
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.reset_configuration
|
27
|
+
@@configuration = nil
|
28
|
+
end
|
29
|
+
end
|
data/lib/sprig/data.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
module Sprig
|
2
|
+
class Dependency
|
3
|
+
attr_reader :id
|
4
|
+
|
5
|
+
def self.for(klass, sprig_id)
|
6
|
+
klass = to_klass(klass)
|
7
|
+
sprig_id = sprig_id.to_s
|
8
|
+
|
9
|
+
collection.get(klass, sprig_id) || new(klass, sprig_id)
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(klass, sprig_id)
|
13
|
+
@klass = klass
|
14
|
+
@sprig_id = sprig_id
|
15
|
+
@id = SecureRandom.uuid
|
16
|
+
|
17
|
+
self.class.collection.set(klass, sprig_id, self)
|
18
|
+
end
|
19
|
+
|
20
|
+
def sprig_record_reference
|
21
|
+
"sprig_record(#{klass}, #{sprig_id})"
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :klass, :sprig_id
|
27
|
+
|
28
|
+
def self.to_klass(klass)
|
29
|
+
if klass.is_a?(String)
|
30
|
+
klass = klass.classify.constantize
|
31
|
+
end
|
32
|
+
|
33
|
+
raise ArgumentError, 'First argument must be a Class.' unless klass.is_a?(Class)
|
34
|
+
|
35
|
+
klass
|
36
|
+
rescue NameError => e
|
37
|
+
raise NameError, e.message
|
38
|
+
#TODO: rescue bad class references
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.collection
|
42
|
+
@@collection ||= DependencyCollection.new
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Sprig
|
2
|
+
class DependencyCollection
|
3
|
+
def initialize
|
4
|
+
@records = {}
|
5
|
+
end
|
6
|
+
|
7
|
+
def get(klass, id)
|
8
|
+
records_for_klass(klass)[id]
|
9
|
+
end
|
10
|
+
|
11
|
+
def set(klass, id, value)
|
12
|
+
records_for_klass(klass)[id] = value
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
attr_reader :records
|
18
|
+
|
19
|
+
def records_for_klass(klass)
|
20
|
+
records[klass] ||= {}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Sprig
|
2
|
+
class DependencySorter
|
3
|
+
class CircularDependencyError < StandardError; end
|
4
|
+
|
5
|
+
attr_reader :items
|
6
|
+
|
7
|
+
def initialize(items)
|
8
|
+
@items = items.to_a
|
9
|
+
end
|
10
|
+
|
11
|
+
def sorted_items
|
12
|
+
sorted_tags.map do |tag|
|
13
|
+
id_dictionary[tag]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def id_dictionary
|
20
|
+
@id_dictionary ||= begin
|
21
|
+
{}.tap do |hash|
|
22
|
+
items.each do |item|
|
23
|
+
hash[item.dependency_id] = item
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def dependency_hash
|
30
|
+
@dependency_hash ||= begin
|
31
|
+
TsortableHash.new.tap do |hash|
|
32
|
+
items.each do |item|
|
33
|
+
hash[item.dependency_id] = item.dependencies.map(&:id)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def sorted_tags
|
40
|
+
dependency_hash.tsort
|
41
|
+
rescue TSort::Cyclic => e
|
42
|
+
raise CircularDependencyError.new("Your sprig directives contain circular dependencies. #{e.message}")
|
43
|
+
rescue KeyError => key_error
|
44
|
+
raise missing_dependency_error_from_key_error(key_error)
|
45
|
+
end
|
46
|
+
|
47
|
+
def dependencies
|
48
|
+
items.map(&:dependencies).flatten
|
49
|
+
end
|
50
|
+
|
51
|
+
def missing_dependency_error_from_key_error(key_error)
|
52
|
+
key = key_error.message.match(/\Akey not found: "(.*)"\Z/)[1]
|
53
|
+
missing_dependency = dependencies.detect { |item| item.id == key }
|
54
|
+
MissingDependencyError.new(missing_dependency)
|
55
|
+
end
|
56
|
+
|
57
|
+
class TsortableHash < Hash
|
58
|
+
include TSort
|
59
|
+
|
60
|
+
alias tsort_each_node each_key
|
61
|
+
|
62
|
+
def tsort_each_child(node, &block)
|
63
|
+
fetch(node).each(&block)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class MissingDependencyError < StandardError
|
68
|
+
def initialize(missing_dependency = nil)
|
69
|
+
super message_for(missing_dependency)
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def message_for(missing_dependency)
|
75
|
+
if missing_dependency.is_a? Dependency
|
76
|
+
"Undefined reference to '#{missing_dependency.sprig_record_reference}'"
|
77
|
+
else
|
78
|
+
"Referenced 'sprig_record' does not have a correlating record."
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Sprig
|
2
|
+
class Directive
|
3
|
+
attr_reader :attributes
|
4
|
+
|
5
|
+
def initialize(args)
|
6
|
+
@attributes = begin
|
7
|
+
case
|
8
|
+
when args.is_a?(Hash)
|
9
|
+
args
|
10
|
+
when args < ActiveRecord::Base
|
11
|
+
{ :class => args }
|
12
|
+
else
|
13
|
+
raise ArgumentError, argument_error_message
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def klass
|
19
|
+
@klass ||= attributes.fetch(:class) { raise ArgumentError, argument_error_message }
|
20
|
+
end
|
21
|
+
|
22
|
+
def options
|
23
|
+
@options ||= attributes.except(:class)
|
24
|
+
end
|
25
|
+
|
26
|
+
def datasource
|
27
|
+
@datasource ||= Source.new(klass.to_s.tableize, options)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def argument_error_message
|
33
|
+
'Sprig::Directive must be instantiated with an '\
|
34
|
+
'ActiveRecord subclass or a Hash with :class defined'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Sprig
|
2
|
+
class DirectiveList
|
3
|
+
|
4
|
+
def initialize(definitions)
|
5
|
+
@definitions = Array(definitions)
|
6
|
+
end
|
7
|
+
|
8
|
+
def add_seeds_to_hopper(hopper)
|
9
|
+
seed_factories.each do |factory|
|
10
|
+
factory.add_seeds_to_hopper(hopper)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
attr_reader :definitions
|
17
|
+
|
18
|
+
def directives
|
19
|
+
@directives ||= definitions.map do |definition|
|
20
|
+
Directive.new(definition)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def seed_factories
|
25
|
+
directives.map do |directive|
|
26
|
+
Seed::Factory.new_from_directive(directive)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|