penman 0.4.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2f3f532cb311801e2259b8040160fa9f090ee507
4
+ data.tar.gz: 2f7920834834e4e048decc6d57b53e8fcb408725
5
+ SHA512:
6
+ metadata.gz: 83bf9f99fe187dc2c74c5cff9a3447df3abb8dfee865ad0d08bb016de6b3d5b1ce33a1296c0cf6eabe7cda6f89c82e13f88d436ddca94d4c3e6b4a8c9ed76981
7
+ data.tar.gz: 3ecb902578d20e1a541d5fea6d3fdaac49026c8fa1a359426a3dda12dfede8180a887b9d83e2621004a5cf954ded22aa741754a1a0eef40411052343575e8ad6
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2015 YOURNAME
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/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Penman'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ Bundler::GemHelper.install_tasks
18
+
19
+ Dir["#{File.dirname(__FILE__)}/lib/tasks/*.rake"].sort.each { |ext| load ext }
data/config/routes.rb ADDED
@@ -0,0 +1,2 @@
1
+ Rails.application.routes.draw do
2
+ end
@@ -0,0 +1,14 @@
1
+ class CreateRecordTags < ActiveRecord::Migration
2
+ def change
3
+ create_table :record_tags do |t|
4
+ t.integer :record_id, null: false, default: 0
5
+ t.string :record_type, null: false
6
+ t.string :candidate_key, null: false
7
+ t.string :tag, null: false
8
+ t.boolean :created_this_session, null: false, default: false
9
+ t.timestamps
10
+ end
11
+
12
+ add_index 'record_tags', ['record_id', 'record_type'], name: 'index_record_tags_on_record_id_and_record_type', using: :btree
13
+ end
14
+ end
@@ -0,0 +1,36 @@
1
+ module Penman
2
+ class Configuration
3
+ attr_accessor :seed_path,
4
+ :default_candidate_key,
5
+ :seed_template_file,
6
+ :file_name_formatter,
7
+ :after_generate,
8
+ :validate_records_before_seed_generation
9
+
10
+ def initialize
11
+ @seed_path = 'db/migrate'
12
+ @default_candidate_key = :reference
13
+
14
+ root = File.expand_path '../..', __FILE__
15
+ @seed_template_file = File.join(root, 'templates', 'default.rb.erb')
16
+
17
+ @file_name_formatter = lambda do |model_name, seed_type|
18
+ "#{model_name.underscore.pluralize}_#{seed_type}"
19
+ end
20
+
21
+ @after_generate = lambda do |version, name|
22
+ return unless ActiveRecord::Base.connection.table_exists? 'schema_migrations'
23
+
24
+ unless Object.const_defined?('SchemaMigration')
25
+ Object.const_set('SchemaMigration', Class.new(ActiveRecord::Base))
26
+ end
27
+
28
+ return unless SchemaMigration.column_names.include? 'version'
29
+
30
+ SchemaMigration.find_or_create_by(version: version)
31
+ end
32
+
33
+ @validate_records_before_seed_generation = false
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,11 @@
1
+ module Penman
2
+ class Engine < ::Rails::Engine
3
+ initializer :append_migrations do |app|
4
+ unless app.config.paths["db/migrate"].include? root.to_s
5
+ config.paths["db/migrate"].expanded.each do |expanded_path|
6
+ app.config.paths["db/migrate"] << expanded_path
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ module RecordTagExceptions
2
+ RecordTagError = Class.new(StandardError)
3
+
4
+ InvalidCandidateKeyForRecord = Class.new(RecordTagError)
5
+ RecordNotFound = Class.new(RecordTagError)
6
+ TooManyTagsForRecord = Class.new(RecordTagError)
7
+ BadTracking = Class.new(RecordTagError)
8
+ end
@@ -0,0 +1,330 @@
1
+ require 'active_record'
2
+ require 'penman/penman_exceptions'
3
+ require 'penman/seed_file_generator'
4
+ require 'penman/seed_code'
5
+
6
+ module Penman
7
+ class RecordTag < ActiveRecord::Base
8
+ belongs_to :record, polymorphic: true
9
+ validates_uniqueness_of :tag, scope: [:record_type, :record_id]
10
+
11
+ before_save :encode_candidate_key
12
+
13
+ def encode_candidate_key
14
+ if self.candidate_key.is_a? Hash
15
+ self.candidate_key = self.candidate_key.to_json
16
+ end
17
+ end
18
+
19
+ def decode_candidate_key(key)
20
+ begin
21
+ ActiveSupport::JSON.decode(key).symbolize_keys
22
+ rescue JSON::ParserError
23
+ # This will occur if the candidate key isn't encoded as json.
24
+ # An example of this would be when we are tagging yaml files as touched when messing with lang.
25
+ # In that case we store the file path in the candidate key column as a regular string.
26
+ key
27
+ end
28
+ end
29
+
30
+ @@enabled = false
31
+ @@taggable_models = []
32
+
33
+ def candidate_key
34
+ decode_candidate_key(super)
35
+ end
36
+
37
+ class << self
38
+ def disable
39
+ @@enabled = false
40
+ end
41
+
42
+ def enable
43
+ @@enabled = true
44
+ end
45
+
46
+ def enabled?
47
+ @@enabled
48
+ end
49
+
50
+ def register(model)
51
+ @@taggable_models |= [model]
52
+ end
53
+
54
+ def tag(record, tag)
55
+ return unless @@enabled
56
+ candidate_key = record.class.try(:candidate_key) || Penman.config.default_candidate_key
57
+ candidate_key = [candidate_key] unless candidate_key.is_a? Array
58
+ raise RecordTagExceptions::InvalidCandidateKeyForRecord unless record_has_attributes?(record, candidate_key)
59
+
60
+ candidate_key_to_store =
61
+ if ['created', 'destroyed'].include? tag
62
+ Hash[candidate_key.map { |k| [k, record.send(k)] }].to_json
63
+ else # updated
64
+ Hash[candidate_key.map { |k| [k, record.send("#{k}_was")] }].to_json
65
+ end
66
+
67
+ created_tag = RecordTag.find_by(record: record, tag: 'created')
68
+ updated_tag = RecordTag.find_by(record: record, tag: 'updated')
69
+ destroyed_tag = RecordTag.find_by(record_type: record.class.name, candidate_key: candidate_key_to_store, tag: 'destroyed')
70
+
71
+ raise RecordTagExceptions::TooManyTagsForRecord if [created_tag, updated_tag, destroyed_tag].count { |t| t.present? } > 1
72
+
73
+ if created_tag.present?
74
+ case tag
75
+ when 'created'
76
+ raise RecordTagExceptions::BadTracking, format_error_message('created', 'created', candidate_key_to_store)
77
+ when 'updated'
78
+ created_tag.update!(tag: tag)
79
+ when 'destroyed'
80
+ created_tag.destroy!
81
+ end
82
+ elsif updated_tag.present?
83
+ case tag
84
+ when 'created'
85
+ raise RecordTagExceptions::BadTracking, format_error_message('updated', 'created', candidate_key_to_store)
86
+ when 'updated'
87
+ updated_tag.update!(tag: tag)
88
+ when 'destroyed'
89
+ if updated_tag.created_this_session
90
+ updated_tag.destroy!
91
+ else
92
+ updated_tag.update!(tag: tag)
93
+ end
94
+ end
95
+ elsif destroyed_tag.present?
96
+ case tag
97
+ when 'created'
98
+ # We make an updated tag in case non-candidate key attributes have changed, since we don't tack those.
99
+ destroyed_tag.update!(tag: 'updated', record_id: record.id)
100
+ when 'updated'
101
+ raise RecordTagExceptions::BadTracking, format_error_message('destroyed', 'updated', candidate_key_to_store)
102
+ when 'destroyed'
103
+ raise RecordTagExceptions::BadTracking, format_error_message('destroyed', 'destroyed', candidate_key_to_store)
104
+ end
105
+ else # new tag
106
+ RecordTag.create!(record: record, tag: tag, candidate_key: candidate_key_to_store, created_this_session: tag == 'created')
107
+ end
108
+ end
109
+
110
+ def find_tags_for_model(model)
111
+ find_tags_for_models(model)
112
+ end
113
+
114
+ def find_tags_for_models(*models)
115
+ RecordTag.where(record_type: models.map { |m| (m.is_a? String) ? m : m.name })
116
+ end
117
+
118
+ def create_custom(attributes = {})
119
+ attributes = { record_type: 'custom_tag', tag: 'touched', candidate_key: 'n/a' }.merge attributes
120
+ record_tag = RecordTag.find_or_create_by(attributes)
121
+ record_tag.update(record_id: record_tag.id) if record_tag.record_id == 0 # notice validation above, this just ensures that we don't violate the table constraint.
122
+ end
123
+
124
+ def generate_seeds
125
+ generate_seed_for_models(seed_order)
126
+ end
127
+
128
+ def generate_seed_for_models(models)
129
+ time = Time.now
130
+ seed_files = []
131
+
132
+ models.each do |model|
133
+ seed_files << generate_update_seed(model, time.strftime('%Y%m%d%H%M%S'))
134
+ time += 1.second
135
+ end
136
+
137
+ models.reverse.each do |model|
138
+ seed_files << generate_destroy_seed(model, time.strftime('%Y%m%d%H%M%S'))
139
+ time += 1.second
140
+ end
141
+
142
+ RecordTag.where(record_type: models.map(&:name)).destroy_all
143
+ seed_files.compact
144
+ end
145
+
146
+ def generate_seed_for_model(model)
147
+ time = Time.now
148
+ seed_files = []
149
+ seed_files << generate_update_seed(model, time.strftime('%Y%m%d%H%M%S'))
150
+ seed_files << generate_destroy_seed(model, (time + 1.second).strftime('%Y%m%d%H%M%S'))
151
+ RecordTag.where(record_type: model.name).delete_all
152
+ seed_files.compact
153
+ end
154
+
155
+ private
156
+ def reset_tree
157
+ @@roots = []
158
+ @@tree = {}
159
+ @@polymorphic = []
160
+ end
161
+
162
+ def add_model_to_tree(model)
163
+ reflections = model.reflect_on_all_associations(:belongs_to)
164
+
165
+ if reflections.find { |r| r.options[:polymorphic] }.present?
166
+ @@polymorphic << model
167
+ else
168
+ @@roots.push(model) unless @@tree.key?(model)
169
+ end
170
+
171
+ @@tree[model] = reflections.reject { |r| r.options[:polymorphic] || r.klass == model }.map(&:klass)
172
+ @@tree[model].each { |ch| @@tree[ch] ||= [] }
173
+
174
+ @@roots -= @@tree[model]
175
+ end
176
+
177
+ def seed_order
178
+ reset_tree
179
+ @@taggable_models.each { |m| add_model_to_tree(m) }
180
+
181
+ seed_order = []
182
+
183
+ recurse_on = -> (node) do
184
+ return unless node.ancestors.include?(Taggable)
185
+ @@tree[node].each { |n| recurse_on.call(n) }
186
+ seed_order |= [node]
187
+ end
188
+
189
+ @@roots.each { |node| recurse_on.call(node) }
190
+ @@polymorphic.each { |node| recurse_on.call(node) }
191
+
192
+ seed_order | @@polymorphic
193
+ end
194
+
195
+ def generate_update_seed(model, timestamp)
196
+ validate_records_for_model(model) if Penman.config.validate_records_before_seed_generation
197
+ touched_tags = RecordTag.where(record_type: model.name, tag: ['created', 'updated']).includes(:record)
198
+ return nil if touched_tags.empty?
199
+ seed_code = SeedCode.new
200
+ seed_code << 'penman_initially_enabled = Penman.enabled?'
201
+ seed_code << 'Penman.disable'
202
+
203
+ touched_tags.each do |tag|
204
+ seed_code << "# Generating seed for #{tag.tag.upcase} tag."
205
+ seed_code << "record = #{model.name}.find_by(#{print_candidate_key(tag.record)})"
206
+ seed_code << "record = #{model.name}.find_or_initialize_by(#{attribute_string_from_hash(model, tag.candidate_key)}) if record.nil?"
207
+
208
+ column_hash = Hash[
209
+ model.attribute_names
210
+ .reject { |col| col == model.primary_key }
211
+ .map { |col| [col, tag.record.send(col)] }
212
+ ]
213
+
214
+ seed_code << "record.update!(#{attribute_string_from_hash(model, column_hash)})"
215
+ end
216
+
217
+ seed_code << 'Penman.enable if penman_initially_enabled'
218
+ seed_file_name = Penman.config.file_name_formatter.call(model.name, 'updates')
219
+ sfg = SeedFileGenerator.new(seed_file_name, timestamp, seed_code)
220
+ sfg.write_seed
221
+ end
222
+
223
+ def generate_destroy_seed(model, timestamp)
224
+ destroyed_tags = RecordTag.where(record_type: model.name, tag: 'destroyed')
225
+ return nil if destroyed_tags.empty?
226
+ seed_code = SeedCode.new
227
+ seed_code << 'penman_initially_enabled = Penman.enabled?'
228
+ seed_code << 'Penman.disable'
229
+
230
+ destroyed_tags.map(&:candidate_key).each do |record_candidate_key|
231
+ seed_code << "record = #{model.name}.find_by(#{attribute_string_from_hash(model, record_candidate_key)})"
232
+ seed_code << "record.try(:destroy)"
233
+ end
234
+
235
+ seed_code << 'Penman.enable if penman_initially_enabled'
236
+ seed_file_name = Penman.config.file_name_formatter.call(model.name, 'destroys')
237
+ sfg = SeedFileGenerator.new(seed_file_name, timestamp, seed_code)
238
+ sfg.write_seed
239
+ end
240
+
241
+ def validate_records_for_model(model)
242
+ RecordTag.where(record_type: model.name, tag: ['updated', 'created'])
243
+ .includes(:record)
244
+ .each { |r| r.record.validate! }
245
+ end
246
+
247
+ def print_candidate_key(record)
248
+ candidate_key = record.class.try(:candidate_key) || Penman.config.default_candidate_key
249
+ candidate_key = [candidate_key] unless candidate_key.is_a? Array
250
+ raise RecordTagExceptions::InvalidCandidateKeyForRecord unless record_has_attributes?(record, candidate_key)
251
+
252
+ candidate_key_hash = {}
253
+ candidate_key.each { |key| candidate_key_hash[key] = record.send(key) }
254
+ attribute_string_from_hash(record.class, candidate_key_hash)
255
+ end
256
+
257
+ def attribute_string_from_hash(model, column_hash)
258
+ column_hash.symbolize_keys!
259
+ formatted_candidate_key = []
260
+
261
+ column_hash.each do |k, v|
262
+ reflection = find_foreign_key_relation(model, k)
263
+
264
+ if reflection && v.present?
265
+ associated_model = if reflection.polymorphic?
266
+ column_hash[reflection.foreign_type.to_sym].constantize
267
+ else
268
+ reflection.klass
269
+ end
270
+
271
+ if associated_model.ancestors.include?(Taggable) || associated_model.respond_to?(:candidate_key)
272
+ primary_key = reflection.options[:primary_key] || associated_model.primary_key
273
+ associated_record = associated_model.find_by(primary_key => v)
274
+ to_add = "#{reflection.name}: #{associated_model.name}.find_by("
275
+
276
+ if associated_record.present?
277
+ to_add += "#{print_candidate_key(associated_record)})"
278
+ else # likely this record was destroyed, so we should have a tag for it
279
+ tag = RecordTag.find_by(record_type: associated_model.name, record_id: v)
280
+ raise RecordTagExceptions::RecordNotFound, "while processing #{column_hash}" if tag.nil?
281
+ to_add += "#{attribute_string_from_hash(associated_model, tag.candidate_key)})"
282
+ end
283
+
284
+ formatted_candidate_key << to_add
285
+ next
286
+ end
287
+ end
288
+
289
+ formatted_candidate_key << "#{k}: #{primitive_string(v)}"
290
+ end
291
+
292
+ formatted_candidate_key.join(', ')
293
+ end
294
+
295
+ def find_foreign_key_relation(model, accessor)
296
+ model.reflect_on_all_associations.find do |r|
297
+ begin
298
+ r.foreign_key.to_sym == accessor.to_sym
299
+ rescue NameError
300
+ false
301
+ end
302
+ end
303
+ end
304
+
305
+ def record_has_attributes?(record, attributes)
306
+ attributes.each do |attribute|
307
+ return false unless record.has_attribute?(attribute)
308
+ end
309
+
310
+ true
311
+ end
312
+
313
+ def primitive_string(p)
314
+ if p.nil?
315
+ 'nil'
316
+ elsif p.is_a? String
317
+ "'#{p}'"
318
+ elsif p.is_a? Time
319
+ "Time.parse('#{p}')"
320
+ else
321
+ "#{p}"
322
+ end
323
+ end
324
+
325
+ def format_error_message(existing_tag, new_tag, record_to_store)
326
+ "found an existing '#{existing_tag}' tag for record while tagging, '#{new_tag}' - #{record_to_store}"
327
+ end
328
+ end
329
+ end
330
+ end
@@ -0,0 +1,21 @@
1
+ module Penman
2
+ class SeedCode
3
+ def initialize(seed_code = [])
4
+ @seed_code = seed_code
5
+ end
6
+
7
+ def << (seed_line)
8
+ @seed_code << seed_line
9
+ end
10
+
11
+ def print_with_leading_spaces(num_spaces)
12
+ spaces = "\n" + ' ' * num_spaces
13
+ @seed_code.join(spaces)
14
+ end
15
+
16
+ def print_with_leading_tabs(num_tabs)
17
+ tabs = "\n" + "\t" * num_tabs
18
+ @seed_code.join(tabs)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ module Penman
2
+ class SeedFileGenerator
3
+ attr_reader :seed_code
4
+ attr_reader :file_name
5
+ attr_reader :timestamp
6
+
7
+ def initialize(file_name, timestamp, seed_code)
8
+ @seed_code = seed_code
9
+ @file_name = file_name
10
+ @timestamp = timestamp
11
+ end
12
+
13
+ def write_seed
14
+ erb = ERB.new(File.read(Penman.config.seed_template_file))
15
+ seed_file_name = "#{@timestamp}_#{@file_name}.rb"
16
+ full_seed_file_path = File.join(Penman.config.seed_path, seed_file_name)
17
+ IO.write(full_seed_file_path, erb.result(binding))
18
+
19
+ if Penman.config.after_generate.present?
20
+ Penman.config.after_generate.call(@timestamp, @file_name)
21
+ end
22
+
23
+ full_seed_file_path
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ module Taggable
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ after_create { Penman::RecordTag.tag(self, 'created') }
6
+ after_update { Penman::RecordTag.tag(self, 'updated') }
7
+ after_destroy { Penman::RecordTag.tag(self, 'destroyed') }
8
+
9
+ Penman::RecordTag.register(self)
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ module Penman
2
+ MAJOR = 0 # api
3
+ MINOR = 4 # features
4
+ PATCH = 9 # bug fixes
5
+
6
+ VERSION = [MAJOR, MINOR, PATCH].compact.join('.')
7
+ end
data/lib/penman.rb ADDED
@@ -0,0 +1,39 @@
1
+ require "penman/engine"
2
+ require 'penman/configuration'
3
+ require 'penman/record_tag'
4
+ require 'penman/taggable'
5
+ require 'penman/seed_file_generator'
6
+
7
+ module Penman
8
+ class << self
9
+ attr_writer :config
10
+ end
11
+
12
+ def self.config
13
+ @config ||= Configuration.new
14
+ end
15
+
16
+ def self.configure
17
+ yield(config)
18
+ end
19
+
20
+ def self.reset
21
+ @config = Configuration.new
22
+ end
23
+
24
+ def self.enable
25
+ RecordTag.enable
26
+ end
27
+
28
+ def self.disable
29
+ RecordTag.disable
30
+ end
31
+
32
+ def self.enabled?
33
+ RecordTag.enabled?
34
+ end
35
+
36
+ def self.generate_seeds
37
+ RecordTag.generate_seeds
38
+ end
39
+ end
@@ -0,0 +1,6 @@
1
+ # namespace :penman do
2
+ # desc 'Initialize Penman'
3
+ # task :init do
4
+
5
+ # end
6
+ # end
@@ -0,0 +1,7 @@
1
+ # generated by penman
2
+
3
+ class <%= file_name.camelize %> < ActiveRecord::Migration
4
+ def change
5
+ <%= seed_code.print_with_leading_spaces(4) %>
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,154 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: penman
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.9
5
+ platform: ruby
6
+ authors:
7
+ - Mat Pataki
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-03-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: mysql2
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.3'
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: 0.3.18
37
+ type: :development
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - "~>"
42
+ - !ruby/object:Gem::Version
43
+ version: '0.3'
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 0.3.18
47
+ - !ruby/object:Gem::Dependency
48
+ name: rspec-rails
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '3.3'
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: 3.3.3
57
+ type: :development
58
+ prerelease: false
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: '3.3'
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: 3.3.3
67
+ - !ruby/object:Gem::Dependency
68
+ name: pry-rails
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '0.3'
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: 0.3.4
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '0.3'
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: 0.3.4
87
+ - !ruby/object:Gem::Dependency
88
+ name: database_cleaner
89
+ requirement: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - "~>"
92
+ - !ruby/object:Gem::Version
93
+ version: '1.5'
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: 1.5.1
97
+ type: :development
98
+ prerelease: false
99
+ version_requirements: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.5'
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: 1.5.1
107
+ description: A scribe for your database and Rails project, Penman records your DB
108
+ changes and produces seed files that reflect them.
109
+ email:
110
+ - matpataki@gmail.com
111
+ executables: []
112
+ extensions: []
113
+ extra_rdoc_files: []
114
+ files:
115
+ - MIT-LICENSE
116
+ - Rakefile
117
+ - config/routes.rb
118
+ - db/migrate/20150909222413_create_record_tags.rb
119
+ - lib/penman.rb
120
+ - lib/penman/configuration.rb
121
+ - lib/penman/engine.rb
122
+ - lib/penman/penman_exceptions.rb
123
+ - lib/penman/record_tag.rb
124
+ - lib/penman/seed_code.rb
125
+ - lib/penman/seed_file_generator.rb
126
+ - lib/penman/taggable.rb
127
+ - lib/penman/version.rb
128
+ - lib/tasks/penman_tasks.rake
129
+ - lib/templates/default.rb.erb
130
+ homepage: http://uken.com
131
+ licenses:
132
+ - MIT
133
+ metadata: {}
134
+ post_install_message:
135
+ rdoc_options: []
136
+ require_paths:
137
+ - lib
138
+ required_ruby_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ required_rubygems_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ requirements: []
149
+ rubyforge_project:
150
+ rubygems_version: 2.4.5.1
151
+ signing_key:
152
+ specification_version: 4
153
+ summary: Tracks database changes and generates representative seed files.
154
+ test_files: []