Tag Archives: Rails

Herokuの手始め

毎回Herokuを使ってみようと思うたびに手順を忘れるのでメモ。

アプリケーションの準備

hello_herokuというRailsアプリケーションを作ることにする。 Herokuの標準のDBがPostgreSQLなので今回もそれを指定する。 Test::UnitとPrototype.jsは要らないので-JTする。

$ rails -v #=> 3.0.9
$ rails new hello_heroku -JT -d postgresql
$ cd hello_heroku

いつもどおりgitリポジトリを作る

$ git init
$ git add .
$ git commit -am "initial commit"

専用のRVM gemsetも作っておく。

$ rvm use 1.9.2@hello_heroku --create
$ echo "rvm use 1.9.2@hello_heroku" > .rvmrc

Gemfileにpgとherokuを追加してインストール。 (heroku gemは:require => falseでもいいのかな?)

$ vim Gemfile
+ gem 'pg'
+ gem 'heroku'
$ bundle
$ git add .
$ git commit -am "add pg and heroku gems"

念のためGitHubに上げておく。 (hubコマンドが便利なので、alias git=hubしてある。)

$ git create hello_heroku
$ git push -u origin master

リポジトリはa2ikm/hello_herokuにある。

Herokuにアップロードする

とりあえずアプリケーションができたので、Herokuにアプリケーションを新規に登録してアップロードする。

$ heroku create hello-heroku
$ git push -u heroku master

アプリケーション名が重複していなければ、これで http://hello-heroku.heroku.com で動いていることが確認できる。

あとはいつもどおり機能を実装して、git pushしてアップロードすればok。

まとめ

使いたいと思いつつも結局使っていないHeroku。SSLが無料だったりするので、うーん、なんか作りたい。


nginx + unicorn + Rails on Mac

とりあえず動かしてみたのでメモ。

unicornを動かす

まずはgemをインストール。

$ gem install unicorn

unicornの処理を設定する

$ cd <RAILS_ROOT>
$ vi config/unicorn.rb

<RAILS_ROOT>/config/unicorn.rbはこんな感じ(nginx + unicorn を試してみたからほぼそのまま拝借):

# ワーカーの数
worker_processes 2

# ソケット経由で通信する
listen File.expand_path('tmp/sockets/unicorn.sock', ENV['RAILS_ROOT'])

# ログ
stderr_path File.expand_path('log/unicorn.log', ENV['RAILS_ROOT'])
stdout_path File.expand_path('log/unicorn.log', ENV['RAILS_ROOT'])

# ダウンタイムなくす
preload_app true

before_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!

  old_pid = "#{ server.config[:pid] }.oldbin"
  unless old_pid == server.pid
    begin
      # SIGTTOU だと worker_processes が多いときおかしい気がする
      Process.kill :QUIT, File.read(old_pid).to_i
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end
end

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

そして起動。

$ unicorn_rails -c config/unicorn.rb -E production -p 5000 -D

unicorn_railsはRailsアプリケーションをunicornで起動するためのコマンド。最近のRailsはRackに乗っかっているからunicornコマンドだけでも十分だけど、unicorn_railsならではのメリットもあるらしい(さくらの VPS に登録してみた(4) nginx + unicorn参照)。

-cオプションで設定ファイルを指定、-EでRAILS_ENVを指定(デフォルトはdevelopment)、-pでポートを指定(デフォルトは8080)、-Dでデーモンとして動作。それ以外については-hオプションで一覧が表示される。

nginxを動かす

まずHomebrewでインストールして起動。

$ brew install nginx
$ cp /usr/local/Cellar/nginx/1.0.4/org.nginx.nginx.plist ~/Library/LaunchAgents/
$ launchctl load -w ~/Library/LaunchAgents/org.nginx.nginx.plist

この時点で http://localhost:8080 にアクセスするとnginxの初期画面が表示されるはず。

apacheでいうところのconf的なものは/usr/local/etc/nginx以下に入れられる。 さて、設定。

$ cd /usr/local/etc/nginx
$ mkdir -p sites-available sites-enabled
$ vi nginx.conf
$ ln -s /usr/local/etc/nginx/sites-available/default sites-enabled/default
$ vi sites-available/default

まずは初期画面に関する設定をnginx.confからsites-available/defaultに書き写す。

nginx.confの差分:

@@ -32,87 +32,6 @@ http {
 
     #gzip  on;
 
-    server {
-        listen       8080;
-        server_name  localhost;
-
-        #charset koi8-r;
-
-        #access_log  logs/host.access.log  main;
-
-        location / {
-            root   html;
-            index  index.html index.htm;
-        }
-
-        #error_page  404              /404.html;
-
-        # redirect server error pages to the static page /50x.html
-        #
-        error_page   500 502 503 504  /50x.html;
-        location = /50x.html {
-            root   html;
-        }
-
-        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
-        #
-        #location ~ \.php$ {
-        #    proxy_pass   http://127.0.0.1;
-        #}
-
-        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
-        #
-        #location ~ \.php$ {
-        #    root           html;
-        #    fastcgi_pass   127.0.0.1:9000;
-        #    fastcgi_index  index.php;
-        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
-        #    include        fastcgi_params;
-        #}
-        # deny access to .htaccess files, if Apache's document root
-        # concurs with nginx's one
-        #
-        #location ~ /\.ht {
-        #    deny  all;
-        #}
-    }
-
-
-    # another virtual host using mix of IP-, name-, and port-based configuration
-    #
-    #server {
-    #    listen       8000;
-    #    listen       somename:8080;
-    #    server_name  somename  alias  another.alias;
-
-    #    location / {
-    #        root   html;
-    #        index  index.html index.htm;
-    #    }
-    #}
-
-
-    # HTTPS server
-    #
-    #server {
-    #    listen       443;
-    #    server_name  localhost;
-
-    #    ssl                  on;
-    #    ssl_certificate      cert.pem;
-    #    ssl_certificate_key  cert.key;
-
-    #    ssl_session_timeout  5m;
-
-    #    ssl_protocols  SSLv2 SSLv3 TLSv1;
-    #    ssl_ciphers  ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
-    #    ssl_prefer_server_ciphers   on;
-
-    #    location / {
-    #        root   html;
-    #        index  index.html index.htm;
-    #    }
-    #}
-
+    include /usr/local/etc/nginx/sites-enabled/*;
 }

末尾の追加項目は、nginx.confにべた書きするのではなく、sites-enabled/以下に分割されたファイルを読み込むように設定するための記述である。 sites-available/defaultは次のようにした。

+server {
+     listen       8081;
+     server_name  localhost;
+     
+     location / {
+         root   html;
+         index  index.html index.htm;
+     }
+}
+

もともとは8080をlistenしていたが、Railsアプリのほうに8080を使いたかったのでここではとりあえず8081にした。 root html;はドキュメントルートをhtmlディレクトリ(ここでは/usr/local/Cellar/nginx/<VERSION>/htmlになる)に設定している。

ここまでやって文法にミスが無ければnginxを再起動して、 http://localhost:8081 にアクセスしてみる。

$ /usr/local/sbin/nginx -t # 文法チェック
$ launchctl unload ~/Library/LaunchAgents/org.nginx.nginx.plist
$ launchctl load ~/Library/LaunchAgents/org.nginx.nginx.plist

nginxとunicornをつなぐ

次にunicornとつないでRailsアプリを表示してみる。

$ ln -s /usr/local/etc/nginx/sites-available/<APPNAME>-unicorn sites-enabled/<APPNAME>-unicorn
$ vi sites-available/<APPNAME>-unicorn
upstream redmine-unicorn {
  server unix:/path/to/RAILS_ROOT/tmp/sockets/unicorn.sock;
}

server {
  listen 8080;
  server_name localhost;

  root /path/to/RAILS_ROOT/public;
  access_log /path/to/RAILS_ROOT/log/access.log;
  error_log /path/to/RAILS_ROOT/log/error.log;

  location / {
    if (-f $request_filename) { break; }
    # ファイルが存在しなければunicornにproxyする
    proxy_set_header X-Real-IP  $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_pass http://<APPNAME>-unicorn;
  }

  # この記述がないと静的ファイルが表示されなかった
  location ~* \.(ico|css|js|gif|jpe?g|png)(\?[0-9]+)?$ {
    expires 1y;
  }
}

ここまでやって文法にミスが無ければnginxを再起動して、 http://localhost:8080 にアクセスしてみる。

$ /usr/local/sbin/nginx -t # 文法チェック
$ launchctl unload ~/Library/LaunchAgents/org.nginx.nginx.plist
$ launchctl load ~/Library/LaunchAgents/org.nginx.nginx.plist

最後に

とりあえず動いた。

以下、気になる点:

  • ApacheとNginx、PassengerとUnicorn、どっちがどういいんだろう?
  • 再起動するためにlaunchctlを呼ぶのは面倒くさい、なにか適当なコマンドはないのかな?
  • passengerのnginxモジュールを入れるためにはnginxを再コンパイルする必要があるみたいで、そうなるとRVMでgemsetを自由に切り替えられる環境とは相性が悪いのでは(本番ではRVMを使わないから関係ないだろうけど)
  • nginxの設定ファイルのセミコロンが面倒くさい

ちなみに今回なぜ80を使わずに808xを使ったかというと、powが80を奪ってしまっているから。Apacheに関してはIPv6で動かして回避するという方法があるらしいけど、nginxはどうなんだろう?


has_manyな関連先をまとめてINSERTする

fields_foraccepts_nested_attributes_forを使って、has_manyな関連先をまとめてINSERTする方法。ソースはgithubに上げておいた。ちなみにRails 3.0.8。

Post has_many Tags through Taggingsというモデルがあったとする。

Post has_many Tags through Taggings.

とりあえずscaffoldはこんな感じ。Taggingだけは画面が要らないのでmodelだけ。

$ rails g scaffold posts title:string text:text
$ rails g scaffold tags name:string
$ rails g model tagging post:references tag:references
$ rake db:migrate

コードの修正で重要なのは次の2点。

app/models/post.rb に accepts_nested_attributes_forを設定する。

class Post < ActiveRecord::Base
  has_many :taggings
  has_many :tags, :through => :taggings
  accepts_nested_attributes_for :taggings
end

これで次のようなコードが実行された時にTaggingもまとめて作ってくれるようになる。

Post.create(
  :title => "タイトル", :text => "本文",
  :taggings_attributes => [
    { :tag_id => 1 }, { :tag_id => 2 }
  ]
)

app/views/posts/_form.html に fields_for を使ってリレーション先についてのフォームを作る。第一引数がtaggings_attributes[]になっているのがポイント。これはaccepts_nested_attributes_forに合わせて設定する。

  <div class="field">
    Tags<br />
    <% @taggings.each do |tagging| %>
      <%= f.fields_for "taggings_attributes[]", tagging do |tf| %>
        <%= tf.select :tag_id, Tag.all.map { |x| [x.name, x.id] } %>
      <% end %>
    <% end %>
  </div>

なお、 @taggings はapp/controllers/posts_controller.rbで事前に用意しておく。 @postからリレーションで辿らないのは、新規にPostを作るフォームでは@post.taggingsが必ず空なのでeachが回らないから。

  # GET /posts/new
  # GET /posts/new.xml
  def new
    @post = Post.new
    @taggings = Array.new(3) { Tagging.new }

    respond_to do |format|
      format.html # new.html.erb
      format.xml  { render :xml => @post }
    end
  end

  # GET /posts/1/edit
  def edit
    @post = Post.find(params[:id])
    @taggings = @post.taggings
  end

あとはサーバを起動して/tagsからタグを登録し、/postsから記事を投稿すればok。 一応どんな感じの画面になるのかスクリーンショットを載せておこう。

作り込むなら、

  • accepts_nested_attributes_for のreject_ifオプションでtag_idがblankなら無視する
  • dependentにdestroyを設定する
  • Taggingでpost_idとtag_idのペアが一致するものが複数できないように制限をかける

などの点が気になるけど、今回は置いておく。

accepts_nested_attributes_for自体はhas_oneでも使えて、それに合わせてfields_forの引数も単数形にすれば良い。詳しくはリファレンスを参照。


Rails勉強会@東京第58回に行ってきた

昨年12/18に行われたRails勉強会@東京第58回に行ってきた。

テストの話

「テストの話」で大まかに扱われるけど、細かく見ると次の3つの話題が出てくる。

  • どうやってテストを書くか
  • うまいテストの仕方・資産としての残し方
  • テストの文化の浸透のさせ方・継続の仕方

今回は@moroさんをファシリテータとして、「rspecでテストをどう書くか」と「RelishでRspecの新機能を見る」の2点を行った。

話題がバラバラになりがちなので、進行をされた@moroさんはすごいなと思っていたんだけど、ここらへんをうまくカバーできるやり方を見つけたいと思う今日この頃。

で、その時の結論をうけた上で今覚えてる限りの結論。

どうやってテストを書くか

(いつか書く)

うまいテストの仕方・資産としての残し方

今回は出なかったけど、捨てるテスト、残すテストの話、かなぁ。

開発時の内部実装まで含めた、細かい挙動確認のためのテストで残しておくと後々リファクタリングすることができなくなるので、開発が一段落したらいっそ捨てるのもアリ。

それに対して外側からアプリケーションを叩いてそのINとOUTを見るためのテストは資産として残さなきゃいけない。

テストの文化の浸透のさせ方・継続の仕方

Hudsonを使って継続的にテストの実行とRCovによるカバレッジの集計をさせてそれを共有するのが良いらしい。Rubyではないけどサイボウズでの事例も興味深い。

あとはペアプロでTDDに慣らしていくとか。

あとこれはちょっと別かもしれないけど、「最初にテストを書かなきゃいけないという話だけど、なかなか難しい」という話が出てきた。それに対して@moroさんの話では、「最終的にテストを書いてチェックができればいいので、実装とテストのどちらが先になっても問題はない」ということだった。

自分の場合も、動作の確認がちゃんとできればいいやと思って実装してからテストを書くことは多い。もちろん実装の規模にもよるんだろうけど。

OmniAuthの話

(あとで書く)

ActiveSupport 3系のソースを読む

(あとで書く)


Cloudant+ActiveResource

nosqlgogoに行ってきた!

そこでCouchDBについて初めて聴き、RESTful APIを持っているならActiveResourceで接続できるんじゃないか?と思い、Cloudantの2GB無料アカウントをもらったのでブログでも作ってみようと試してみた。

で、現在挫折中。残骸はgithubにあげてある。 実際はCouchRest Modelが使えそう(教えてくれた@d6rkaizさんありがとう!)。

一応作業のメモ。これでfind, firstは使えるようになる。

  • CloudantというActiveResource::Baseを継承した抽象クラスを作り、そこでCloudantにあるCouchDBとのやりとりを仲介することにした
  • レスポンスはすべてJSONで返ってくるが、APIのURL末尾に.jsonが不要なのでcollection_path、element_path、new_element_pathから.jsonを除く
  • さらにCouchDBでドキュメントの一覧を取得するには/some_documents/_all_docsというURLになるのでcollection_pathに_all_docsを書き加える
  • またドキュメントの一覧を取得した場合には以下のように多段rowsというキーに対して配列
  • 各ドキュメントごとに_idというプライマリキーを持つのでself.primary_keyを書き換える。ただしActiveResource::Baseのクラス変数のprimary_keyは継承したクラスごとに設定されてしまうため、そのままでは子クラスで共通して使うことができない。そこでcattr_accessorで上書きしてから使う。
class Cloudant < ActiveResource::Base
  self.site = "https://#{CLOUDANT_USER}.cloudant.com/"
  self.user     = CLOUDANT_USER
  self.password = CLOUDANT_PASS
  self.format   = :json

  # primary_key is for each model by default,
  # so override them by one class variable
  cattr_accessor :primary_key
  self.primary_key = "_id"

  def id_from_response(response)
    return nil if response.body.empty?

    ::JSON.parse(response.body)["id"].to_i
  end

  class << self
    def collection_path(prefix_options = {}, query_options = nil)
      prefix_options, query_options = split_options(prefix_options) if query_options.nil?
      "#{prefix(prefix_options)}#{collection_name}/_all_docs#{query_string(query_options)}"
    end

    def element_path(id, prefix_options = {}, query_options = nil)
      prefix_options, query_options = split_options(prefix_options) if query_options.nil?
      "#{prefix(prefix_options)}#{collection_name}/#{URI.escape id.to_s}#{query_string(query_options)}"
    end

    def new_element_path(prefix_options = {})
      "#{prefix(prefix_options)}#{collection_name}/new"
    end

    # in cloudant response collections are wrapped in a hash, so we have to peal them
    def instantiate_collection_with_cloudant(collection, prefix_options = {})
      if collection.is_a?(Hash) && collection["rows"].present?
        rows = collection["rows"].map { |row| { "_id" => row["id"] } }
        instantiate_collection_without_cloudant(rows, prefix_options)
       else
         instantiate_collection_without_cloudant(collection, prefix_options)
      end
    end
    alias_method_chain :instantiate_collection, :cloudant
  end
end

ActionMailerでShift_JISなメールを送る

ActionMailerを使うとメールはデフォルトではUTF-8で送信されるけど、それをShift_JISで送信するには次のようにする。ちなみに2.3.8で確認済み。

  1. ビューのファイルをShift_JISで記述する
  2. ActionMailer::BaseでShift_JISにするように設定する

2については以下のような感じ。

# -*- coding: utf-8 -*-
class ShiftjisMailer < ActionMailer::Base
  self.default_charset       = "Shift_JIS"  # デフォルトの文字コード
  self.default_content_type  = "text/plain"

  def test
    recipients    "foo@example.com"
    from          "bar@example.com"
    subject       "てすと".tosjis # 件名はShift_JISに変換してから渡す
  end
end

named_scopeの自動生成+お手軽検索

@ukstudioさんの「scopeでお手軽検索」 をRails2.3向けに少し修正1しつつ、カラムごとに一致検索をするようなnamed_scopeを自動生成するようにフック部分にしかけをした。

例えばnameカラムとageカラムを持ったUserモデルについて、

class User < ActiveRecord::Base
  extend Searchable
end

とすると、次のような感じでカラムの条件で簡単に検索が可能になる。

User.name("jsmith").first #=> User named jsmith
User.search(:name => "jsmith", :age => 20..30)

追記

これだけだとwhereの導入された3.0では記法的なあまりメリットはないかもしれない。2.x系なら:conditionsをキーとしたハッシュが不要になるというメリットはあるけど。

やっぱり独自のnamed_scopeを作ってこそ便利になる気がする。


  1. Rails2.3だとscopedに続けてnamed_scopeのメソッドを呼び出すとエラーが出るらしく、injectの引数にはselfのみを渡すようにした。 


Rails3でTest::Unitを無効化

Rails3でTest::Unitを使わないようにする方法。rspecの導入方法については参考資料を参照。

まずrails newコマンドで新規プロジェクトを作成する際に-Tオプションを付けて、testディレクトリを作成させないようになる。

$ rails new new_project -T

次にconfig/application.rbを次のように編集して、rails/test_unit/railtieを読み込まないようにする。こうすることでTest::Unitのテンプレート自動生成等を行わないようにする。

--- config/application.rb.org  2010-10-05 11:39:47.000000000 +0900
+++ config/application.rb   2010-10-05 11:41:59.000000000 +0900
@@ -1,6 +1,9 @@
 require File.expand_path('../boot', __FILE__)
 
-require 'rails/all'
+require 'active_record/railtie'
+require 'action_controller/railtie'
+require 'action_mailer/railtie'
+require 'active_resource/railtie'
 
 # If you have a Gemfile, require the gems listed there, including any gems
 # you've limited to :test, :development, or :production.

参考資料


ActiveSupport::Concern

以前書いたモジュールの特異メソッドをincludeして使うと同じことが、ActiveSupport::Concernを使うことでもうちょっと綺麗に書ける。

module A
  extend ActiveSupport::Concern

  included do
    # Aがincludeされた際に、includeしたクラスのコンテキストで実行される
  end

  module InstanceMethods
    def instance_method_of_a
      p "instance_method_of_a"
    end
  end

  module ClassMethods
    def class_method_of_a
      p "class_method_of_a"
    end
  end
end
class B
  include A
end

こんなモジュールをとあるクラスBでincludeすることで、

  • InstansMethods以下に定義されたメソッドがincludeによってインスタンスメソッドとして追加
  • ClassMethods以下に定義されたメソッドがextendによってクラスメソッドとして追加

される。

モジュールをincludeすることによって追加されるクラスメソッド、インスタンスメソッドが明確になるので嬉しい。

ただそれだけだとちょっとメリットが弱い気がしなくもない。きっとincludedや、今回触れなかったextendedあたりが活躍するんだろう。

参考資料


ActiveModel::Callbacks

ActiveModel::Callbacksを使えばafter_saveなどのコールバックを手軽に実装できる。

まずActiveRecord::Baseのような抽象クラスを次のように定義する。

class AbstractModel
  extend ActiveModel::Callbacks
  define_model_callbacks :save

  def save
    _run_save_callbacks do
      # 具体的なsaveメソッドの内容
      # この前後にbefore_saveとafter_save、そしてaround_saveが呼ばれる
    end
  end
end

重要なのはsaveメソッドの内容を_run_save_callbacksでラップすること。これはコールバックを利用するメソッドに応じて動的に生成され、createメソッドであれば_run_create_callbacksを利用する。

そしてコールバックを利用したい具象クラスでは次のようにする。

class SomeModel < AbstractModel
  before_save :foo

  protected

  def foo
    # before_saveで実行したい内容
  end
end

これ以外にもbefore_saveにブロックを渡したり、before_saveをインスタンスメソッドとして定義しているオブジェクトを渡したりすることで処理を実装できる。

define_model_callbacksでは複数のコールバック対象のメソッドを指定することができる。

define_model_callbacks :save, :create

:onlyオプションを使うことで例えばafter_*だけに制限することもできる。

define_model_callbacks :save, :only => :after