きゃまなかのブログ

新卒6年目の WEB エンジニアです。 直近3年間は大規模システムの運用を担当していました。 今期から開発チームに移動になって、Java を勉強しています。 好きな言語が Ruby なので、Ruby 系の記事が多いです。

【Ruby on Rails】require と permit の使い方がよく分からない

概要

Rails 4 からストロングパラメータと言う新機能が導入されました。

具体的には require と permit と言うメソッドのことです。

Scaffold で Controller を作成する際にデフォルトで適用されるのですが、使い方がよく分からず、いつも削除していました。。

今回、require と permit の使い方を調べてまとめました。

はじめに

「名前」と「メールアドレス」を属性に持つ User モデルを Scaffold で作成してみます。

$ rails generate scaffold User name:string email:string

すると、Controller にストロングパラメータを適用した private メソッドがデフォルトで用意されました。

private
  # Never trust parameters from the scary internet, only allow the white list through.
  def user_params
    params.require(:user).permit(:name, :email)
  end

この user_params こそが、使い方がよく分からず削除していたメソッドです。。

これから順を追って中身を説明していきます。

params の中身を確認

初めに params の中身を確認します。

curl コマンドで user ハッシュを POST した結果は以下になります。

$ curl -X POST 'http://localhost/users' -H "Content-Type: application/json" -d '{"user":{"name":"aaa","email":"bbb"}}'
{
  "action" : "create",
  "user" : {
    "name" : "aaa",
    "email" : "bbb"
  },
  "controller" : "users"
}

返り値を見ると controller 名や action 名などもパラメータに含まれていることが分かります。

これがリクエストを投げたときの全ての返り値です。

params.require の中身を確認

次に params.require( :user ) の中身を確認します。

先ほどと同じように user ハッシュを POST した結果が以下になります。

$ curl -X POST 'http://localhost/users' -H "Content-Type: application/json" -d '{"user":{"name":"aaa","email":"bbb"}}'
{
  "name" : "aaa",
  "email" : "bbb"
}

user ハッシュの値だけになりましたね!

require メソッドを利用することで、引数に設定した key の 値だけを取得することができます。 

余談ですが、、、

params.require( :controller ) とすれば  "users" が取得できます。

params.require( :action ) とすれば "create" が取得できます。

params.require.permit の中身を確認

最後に params.require( :user ).permit( :name, :email ) の中身を確認します。

params.require( :user ) のままでは、user ハッシュの全ての値を取得してしまいます。

そこで permit( :name, :email ) を利用します。

permit メソッドは許可したいパラメータだけをフィルタしてくれます。

例えば、User モデルに存在しない「電話番号」を一緒に POST したとしても、許可されていないので返り値に存在しません。

$ curl -X POST 'http://localhost/users' -H "Content-Type: application/json" -d '{"user":{"name":"aaa","email":"bbb","tel":"111"}}'
{
  "name" : "aaa",
  "email" : "bbb"
}

これは API を作成する場合など、予期せぬパラメータの付与を避けたい場合に非常に便利です。

おまけ

配列のパラメータを受け取りたい場合

ちなみに「メールアドレス」が複数ある場合など、配列でパラメータを受け取りたい場合は以下のように記載します。

private
  # Never trust parameters from the scary internet, only allow the white list through.
  def user_params
    params.require(:user).permit(:name, email: []) # 配列の場合
  end

user ハッシュを POST して見ると、以下のような返り値が取得できます。

$ curl -X POST 'http://localhost/users' -H "Content-Type: application/json" -d '{"user":{"name":"aaa","email":["aa","bb","cc"]}}'
{
  "name" : "aaa",
  "email" : [
    "aa",
    "bb",
    "cc"
  ]
}

まとめ

3 段階に分けて返り値を確認しました。

  1. params の中身を確認
  2. params.require の中身を確認
  3. params.require.permit の中身を確認

返り値を比べることで require と permit メソッドの役割が分かったかなと思います。

基本的には create や update メソッドで 今回のストロングパラメータが使われることが多いです。

ストロングパラメータは mass assignment 脆弱性の対策に導入されたと言われています。ユーザ操作によって本来更新すべきでないカラムが更新されるのを防ぐためです。

ストロングパラメータを利用して安全で綺麗なコードを書きましょう。

【AWS】.ssh/config を利用して、ログイン時のコマンドを省略する

概要

インスタンスに毎回ログインする際、ユーザ名と鍵 PATH を記載するのが面倒くさいので、設定ファイル (.ssh/config) にログインに必要な情報を記載して、ログイン時のコマンドを省略しようと思います。

.ssh/config とは

SSH を利用してインスタンスへログインする際に利用される設定ファイルです。

インスタンスに SSH する前に...

ユーザ名の確認

サーバ OS によって、ログイン時に利用するユーザ名が異なる様です。

サーバ OS ユーザ名
 Amazon Linux  ec2-user
 RHEL  ec2-user または root
 Ubuntu  ubuntu または root
 Centos  centos
 Fedora  ec2-user
 SUSE  ec2-user または root

参考:SSH を使用した Linux インスタンスへの接続 - Amazon Elastic Compute Cloud

鍵 PATH の確認

初めてインスタンスを作成したときに、キーペアを作成して、自分のPCにダウンロードしていると思います。

もし、キーペアが見つからない時は、既存のキーペアは再ダウンロードできないので、新しく作り直してください。

f:id:kyamanak83:20170815001757p:plain

ログイン方法

SSHコマンドでログイン

ssh -i <鍵 PATH> <ユーザ名>@<ホスト名> のフォーマットでコマンドを実行します。

$ ssh -i amazon_private_key-20160805.pem ec2-user@ec2-00-00-000-000.us-west-2.compute.amazonaws.com

.ssh/config を利用してログイン

設定ファイルの記載内容を確認します。ホスト名や鍵名は自分のものに書き換えてください。

$ cat .ssh/config
Host AWS
    HostName ec2-00-00-000-000.us-west-2.compute.amazonaws.com
    User ec2-user
    IdentityFile amazon_private_key-20160805.pem

設定ファイルに記載しておくと、簡単なコマンドでログインができます。

$ ssh AWS
Last login: Wed Nov 23 06:38:46 2016 from aa200000000000000000.userreverse.dion.ne.jp

       __|  __|_  )
       _|  (     /   Amazon Linux AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-ami/2016.09-release-notes/

 まとめ

今回は、Amazon Linux を利用してログインしてみました。

インスタンスにログインするための最低限の .ssh/config の設定をまとめました。

【Ruby on Rails】ルーティング member と collection の違い

概要

Rails には index, show, new, edit, create, update, destroy の 7 つのデフォルトアクションが用意されています。

もし、これ以外のアクションを新しく追加したい場合、ルーティングに設定を追記する必要があります。

この場合、routes.rb をどう書くのか調べたところルーティングのメソッドに、member と collection と言うものがありました。

この 2 つの使い方を簡単にまとめました。

member の使い方

member は特定のデータに対するアクションに利用します。

例えば、ユーザーのフォローを行う follow アクションを新しく追加したいとします。

この場合、フォローを行うのは、特定のユーザーです。

『 A さんが B さんをフォローする』の様な処理になると思います。

なので、URL を作成するとしたら、下記の様になります。

特定のユーザーを示す id が含まれていることに注目してください。

http://$(DNS)/users/1/follow

member を使った routes.rb の記載方法は下記の様になります。

$ cat config/routes.rb
Rails.application.routes.draw do
  resources :users do
    post :follow, on: :member
  end
end

collection の使い方

collection は全部のデータに対するアクションに利用します。

例えば、ユーザーの検索を行う search アクションを新しく追加したいとします。

この場合、検索対象は全部のユーザーです。

A さんが検索しても、B さんが検索しても同じ結果になるはずです。

なので、URL を作成するとしたら、下記の様になります。

http://$(DNS)/users/search

collection を使った routes.rb の記載方法は下記の様になります。

$ cat config/routes.rb
Rails.application.routes.draw do
  resources :users do
    get :search, on: :collection
  end
end

まとめ

member と collection の使い方をまとめました。

member は特定のデータに対するアクションに利用します。

collection は全部のデータに対するアクションに利用します。

和訳すると collection は集団、member は (集団の) 一員 みたいな意味なので、そう覚えておけば分かりやすいですね。

【Ruby on Rails】ルーティング scope と namespace の違い

概要

Rails で API を作成するときに、URL 設計を気にすると思います。

例えは、ユーザ情報を操作する users API のエンドポイントを下記のようなパスで作成したいとします。

http://$(DNS)/api/v1/users

この場合、routes.rb をどう書くのか調べたところルーティングのメソッドに namespace と scope と言うものがありました。

この 2 つの使い方を簡単にまとめました。

namespace の使い方

namespace を使うときは、Controller に URL のパス同様に /api/v1/ のディレクトリを作成する必要があります。

$ tree app/controllers/
app/controllers/
├── api
│   └── v1
│       └── users_controller.rb
├── application_controller.rb
└── concerns

app/controllers/api/v1/users_controller.rb の中身は、module を使って URL のパス同様の構成にします。

$ cat app/controllers/api/v1/users_controller.rb
module Api
  module V1
    class UsersController < ApplicationController
      def index
       render json: { status: 200, message: 'Success' }
      end
    end
  end
end

routes.rb は namespace を利用すると、このように記載できます。

$ cat config/routes.rb
Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      resources :users, only: :index
    end
  end
end

ルーティングを確認します。

ひとまず、作成したい API のエンドポイントが用意できました。

$ rake routes
      Prefix Verb URI Pattern             Controller#Action
api_v1_users GET  /api/v1/users(.:format) api/v1/users#index

最後にエンドポイントにアクセスできるか確認してみます。

$ curl 'http://localhost/api/v1/users'
{"status":200,"message":"Success"}

出来ました!レスポンスに想定通りの結果が返ってきました!

scope の使い方

scope を使うときには、Controller にわざわざディレクトリを作成する必要はありません。そのままで大丈夫です。

$ tree app/controllers/
app/controllers/
├── application_controller.rb
├── concerns
└── users_controller.rb

app/controllers/users_controller.rb の中身もそのままで大丈夫です。

$ cat users_controller.rb
class UsersController < ApplicationController
  def index
    render json: { status: 200, message: 'Success' }
  end
end

routes.rb は scope を利用すると、このように記載できます。

$ cat config/routes.rb
Rails.application.routes.draw do
  scope :api do
    scope :v1 do
      resources :users, only: :index
    end
  end
end

ルーティングを確認します。

ひとまず、作成したい API のエンドポイントが用意できました。

$ rake routes
Prefix Verb URI Pattern             Controller#Action
 users GET  /api/v1/users(.:format) users#index

最後に、エンドポイントにアクセスできるか確認してみます。

$ curl 'http://localhost/api/v1/users'
{"status":200,"message":"Success"}

こちらの方法でも、想定通りの結果を受けることが出来ました!

まとめ

namespace と scope で URL を設計する方法をまとめました。

routes.rb の記載方法はどちらも同じです。

大きく異なるのは namespace を利用するときは、Controller 内に実際の URL パスと同様のディレクトリ構成を作る必要があるという点です。

namespace と scope どちらを使って URL 設計すれば良いかは、アプリケーションによると思います。

例えば、Web サーバの機能の一部に API の機能が混じっているアプリケーションの場合、Contoroller 内に API 用のディレクトリを作成して、namespace を使った URL 設計を行うのが綺麗な気がします。

API 機能しかないアプリケーションであれば、コントローラー内にわざわざディレクトリを作成せずに、scope を使ってを URL 設計を使うのが綺麗な気がします。

【AWS】Ruby on Rails + Nginx + Unicorn + MySQL 環境構築

概要

AWS に初めて Rails のアプリを作成した時の手順をまとめました。

はじめに

こちらの環境でアプリケーションを作成しました。

サーバOS   Amazon Linux
Web サーバ   Nginx
Rack サーバ   Unicorn
データベース   MySQL
フレームワーク   Rails 5.0.X
プログラミング言語   Ruby 2.4.X

環境構築

下記手順に沿って実行して頂ければ、Rails アプリの起動まで出来ると思います。

タイムゾーンの設定

初めにタイムゾーンの設定を変更します。

Amazon Linux では、デフォルトのタイムゾーンが UTC (協定世界時間) に設定されています。

このままでは扱いづらいので、JST (日本標準時間) に変更します。

$ sudo vim /etc/sysconfig/clock
ZONE="Asia/Tokyo" # ZONE エントリを書き換えます
UTC=true          # 変更しないでください

$ sudo ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
$ sudo shutdown -r now

$ date
Tue Jul 25 21:57:06 JST 2017 # JST に変更されています

参考:Linux インスタンスの時刻の設定 - Amazon Elastic Compute Cloud

 ロケールの設定

次にロケールの設定を行います。

基本的に UTF-8 に変更しようと思います。

$ sudo vim /etc/sysconfig/i18n
LANG=ja_JP.UTF-8
LC_CTYPE=ja_JP.UTF-8
LC_NUMERIC=ja_JP.UTF-8
LC_TIME=ja_JP.UTF-8
LC_COLLATE=ja_JP.UTF-8
LC_MONETARY=ja_JP.UTF-8
LC_MESSAGES=ja_JP.UTF-8
LC_PAPER=ja_JP.UTF-8
LC_NAME=ja_JP.UTF-8
LC_ADDRESS=ja_JP.UTF-8
LC_TELEPHONE=ja_JP.UTF-8
LC_MEASUREMENT=ja_JP.UTF-8
LC_IDENTIFICATION=ja_JP.UTF-8

先ほどと date コマンドを実行してみると、日本語表記になりました。

$ date
2017年  7月 25日 火曜日 21:58:56 JST

参考:ロケールとは - 国際化対応言語環境の利用ガイド

標準ライブラリ インストール

$ sudo yum install gcc-c++ glibc-headers openssl-devel readline libyaml-devel readline-devel zlib zlib-devel

Git インストール

$ sudo yum install git

MySQL インストール

$ sudo yum install mysql mysql-devel

Nginx インストール

$ sudo yum install nginx

Ruby インストール

現在の Ruby の Version を確認します。

$ ruby -v
ruby 2.0.0p648 (2015-12-16) [x86_64-linux]

Rails 5.0.0 以上は Ruby 2.2.2 以上が必要となります。

Rails 5.0.0 以上を使いたいので、Ruby の Version を 2.4.0 にアップデートします。

$ git clone git://github.com/sstephenson/rbenv.git ~/.rbenv
$ git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ source ~/.bash_profile

$ rbenv install -v 2.4.0
$ rbenv rehash
$ rbenv global 2.4.0
$ ruby -v
ruby 2.4.0p0 (2016-12-24 revision 57164) [x86_64-linux]

参考

Bundler インストール

$ gem install bundler

Rails インストール

Rails 5.0.X 系の最新バージョンをインストールします

$ gem install --no-ri --no-rdoc rails --version="~>5.0.0"
$ rails -v
Rails 5.0.4

 Rails アプリ作成

hogehoge アプリケーションを作成します。

# 作業用ディレクトリ確保します
$ sudo mkdir /var/workspace
$ sudo chown ec2-user /var/workspace
$ cd /var/workspace

# データベースは MySQL を指定します
# Gemfile を修正したいので、この段階では bundle install を skip します
$ rails new hogehoge -d mysql --skip-test --skip-bundle

# 作成したアプリケーションに移動してください
$ cd hogehoge

Gemfile を修正

Rails では アプリケーションで利用したい gem パッケージを管理した、Gemfile というものがあります。

この Gemfile に unicorn の設定を追加してインストールする必要があります。

ちなみに、Javascript を利用するために必要なので、therubyracer も追加してください。(既に Gemfile に記載されているので、コメントアウトを外すだけでもいいです。)

# この 2 行を Gemfile に追加してください
$ vim Gemfile
gem 'unicorn', '~> 5.3'
gem 'therubyracer', platforms: :ruby

# Gemfile に書かれた gem パッケージと、その依存パッケージをインストールします
$ bundle install --path vendor/bundle

 Nginx の設定

3 箇所ほど修正点があります。

$ sudo vim /etc/nginx/nginx.conf
user ec2-user; # user 変更
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;

include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    include /etc/nginx/conf.d/*.conf;

    index   index.html index.htm;
    
   # unicornapp 追加
    upstream unicornapp {
        server unix:/var/workspace/hogehoge/tmp/unicorn.sock;
    }
    server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  localhost;
        root         /usr/share/nginx/html;

        include /etc/nginx/default.d/*.conf;

        location / {
            proxy_pass http://unicornapp; # proxy_pass 追加
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }
}

設定ファイルを更新したら、Nginx を再起動します

$ sudo service nginx restart
nginx を停止中:                                            [  OK  ]
nginx を起動中:                                            [  OK  ]

 EC2 ダッシュボードに戻って、セキュリティグループにインターネットからの HTTP アクセス許可のルールを追加してください。

f:id:kyamanak83:20170725235134p:plain

ここまで完了すると、インスタンスのパブリックDNS、又はパブリック IP にブラウザからアクセスした時、Nginx のエラー画面が見れるはずです。

 

f:id:kyamanak83:20170726223713p:plain

データベース接続の設定

Rails アプリケーションで利用する、データベースとの接続設定を行います。

データベースは既にRDSインスタンスに用意してあるものとして話を進めます。

$ vim config/database.yml
default: &default
 adapter: mysql2
 encoding: utf8
 pool: 5
 port: 3306
 username:  # ユーザ名
 password:  # パスワード
development:
 <<: *default
 host: # ホスト名
 database: # データベース名

RDS ダッシュボードに戻って、セキュリティグループに、データベースに接続したい EC2 インスタンスのプライベート IP を追加してください。

f:id:kyamanak83:20170726225648p:plain

Unicorn の設定

最後に Rails アプリケーションの画面を表示するために、Unicorn の設定を行います。

Google で検索すると色んな人の設定が出てくると思います。

自分は下記のように設定しました。(自分も Google 検索で誰かのをパクりました^^;)

新しく config/unicorn.rb ファイルを作成します。

$ vim config/unicorn.rb
rails_root = File.expand_path('../../', __FILE__)

worker_processes 2

working_directory rails_root
timeout 30
preload_app true

# unicorn.sock ファイルの PATH を変更する場合は nginx.conf の修正も必要です
listen "#{rails_root}/tmp/unicorn.sock"
pid "#{rails_root}/tmp/unicorn.pid"

stderr_path File.expand_path('../../log/unicorn_stderr.log', __FILE__)
stdout_path File.expand_path('../../log/unicorn_stdout.log', __FILE__)

preload_app true

before_fork do |server, worker|
  old_pid = "#{server.config[:pid]}.oldbin"
  if old_pid != server.pid
    begin
      sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
      puts "Sending #{sig} signal to old unicorn master..."
      Process.kill(sig, File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end

  sleep 1
end

after_fork do |server, worker|
  ActiveRecord::Base.establish_connection if defined?(ActiveRecord::Base)
end

Unicorn を起動させます

$ bundle exec unicorn -c config/unicorn.rb -E development -D

すると 、Rails の画面が表示されました!

 

f:id:kyamanak83:20170726225834p:plain

 これで、Rails アプリケーションの作成が完了です。おしまい。

【Ruby on Rails】緯度経度から 2 点間の距離を算出する

概要

最近では企業が多くの API を公開しており、それらを利用して簡単にアプリケーションを作成できるようになりました。

その中には、店舗情報を扱う API も数多く存在します。

店舗情報を扱う API の多くは、店舗の位置情報を表すため緯度経度を利用しています。

位置情報を利用したアプリケーションを作成する場合、緯度経度から 2 点間の距離を求めたいと思うことがあるかもしれません。

例えば、店舗情報 API と組み合わせて、『現在地周辺の半径 ○○ m 以内のレストランを近い順に表示したい』と思った場合、これに該当します。

今回、緯度経度から 2 点間の距離を算出するプログラムを作成する機会があったので、その時のことをまとめました。

計算式

地球は球体なので、地球上の 2 点間の距離を算出するには、大円距離を求める必要があります。

大円距離 - Wikipedia

大円距離の計算式はいくつかあるようですが、全ての距離に対して用いることのできる Vincenty 法を利用することとします。

Vincenty法 - Wikipedia

プログラムを作成

Vincenty 法に当てはめていきます。

def distance(lat1, lng1, lat2, lng2)
  # ラジアン単位に変換
  x1 = lat1.to_f * Math::PI / 180
  y1 = lng1.to_f * Math::PI / 180
  x2 = lat2.to_f * Math::PI / 180
  y2 = lng2.to_f * Math::PI / 180
  
# 地球の半径 (km) radius = 6378.137
# 差の絶対値 diff_y = (y1 - y2).abs
calc1 = Math.cos(x2) * Math.sin(diff_y) calc2 = Math.cos(x1) * Math.sin(x2) - Math.sin(x1) * Math.cos(x2) * Math.cos(diff_y)
# 分子 numerator = Math.sqrt(calc1 ** 2 + calc2 ** 2)
# 分母 denominator = Math.sin(x1) * Math.sin(x2) + Math.cos(x1) * Math.cos(x2) * Math.cos(diff_y)
# 弧度 degree = Math.atan2(numerator, denominator)
# 大円距離 (km) degree * radius end

 

abs は数値の絶対値を取得するメソッドです。

絶対値を取得する - 数値(Numeric)クラス - Ruby入門

数値計算用のメソッドはこちらを参考にしてください。

module Math (Ruby 2.4.0)

算出結果の確認

先ほどのプログラムで新宿駅と渋谷駅の距離を求めます。

# 新宿駅
lat1 = 35.689407
lng1 = 139.700306

# 渋谷駅
lat2 = 35.658034
lng2 = 139.701636

distance = distance(lat1, lng1, lat2, lng2)

# 小数点 6 桁で四捨五入
puts "#{distance.round(6)} km"

プログラムを実行します。

$ ruby distance.rb
3.494497 km

 

下記サイトで 2 点間の緯度経度を入力した結果と同じになりました。

2地点間の距離と方位角 - 高精度計算サイト

まとめ

緯度経度から 2 点間の距離を算出する場合、大円距離を求める必要があります。

プログラムは Vincenty 法の公式に当てはめれば実装できます。

最後に

Ruby on Rails には位置情報を扱う gem がいくつか用意されています。

github.com

github.com

今回、作成した 2 点間距離を求めるプログラムも機能として盛り込まれています。

他にも多くの機能が用意されているようなので gem を使ってみるのも有りだと思います。

自分のアプリケーションにあった方法を採用してください。

【Ruby on Rails】 gem を使わないで環境毎に定数管理をする

概要

Ruby on Rails には定数管理用の gem がたくさん存在しています。

有名な gem だと以下の名前が上がるかと思います。

github.com

github.com

  • config は環境毎に yaml ファイルを用意して定数を管理するので、大規模アプリケーション向けのイメージがあります。管理する定数が多い場合にオススメです。
  • settingslogic は 1 つの yaml ファイルで環境毎の定数を管理するので、中小規模アプリケーション向けのイメージがあります。管理する定数が少ないのであれば見やすくてオススメです。

ただ、ここで『定数管理ぐらい自分で実装できないのか?』と疑問に思ったことがあります。

そこで今回、自分で簡単に実装できる環境毎に定数を管理する方法をまとめました。

はじめに

gem を使わない定数管理方法だと、以下の 2 つがオーソドックスな方法かと思います。

  • application_controller.rb で定数を管理する方法
  • config/initializers/constants.rb のようなファイルを用意して、そこで定数を管理する方法

application_controller.rb はアプリケーション共通の処理を管理する場所なので、ここで定数を管理するのはあまり相応しくないと思います。

config/initializers/constants.rb のようにファイルを用意して、そこで定数を管理する方法は賛成です。

ただし ruby ファイルではなく、環境毎に設定を管理しやすい yaml ファイルを利用しようと思います。

yaml を利用した定数管理用法

用意するファイルは以下の2 つです。

  • config/constants.yml
  • config/initializers/00_load_config.rb

config/constants.yml

これが定数を管理するためのファイルです。

yaml の機能であるアンカーとエイリアスを利用して、環境毎に定数を出し分けています。

common の設定をベースに development と production で設定を上書きしている感じです。

# アプリケーション共通の設定を記載します
common: &common
  domain:
    example: http://example.com/
    port:
      - 80
      - 443
  message: This is application common setting

# 開発環境の設定を記載します
development:
  <<: *common
  message: This is development environment setting

# 本番環境の設定を記載します
production:
  <<: *common
  message: This is production environment setting

config/initializers/00_load_config.rb

これは先程の /config/constants.yml に記載した設定をアプリケーション起動時に読み込んで、ruby で扱える定数に変換しています。

file = "#{Rails.root}/config/constants.yml"

# 再帰的にオブジェクトを凍結します
def deep_freeze(hash)
  hash.freeze.each_value do |i|
    i.kind_of?(Hash) ? deep_freeze(i) : i.freeze
  end
end

CONFIG = deep_freeze(YAML.load_file(file)[Rails.env].deep_symbolize_keys)

 

deep_symbolize_keys は再帰的にシンボル形式に変換してくれます。

https://apidock.com/rails/v4.0.2/Hash/deep_symbolize_keys

freeze は オブジェクトを凍結、つまり変更不可 (定数) にします。

http://ref.xaio.jp/ruby/classes/object/freeze

動作確認

期待通り定数が取得出来ることをコンソール経由で試してみます。

設定した定数は以下のように CONFIG で呼び出す事が出来ます。

$ rails c
Loading development environment (Rails 5.0.2)
irb(main):001:0> CONFIG
=> {:domain=>{:example=>"http://example.com/", :port=>[80, 443]}, :message=>"This is development environment setting"}

# 開発環境用の設定に上書きされています
irb(main):002:0> CONFIG[:message]
=> "This is development environment setting"

# 凍結されています
irb(main):003:0> CONFIG.frozen?
=> true

# ネストした値も凍結されています
irb(main):004:0> CONFIG[:domain][:exapmle].frozen?
=> true 

# 変更しようとするとエラーになります
irb(main):005:0> CONFIG[:message] << 'Hello'
RuntimeError: can't modify frozen String

まとめ

gem を使わずに環境毎に定数管理する方法は割と簡単に実装出来ました。

自分で実装することの利点はこの 2 つかなと思います。

  • 仕様を把握できる点
  • 自分で拡張できる点

ただし、1 つの yaml ファイルで全ての定数を管理するので、中小規模アプリケーション向けの方法だと思います。

Ruby on Rails の定数管理方法に答えはないと思います。

どの方法も一長一短なので、開発しているアプリケーションに合った方法を見極めて採用するのがいいと思います。