sprig 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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