-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This reverts commit 428262a.
- Loading branch information
sugeng tigefa
committed
Dec 27, 2014
1 parent
85d0276
commit 46d4bc7
Showing
8 changed files
with
783 additions
and
0 deletions.
There are no files selected for viewing
208 changes: 208 additions & 0 deletions
208
_posts/2013-01-15-run-old-migrations-in-the-new-migrations.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
--- | ||
layout: post | ||
title: "Run old migrations in the new migrations" | ||
description: "This post describes steps of using old migrations in the new migrations which you have just created and need to write code which you have already wrote in the older migrations." | ||
tags: [rails, active_record, migrations] | ||
--- | ||
|
||
Imagine you have to write new migration with a lot of code and this migration should have a lot of code, code which you have already wrote in older migration and this required code is exactly up/down/change part of those migration. Another words you would like to run migration part (up, down or change) in the new migration. Pay attention - you have to write or copy paste a lot of code You have 2 variants how to overcome this issue: | ||
|
||
1. The simplest and boring solution - it is copy and paste this code, or write it with scratch. This is not our case. Old code and new code will mix and you will have a huge amount of disgusting code - you won't able to detect which is new code and which is old | ||
2. Use oldest migration in DRY way. Just use those code as you use another class, for instance `User` model which you probably have | ||
|
||
## Input data | ||
|
||
In one project I had to rollback my old migration which has a lot of code. Check out this code: | ||
|
||
> db/migrate/20121107173946_add_search_content_to_products.rb | ||
{% highlight ruby %} | ||
class AddSearchContentToProducts < ActiveRecord::Migration | ||
def up | ||
add_column :products, :tsvector_content_tsearch, :tsvector | ||
add_column :products, :tsvector_content_dmetaphone, :tsvector | ||
|
||
execute <<-EOS | ||
CREATE INDEX products_tsvector_content_tsearch_idx ON products USING gin(tsvector_content_tsearch); | ||
CREATE INDEX products_tsvector_content_dmetaphone_idx ON products USING gin(tsvector_content_dmetaphone); | ||
EOS | ||
|
||
execute <<-EOS | ||
CREATE OR REPLACE FUNCTION get_tsvector(c text, w "char", lang regconfig DEFAULT 'pg_catalog.russian') RETURNS tsvector LANGUAGE plpgsql AS $$ | ||
begin | ||
return setweight(to_tsvector(lang, coalesce(c, '')), w); | ||
end | ||
$$; | ||
EOS | ||
|
||
execute <<-EOS | ||
CREATE OR REPLACE FUNCTION products_trigger() RETURNS trigger LANGUAGE plpgsql AS $$ | ||
declare | ||
product_model record; | ||
product_part record; | ||
product_brand record; | ||
product_seller record; | ||
|
||
begin | ||
select name, aliases, brand_id into product_model from models where id = new.model_id; | ||
select name, aliases into product_brand from brands where id = product_model.brand_id; | ||
select name, aliases into product_part from parts where id = new.part_id; | ||
select email, name into product_seller from sellers where id = new.seller_id; | ||
|
||
new.tsvector_content_tsearch := | ||
get_tsvector(new.note, 'C') || | ||
|
||
get_tsvector(product_model.name, 'A') || | ||
get_tsvector(product_model.aliases, 'A') || | ||
|
||
get_tsvector(product_part.name, 'A') || | ||
get_tsvector(product_part.aliases, 'A') || | ||
|
||
get_tsvector(product_brand.name, 'A') || | ||
get_tsvector(product_brand.aliases, 'A') || | ||
|
||
get_tsvector(product_seller.name, 'D') || | ||
get_tsvector(product_seller.email, 'D'); | ||
|
||
new.tsvector_content_dmetaphone := | ||
get_tsvector(new.note, 'C', 'simple') || | ||
|
||
get_tsvector(product_model.name, 'A', 'simple') || | ||
get_tsvector(product_model.aliases, 'A', 'simple') || | ||
|
||
get_tsvector(product_part.name, 'A', 'simple') || | ||
get_tsvector(product_part.aliases, 'A', 'simple') || | ||
|
||
get_tsvector(product_brand.name, 'A', 'simple') || | ||
get_tsvector(product_brand.aliases, 'A', 'simple') || | ||
|
||
get_tsvector(product_seller.name, 'D', 'simple') || | ||
get_tsvector(product_seller.email, 'D', 'simple'); | ||
|
||
return new; | ||
end | ||
$$; | ||
EOS | ||
|
||
execute <<-EOS | ||
CREATE TRIGGER products_content_to_search_trigger BEFORE INSERT OR UPDATE | ||
ON products FOR EACH ROW EXECUTE PROCEDURE products_trigger(); | ||
EOS | ||
|
||
Product.all.each(&:touch) | ||
end | ||
|
||
def down | ||
remove_column :products, :tsvector_content_tsearch | ||
remove_column :products, :tsvector_content_dmetaphone | ||
|
||
execute <<-EOS | ||
DROP TRIGGER products_content_to_search_trigger ON products; | ||
DROP FUNCTION products_trigger(); | ||
DROP FUNCTION get_tsvector(c text, w "char", lang regconfig); | ||
EOS | ||
end | ||
end | ||
{% endhighlight %} | ||
|
||
As I have already said I would like to rollback it in my new migration on up. | ||
Write new migration and paste there this code is not right solution. I think you are agreed with me. | ||
|
||
## Solution | ||
|
||
I think the best solution will be to include this migration in my new migration then use this migration class as usual code (yes - migration is a class too and we are able to do it). So the final code below: | ||
|
||
{% highlight ruby %} | ||
require File.join(Rails.root, 'db/migrate/20121107173946_add_search_content_to_products.rb') # (1) | ||
|
||
class ChangeSearchFunctions < ActiveRecord::Migration | ||
def up | ||
AddSearchContentToProducts.new.down # (2) | ||
|
||
add_column :products, :search_vector, :tsvector | ||
|
||
execute <<-EOS | ||
CREATE INDEX products_search_vector_idx ON products USING gin(search_vector); | ||
|
||
CREATE FUNCTION get_tsvector(c text, lang regconfig DEFAULT 'russian'::regconfig) RETURNS tsvector | ||
LANGUAGE plpgsql | ||
AS $$ | ||
begin | ||
return to_tsvector(lang, coalesce(c, '')); | ||
end | ||
$$; | ||
|
||
CREATE FUNCTION products_trigger() RETURNS trigger | ||
LANGUAGE plpgsql | ||
AS $$ | ||
declare | ||
product_model record; | ||
product_part record; | ||
product_brand record; | ||
product_seller record; | ||
|
||
begin | ||
select name, aliases, brand_id into product_model from models where id = new.model_id; | ||
select name, aliases into product_brand from brands where id = product_model.brand_id; | ||
select name, aliases into product_part from parts where id = new.part_id; | ||
select email, name into product_seller from sellers where id = new.seller_id; | ||
|
||
new.search_vector := | ||
get_tsvector(new.note) || | ||
|
||
get_tsvector(product_model.name) || | ||
get_tsvector(product_model.aliases) || | ||
|
||
get_tsvector(product_part.name) || | ||
get_tsvector(product_part.aliases) || | ||
|
||
get_tsvector(product_brand.name) || | ||
get_tsvector(product_brand.aliases) || | ||
|
||
get_tsvector(product_seller.name) || | ||
get_tsvector(product_seller.email); | ||
|
||
return new; | ||
end | ||
$$; | ||
|
||
CREATE TRIGGER products_content_to_search_trigger BEFORE INSERT OR UPDATE | ||
ON products FOR EACH ROW EXECUTE PROCEDURE products_trigger(); | ||
EOS | ||
Product.find_each(&:touch) | ||
end | ||
|
||
def down | ||
remove_column :products, :search_vector | ||
|
||
execute <<-EOS | ||
DROP TRIGGER products_content_to_search_trigger ON products; | ||
DROP FUNCTION products_trigger(); | ||
DROP FUNCTION get_tsvector(c text, lang regconfig); | ||
EOS | ||
|
||
AddSearchContentToProducts.new.up # (3) | ||
Product.find_each(&:touch) | ||
end | ||
end | ||
{% endhighlight %} | ||
|
||
See the first line (1): `require File.join(Rails.root, 'db/migrate/20121107173946_add_search_content_to_products.rb')`. With this line I include migration file and after this we are able to use old migration's class. On the fifth line (2) we I just use it: `AddSearchContentToProducts.new.down` to rollback old migration. On down I have to up this migration, see mark (3): `AddSearchContentToProducts.new.up`. | ||
|
||
In this example I had to rewrite stored procedure for postgresql database and I've got clean and DRY solution as you can see. Imagine how many code I would have if I just pasted all entire migration in the new! I hope you will find this article useful and if you have issues like I had you won't have problems now to solve them. | ||
|
||
UPDATE 26.06.2013: In Rails 4 new method is appeared which allows to revert all migrations. Check it out: | ||
|
||
{% highlight ruby %} | ||
require_relative '2012121212_example_migration' | ||
|
||
class FixupExampleMigration < ActiveRecord::Migration | ||
def change | ||
revert ExampleMigration | ||
|
||
create_table(:apples) do |t| | ||
t.string :variety | ||
end | ||
end | ||
end | ||
{% endhighlight %} |
114 changes: 114 additions & 0 deletions
114
_posts/2013-02-15-how-to-define-environment-variables-in-rails.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
--- | ||
layout: post | ||
title: "Environment variables in Rails" | ||
description: "The post about defining ENV variables in Rails. Explanation what does ENV['FOG_PROVIDER'] mean and how to configure Capistrano deployment with ENV variables" | ||
tags: [rails, capistrano] | ||
--- | ||
|
||
When you see configuration examples in gems README like this: | ||
|
||
> This real example you can find in the README of awesome gem [assets_sync](https://github.com/rumblelabs/asset_sync). | ||
{% highlight ruby %} | ||
AssetSync.configure do |config| | ||
config.fog_provider = 'AWS' | ||
config.fog_directory = ENV['FOG_DIRECTORY'] | ||
config.aws_access_key_id = ENV['AWS_ACCESS_KEY_ID'] | ||
config.aws_secret_access_key = ENV['AWS_SECRET_ACCESS_KEY'] | ||
|
||
# Don't delete files from the store | ||
# config.existing_remote_files = "keep" | ||
# | ||
# Increase upload performance by configuring your region | ||
# config.fog_region = 'eu-west-1' | ||
# | ||
# Automatically replace files with their equivalent gzip compressed version | ||
# config.gzip_compression = true | ||
# | ||
# Use the Rails generated 'manifest.yml' file to produce the list of files to | ||
# upload instead of searching the assets directory. | ||
# config.manifest = true | ||
# | ||
# Fail silently. Useful for environments such as Heroku | ||
# config.fail_silently = true | ||
end | ||
{% endhighlight %} | ||
|
||
what are they do you think? | ||
Where should you define `ENV['FOG_DIRECTORY']`? Should you define it in the shell script (like `~/.bashrc`, `~/.bash_profile`, `~/.profile` and etc.) or may be in the `/etc/environment`? If you think so I have to disappoint you - you are wrong! | ||
|
||
## Challenge | ||
|
||
Let's define `FOG_DIRECTORY` variable in the `~/.bashrc` file: | ||
|
||
> Pay attention if `~/.bashrc` includes line `[ -z "$PS1" ] && return` or equal you have to define variables above it | ||
{% highlight bash %} | ||
export FOG_DIRECTORY=my-bucket-name | ||
{% endhighlight %} | ||
|
||
Then reboot shell and start Rails application. You will see that application is configured correctly. You enjoy it and going to deploy application to the production on the VPS or VDS the same way and everything will be work there... before first reboot. Why `ENV['FOG_DIRECTORY']` is `nil` after server reboot? The answer is simple - [nginx](http://nginx.org/) or another web server (I don't know which one you use, but I prefer using **nginx**) starts before evaluating `~/.bashrc` and even `/etc/environment`. | ||
|
||
If you use [assets_sync](https://github.com/rumblelabs/asset_sync) to upload your assets to the **cloud** you can have this error during deployment with capistrano](https://github.com/capistrano/capistrano): | ||
|
||
{% highlight ruby %} | ||
AssetSync: using default configuration from built-in initializer | ||
rake aborted! | ||
Fog provider can't be blank, Fog directory can't be blank | ||
|
||
Tasks: TOP => assets:precompile:nondigest | ||
(See full trace by running task with --trace) | ||
{% endhighlight %} | ||
|
||
So we have to looking for another way how to define these variables. | ||
|
||
## Solution | ||
|
||
Considering the problem above there is a reasonable question - what are these variables in config at all and what to do? How should we define these variables? | ||
|
||
During surfing the Internet I've found a good [article](http://railsapps.github.com/rails-environment-variables.html) which explains cases how to achieve our goals. | ||
|
||
I will describe here the case which I prefer because I think it is the simplest and faster than others. | ||
|
||
Insert these lines of code to the **config/application.rb** after line `config.assets.version = '1.0'`: | ||
|
||
{% highlight ruby %} | ||
config.before_configuration do | ||
env_file = File.join(Rails.root, 'config', 'local_env.yml') | ||
YAML.load(File.open(env_file)).each do |key, value| | ||
ENV[key.to_s] = value | ||
end if File.exists?(env_file) | ||
end | ||
{% endhighlight %} | ||
|
||
|
||
Now you have to create **yml** file in **config** folder and add it to **.gitignore** if you have to define these variables locally. Example of `config/local_env.yml`: | ||
|
||
> Key values are not real, so it doesn't make sense to paste them in your configuration files | ||
{% highlight ruby %} | ||
FOG_PROVIDER: AWS | ||
FOG_DIRECTORY: my-bycket-name | ||
AWS_ACCESS_KEY_ID: ASFAWFSFDGSDEQWEFGD | ||
AWS_SECRET_ACCESS_KEY: Afsdgd35gSFsdgSDF46GDSG4ghdf356 | ||
FOG_REGION: eu-west-1 | ||
{% endhighlight %} | ||
|
||
|
||
And finally if we deploy application with **Capistrano** we have to deploy it properly. We should put **local_env.yml** to the **Capistrano** shared folder on the server and change **config/deploy.rb** like this: | ||
|
||
{% highlight ruby %} | ||
before 'deploy:assets:precompile', :symlink_config_files | ||
|
||
desc "Link shared files" | ||
task :symlink_config_files do | ||
symlinks = { | ||
"#{shared_path}/config/database.yml" => "#{release_path}/config/database.yml", | ||
"#{shared_path}/config/local_env.yml" => "#{release_path}/config/local_env.yml" | ||
} | ||
run symlinks.map{|from, to| "ln -nfs #{from} #{to}"}.join(" && ") | ||
end | ||
{% endhighlight %} | ||
|
||
|
||
Here it is assumed that **local_env.yml** is existed in the **{shared_path}/config/** folder on the server. Check out here that I've done the same things with my **database.yml** config (by the way ignore the **database.yml** in your [CVS](http://en.wikipedia.org/wiki/Concurrent_Versions_System) it's the best practice too). |
Oops, something went wrong.