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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +131 -0
  4. data/Rakefile +5 -0
  5. data/lib/sprig.rb +29 -0
  6. data/lib/sprig/configuration.rb +16 -0
  7. data/lib/sprig/data.rb +6 -0
  8. data/lib/sprig/dependency.rb +45 -0
  9. data/lib/sprig/dependency_collection.rb +23 -0
  10. data/lib/sprig/dependency_sorter.rb +83 -0
  11. data/lib/sprig/directive.rb +37 -0
  12. data/lib/sprig/directive_list.rb +30 -0
  13. data/lib/sprig/helpers.rb +25 -0
  14. data/lib/sprig/parser.rb +9 -0
  15. data/lib/sprig/parser/base.rb +15 -0
  16. data/lib/sprig/parser/csv.rb +22 -0
  17. data/lib/sprig/parser/google_spreadsheet_json.rb +35 -0
  18. data/lib/sprig/parser/json.rb +9 -0
  19. data/lib/sprig/parser/yml.rb +9 -0
  20. data/lib/sprig/planter.rb +39 -0
  21. data/lib/sprig/seed.rb +9 -0
  22. data/lib/sprig/seed/attribute.rb +54 -0
  23. data/lib/sprig/seed/attribute_collection.rb +33 -0
  24. data/lib/sprig/seed/entry.rb +80 -0
  25. data/lib/sprig/seed/factory.rb +56 -0
  26. data/lib/sprig/seed/record.rb +35 -0
  27. data/lib/sprig/source.rb +143 -0
  28. data/lib/sprig/sprig_logger.rb +68 -0
  29. data/lib/sprig/sprig_record_store.rb +31 -0
  30. data/lib/sprig/version.rb +3 -0
  31. data/spec/db/activerecord.db +0 -0
  32. data/spec/feature/configurations_spec.rb +30 -0
  33. data/spec/fixtures/cassettes/google_spreadsheet_json_posts.yml +60 -0
  34. data/spec/fixtures/models/comment.rb +5 -0
  35. data/spec/fixtures/models/post.rb +2 -0
  36. data/spec/fixtures/seeds/staging/posts.yml +6 -0
  37. data/spec/fixtures/seeds/test/comments.yml +6 -0
  38. data/spec/fixtures/seeds/test/legacy_posts.yml +6 -0
  39. data/spec/fixtures/seeds/test/posts.csv +2 -0
  40. data/spec/fixtures/seeds/test/posts.json +1 -0
  41. data/spec/fixtures/seeds/test/posts.yml +6 -0
  42. data/spec/fixtures/seeds/test/posts_find_existing_by_multiple.yml +9 -0
  43. data/spec/fixtures/seeds/test/posts_find_existing_by_single.yml +8 -0
  44. data/spec/fixtures/seeds/test/posts_missing_dependency.yml +7 -0
  45. data/spec/lib/sprig/configuration_spec.rb +21 -0
  46. data/spec/lib/sprig/directive_list_spec.rb +24 -0
  47. data/spec/lib/sprig/directive_spec.rb +60 -0
  48. data/spec/spec_helper.rb +75 -0
  49. data/spec/sprig_spec.rb +205 -0
  50. metadata +211 -0
@@ -0,0 +1,143 @@
1
+ module Sprig
2
+ class Source
3
+
4
+ def initialize(table_name, args = {})
5
+ @table_name = table_name
6
+ @args = args
7
+ end
8
+
9
+ def records
10
+ data[:records] || []
11
+ end
12
+
13
+ def options
14
+ data[:options] || {}
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :table_name, :args
20
+
21
+ def data
22
+ @data ||= begin
23
+ parser_class.new(source).parse.to_hash.with_indifferent_access
24
+ ensure
25
+ source.close
26
+ end
27
+ end
28
+
29
+ def source
30
+ @source ||= begin
31
+ source = args.fetch(:source) { default_source }
32
+
33
+ unless source.respond_to?(:read) && source.respond_to?(:close)
34
+ raise ArgumentError, 'Data sources must act like an IO.'
35
+ end
36
+
37
+ source
38
+ end
39
+ end
40
+
41
+ def parser_class
42
+ @parser_class ||= begin
43
+ parser_class = args.fetch(:parser) { default_parser_class }
44
+
45
+ unless parser_class.method_defined?(:parse)
46
+ raise ArgumentError, 'Parsers must define #parse.'
47
+ end
48
+
49
+ parser_class
50
+ end
51
+ end
52
+
53
+ def default_source
54
+ File.open(SourceDeterminer.new(table_name).file)
55
+ end
56
+
57
+ def default_parser_class
58
+ ParserDeterminer.new(source).parser
59
+ end
60
+
61
+
62
+ class SourceDeterminer
63
+ attr_reader :table_name
64
+
65
+ def initialize(table_name)
66
+ @table_name = table_name
67
+ end
68
+
69
+ def file
70
+ File.new(seed_directory.join(filename))
71
+ end
72
+
73
+ private
74
+
75
+ class FileNotFoundError < StandardError; end
76
+
77
+ def filename
78
+ available_files.detect {|name| name =~ /^#{table_name}\./ } || file_not_found
79
+ end
80
+
81
+ def available_files
82
+ Dir.entries(seed_directory)
83
+ end
84
+
85
+ def file_not_found
86
+ raise FileNotFoundError,
87
+ "No datasource file could be found for '#{table_name}'. Try creating "\
88
+ "#{table_name}.yml, #{table_name}.json, or #{table_name}.csv within "\
89
+ "#{seed_directory}, or define a custom datasource."
90
+ end
91
+ end
92
+
93
+
94
+ class ParserDeterminer
95
+
96
+ def initialize(file)
97
+ @file = file
98
+ end
99
+
100
+ def parser
101
+ match = parser_matchers.detect {|p| p[:extension] =~ extension } || unparsable_file
102
+ match[:parser]
103
+ end
104
+
105
+ private
106
+
107
+ class UnparsableFileError < StandardError; end
108
+
109
+ attr_reader :file
110
+
111
+ def extension
112
+ File.extname(file)
113
+ end
114
+
115
+ def parser_matchers
116
+ [
117
+ {
118
+ extension: /\.y(a)?ml/i,
119
+ parser: Sprig::Parser::Yml
120
+ },
121
+ {
122
+ extension: /\.json/i,
123
+ parser: Sprig::Parser::Json
124
+ },
125
+ {
126
+ extension: /\.csv/i,
127
+ parser: Sprig::Parser::Csv
128
+ }
129
+ ]
130
+ end
131
+
132
+ def parsable_formats
133
+ ['YAML', 'JSON', 'CSV']
134
+ end
135
+
136
+ def unparsable_file
137
+ raise UnparsableFileError,
138
+ "No parser was found for the file '#{file}'. Provide a custom parser, or "\
139
+ "use a supported data format (#{parsable_formats})."
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,68 @@
1
+ require 'active_support/inflector'
2
+
3
+ module Sprig
4
+ class SprigLogger
5
+ def initialize
6
+ @success_count = 0
7
+ @error_count = 0
8
+ @errors = []
9
+ end
10
+
11
+ def log_success(seed)
12
+ message = seed.success_log_text
13
+ puts green(message)
14
+ @success_count += 1
15
+ end
16
+
17
+ def log_error(seed)
18
+ message = seed.error_log_text
19
+ @errors << seed.record
20
+ puts red(message)
21
+ @error_count += 1
22
+ end
23
+
24
+ def log_summary
25
+ puts 'Seeding complete.'
26
+
27
+ if @success_count > 0
28
+ puts green(success_summary)
29
+ else
30
+ puts red(success_summary)
31
+ end
32
+
33
+ if @error_count > 0
34
+ puts red(error_summary)
35
+
36
+ @errors.each do |error|
37
+ puts red("#{error}\n#{error.errors.messages}\n\n")
38
+ end
39
+ end
40
+ end
41
+
42
+ def processing
43
+ print "Planting those seeds...\r"
44
+ end
45
+
46
+ private
47
+
48
+ def colorize(message, color_code)
49
+ "\e[#{color_code}m#{message}\e[0m"
50
+ end
51
+
52
+ def red(message)
53
+ colorize(message, 31)
54
+ end
55
+
56
+ def green(message)
57
+ colorize(message, 32)
58
+ end
59
+
60
+ def success_summary
61
+ "#{@success_count} #{'seed'.pluralize(@success_count)} successfully planted."
62
+ end
63
+
64
+ def error_summary
65
+ "#{@error_count} #{'seed'.pluralize(@error_count)} couldn't be planted:"
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,31 @@
1
+ require 'singleton'
2
+
3
+ module Sprig
4
+ class SprigRecordStore
5
+ include Singleton
6
+
7
+ class RecordNotFoundError < StandardError;end
8
+
9
+ def save(record, sprig_id)
10
+ records_of_klass(record.class)[sprig_id.to_s] = record
11
+ end
12
+
13
+ def get(klass, sprig_id)
14
+ records_of_klass(klass)[sprig_id.to_s] || record_not_found
15
+ end
16
+
17
+ private
18
+
19
+ def records_of_klass(klass)
20
+ records[klass.name.tableize] ||= {}
21
+ end
22
+
23
+ def records
24
+ @records ||= {}
25
+ end
26
+
27
+ def record_not_found
28
+ raise(RecordNotFoundError, "Record for class #{klass} and sprig_id #{sprig_id} could not be found.")
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ module Sprig
2
+ VERSION = "0.1.0"
3
+ end
Binary file
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Configurating Sprig" do
4
+ before do
5
+ stub_rails_root '~'
6
+ stub_rails_env 'development'
7
+ end
8
+
9
+ it "can set the directory" do
10
+ Sprig.configuration.directory.to_path.should == '~/db/seeds/development'
11
+
12
+ Sprig.configure do |c|
13
+ c.directory = 'seed_files'
14
+ end
15
+
16
+ Sprig.configuration.directory.to_path.should == '~/seed_files/development'
17
+ end
18
+
19
+ it "can reset the configuration" do
20
+ Sprig.configure do |c|
21
+ c.directory = 'seed_files'
22
+ end
23
+
24
+ Sprig.configuration.directory.to_path.should == '~/seed_files/development'
25
+
26
+ Sprig.reset_configuration
27
+
28
+ Sprig.configuration.directory.to_path.should == '~/db/seeds/development'
29
+ end
30
+ end
@@ -0,0 +1,60 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://spreadsheets.google.com/feeds/list/0AjVLPMnHm86rdDVHQ2dCUS03RTN5ZUtVNzVOYVBwT0E/1/public/values?alt=json
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ Accept:
11
+ - ! '*/*'
12
+ User-Agent:
13
+ - Ruby
14
+ response:
15
+ status:
16
+ code: 200
17
+ message: OK
18
+ headers:
19
+ Content-Type:
20
+ - application/json; charset=UTF-8
21
+ Access-Control-Allow-Origin:
22
+ - ! '*'
23
+ Expires:
24
+ - Thu, 02 Jan 2014 22:06:43 GMT
25
+ Date:
26
+ - Thu, 02 Jan 2014 22:06:43 GMT
27
+ Cache-Control:
28
+ - private, max-age=0, must-revalidate, no-transform
29
+ Vary:
30
+ - Accept, X-GData-Authorization, GData-Version
31
+ Gdata-Version:
32
+ - '1.0'
33
+ Last-Modified:
34
+ - Thu, 02 Jan 2014 21:46:27 GMT
35
+ Set-Cookie:
36
+ - NID=67=twk9_9Iqw6h1ZztnpEkt7dtJJIvFCbyeEjQhC5XRwV7jMa4uPJoArpItl5KnmhwPif-owRtCYsUwyChgEMO7GDroZPOFBOV0KvMLzUQmlSn-0tGxTefBSvdVHJ8MIHsi;Domain=.google.com;Path=/;Expires=Fri,
37
+ 04-Jul-2014 22:06:43 GMT;HttpOnly
38
+ P3p:
39
+ - CP="This is not a P3P policy! See http://www.google.com/support/accounts/bin/answer.py?hl=en&answer=151657
40
+ for more info."
41
+ X-Content-Type-Options:
42
+ - nosniff
43
+ X-Frame-Options:
44
+ - SAMEORIGIN
45
+ X-Xss-Protection:
46
+ - 1; mode=block
47
+ Server:
48
+ - GSE
49
+ Alternate-Protocol:
50
+ - 443:quic
51
+ Transfer-Encoding:
52
+ - chunked
53
+ body:
54
+ encoding: US-ASCII
55
+ string: ! '{"version":"1.0","encoding":"UTF-8","feed":{"xmlns":"http://www.w3.org/2005/Atom","xmlns$openSearch":"http://a9.com/-/spec/opensearchrss/1.0/","xmlns$gsx":"http://schemas.google.com/spreadsheets/2006/extended","id":{"$t":"https://spreadsheets.google.com/feeds/list/0AjVLPMnHm86rdDVHQ2dCUS03RTN5ZUtVNzVOYVBwT0E/1/public/values"},"updated":{"$t":"2014-01-02T21:46:27.808Z"},"category":[{"scheme":"http://schemas.google.com/spreadsheets/2006","term":"http://schemas.google.com/spreadsheets/2006#list"}],"title":{"type":"text","$t":"Sheet1"},"link":[{"rel":"alternate","type":"text/html","href":"https://spreadsheets.google.com/pub?key\u003d0AjVLPMnHm86rdDVHQ2dCUS03RTN5ZUtVNzVOYVBwT0E"},{"rel":"http://schemas.google.com/g/2005#feed","type":"application/atom+xml","href":"https://spreadsheets.google.com/feeds/list/0AjVLPMnHm86rdDVHQ2dCUS03RTN5ZUtVNzVOYVBwT0E/1/public/values"},{"rel":"self","type":"application/atom+xml","href":"https://spreadsheets.google.com/feeds/list/0AjVLPMnHm86rdDVHQ2dCUS03RTN5ZUtVNzVOYVBwT0E/1/public/values?alt\u003djson"}],"author":[{"name":{"$t":"ryan.foster"},"email":{"$t":"ryan.foster@viget.com"}}],"openSearch$totalResults":{"$t":"1"},"openSearch$startIndex":{"$t":"1"},"entry":[{"id":{"$t":"https://spreadsheets.google.com/feeds/list/0AjVLPMnHm86rdDVHQ2dCUS03RTN5ZUtVNzVOYVBwT0E/1/public/values/cokwr"},"updated":{"$t":"2014-01-02T21:46:27.808Z"},"category":[{"scheme":"http://schemas.google.com/spreadsheets/2006","term":"http://schemas.google.com/spreadsheets/2006#list"}],"title":{"type":"text","$t":"1"},"content":{"type":"text","$t":"title:
56
+ Google spreadsheet json title, content: Google spreadsheet json content"},"link":[{"rel":"self","type":"application/atom+xml","href":"https://spreadsheets.google.com/feeds/list/0AjVLPMnHm86rdDVHQ2dCUS03RTN5ZUtVNzVOYVBwT0E/1/public/values/cokwr"}],"gsx$sprig-id":{"$t":"1"},"gsx$title":{"$t":"Google
57
+ spreadsheet json title"},"gsx$content":{"$t":"Google spreadsheet json content"}}]}}'
58
+ http_version:
59
+ recorded_at: Thu, 02 Jan 2014 22:06:42 GMT
60
+ recorded_with: VCR 2.8.0
@@ -0,0 +1,5 @@
1
+ class Comment < ActiveRecord::Base
2
+ belongs_to :post
3
+
4
+ validates :post, :presence => true
5
+ end
@@ -0,0 +1,2 @@
1
+ class Post < ActiveRecord::Base
2
+ end
@@ -0,0 +1,6 @@
1
+ # posts.yml
2
+
3
+ records:
4
+ - sprig_id: 1
5
+ title: 'Staging yaml title'
6
+ content: 'Staging yaml content'
@@ -0,0 +1,6 @@
1
+ # comments.yml
2
+
3
+ records:
4
+ - sprig_id: 1
5
+ post_id: "<%= sprig_record(Post, 1).id %>"
6
+ body: "Yaml Comment body"
@@ -0,0 +1,6 @@
1
+ # posts.yml
2
+
3
+ records:
4
+ - sprig_id: 'l_1'
5
+ title: 'Legacy yaml title'
6
+ content: 'Legacy yaml content'
@@ -0,0 +1,2 @@
1
+ sprig_id,title,content
2
+ 1,Csv title,Csv content
@@ -0,0 +1 @@
1
+ {"records":[{"sprig_id":1,"title":"Json title","content":"Json content"}]}
@@ -0,0 +1,6 @@
1
+ # posts.yml
2
+
3
+ records:
4
+ - sprig_id: 1
5
+ title: 'Yaml title'
6
+ content: 'Yaml content'
@@ -0,0 +1,9 @@
1
+ # posts_find_existing_by_multiples.yml
2
+ options:
3
+ find_existing_by: ['title', 'content']
4
+
5
+ records:
6
+ - sprig_id: 1
7
+ title: 'Existing title'
8
+ content: 'Existing content'
9
+ published: true
@@ -0,0 +1,8 @@
1
+ # posts_find_existing_by_single.yml
2
+ options:
3
+ find_existing_by: 'title'
4
+
5
+ records:
6
+ - sprig_id: 1
7
+ title: 'Existing title'
8
+ content: 'Updated content'
@@ -0,0 +1,7 @@
1
+ # posts_missing_dependency.yml
2
+
3
+ records:
4
+ - sprig_id: 1
5
+ title: 'Yaml title'
6
+ content: 'Yaml content'
7
+ comment_id: "<%= sprig_record(Comment, 42).id %>" # This sprig record does not exist.
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe Sprig::Configuration do
4
+ before do
5
+ stub_rails_root '~'
6
+ stub_rails_env 'development'
7
+ end
8
+
9
+ describe "#directory" do
10
+
11
+ it "returns db/seeds/:env by default" do
12
+ subject.directory.to_path.should == '~/db/seeds/development'
13
+ end
14
+
15
+ it "returns a custom directory" do
16
+ subject.directory = 'seed_files'
17
+
18
+ subject.directory.to_path.should == '~/seed_files/development'
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe Sprig::DirectiveList do
4
+
5
+ describe "#add_seeds_to_hopper" do
6
+ let(:hopper) { Array.new }
7
+ let(:directive) { double('directive') }
8
+ let(:seed_factory) { double('seed_factory') }
9
+
10
+ subject { described_class.new(Post) }
11
+
12
+ before do
13
+ Sprig::Directive.stub(:new).with(Post).and_return(directive)
14
+
15
+ Sprig::Seed::Factory.stub(:new_from_directive).with(directive).and_return(seed_factory)
16
+ end
17
+
18
+ it "builds seeds from directives and adds to the given array" do
19
+ seed_factory.should_receive(:add_seeds_to_hopper).with(hopper)
20
+
21
+ subject.add_seeds_to_hopper(hopper)
22
+ end
23
+ end
24
+ end