active_decorator のdecoratorをrspecでテストする方法

active_decorator は便利なのですが、rspecでテストが上手く書けなくて、書き方を調べていた。
ただ、web上に情報が無く・・・


あ…ありのまま 今 起こった事を話すぜ!
「おれは decoratorのspecを書こうとしていたと
思ったら いつのまにかactive_decorator, rspec-rails, railsソースコードを読んでいた」

decorator のspecを書けるように色々と設定する

そのままだとspecが上手く書けないので、設定を追加しテストを書きやすくする。

application.rb でlib/のファイルを自動で読み込むようにする

config/application.rb の

    # config.autoload_paths += %W(#{config.root}/extras)

こんな感じでコメントアウトされている所を

    config.autoload_paths += %W(#{config.root}/lib)

に変更して、lib/ を読み込むようにする。*1

rspecのexample groupを追加する

spec/decorators/*.rb に対応するgroupを追加する。

lib/rspec/rails/example/decorator_example_group.rb

module RSpec::Rails
  module DecoratorExampleGroup
    extend ActiveSupport::Concern
    include RSpec::Rails::RailsExampleGroup
    include ActionView::TestCase::Behavior

    def decorate(obj)
      ActiveDecorator::Decorator.instance.decorate(obj)
      obj 
    end 

    included do
      metadata[:type] = :decorator

      before do
        ActiveDecorator::ViewContext.current = controller.view_context
      end 
    end 
  end 
end

spec_helper.rb でdecoratorの設定を追加する

spec/spec_helper.rb

RSpec.configure do |config|
    # for active_decorator
    require 'rspec/rails/example/decorator_example_group'
    config.include RSpec::Rails::DecoratorExampleGroup, :type => :decorator, :example_group => {
      :file_path => config.escaped_path(%w[spec decorators])
    } 
end

使い方の例

active_decorator のExamplesのコードに対応したspecを書いてみる。

app/decorators/user_decorator.rb

# app/decorators/user_decorator.rb
module UserDecorator
  def full_name
    "#{first_name} #{last_name}"
  end

  def link
    link_to full_name, website
  end
end

spec/decorators/user_decorator_spec.rb

describe UserDecorator do
  before do
    @user = User.create(first_name: 'sinsoku', last_name: 'listy',
                        website: 'http://d.hatena.ne.jp/sinsoku')
  end 

  subject { decorate @user }
  its(:full_name) { should eq 'sinsoku listy' }
  its(:link) { should eq '<a href="http://d.hatena.ne.jp/sinsoku">sinsoku listy</a>' }
end
実行結果
$ rspec spec/decorators/

UserDecorator
  link
    should eq "<a href=\"http://d.hatena.ne.jp/sinsoku\">sinsoku listy</a>"
  full_name
    should eq "sinsoku listy"

Finished in 0.07222 seconds
2 examples, 0 failures

追記

sinsoku/active_decorator at support_rspec https://github.com/sinsoku/active_decorator/tree/support_rspec

github でforkしてみた。
forkしたやつはspec_helper.rb に「require 'active_decorator/rspec'」を書くだけで済むようになってる

*1:decorator_example_group.rb が require さえ出来れば、どこに置いても問題ない

Gruffをruby1.9.3 + Railsで動くようにする

ruby 1.9.3 だとZeroDivisionErrorで動かなかったので、動く方法を調べた

参考ページ

解決方法

ググったら解決方法は色々と出てくるけど「gemのソースを直接変更する」は微妙なので、alias_method_chainで直すようにした。

# config/initializers/gruff/base.rb
module Gruff
  class Base
    def label_with_ruby193(value)
      if @marker_count.to_f == 0
        label = value.to_i.to_s

        parts = label.split('.')
        parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{THOUSAND_SEPARATOR}")
        parts.join('.')
      else
        label_without_ruby193(value)
      end 
    end 
    alias_method_chain :label, :ruby193
  end 
end

Railsで複数データベースを扱えるBanana

初めてgemを作って、RubyGems.orgで公開してみました。
ソースはGithubに置いてあります。

これは何?

1つのrailsアプリで複数データベースのマイグレーションを実行できるようにするgemです。
gem入れて、設定しておけば普段どおり rake db:create, rake db:migrate, rake db:drop が使えます。

インストール方法

gemなので、Gemfileに書いてbundle installするか、gem installで入ります。

Gemfile

gem 'banana'

もしくは

$ gem install banana

使い方

config/database.yml にdatabaseの設定を追加し、model/migrationに接続先の情報を書きます。
接続先の情報は /#{Rails.env}$/ になるような名前にして下さい。

config/database.yml

default: &default
  adapter: mysql2
  encoding: utf8
  reconnect: false
  pool: 5
  username: root
  password:
  socket: /var/run/mysqld/mysqld.sock

development:
  <<: *default
  database: fruit_development

test:
  <<: *default
  database: fruit_test

production:
  <<: *default
  database: fruit_production

vegetable_development:
  <<: *default
  database: vegetable_development

vegetable_test:
  <<: *default
  database: vegetable_test

vegetable_production:
  <<: *default
  database: vegetable_production

app/models/vegetable.rb

class Vegetable < ActiveRecord::Base
  establish_connection "vegetable_#{Rails.env}"
end

app/models/onion.rb

class Onion < Vegetable
end

db/migrate/20120728121720_create_onions.rb

class CreateOnions < ActiveRecord::Migration
  DATABASE_NAME = "vegetable_#{Rails.env}"

  def change
    create_table :onions do |t|

      t.timestamps
    end
  end
end
タスクの実行

いつものようにrake db:create, rake db:migrate, rake db:dropが使えます。

$ rake db:create
  • /#{Rails.env}$/ なデータベースを作成します
  • development環境の場合、/test$/ なデータベースも作ります。
$ rake db:drop
  • /#{Rails.env}$/ なデータベースを削除します。
  • development環境の場合、/test$/ なデータベースも削除します。
$ rake db:migrate
  • migrationファイルの接続先情報にあわせてテーブルを作成・更新します。

縛りプレイで勉強するGitの基礎

縛りプレイをすることで、Gitの基礎であるハッシュ(sha-1)を意識してみる。


縛りプレイ とは

1. SMプレイのうち亀甲縛りなど縄を使うプレイ(性行為)のこと。昭和から存在する言葉。
2. ゲームをプレイする際、本来ゲーム側からは設定されていない制限(縛り)を自ら科す事によって、より難易度の高いゲームをプレイする事。

記事のタイトルの意味は 2. の方です。

概要

縛りの条件は下記の通り。

つまり、リモート(origin含む)、ブランチ(master含む)、タグの使用禁止です。

関連して、下記のコマンド/操作も使用禁止です。

  • git clone (origin が作成されるため)
  • git checkout -b (-b オプションはブランチ作られるので禁止、それ以外はOK)
  • git pull (動作が複雑なので)
  • .git/ の直接編集(邪道なので)

つまり、常にdetached HEADで開発し、sha-1ハッシュをノートにメモしながら開発することになります。
これにより、「Gitの基礎であるハッシュ(sha-1)がどういうものか?」というのが分かります。*1

この記事には、cloneとか、pullみたいな上級者用のコマンドは登場しません。

Gitリポジトリの準備

まずはgitリポジトリを用意する必要があります。

$ mkdir git_proj
$ cd git_proj
$ git init

masterなんてブランチは不要なので、削除します。ただ、すぐに削除が出来ないため、空のコミットを作成し、その後にmasterブランチを削除します。

$ git commit --allow-empty -m "first commit"
$ git log -n 1
(表示されたsha-1の値をノートにメモする)
$ git checkout <メモしたsha-1>
$ git branch -d master

これで準備が出来ました。
実際に上記の手順で作業したのが下記の画像です。

ファイルの追加/コミットの手順

基本的に普通のadd/commitと同じです。

新しいファイルを作成して、追加した場合のコマンドなどを記載してみました。

$ echo "Hello, Git World" > README.md
$ git add README.md
$ git commit -m "add README"

コミット毎にノートにsha-1の値をメモしておいた方が良いです。(ブランチが使えないので)

$ git log
commit 421bcacadf8581e8cc459ea1254ee3990af967af
Author: sinsoku <sinsoku.listy@gmail.com>
Date:   Wed Jul 18 00:07:49 2012 +0900

    add README

commit 62f92f671d0e83d8a75fabd1dd46f64aa7b36dc6
Author: sinsoku <sinsoku.listy@gmail.com>
Date:   Tue Jul 17 23:59:08 2012 +0900

    first commit
sinsoku@sinsoku-vm:~/Projects/sample/git_proj(git:(421bcac...))
$ 

ファイルを変更したコミットを作ってみる。
markdown形式にREADME.mdを変更してみました。

$ echo "# Hello, Git World" > README.md
$ git add README.md
$ git commit -m "markdown形式に直した"

ログを確認してみる。

$ git log -n 1
commit 1a8905309eac98743b3b2facd30db992ff0d5b9e
Author: sinsoku <sinsoku.listy@gmail.com>
Date:   Wed Jul 18 00:15:44 2012 +0900

    markdown形式に直した

私の環境では、このような出力になりました。
sha-1の値は環境により異なりますが、大体の表示は同じになると思います。

ブランチ関連

branchコマンドを使わなくても、sha-1をcheckoutしてコミットをする事で自然にログが分岐します。

まず「add README」のsha-1の値をcheckoutしてみます。
(この値は各人で異なるので、自分の環境の値を使う)

$ git checkout 421bcacadf8581e8cc459ea1254ee3990af967af
Warning: you are leaving 3 commits behind, not connected to
any of your branches:

  1a89053 markdown形式に直した
  421bcac add README
  62f92f6 first commit

If you want to keep them by creating a new branch, this may be a good time
to do so with:

 git branch new_branch_name 1a8905309eac98743b3b2facd30db992ff0d5b9e

HEAD is now at 421bcac... add README
sinsoku@sinsoku-vm:~/Projects/sample/git_proj(git:(421bcac...))
$ 

警告が出ていますが、気にせずに作業します。
README.md は作られているので、Licenseのファイルでも作成してみましょう。

$ touch LGPL-2.1
$ git add LGPL-2.1
$ git commit -m "add License"

ログを確認しましょう。

$ git log

commit 1aeb413b6d839d85074cc0e3f5fee9f1dfe16252
Author: sinsoku <sinsoku.listy@gmail.com>
Date:   Wed Jul 18 00:29:18 2012 +0900

    add License

commit 421bcacadf8581e8cc459ea1254ee3990af967af
Author: sinsoku <sinsoku.listy@gmail.com>
Date:   Wed Jul 18 00:07:49 2012 +0900

    add README

commit 62f92f671d0e83d8a75fabd1dd46f64aa7b36dc6
Author: sinsoku <sinsoku.listy@gmail.com>
Date:   Tue Jul 17 23:59:08 2012 +0900

    first commit
sinsoku@sinsoku-vm:~/Projects/sample/git_proj(git:(1aeb413...))
$ 

「add License」のコミットは「add README」の次に作成されました。
現在の状態の詳細をlogのオプションを使って表示してみます。

$ git log --oneline --graph --decorate 1a8905309eac98743b3b2facd30db992ff0d5b9e 1aeb413b6d839d85074cc0e3f5fee9f1dfe16252
* 1aeb413 (HEAD) add License
| * 1a89053 markdown形式に直した
|/  
* 421bcac add README
* 62f92f6 first commit
sinsoku@sinsoku-vm:~/Projects/sample/git_proj(git:(1aeb413...))
$

上記のようにブランチが出来ています。
では、これをマージします。

$ git merge 1a8905309eac98743b3b2facd30db992ff0d5b9e

Merge made by the 'recursive' strategy.
 README.md |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
sinsoku@sinsoku-vm:~/Projects/sample/git_proj(git:(9e97a14...))
$ 

グラフ表示でログを確認してみます。

$ git log --oneline --graph --decorate
*   9e97a14 (HEAD) Merge commit '1a8905309eac98743b3b2facd30db992ff0d5b9e' into HEAD
|\  
| * 1a89053 markdown形式に直した
* | 1aeb413 add License
|/  
* 421bcac add README
* 62f92f6 first commit
sinsoku@sinsoku-vm:~/Projects/sample/git_proj(git:(9e97a14...))
$ 

ちゃんとマージされています。

リモートにブランチをpush

remoteコマンドやbranchコマンドが使えないため、で直接指定します。

$ git push git@github.com:sinsoku/git_proj.git 9e97a14:refs/heads/develop

これで、リモートリポジトリにdevelopブランチが作成されます。

リモートにタグをpush

これはブランチとほとんど同じです。

$ git push git@github.com:sinsoku/git_proj.git 9e97a14:refs/tags/v1.0.0

リモートの変更を取り込む

clone, pullが使えず、branchも使えないため、fetchを使います。
取り込んだ変更個所の指定は常にsha-1を使います。

例として、gitのリポジトリの変更を取り込んでみます。

$ mkdir git
$ cd git
$ git init
$ git fetch git://github.com/git/git.git

github上で確認すると、タグ v1.7.11.2 のハッシュタグは 8d141a1d562abb31f27f599dbf6e10a6c06ed73e のようです。

$ git log 8d141a1d562abb31f27f599dbf6e10a6c06ed73e
commit 8d141a1d562abb31f27f599dbf6e10a6c06ed73e
Author: Junio C Hamano <gitster@pobox.com>
Date:   Wed Jul 11 12:55:38 2012 -0700

    Git 1.7.11.2
    
    Signed-off-by: Junio C Hamano <gitster@pobox.com>

commit b700086d840ceaee339f9f031be72b34e237f84f
Merge: 2e1e8ef b31272f
Author: Junio C Hamano <gitster@pobox.com>
Date:   Wed Jul 11 12:58:28 2012 -0700

    Merge branch 'jc/maint-blame-unique-abbrev' into maint
    
    "git blame" did not try to make sure that the abbreviated commit
    object names in its output are unique.
    
    * jc/maint-blame-unique-abbrev:
      blame: compute abbreviation width that ensures uniqueness

上記のように、ログが表示されています(=変更が取得できています)。

参考書籍

入門Git

入門Git

Gitポケットリファレンス

Gitポケットリファレンス

後日談

この記事の話を隣の人に話したら「努力の方向性が間違っている」と言われました(´・ω・)

*1:たぶん

Ubuntu 12.04でEmacs24を入れる

備忘録。

$ add-apt-repository ppa:cassou/emacs
$ apt-get update
$ apt-get install emacs-snapshot

railsを少しずつ勉強する - 2. シンプルなブログ

前の記事の続きです。
シンプルなブログwebアプリを作成していきたいと思います。

シンプルなブログ の仕様

  • 記事を新規作成、編集、削除ができる
  • 記事に対して、コメントを新規作成、編集、削除ができる

モデルの設計

ブログ記事とコメントのモデルを設計してみます。

まずはCacooで設計などをしておき、必要な属性や関連などを洗い出す。
こんな感じ。

次に英語に直します。
もし英単語が分からない場合はgoo辞書などで調べると良いです。

記事のモデル

Article
 - title
 - body

コメントのモデル

Comment
 - article_id
 - body

railsのアプリ作成

railsでアプリを作成します。"sinsoku-blog"がアプリの名前になります。

  • -T はデフォルトのTest::Unitを使わないようにします。RSpecを使うので、指定しています。
  • --skip-bundle はアプリ作成後のbundle installを抑止します。
$ rails new sinsoku-blog -T --skip-bundle

Gemfileを編集します

Gemfileにはアプリで使用するrubyライブラリ(gem)が書かれています。
このファイルを追記・修正します。

Gemfile

@@ -15,7 +15,7 @@ group :assets do
   gem 'coffee-rails', '~> 3.2.1'
 
   # See https://github.com/sstephenson/execjs#readme for more supported ru
-  # gem 'therubyracer', :platforms => :ruby
+  gem 'therubyracer', :platforms => :ruby
 
   gem 'uglifier', '>= 1.0.3'
 end
@@ -36,3 +36,7 @@ gem 'jquery-rails'
 
 # To use debugger
 # gem 'debugger'
+
+group :test, :development do
+  gem 'rspec-rails'
+end

変更後、bundlerでgemをインストールします。

  • --path で他railsアプリに影響しないように、vendor/bundle のディレクトリにgemをインストールしています*1
$ bundle install --path vendor/bundle

railsのscaffold

rails generator(gに省略可)を使い、RESTful*2になるようなMVC(Model,View,Controller)の各ファイルを生成することができます。

$ rails g scaffold Article title:string body:text
$ rails g scaffold Comment article:belongs_to body:text
テーブルの作成

まだDBにテーブルが作成されていないため、テーブルを作成します。

データベースを作成

$ bundle exec rake db:create

マイグレーションを実行

$ bundle exec rake db:migrate

簡単な動作確認

rails server(sに省略可)を使い、サーバを立ち上げてみます。

$ rails s

サーバ起動後、http://localhost:3000/articles にアクセスすると、記事のページが表示されます。

関連(belongs_to)の所のview修正

belongs_to のあるモデルをscaffoldで作成しても、Commentの新規作成の所でエラーが発生してしまいます。
これを修正します。

app/models/comment.rb

@@ -1,4 +1,4 @@
 class Comment < ActiveRecord::Base
   belongs_to :article
-  attr_accessible :body
+  attr_accessible :body, :article_id
 end

app/views/comments/_form.html.erb

@@ -12,8 +12,8 @@
   <% end %>
 
   <div class="field">
-    <%= f.label :article %><br />
-    <%= f.text_field :article %>
+    <%= f.label :article_id %><br />
+    <%= f.text_field :article_id %>
   </div>
   <div class="field">
     <%= f.label :body %><br />

これで記事のidを入力することで、記事とコメントを紐付ける事ができるようになります。

twitter-bootstrap-railsで画面を整える

twitter-bootstrap-railsを使います。
GithubのREADMEに従って、インストール・セットアップをする。

Gemfileにgemを追加する

Gemfile

@@ -40,3 +40,5 @@ gem 'jquery-rails'
 group :test, :development do
   gem 'rspec-rails'
 end
+
+gem "twitter-bootstrap-rails"

そして、インストール。

$ bundle install
bootstrapに必要なファイルを生成する
$ rails g bootstrap:install
$ rails g bootstrap:layout application fluid
  • fixed: 画面の大きさ固定。
  • fluid: 画面の大きさ可変。スマホっぽい画面になります。
ページにtwitter-bootstrapを適用する

記事とコメントのページにtwitter-bootstrapを適用します。

  • -f でscaffoldで生成したviewを強制的に上書きしています
$ rails g bootstrap:themed Articles -f
$ rails g bootstrap:themed Comments -f

ナビゲーションバー、サイドバーの修正

自動生成されたナビゲーションバー、サイドバーを修正する。

app/views/layouts/application.html.erb

@@ -31,9 +31,8 @@
           <a class="brand" href="#">SinsokuBlog</a>
           <div class="container nav-collapse">
             <ul class="nav">
-              <li><%= link_to "Link1", "/path1"  %></li>
-              <li><%= link_to "Link2", "/path2"  %></li>
-              <li><%= link_to "Link3", "/path3"  %></li>
+              <li><%= link_to "記事一覧", "/articles"  %></li>
+              <li><%= link_to "コメント一覧", "/comments"  %></li>
             </ul>
           </div><!--/.nav-collapse -->
         </div>
@@ -51,9 +50,8 @@
                 <h3>Sidebar</h3>
                 <ul class="nav nav-list">
                   <li class="nav-header">Sidebar</li>
-                  <li><%= link_to "Link1", "/path1"  %></li>
-                  <li><%= link_to "Link2", "/path2"  %></li>
-                  <li><%= link_to "Link3", "/path3"  %></li>
+                  <li><%= link_to "記事一覧", "/articles"  %></li>
+                  <li><%= link_to "コメント一覧", "/comments"  %></li>
                 </ul>
               </div><!--/.well -->
             </div><!--/span-->

コメントを記事の下に表示する

記事とコメントが別の画面に表示されても微妙ですので、同じ画面に表示されるようにします。

app/views/articles/show.html.erb

@@ -10,6 +10,12 @@
   <dd><%= @article.body %></dd>
 </dl>
 
+<ul>
+  <% @article.comments.each do |comment| %>
+    <li><%= comment.body %></li>
+  <% end %>
+</ul>
+
 <div class="form-actions">
   <%= link_to t('.back', :default => t("helpers.links.back")),
               articles_path, :class => 'btn'  %>

動作確認

サーバを起動させ、http://localhost:3000/articles にアクセスすると、下記のような画面が表示されるようになります。

$ rails s

記事一覧の画面

記事にコメントが表示されている様子

*1:一度、--pathを使うと、2回目からは bundle installだけで済むようになります

*2:たぶん、次の記事で説明します

railsを少しずつ勉強する - 1.1. 環境構築(追記)

前回の記事の続き。書き忘れがあったので。。。

Ubuntuのホームディレクトリのフォルダ名を英語にする

$ LANG=C xdg-user-dirs-gtk-update

これで「ダウンロード」のフォルダ名が「Downloads」のように変更されます。

VMware Tools のインストール

VMware Playerのメニューから「仮想マシン」→「VMware Toolsのインストール」をクリックする。
CDがマウントされるので、そのCD内のファイルを解凍する。
コマンドプロンプトを開き、解凍したディレクトリまでcdで移動し、vmware-install.pl を実行する。
設定は特に問題なければ、デフォルトのままで[Enter]で大丈夫だと思います。

$ sudo ./vmware-install.pl

これで、ホストOS/ゲストOSの間でクリップボードの共有、ファイルのコピペ、ファイルのD&Dなどが出来るようになります。

gitの初期設定

ユーザ名、メールアドレス、色などを設定する。

$ git config --global user.name "sinsoku"
$ git config --global user.email "sinsoku.listy[at]gmail.com"
$ git config --global color.ui auto