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
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
|
+
[](https://codeclimate.com/github/vigetlabs/sprig) [](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
|