Setting up CI for a Multi-Engine Rails 3.2 Application
Tags: rails • Categories: Web Development
I recently setup continuous integration (via CircleCI, which I’m really impressed with). It ended up being a bit more tricky for a couple of reasons:
- I needed to pull semi-static seed data into the application. For this app, this was product data that isn’t changed often and is pulled from an external system. I wanted to use seed data from the production database because the integrity of the data in the external DB couldn’t be trusted and I wanted to ensure that if the tests passed it was a real representation of if the app would function in the live environment.
- There were a number of custom rails engines that app was compromised of. Each of these engines had separate unit tests, but not separate integration (feature) tests. The view layer had too many dependencies on the host app to separate out the feature tests.
- The migrations for each of the engines were inside the engine folder using this trick.
- I wanted the ability to visually debug feature test via the selenium capybara driver, but I wanted the speed of `capybara-webkit`
- For whatever reason, the `seed_dump` and `seed_dumper` gem was not working on this 3.2.x app
- There is a bunch of persistent app-wide configuration settings I needed to set via a rake task
Use SQL Dumps for Seed Data
After not being able to get seed_dump extracting data from the development database, I decided on using SQL dumps for seed data. Here are the benefits:
- It’s easy to update the data, just run a DB table export
- The relationships between various models are maintained (this was very important for this specific project, the eCommerce product model was fairly complex)
- Not dependent on a gem to update the data in the future
The engineer in me doesn’t like the fact that running tests are dependent on non-factory test data, but when dealing with an external data source that has data integrity issues, it’s the most robust way to test that your app will hold up in the wild.
I found a seeds.rb SQL import snippet and expanded upon it to dynamically load in any SQL files in db/seeds/
:
unless Rails.env.production?
# country and state seed data
Spree::Core::Engine.load_seed if defined?(Spree::Core)
# https://github.com/spree/spree_auth_devise/blob/1-3-stable/db/default/users.rb
ENV['AUTO_ACCEPT'] = 'true'
Spree::Auth::Engine.load_seed if defined?(Spree::Auth)
# don't export table structures or schema_migrations table; that is handled by load:schema
Dir[File.join(Rails.root, "db/seeds/*.sql")].each do |sql_file|
sql = File.read(sql_file)
puts "Reading seed: #{sql_file}"
connection = ActiveRecord::Base.connection
# AR can only manually execute one statement at a time
statements = sql.split(/;$/)
statements.pop
ActiveRecord::Base.transaction do
statements.each do |statement|
connection.execute(statement)
end
end
end
# nil out the user generated paper clip attribute
Spree::Author.all.each do |a|
a.update_attribute :photo, nil
end
end
Rake Task Execution Order for Database Setup
I had a couple custom rake tasks I needed to run to setup my test environment. However, some of those rake tasks required the database schema to be setup for some of rails accessors that are determined by the database schema to be created.
I found that running multiple rake tasks in a single rake command does not trigger a model refresh if the DB schema changes. Run the rake task in a separate bash command to pick up the updated schema:
rake db:drop db:create db:schema:load --trace RAILS_ENV=test
rake db:seed custom:configure RAILS_ENV=test --trace
Add Engine Migration Paths to ActiveRecord::Migrator
I’ve been adding the db/migrate
path in my child engines to my parent engine in order to keep my codebase DRY.
With the following snippet in place running db:schema:load
would trigger migration errors. The database schema/creation logic wasn’t picking the migrations inside the engines although db:migrate
ran them just fine.
After some digging I found the magical configuration tweak to fix the issue:
initializer :append_migrations do |app|
unless app.root.to_s.match root.to_s
app.config.paths["db/migrate"] += config.paths["db/migrate"].expanded
ActiveRecord::Migrator.migrations_paths += config.paths["db/migrate"].expanded
end
end
That’s it for now, stay tuned for part 2!