application_seeds 0.0.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.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .ruby-version
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in application_seeds.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,319 @@
1
+ # application_seeds
2
+
3
+ A library for managing a standardized set of seed data for applications
4
+ in a non-production environment.
5
+
6
+
7
+ ## Requirements
8
+
9
+ * Postgresql - This library currently only works with the Postgresql
10
+ database.
11
+
12
+
13
+ ## Usage
14
+
15
+ #### Include the gem in your Gemfile
16
+
17
+ group :development, :test, :integration, :staging do
18
+ gem 'application_seeds', :git => 'git@github.com:centro/application_seeds.git'
19
+ end
20
+
21
+
22
+ #### Create a rake task to create data model objects from the seed data
23
+
24
+ **The application** needs to create objects from the common seed data. To
25
+ do this, the application will need to create Rake task (such as the one
26
+ below) that reads the seed data, and uses it to create the objects in
27
+ the application's own data model.
28
+
29
+ `ApplicationSeeds` provides an API to allow for the easy retrieveal of
30
+ seed data. See blow for more information about the API.
31
+
32
+ ```ruby
33
+ namespace :application_seeds do
34
+ desc 'Dump the development database and load it with standardized application seed data'
35
+ task :load, [:dataset] => ['db:drop', 'db:create', 'db:migrate', :environment] do |t, args|
36
+ ApplicationSeeds.dataset = args[:dataset]
37
+
38
+ seed_campaigns
39
+ seed_line_items
40
+ seed_some_other_objects
41
+ end
42
+
43
+ def seed_campaigns
44
+ # If we do not need to change the attirbute hash, we can just create
45
+ # the object with the attributes that are specified in the seed data
46
+ # file.
47
+ ApplicationSeeds.campaigns.each do |id, attributes|
48
+ ApplicationSeeds.create_object!(Campaign, id, attributes)
49
+ end
50
+ end
51
+
52
+ def seed_line_items
53
+ # If we need to reject attributes from the attribute hash, or
54
+ # only use specific attributes, we can use the select_attributes or
55
+ # the reject_attributes helper methods.
56
+ ApplicationSeeds.line_items.each do |id, attributes|
57
+ ApplicationSeeds.create_object!(LineItem, id, attributes.reject_attributes(:some_unused_attribute))
58
+ end
59
+ end
60
+
61
+ def seed_some_objects
62
+ # If we need to modify attribute names, we can do so using the
63
+ # map_attributes helper method.
64
+ ApplicationSeeds.some_objects.each do |id, attributes|
65
+ ApplicationSeeds.create_object!(SomeObject, id, attributes.map_attributes(
66
+ :old_name1 => :new_name1, :old_name2 => :new_name2))
67
+ end
68
+ end
69
+
70
+ def seed_some_other_objects
71
+ # If we need tighter control over how the object is created, we can
72
+ # simply create it ourselves.
73
+ ApplicationSeeds.some_other_objects.each do |id, attributes|
74
+ x = SomeOtherObject.new(param1: attributes['param1'],
75
+ param2: attributes['param2'],
76
+ param3: attributes['param3'])
77
+ x.id = id
78
+ x.save!
79
+ end
80
+ end
81
+ end
82
+ ```
83
+
84
+ #### Run the rake task
85
+
86
+ bundle exec rake application_seeds:load[your_data_set]
87
+
88
+ You must specify the seed data set that you would like to use. The dataset name is
89
+ simply the name of the directory containing the seed YAML files.
90
+
91
+
92
+ #### Or, run the capistrano task
93
+
94
+ Add the following line to your deploy.rb file:
95
+
96
+ require "application_seeds/capistrano"
97
+
98
+ Then, you can seed a remote database by running the following:
99
+
100
+ bundle exec cap <environment> deploy:application_seeds -s dataset=your_data_set
101
+
102
+
103
+ ## The API
104
+
105
+ The `ApplicationSeeds` module provides an API that enables the programmatic retrieval of seed data,
106
+ so the rake task can easily access all the seed data necessary to build the data object.
107
+
108
+
109
+ ### Specify the name of the directory containing the seed data
110
+
111
+ ```ruby
112
+ ApplicationSeeds.data_directory = "/path/to/seeds/directory"
113
+ ```
114
+
115
+ Specify the name of the directory that contains the application seed data.
116
+
117
+
118
+ ### Specify the name of the gem containing the seed data
119
+
120
+ ```ruby
121
+ ApplicationSeeds.data_gem_name = "my-seed-data-gem"
122
+ ```
123
+
124
+ Specify the name of the gem that contains the application seed data.
125
+ Defaults to `application_seed_data` if this method is not called.
126
+
127
+
128
+ ### Specify the dataset to be loaded
129
+
130
+ ```ruby
131
+ ApplicationSeeds.dataset = "name_of_your_dataset"
132
+ ```
133
+
134
+ Specify the name of the dataset to use. An exception will be raised if
135
+ the dataset could not be found.
136
+
137
+
138
+ ### Determining the dataset that has been loaded
139
+
140
+ ```ruby
141
+ ApplicationSeeds.dataset
142
+ ```
143
+
144
+ Returns the name of the dataset that has been loaded, or nil if not
145
+ running an application_seeds dataset.
146
+
147
+
148
+ ### Checking if a seed file exists in the dataset
149
+
150
+ ```ruby
151
+ ApplicationSeeds.seed_data_exists?(:campaigns)
152
+ ```
153
+
154
+ Returns `true` if `campaigns.yml` exists in this dataset, `false` if it
155
+ does not.
156
+
157
+
158
+ ### Fetching all seeds of a given type
159
+
160
+ ```ruby
161
+ ApplicationSeeds.campaigns # where "campaigns" is the name of the seed file
162
+ ```
163
+
164
+ This call returns a hash with one or more entries (depending on the contentes of the seed file).
165
+ The IDs of the object are the keys, and a hash containing the object's attributes are the values.
166
+ An exception is raised if no seed data could be with the given name.
167
+
168
+
169
+ ### Fetching seed data by ID
170
+
171
+ ```ruby
172
+ ApplicationSeeds.campaigns(1) # where "campaigns" is the name of the seed file, and 1 is the ID of the campaign
173
+ ```
174
+
175
+ This call returns a hash containing the object's attributes. An exception is raised if no
176
+ seed data could be found with the given ID.
177
+
178
+
179
+ ### Fetching seed data by some other attribute
180
+
181
+ ```ruby
182
+ ApplicationSeeds.campaigns(foo: 'bar', name: 'John') # where "campaigns" is the name of the seed file
183
+ ```
184
+
185
+ This call returns the seed data that contains the specified attributes,
186
+ and the specified attribute values. It returns a hash with zero or more
187
+ entries. The IDs of the object are the keys of the hash, and a hash
188
+ containing the object's attributes are the values. Any empty hash will
189
+ be returned if no seed data could be found with the given attribute names
190
+ and values.
191
+
192
+
193
+ ### Creating an object
194
+
195
+ ```ruby
196
+ ApplicationSeeds.create_object!(Campaign, id, attributes)
197
+ ```
198
+
199
+ This call will create a new instance of the `Campaign` class, with the
200
+ specified id and attributes.
201
+
202
+
203
+ ### Rejecting specific attributes
204
+
205
+ ```ruby
206
+ ApplicationSeeds.create_object!(Campaign, id, attributes.reject_attributes(:unused_attribute))
207
+ ```
208
+
209
+ This call will create a new instance of the `Campaign` class without the
210
+ `unused_attribute` attribute.
211
+
212
+
213
+ ### Selecting specific attributes
214
+
215
+ ```ruby
216
+ ApplicationSeeds.create_object!(Campaign, id, attributes.select_attributes(:attribute1, :attribute2))
217
+ ```
218
+
219
+ This call will create a new instance of the `Campaign` class with only the
220
+ `attribute1` and `attribute2` attributes.
221
+
222
+
223
+ ### Mapping attribute names
224
+
225
+ ```ruby
226
+ ApplicationSeeds.create_object!(Campaign, id, attributes.map_attributes(
227
+ :old_name1 => :new_name1, :old_name2 => :new_name2))
228
+ ```
229
+
230
+ This call will create a new instance of the `Campaign` class, using the
231
+ seed data for old_name1 as the attribute value for new_name1, and the
232
+ seed data for old_name2 as the attribute value for new_name2. This
233
+ method let's you easly account for slight differences is attribute names
234
+ across applications.
235
+
236
+
237
+ ### Reset id column sequence numbers
238
+
239
+ ```ruby
240
+ ApplicationSeeds.reset_sequence_numbers
241
+ ```
242
+
243
+ This method will reset the sequence numbers on id columns for all tables
244
+ in the database with an id column. If you are having issues where you
245
+ are unable to insert new data into the databse after your dataset has
246
+ been imported, then this should correct them.
247
+
248
+
249
+ ## The Problem
250
+
251
+ Applications in a service oriented architecture (SOA) are often
252
+ interconnected. One of the challenges with a SOA is that, since the
253
+ applications are (and must be to some extent) all interconnected, the
254
+ data sets used by the different applications must be *in sync*.
255
+
256
+ Applications will need to store keys to data in other applications that
257
+ can be used to fetch more detailed information from the services that
258
+ own that data. In order for one application to lookup data owned by
259
+ another application, the key specified by the client must be in the server's
260
+ data set, along with the other data associated with the key that the
261
+ client is requesting.
262
+
263
+ Often, each application will have its own, sioled seed data, making
264
+ inter-app communication impossible. In order to get all of the
265
+ application data in sync, developers will often resort to populating
266
+ their development databases with production data. Production data on a
267
+ developer machine (*especially* a laptop) is bad business. Do you want
268
+ to send the email to all of your customers telling them that their
269
+ sensitive data was on a stolen laptop? I didn't think so.
270
+
271
+
272
+ ## The Goal
273
+
274
+ The goal of this project is to create a common set of seed data that can
275
+ be used by all applications running in development. Re-seeding
276
+ the applications in development with this shared seed data would put them
277
+ all "on the same page", preparing them for inter-app communication.
278
+
279
+ The seed data would be in a general format, not formatted to any
280
+ application's data model. Each application will have a script that
281
+ mutates this seed data to confirm to its data model, and then persist it
282
+ to its database.
283
+
284
+
285
+ ## FAQ
286
+
287
+ #### Why not just stub calls to the respective services?
288
+
289
+ Easier said than done :) Yes, it would be fantastic if we could run an
290
+ application in isolation, and everything just works. But maintaining
291
+ the stubs can be difficult. Also, when you stub out service calls,
292
+ you're not really testing the inter-app communication process. More importantly,
293
+ stubbing out the calls really only works for read-only APIs. For APIs that
294
+ create or mutate data, stubbing isn't an ideal strategy. What happens
295
+ when the app tries to fetch data that it just created/updated on a remote
296
+ service? How will you see the data you created/updated?
297
+
298
+
299
+ #### Doesn't this mean that I need all applications running, all of the time?
300
+
301
+ Not really. But, you will need to be running the applications that
302
+ service API calls for whatever it is that you are developing/testing.
303
+ This is where [POW](http://pow.cx/) comes in. POW is a zero-config Rack
304
+ server for OSX. After installing POW, your apps will be accessible via a
305
+ .dev url, like http://myapp.dev No more remembering to
306
+ start an application before you use one of its services. No more
307
+ remembering which applications run on which ports. If your application
308
+ is not currently running, POW will start it automatically on the fly.
309
+
310
+
311
+ #### Sounds great, what's the catch?
312
+
313
+ Making it easier for our applications to talk to one another does have
314
+ some disadvantages. One being that it makes it easier to
315
+ couple applications. The goal of a service oriented
316
+ architecture is to prevent this. With great power comes great
317
+ responsibility. Carefully consider the trade offs any time you
318
+ introduce an API call to fetch data from a remote service.
319
+
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'application_seeds/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "application_seeds"
8
+ gem.version = ApplicationSeeds::VERSION
9
+ gem.authors = ["John Wood"]
10
+ gem.email = ["john.wood@centro.net"]
11
+ gem.description = %q{A library for managing standardized application seed data}
12
+ gem.summary = %q{A library for managing a standardized set of seed data for applications in a non-production environment}
13
+ gem.homepage = "https://github.com/centro/application_seeds"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency "activesupport"
21
+ gem.add_dependency "pg"
22
+
23
+ gem.add_development_dependency "rspec"
24
+ gem.add_development_dependency "rake"
25
+ end
@@ -0,0 +1,35 @@
1
+ require 'delegate'
2
+
3
+ module ApplicationSeeds
4
+ class Attributes < DelegateClass(Hash)
5
+
6
+ def initialize(attributes)
7
+ super(attributes)
8
+ end
9
+
10
+ def select_attributes(*attribute_names)
11
+ attribute_names.map!(&:to_s)
12
+ select { |k,v| attribute_names.include?(k) }
13
+ end
14
+
15
+ def reject_attributes(*attribute_names)
16
+ attribute_names.map!(&:to_s)
17
+ reject { |k,v| attribute_names.include?(k) }
18
+ end
19
+
20
+ def map_attributes(mapping)
21
+ mapping.stringify_keys!
22
+
23
+ mapped = {}
24
+ each do |k,v|
25
+ if mapping.keys.include?(k)
26
+ mapped[mapping[k].to_s] = v
27
+ else
28
+ mapped[k] = v
29
+ end
30
+ end
31
+ Attributes.new(mapped)
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,30 @@
1
+ require 'capistrano'
2
+
3
+ module ApplicationSeeds
4
+ module Capistrano
5
+
6
+ def self.load_into(configuration)
7
+ configuration.load do
8
+ set :dataset, ""
9
+
10
+ namespace :deploy do
11
+ task :application_seeds do
12
+ raise "You cannot run this task in the production environment" if rails_env == "production"
13
+
14
+ if dataset == ""
15
+ run %Q{cd #{latest_release} && #{rake} RAILS_ENV=#{rails_env} db:seed}
16
+ else
17
+ run %Q{cd #{latest_release} && #{rake} RAILS_ENV=#{rails_env} application_seeds:load\[#{dataset}\]}
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ end
25
+ end
26
+
27
+ if Capistrano::Configuration.instance
28
+ ApplicationSeeds::Capistrano.load_into(Capistrano::Configuration.instance)
29
+ end
30
+
@@ -0,0 +1,27 @@
1
+ module ApplicationSeeds
2
+ class Database
3
+
4
+ class << self
5
+ def connection
6
+ return @connection unless @connection.nil?
7
+
8
+ database_config = YAML.load(ERB.new(File.read("config/database.yml")).result)[Rails.env]
9
+
10
+ pg_config = {}
11
+ pg_config[:dbname] = database_config['database']
12
+ pg_config[:host] = database_config['host'] if database_config['host']
13
+ pg_config[:port] = database_config['port'] if database_config['port']
14
+ pg_config[:user] = database_config['username'] if database_config['username']
15
+ pg_config[:password] = database_config['password'] if database_config['password']
16
+
17
+ @connection = PG.connect(pg_config)
18
+ end
19
+
20
+ def create_metadata_table
21
+ connection.exec('DROP TABLE IF EXISTS application_seeds;')
22
+ connection.exec('CREATE TABLE application_seeds (dataset varchar(255));')
23
+ end
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,3 @@
1
+ module ApplicationSeeds
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,255 @@
1
+ require "yaml"
2
+ require "erb"
3
+ require "pg"
4
+ require "active_support"
5
+ require "active_support/core_ext"
6
+
7
+ require "application_seeds/database"
8
+ require "application_seeds/version"
9
+ require "application_seeds/attributes"
10
+
11
+ # A library for managing a standardized set of seed data for applications in a non-production environment.
12
+ #
13
+ # == The API
14
+ #
15
+ # === Fetching all seeds of a given type
16
+ #
17
+ # ApplicationSeeds.campaigns # where "campaigns" is the name of the seed file
18
+ #
19
+ # This call returns a hash with one or more entries (depending on the contentes of the seed file).
20
+ # The IDs of the object are the keys, and a hash containing the object's attributes are the values.
21
+ # An exception is raised if no seed data could be with the given name.
22
+ #
23
+ # === Fetching seed data by ID
24
+ #
25
+ # ApplicationSeeds.campaigns(1) # where "campaigns" is the name of the seed file, and 1 is the ID of the campaign
26
+ #
27
+ # This call returns a hash containing the object's attributes. An exception is raised if no
28
+ # seed data could be found with the given ID.
29
+ #
30
+ # === Fetching seed data by some other attribute
31
+ #
32
+ # ApplicationSeeds.campaigns(foo: 'bar', name: 'John') # where "campaigns" is the name of the seed file
33
+ #
34
+ # This call returns the seed data that contains the specified attributes,
35
+ # and the specified attribute values. It returns a hash with zero or more
36
+ # entries. The IDs of the object are the keys of the hash, and a hash
37
+ # containing the object's attributes are the values. Any empty hash will
38
+ # be returned if no seed data could be found with the given attribute names
39
+ # and values.
40
+ #
41
+ # === Creating an object
42
+ #
43
+ # ApplicationSeeds.create_object!(Campaign, id, attributes)
44
+ #
45
+ # This call will create a new instance of the <tt>Campaign</tt> class, with the
46
+ # specified id and attributes.
47
+ #
48
+ # === Rejecting specific attributes
49
+ #
50
+ # ApplicationSeeds.create_object!(Campaign, id, attributes.reject_attributes(:unused_attribute))
51
+ #
52
+ # This call will create a new instance of the <tt>Campaign</tt> class without the
53
+ # <tt>unused_attribute</tt> attribute.
54
+ #
55
+ # === Selecting specific attributes
56
+ #
57
+ # ApplicationSeeds.create_object!(Campaign, id, attributes.select_attributes(:attribute1, :attribute2))
58
+ #
59
+ # This call will create a new instance of the <tt>Campaign</tt> class with only the
60
+ # <tt>attribute1</tt> and <tt>attribute2</tt> attributes.
61
+ #
62
+ # === Mapping attribute names
63
+ #
64
+ # ApplicationSeeds.create_object!(Campaign, id, attributes.map_attributes(
65
+ # :old_name1 => :new_name1, :old_name2 => :new_name2))
66
+ #
67
+ # This call will create a new instance of the <tt>Campaign</tt> class, using the
68
+ # seed data for old_name1 as the attribute value for new_name1, and the
69
+ # seed data for old_name2 as the attribute value for new_name2. This
70
+ # method let's you easly account for slight differences is attribute names
71
+ # across applications.
72
+ #
73
+ module ApplicationSeeds
74
+ class << self
75
+
76
+ #
77
+ # Specify the name of the gem that contains the application seed data.
78
+ #
79
+ def data_gem_name=(gem_name)
80
+ spec = Gem::Specification.find_by_name(gem_name)
81
+ if Dir.exist?(File.join(spec.gem_dir, "lib", "seeds"))
82
+ @data_gem_name = gem_name
83
+ else
84
+ raise "ERROR: The #{gem_name} gem does not appear to contain application seed data"
85
+ end
86
+ end
87
+
88
+ #
89
+ # Fetch the name of the directory where the application seed data is loaded from.
90
+ # Defaults to <tt>"applicadtion_seed_data"</tt> if it was not set using <tt>data_gem_name=</tt>.
91
+ #
92
+ def data_gem_name
93
+ @data_gem_name || "application_seed_data"
94
+ end
95
+
96
+ #
97
+ # Specify the name of the directory that contains the application seed data.
98
+ #
99
+ def data_directory=(directory)
100
+ if Dir.exist?(directory)
101
+ @data_directory = directory
102
+ else
103
+ raise "ERROR: The #{directory} directory does not appear to contain application seed data"
104
+ end
105
+ end
106
+
107
+ #
108
+ # Fetch the name of the directory where the application seed data is loaded from,
109
+ # if it was set using <tt>data_diretory=</tt>.
110
+ #
111
+ def data_directory
112
+ @data_directory
113
+ end
114
+
115
+ #
116
+ # Specify the name of the dataset to use. An exception will be raised if
117
+ # the dataset could not be found.
118
+ #
119
+ def dataset=(dataset)
120
+ if dataset.nil? || dataset.strip.empty? || !Dir.exist?(File.join(seed_data_path, dataset))
121
+ datasets = Dir[File.join(seed_data_path, "*")].map { |x| File.basename(x) }.join(', ')
122
+
123
+ error_message = "\nERROR: A valid dataset is required!\n"
124
+ error_message << "Usage: bundle exec rake application_seeds:load[your_data_set]\n\n"
125
+ error_message << "Available datasets: #{datasets}\n\n"
126
+ raise error_message
127
+ end
128
+
129
+ Database.create_metadata_table
130
+ Database.connection.exec("INSERT INTO application_seeds (dataset) VALUES ('#{dataset}');")
131
+
132
+ @dataset = dataset
133
+ end
134
+
135
+ #
136
+ # Returns the name of the dataset that has been loaded, or nil if not
137
+ # running an application_seeds dataset.
138
+ #
139
+ def dataset
140
+ res = Database.connection.exec("SELECT dataset from application_seeds LIMIT 1;")
141
+ res.getvalue(0, 0)
142
+ rescue PG::Error => e
143
+ e.message =~ /relation "application_seeds" does not exist/ ? nil : raise
144
+ end
145
+
146
+ #
147
+ # This call will create a new instance of the specified class, with the
148
+ # specified id and attributes.
149
+ #
150
+ def create_object!(clazz, id, attributes, options={})
151
+ validate = options[:validate].nil? ? true : options[:validate]
152
+
153
+ x = clazz.new
154
+ x.attributes = attributes.reject { |k,v| !x.respond_to?("#{k}=") }
155
+ x.id = id
156
+ x.save!(:validate => validate)
157
+ x
158
+ end
159
+
160
+ #
161
+ # Returns <tt>true</tt> if the specified data file exists in this dataset, <tt>false</tt> if it
162
+ # does not.
163
+ #
164
+ # Examples:
165
+ # ApplicationSeeds.seed_data_exists?(:campaigns)
166
+ #
167
+ def seed_data_exists?(type)
168
+ File.exist?(File.join(seed_data_path, @dataset, "#{type}.yml"))
169
+ end
170
+
171
+ #
172
+ # This method will reset the sequence numbers on id columns for all tables
173
+ # in the database with an id column. If you are having issues where you
174
+ # are unable to insert new data into the databse after your dataset has
175
+ # been imported, then this should correct them.
176
+ #
177
+ def reset_sequence_numbers
178
+ result = Database.connection.exec("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public';")
179
+ table_names = result.map { |row| row.values_at('table_name')[0] }
180
+
181
+ table_names_with_id_column = table_names.select do |table_name|
182
+ result = Database.connection.exec("SELECT column_name FROM information_schema.columns WHERE table_name = '#{table_name}';")
183
+ column_names = result.map { |row| row.values_at('column_name')[0] }
184
+ column_names.include?('id')
185
+ end
186
+
187
+ table_names_with_id_column.each do |table_name|
188
+ result = Database.connection.exec("SELECT pg_get_serial_sequence('#{table_name}', 'id');")
189
+ sequence_name = result.getvalue(0, 0)
190
+ Database.connection.exec("SELECT setval('#{sequence_name}', (select MAX(id) from #{table_name}));")
191
+ end
192
+ end
193
+
194
+ private
195
+
196
+ def method_missing(method, *args)
197
+ self.send(:seed_data, method, args.shift)
198
+ end
199
+
200
+ def seed_data(type, options)
201
+ @seed_data ||= {}
202
+ @seed_data[type] ||= load_seed_data(type)
203
+ raise "No seed data could be found for '#{type}'" if @seed_data[type].nil?
204
+
205
+ if options.nil?
206
+ fetch(type)
207
+ elsif options.is_a?(Fixnum) || options.is_a?(String)
208
+ fetch_with_id(type, options)
209
+ elsif options.is_a? Hash
210
+ fetch(type) do |attributes|
211
+ (options.stringify_keys.to_a - attributes.to_a).empty?
212
+ end
213
+ end
214
+ end
215
+
216
+ def load_seed_data(type)
217
+ data_file = File.join(seed_data_path, @dataset, "#{type}.yml")
218
+ if File.exist?(data_file)
219
+ YAML.load(ERB.new(File.read(data_file)).result)
220
+ else
221
+ nil
222
+ end
223
+ end
224
+
225
+ def seed_data_path
226
+ return @seed_data_path unless @seed_data_path.nil?
227
+
228
+ if data_directory
229
+ @seed_data_path = data_directory
230
+ else
231
+ spec = Gem::Specification.find_by_name(data_gem_name)
232
+ @seed_data_path = File.join(spec.gem_dir, "lib", "seeds")
233
+ end
234
+ end
235
+
236
+ def fetch(type, &block)
237
+ result = {}
238
+ @seed_data[type].each do |d|
239
+ attributes = d.clone
240
+ id = attributes.delete('id')
241
+ if !block_given? || (block_given? && yield(attributes) == true)
242
+ result[id] = Attributes.new(attributes)
243
+ end
244
+ end
245
+ result
246
+ end
247
+
248
+ def fetch_with_id(type, id)
249
+ data = @seed_data[type].find { |d| d['id'].to_s == id.to_s }
250
+ raise "No seed data could be found for '#{type}' with id #{id}" if data.nil?
251
+ Attributes.new(data)
252
+ end
253
+
254
+ end
255
+ end
@@ -0,0 +1,83 @@
1
+ require 'application_seeds'
2
+
3
+ describe "ApplicationSeeds" do
4
+ before do
5
+ ApplicationSeeds.data_directory = File.join(File.dirname(__FILE__), "seed_data")
6
+ end
7
+
8
+ describe "#data_gem_name=" do
9
+ it "raises an error if no gem could be found with the specified name" do
10
+ expect { ApplicationSeeds.data_gem_name = "foo" }.to raise_error(Gem::LoadError)
11
+ end
12
+ it "raises an error if the specified gem does not contain seed data" do
13
+ expect { ApplicationSeeds.data_gem_name = "rspec" }.to raise_error(RuntimeError, /does not appear to contain application seed data/)
14
+ end
15
+ end
16
+
17
+ describe "#data_gem_name" do
18
+ it "defaults to 'application_seed_data'" do
19
+ ApplicationSeeds.data_gem_name.should == "application_seed_data"
20
+ end
21
+ end
22
+
23
+ describe "#data_directory" do
24
+ it "is able to set the data directory successfully" do
25
+ ApplicationSeeds.data_directory.should == File.join(File.dirname(__FILE__), "seed_data")
26
+ end
27
+ it "raises an error if a non-existant directory specified" do
28
+ expect { ApplicationSeeds.data_directory = "/foo/bar" }.to raise_error
29
+ end
30
+ end
31
+
32
+ describe "#dataset=" do
33
+ context "when an invalid dataset is specified" do
34
+ it "raises an error if a nil dataset is specified" do
35
+ expect { ApplicationSeeds.dataset = nil }.to raise_error
36
+ end
37
+ it "raises an error if a blank dataset is specified" do
38
+ expect { ApplicationSeeds.dataset = " " }.to raise_error
39
+ end
40
+ it "raises an error if an unknown dataset is specified" do
41
+ expect { ApplicationSeeds.dataset = "foo" }.to raise_error
42
+ end
43
+ it "lists the available datasets in the error message" do
44
+ expect { ApplicationSeeds.dataset = nil }.to raise_error(RuntimeError, /Available datasets: test_data_set/)
45
+ end
46
+ end
47
+
48
+ context "when a valid dataset is specified" do
49
+ before do
50
+ connection_dummy = double
51
+ connection_dummy.should_receive(:exec).with("INSERT INTO application_seeds (dataset) VALUES ('test_data_set');")
52
+ ApplicationSeeds::Database.should_receive(:create_metadata_table)
53
+ ApplicationSeeds::Database.should_receive(:connection) { connection_dummy }
54
+ ApplicationSeeds.dataset = "test_data_set"
55
+ end
56
+ it "sets the dataset" do
57
+ ApplicationSeeds.instance_variable_get(:@dataset).should == "test_data_set"
58
+ end
59
+ end
60
+ end
61
+
62
+ describe "#dataset" do
63
+ before do
64
+ connection_dummy = double
65
+ response_dummy = double(:getvalue => "test_data_set")
66
+ connection_dummy.should_receive(:exec).with("SELECT dataset from application_seeds LIMIT 1;") { response_dummy }
67
+ ApplicationSeeds::Database.should_receive(:connection) { connection_dummy }
68
+ end
69
+ it "fetches the dataset name from the database" do
70
+ ApplicationSeeds.dataset.should == "test_data_set"
71
+ end
72
+ end
73
+
74
+ describe "#seed_data_exists?" do
75
+ it "returns true if the specified seed data exists" do
76
+ ApplicationSeeds.seed_data_exists?(:people).should be_true
77
+ end
78
+ it "returns false if the specified seed data does not exist" do
79
+ ApplicationSeeds.seed_data_exists?(:missing).should_not be_true
80
+ end
81
+ end
82
+
83
+ end
@@ -0,0 +1,18 @@
1
+ - id: 1
2
+ first_name: Joe
3
+ last_name: Smith
4
+ company_id: 1
5
+ start_date: <%= 2.months.ago.to_date %>
6
+
7
+ - id: 2
8
+ first_name: Jane
9
+ last_name: Doe
10
+ company_id: 1
11
+ start_date: <%= 10.months.ago.to_date %>
12
+
13
+ - id: 3
14
+ first_name: John
15
+ last_name: Walsh
16
+ company_id: 2
17
+ start_date: <%= 10.years.ago.to_date %>
18
+
metadata ADDED
@@ -0,0 +1,130 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: application_seeds
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - John Wood
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-07-26 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activesupport
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: pg
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rake
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: A library for managing standardized application seed data
79
+ email:
80
+ - john.wood@centro.net
81
+ executables: []
82
+ extensions: []
83
+ extra_rdoc_files: []
84
+ files:
85
+ - .gitignore
86
+ - Gemfile
87
+ - README.md
88
+ - Rakefile
89
+ - application_seeds.gemspec
90
+ - lib/application_seeds.rb
91
+ - lib/application_seeds/attributes.rb
92
+ - lib/application_seeds/capistrano.rb
93
+ - lib/application_seeds/database.rb
94
+ - lib/application_seeds/version.rb
95
+ - spec/application_seeds_spec.rb
96
+ - spec/seed_data/test_data_set/people.yml
97
+ homepage: https://github.com/centro/application_seeds
98
+ licenses: []
99
+ post_install_message:
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ! '>='
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ segments:
110
+ - 0
111
+ hash: -2100833736178153226
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ segments:
119
+ - 0
120
+ hash: -2100833736178153226
121
+ requirements: []
122
+ rubyforge_project:
123
+ rubygems_version: 1.8.23
124
+ signing_key:
125
+ specification_version: 3
126
+ summary: A library for managing a standardized set of seed data for applications in
127
+ a non-production environment
128
+ test_files:
129
+ - spec/application_seeds_spec.rb
130
+ - spec/seed_data/test_data_set/people.yml