config_scripts 0.4.1

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 (83) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.rspec +2 -0
  4. data/.yardopts +1 -0
  5. data/CHANGELOG.md +17 -0
  6. data/Gemfile +4 -0
  7. data/Gemfile.lock +124 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +55 -0
  10. data/Rakefile +1 -0
  11. data/TODO.md +8 -0
  12. data/config_scripts.gemspec +31 -0
  13. data/lib/config_scripts/generators/config_script.rb +24 -0
  14. data/lib/config_scripts/generators/migrations.rb +36 -0
  15. data/lib/config_scripts/generators.rb +2 -0
  16. data/lib/config_scripts/scripts/script.rb +135 -0
  17. data/lib/config_scripts/scripts/script_history.rb +39 -0
  18. data/lib/config_scripts/scripts.rb +9 -0
  19. data/lib/config_scripts/seeds/seed_set.rb +321 -0
  20. data/lib/config_scripts/seeds/seed_type.rb +361 -0
  21. data/lib/config_scripts/seeds.rb +8 -0
  22. data/lib/config_scripts/tasks/pending_migrations.rake +11 -0
  23. data/lib/config_scripts/tasks/seeds.rake +18 -0
  24. data/lib/config_scripts/tasks.rb +12 -0
  25. data/lib/config_scripts/version.rb +4 -0
  26. data/lib/config_scripts.rb +9 -0
  27. data/spec/dummy/README.rdoc +28 -0
  28. data/spec/dummy/Rakefile +6 -0
  29. data/spec/dummy/app/assets/images/.keep +0 -0
  30. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  31. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  32. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  33. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  34. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  35. data/spec/dummy/app/mailers/.keep +0 -0
  36. data/spec/dummy/app/models/.keep +0 -0
  37. data/spec/dummy/app/models/concerns/.keep +0 -0
  38. data/spec/dummy/app/models/hair_color.rb +2 -0
  39. data/spec/dummy/app/models/person.rb +4 -0
  40. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  41. data/spec/dummy/bin/bundle +3 -0
  42. data/spec/dummy/bin/rails +4 -0
  43. data/spec/dummy/bin/rake +4 -0
  44. data/spec/dummy/config/application.rb +23 -0
  45. data/spec/dummy/config/boot.rb +5 -0
  46. data/spec/dummy/config/database.yml +25 -0
  47. data/spec/dummy/config/environment.rb +5 -0
  48. data/spec/dummy/config/environments/development.rb +29 -0
  49. data/spec/dummy/config/environments/production.rb +80 -0
  50. data/spec/dummy/config/environments/test.rb +36 -0
  51. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  52. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  53. data/spec/dummy/config/initializers/inflections.rb +16 -0
  54. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  55. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  56. data/spec/dummy/config/initializers/session_store.rb +3 -0
  57. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  58. data/spec/dummy/config/locales/en.yml +23 -0
  59. data/spec/dummy/config/routes.rb +56 -0
  60. data/spec/dummy/config.ru +4 -0
  61. data/spec/dummy/db/migrate/20140208014550_create_config_scripts.rb +7 -0
  62. data/spec/dummy/db/migrate/20140208161829_create_people.rb +9 -0
  63. data/spec/dummy/db/migrate/20140208182050_create_hair_colors.rb +9 -0
  64. data/spec/dummy/db/migrate/20140208182101_add_hair_color_to_person.rb +6 -0
  65. data/spec/dummy/db/migrate/20140208225801_add_scope_to_people.rb +6 -0
  66. data/spec/dummy/db/migrate/20140209132911_add_hex_value_to_hair_color.rb +5 -0
  67. data/spec/dummy/db/schema.rb +38 -0
  68. data/spec/dummy/lib/assets/.keep +0 -0
  69. data/spec/dummy/log/.keep +0 -0
  70. data/spec/dummy/public/404.html +58 -0
  71. data/spec/dummy/public/422.html +58 -0
  72. data/spec/dummy/public/500.html +57 -0
  73. data/spec/dummy/public/favicon.ico +0 -0
  74. data/spec/generators/config_script_spec.rb +23 -0
  75. data/spec/generators/migrations_spec.rb +23 -0
  76. data/spec/scripts/script_history_spec.rb +53 -0
  77. data/spec/scripts/script_spec.rb +282 -0
  78. data/spec/seeds/seed_set_spec.rb +371 -0
  79. data/spec/seeds/seed_type_spec.rb +439 -0
  80. data/spec/spec_helper.rb +38 -0
  81. data/templates/config_script.rb +9 -0
  82. data/templates/create_config_scripts_migration.rb +7 -0
  83. metadata +321 -0
@@ -0,0 +1,321 @@
1
+ module ConfigScripts
2
+ module Seeds
3
+ # This class represents a set of related seeds.
4
+ #
5
+ # These seeds will be stored as CSV files in a folder together.
6
+ class SeedSet
7
+ # @return [String]
8
+ # The name of the folder for this seed set.
9
+ attr_reader :name
10
+
11
+ # @return [Integer]
12
+ # A number identifying this set. Seed sets will be run from the one with
13
+ # the lowest number to the highest.
14
+ attr_accessor :set_number
15
+
16
+ # @return [String]
17
+ # The name of the folder to which we will write the seeds.
18
+ attr_reader :folder
19
+
20
+ # @return [Hash]
21
+ # Arbitrary extra data passed in when defining the seed set.
22
+ attr_reader :options
23
+
24
+ # @return [Hash]
25
+ # A hash mapping class names to the {SeedType} instances describing how
26
+ # to handle seeds for that class within this set.
27
+ attr_reader :seed_types
28
+
29
+ # @return [Proc]
30
+ # The block that will be run when resetting the records during a load.
31
+ attr_reader :reset_block
32
+
33
+ class << self
34
+ # @!group Registration
35
+
36
+ # @return [Hash<Integer, SeedSet>]
37
+ # The seed sets that have been defined.
38
+ attr_reader :registered_sets
39
+
40
+ # This method adds a new seed set to our registry.
41
+ #
42
+ # If there is already a registered seed set with this set's set_number,
43
+ # the number will be incremented until it is available.
44
+ #
45
+ # @param [SeedSet] set
46
+ # The new seed set.
47
+ #
48
+ # @return [SeedSet]
49
+ def register_seed_set(set)
50
+ @registered_sets ||= {}
51
+ while @registered_sets[set.set_number]
52
+ return if @registered_sets[set.set_number] == set
53
+ set.set_number += 1
54
+ end
55
+ @registered_sets[set.set_number] = set
56
+ end
57
+
58
+ # This method wipes out our records of registered seed sets.
59
+ #
60
+ # @return [Hash]
61
+ # The new list of seed sets.
62
+ def clear_registered_sets
63
+ @registered_sets = {}
64
+ end
65
+
66
+ # This method loads all of the seed definitions from the files in the
67
+ # +db/seeds/definitions+ directory.
68
+ def load_seed_sets
69
+ Dir[Rails.root.join('db', 'seeds', 'definitions', '*')].each do |file|
70
+ require file
71
+ end
72
+ end
73
+
74
+ # @!group Batch Operations
75
+
76
+ # This method writes the data for every seed set to its seed data
77
+ # folder.
78
+ #
79
+ # @param [Integer] set_number
80
+ # The number of the set to write.
81
+ #
82
+ # @return [Array]
83
+ def write(set_number=nil)
84
+ self.each_set(set_number, &:write)
85
+ end
86
+
87
+ # This method loads the data from every seed set into the database.
88
+ #
89
+ # @param [Integer] set_number
90
+ # The number of the set to read.
91
+ #
92
+ # @return [Array]
93
+ def read(set_number=nil)
94
+ self.each_set set_number do |set|
95
+ set.read(set_number.present?)
96
+ end
97
+ end
98
+
99
+ # This method lists every seed set, with its set number.
100
+ # @return [Array]
101
+ def list
102
+ self.each_set do |set|
103
+ puts "#{set.set_number}: #{set.name}"
104
+ end
105
+ end
106
+
107
+ # This method runs a block on each set that the app has defined.
108
+ #
109
+ # The block will be given one parameter, which is the seed set.
110
+ #
111
+ # @param [String] set_number
112
+ # The number of the set that we should run.
113
+ #
114
+ # @return [Array]
115
+ def each_set(set_number=nil, &block)
116
+ @registered_sets ||= {}
117
+ self.load_seed_sets
118
+ if set_number
119
+ if self.registered_sets[set_number]
120
+ block.call(self.registered_sets[set_number])
121
+ end
122
+ else
123
+ self.registered_sets.keys.sort.each do |set_number|
124
+ block.call(self.registered_sets[set_number])
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ # @!group Creation
131
+
132
+ # This method creates a new seed set.
133
+ #
134
+ # It should be given a block, which will be run on the instance, and
135
+ # which should use the {#seeds_for} method to define seed types.
136
+ #
137
+ # @param [String] name
138
+ # The name for the folder for the seeds.
139
+ #
140
+ # @param [Integer] set_number
141
+ # The set_number in which this seed set should be run.
142
+ #
143
+ # @param [String] folder
144
+ # The folder that we should use for this seed set. If this is not
145
+ # provided, we will use the name.
146
+ #
147
+ # @param [Hash] options
148
+ # Additional information that can be made accessible to the seed type
149
+ # definitions.
150
+ def initialize(name, set_number=1, folder=nil, options = {}, &block)
151
+ @name = name.to_s
152
+ @set_number = set_number
153
+ @folder = folder || @name
154
+ @options = options
155
+ @seed_types = {}
156
+ self.instance_eval(&block) if block_given?
157
+ ConfigScripts::Seeds::SeedSet.register_seed_set(self)
158
+ end
159
+
160
+ # @!group Reading and Writing
161
+
162
+ # This method writes the data for this seed set to its seed folder.
163
+ #
164
+ # It will create the folder and then write the file for each seed type
165
+ # that has been defined.
166
+ def write
167
+ folder_path = Rails.root.join('db', 'seeds', 'data', self.folder)
168
+ FileUtils.mkdir_p(folder_path)
169
+ puts "Writing seeds for #{self.name} to #{folder_path}"
170
+ self.seed_types.each do |klass, seed_type|
171
+ seed_type.write_to_folder(folder_path)
172
+ end
173
+ end
174
+
175
+ # This method reads the data for this seed set from its seed folder.
176
+ #
177
+ # @param [Boolean] reset
178
+ # Whether we should reset the existing records before loading the seeds.
179
+ #
180
+ # It will load the data for each seed type's file, enclosing all the
181
+ # seed types in a transaction block.
182
+ def read(reset=false)
183
+ folder_path = Rails.root.join('db', 'seeds', 'data', self.folder)
184
+ FileUtils.mkdir_p(folder_path)
185
+ puts "Reading seeds for #{self.name} from #{folder_path}"
186
+ ActiveRecord::Base.transaction do
187
+ self.reset_records if reset
188
+ self.seed_types.each do |klass, seed_type|
189
+ seed_type.read_from_folder(folder_path)
190
+ end
191
+ end
192
+ end
193
+
194
+ # This method resets all the existing records that could be populated by
195
+ # this seed set.
196
+ def reset_records
197
+ self.reset_block.call if self.reset_block
198
+ end
199
+
200
+ # @!group DSL
201
+
202
+ # This method defines a new seed type within this seed set.
203
+ #
204
+ # This method should be given a block, which will be passed to the
205
+ # initializer for the new seed type.
206
+ #
207
+ # @param [Class] klass
208
+ # The model class whose seed data this stores.
209
+ #
210
+ # @param [String] filename
211
+ # The name of the file in which the seed data should be stored.
212
+ # If this is not provided, it will use the name of the class.
213
+ # This should not include the file extension.
214
+ #
215
+ # @return [SeedType]
216
+ def seeds_for(klass, filename=nil, &block)
217
+ filename ||= klass.name.underscore.pluralize
218
+ @seed_types[klass] = SeedType.new(self, klass, filename, &block)
219
+ end
220
+
221
+ # This method defines a block that will be run when resetting existing
222
+ # records.
223
+ #
224
+ # This block will be run when loading a seed set as a one-off, but not
225
+ # when loading all the seed sets.
226
+ #
227
+ # @return [Proc]
228
+ def when_resetting(&block)
229
+ @reset_block = block
230
+ end
231
+
232
+ # @!group Seed Identifiers
233
+
234
+ # This method gets a seed type that we have on file for a class.
235
+ #
236
+ # @param [Class] klass
237
+ # The class whose seeds we are dealing with.
238
+ #
239
+ # @return [SeedType]
240
+ def seed_type_for_class(klass)
241
+ while klass && klass != ActiveRecord::Base
242
+ seed_type = self.seed_types[klass]
243
+ if seed_type
244
+ return seed_type
245
+ end
246
+ klass = klass.superclass
247
+ end
248
+ end
249
+
250
+
251
+ # This method gets a unique identifier for a record when writing seeds
252
+ # that refer to it.
253
+ #
254
+ # It will look for a seed type that handles seeds for a class in the
255
+ # record's class heirarchy, and use that seed type's
256
+ # {#seed_identifier_for_record} method. If it cannot find a seed type,
257
+ # it will use the record's ID.
258
+ #
259
+ # @param [ActiveRecord::Base] record
260
+ # The record whose identifier we are generating.
261
+ #
262
+ # @return [String]
263
+ def seed_identifier_for_record(record)
264
+ seed_type = self.seed_type_for_class(record.class)
265
+ if seed_type
266
+ seed_type.seed_identifier_for_record(record)
267
+ else
268
+ record.id rescue nil
269
+ end
270
+ end
271
+
272
+ # This method finds a record based on a unique identifier in the seed
273
+ # data.
274
+ #
275
+ # This will look for a seed type for the class, and use its
276
+ # {#record_for_seed_identifier} method to get the record.
277
+ #
278
+ # The result of this will be memoized so that we do not have to keep
279
+ # looking up the records.
280
+ #
281
+ # @param [Class] klass
282
+ # The model class for the record we are finding.
283
+ #
284
+ # @param [Array<String>] identifier
285
+ # The identifier components from the seed data.
286
+ #
287
+ # @return [ActiveRecord::Base]
288
+ # The model record.
289
+ def record_for_seed_identifier(klass, identifier)
290
+ seed_type = self.seed_type_for_class(klass)
291
+ return nil unless seed_type
292
+
293
+ @record_cache ||= {}
294
+ @record_cache[klass] ||= {}
295
+
296
+ cache_identifier = identifier.dup
297
+
298
+ while cache_identifier.any?
299
+ record = @record_cache[klass][cache_identifier]
300
+ if record
301
+ cache_identifier.count.times { identifier.shift }
302
+ return record
303
+ end
304
+ cache_identifier.pop
305
+ end
306
+
307
+ if seed_type
308
+ cache_identifier = identifier.dup
309
+ record = seed_type.record_for_seed_identifier(identifier)
310
+
311
+ if identifier.any?
312
+ cache_identifier = cache_identifier[0...-1*identifier.count]
313
+ end
314
+
315
+ @record_cache[klass][cache_identifier] = record
316
+ end
317
+ record
318
+ end
319
+ end
320
+ end
321
+ end
@@ -0,0 +1,361 @@
1
+ require 'csv'
2
+
3
+ module ConfigScripts
4
+ module Seeds
5
+ # This class encapsulates information about how to write seeds for a class
6
+ # to a seed file.
7
+ class SeedType
8
+ # @!group Attributes
9
+
10
+ # @return [SeedSet]
11
+ # The seed set that this type has been defined within.
12
+ attr_reader :seed_set
13
+
14
+ # @return [Class]
15
+ # The model class whose records we are storing in this seed file.
16
+ attr_reader :klass
17
+
18
+ # @return [String]
19
+ # The name of the file that we will store these records in.
20
+ attr_reader :filename
21
+
22
+ # @return [Array<Symbol>]
23
+ # The names of the attributes on the model object that we store in the
24
+ # seed file.
25
+ attr_reader :attributes
26
+
27
+ # @return [Array<Symbol>]
28
+ # The names of the attributes used to compose a unique identifier for
29
+ # a record.
30
+ attr_reader :identifier_attributes
31
+
32
+ # @return [Array]
33
+ # The active record associations for the model class for this seed file.
34
+ attr_reader :associations
35
+
36
+ # @return [Hash<Symbol, Proc>]
37
+ # The attributes that we generate dynamically after loading the ones from
38
+ # the seed file.
39
+ attr_reader :dynamic_readers
40
+
41
+ # @return [Hash<Symbol, Proc>]
42
+ # The attributes that we generate dynamically when writing things to the
43
+ # seed file.
44
+ attr_reader :dynamic_writers
45
+
46
+ # @return [Array<Array>]
47
+ # The scopes that we apply when fetching items.
48
+ #
49
+ # Each entry will be an array. The first entry in the inner arrays will
50
+ # be a symbol, the name of a method that can be run on a relation. The
51
+ # result of the array will be passed in when running the method on the
52
+ # scope.
53
+ attr_reader :scopes
54
+
55
+ # @!group Creation
56
+
57
+ # This method creates a new seed type.
58
+ #
59
+ # This method should be given a block, which will be run in the instance
60
+ # context of the new seed type. That block should use the DSL methods to
61
+ # fill in the details for the seed type.
62
+ #
63
+ # @param [SeedSet] seed_set
64
+ # The seed set that this seed type is defined within.
65
+ #
66
+ # @param [Class] klass
67
+ # The model class whose data we are running.
68
+ #
69
+ # @param [String] filename
70
+ # The name of the file in which the seed data will be stored.
71
+ def initialize(seed_set, klass, filename, &block)
72
+ @seed_set = seed_set
73
+ @klass = klass
74
+ @filename = filename
75
+ @attributes = []
76
+ @identifier_attributes = [:id]
77
+ @scopes = []
78
+ @dynamic_writers = {}
79
+ @dynamic_readers = {}
80
+
81
+ @associations = {}
82
+ @klass.reflect_on_all_associations.each do |association|
83
+ @associations[association.name] = association.klass rescue nil
84
+ end
85
+
86
+ self.instance_eval(&block) if block_given?
87
+ end
88
+
89
+ # @!group DSL
90
+
91
+ # This method adds new attributes to the ones written in this seed type.
92
+ #
93
+ # @param [Array<Symbol>] new_attributes
94
+ # The attributes to add.
95
+ #
96
+ # @return [Array<Symbol>]
97
+ # The full list of attributes after the new ones are added.
98
+ def has_attributes(*new_attributes)
99
+ @attributes += new_attributes
100
+ end
101
+
102
+ # This method defines the attributes used to generate a unique identifier
103
+ # for a record.
104
+ #
105
+ # @param [Array<Symbol>] attributes
106
+ # The attributes that form the unique identifier.
107
+ #
108
+ # @return [Array<Symbol>]
109
+ # The attributes.
110
+ def has_identifier_attributes(*attributes)
111
+ @identifier_attributes = attributes
112
+ end
113
+
114
+ # This method adds a scope to the list used to filter the records for
115
+ # writing.
116
+ #
117
+ # @param [Symbol] method
118
+ # The name of the method to call on the relation.
119
+ #
120
+ # @param [Array] args
121
+ # The arguments that will be passed into the scope method on the
122
+ # relation.
123
+ #
124
+ # @return [Array]
125
+ # The full list of scopes.
126
+ def has_scope(method, *args)
127
+ @scopes << [method, args]
128
+ end
129
+
130
+ # This method registers a custom block that will be run when reading a
131
+ # value from the seed file.
132
+ #
133
+ # This method takes a block that will be run on the value from the seed
134
+ # file. The return value of the block will be used in place of the
135
+ # original value from the seed file.
136
+ #
137
+ # @param [Symbol] attribute
138
+ # The attribute that we are reading.
139
+ def when_reading(attribute, &block)
140
+ @dynamic_readers[attribute] = block
141
+ end
142
+
143
+ # This method registers a custom block that will be run when writing a
144
+ # value to the seed file.
145
+ #
146
+ # This method takes a block that will be run on the item whose values we
147
+ # are writing. The return value of the block will be used instead of
148
+ # running the method on the record.
149
+ #
150
+ # @param [Symbol] attribute
151
+ # The attribute we are writing.
152
+ def when_writing(attribute, &block)
153
+ @dynamic_writers[attribute] = block
154
+ end
155
+
156
+ # @!group Reading and Writing
157
+
158
+ # This method writes the seed data file to a folder.
159
+ #
160
+ # This will write a header row with the names of the attributes, and then
161
+ # write a row for each item from the {#items} method. It will use the
162
+ # {#write_value_for_attribute} method to get the values for the CSV file.
163
+ #
164
+ # If this seed type has no attributes, this method will not write
165
+ # anything.
166
+ #
167
+ # @param [String] folder
168
+ # The full path to the folder to write to.
169
+ def write_to_folder(folder)
170
+ return unless attributes.any?
171
+ CSV.open(File.join(folder, "#{self.filename}.csv"), 'w') do |csv|
172
+ csv << self.attributes
173
+ self.items.each do |item|
174
+ data = self.attributes.collect { |attribute| self.write_value_for_attribute(item, attribute) }
175
+ csv << data
176
+ end
177
+ end
178
+ end
179
+
180
+ # This method reads the seed data from a file, and creates new records
181
+ # from it.
182
+ #
183
+ # This will extract all the rows from the CSV file, and use
184
+ # {#read_value_for_attribute} to get the attributes for the record from
185
+ # each cell in the CSV file.
186
+ #
187
+ # If this seed type has no attributes, this method will not try to read
188
+ # the file.
189
+ #
190
+ # @param [String] folder
191
+ # The full path to the folder with the seed files.
192
+ def read_from_folder(folder)
193
+ return unless attributes.any?
194
+ CSV.open(File.join(folder, "#{self.filename}.csv"), headers: true) do |csv|
195
+ csv.each do |row|
196
+ record = self.klass.new
197
+ row.each do |attribute, value|
198
+ attribute = attribute.to_sym
199
+ value = self.read_value_for_attribute(value, attribute)
200
+ record.send("#{attribute}=", value)
201
+ end
202
+ record.save!
203
+ end
204
+ end
205
+ end
206
+
207
+ # This method gets the value that we should write into the CSV file for
208
+ # an attribute.
209
+ #
210
+ # If the value for that attribute is another model record, this will get
211
+ # its seed identifier from the seed set. Otherwise, it will just use the
212
+ # value.
213
+ #
214
+ # If the attribute is a polymorphic foreign key, this will prefix the seed
215
+ # identifier with the class name.
216
+ #
217
+ # @param [ActiveRecord::Base] item
218
+ # The record whose value we are getting.
219
+ #
220
+ # @param [Symbol] attribute
221
+ # The attribute we are getting.
222
+ #
223
+ # @return [String]
224
+ # The value to write.
225
+ def write_value_for_attribute(item, attribute)
226
+ if @dynamic_writers[attribute]
227
+ value = @dynamic_writers[attribute].call(item)
228
+ else
229
+ value = item.send(attribute)
230
+ end
231
+
232
+ if value.is_a?(ActiveRecord::Base)
233
+ identifier = self.seed_set.seed_identifier_for_record(value)
234
+ if !self.associations[attribute]
235
+ identifier = "#{value.class.name}::#{identifier}"
236
+ end
237
+ value = identifier
238
+ end
239
+ value
240
+ end
241
+
242
+ # This method takes a value from the CSV file and gives back the value
243
+ # that should be set on the record.
244
+ #
245
+ # If the attribute is an association, this will pass it to the seed set
246
+ # as a seed identifier. If it is a polymorphic association, it will use
247
+ # the first part of the seed identifier as a class name.
248
+ #
249
+ # @param [String] value
250
+ # The value from the CSV file.
251
+ #
252
+ # @param [Symbol] attribute
253
+ # The name of the attribute that we are formatting.
254
+ #
255
+ # @return [Object]
256
+ # The value to set on the record.
257
+ def read_value_for_attribute(value, attribute)
258
+ if @dynamic_readers[attribute]
259
+ value = @dynamic_readers[attribute].call(value)
260
+ end
261
+
262
+ if @associations.has_key?(attribute)
263
+ return nil if value.blank?
264
+ value = self.read_value_for_association(attribute, value.split("::"))
265
+ end
266
+ value
267
+ end
268
+
269
+ # This method gets the value that we will assign to an attribute for an
270
+ # association.
271
+ #
272
+ # This will remove as many entries from the identifier as it needs to get
273
+ # the value.
274
+ #
275
+ # @param [Symbol] attribute
276
+ # The name of the association.
277
+ #
278
+ # @param [Array<String>] identifier
279
+ # The components of the seed identifier.
280
+ #
281
+ # @return [ActiveRecord::Base]
282
+ def read_value_for_association(attribute, identifier)
283
+ klass = @associations[attribute]
284
+ unless klass
285
+ class_name = identifier.shift
286
+ klass = class_name.constantize
287
+ end
288
+ value = self.seed_set.record_for_seed_identifier(klass, identifier)
289
+ end
290
+
291
+ # @!group Fetching
292
+
293
+ # This method gets a relation encompassing all the records in the class.
294
+ #
295
+ # We encapsulate this here so that we can use different calls in Rails 3
296
+ # and Rails 4.
297
+ #
298
+ # @return [Relation]
299
+ def all
300
+ version = Rails.version[0]
301
+ records = version == '3' ? self.klass.scoped : self.klass.all
302
+ end
303
+ # This method gets the items that we should write to our seed file.
304
+ #
305
+ # It will start with the scoped list for the model class, and then apply
306
+ # all the scopes in our {#scopes} list.
307
+ #
308
+ # @return [Relation]
309
+ def items
310
+ records = self.all
311
+ self.scopes.each { |method, args| records = records.send(method, *args) }
312
+ records
313
+ end
314
+
315
+ # This method gets the additional information passed in when defining our
316
+ # seed set.
317
+ #
318
+ # @return [Hash]
319
+ def options
320
+ self.seed_set.options
321
+ end
322
+
323
+ # @!group Seed Identifiers
324
+
325
+ # This method gets the unique identifier for a record of the class that
326
+ # this seed type handles.
327
+ #
328
+ # @param [ActiveRecord::Base] record
329
+ # The record
330
+ #
331
+ # @return [String]
332
+ # The identifier for the seed files.
333
+ def seed_identifier_for_record(record)
334
+ self.identifier_attributes.collect { |param| self.write_value_for_attribute(record, param) }.join("::")
335
+ end
336
+
337
+ # This method finds a record for our model class based on the unique seed
338
+ # identifier.
339
+ #
340
+ # @param [Array<String>] identifier
341
+ # The identifier from the CSV file.
342
+ #
343
+ # @return [ActiveRecord::Base]
344
+ # The record
345
+ def record_for_seed_identifier(identifier)
346
+ return nil if identifier.blank?
347
+ records = self.all
348
+ self.identifier_attributes.each_with_index do |attribute, index|
349
+ if self.associations.has_key?(attribute)
350
+ value = self.read_value_for_association(attribute, identifier)
351
+ records = records.where("#{attribute}_id" => value.try(:id))
352
+ else
353
+ value = self.read_value_for_attribute(identifier.shift, attribute)
354
+ records = records.where(attribute => value)
355
+ end
356
+ end
357
+ records.first
358
+ end
359
+ end
360
+ end
361
+ end
@@ -0,0 +1,8 @@
1
+ module ConfigScripts
2
+ # This module encapsulates code for reading and writing seed data.
3
+ module Seeds
4
+ end
5
+ end
6
+
7
+ require 'config_scripts/seeds/seed_set'
8
+ require 'config_scripts/seeds/seed_type'
@@ -0,0 +1,11 @@
1
+ namespace :config_scripts do
2
+ desc "Run pending config scripts"
3
+ task :run_pending => :environment do
4
+ ConfigScripts::Scripts::Script.run_pending_scripts
5
+ end
6
+
7
+ desc "List pending config scripts"
8
+ task :list_pending => :environment do
9
+ ConfigScripts::Scripts::Script.list_pending_scripts
10
+ end
11
+ end