2012年10月31日水曜日

Railsでコンボボックスを連動させるサンプル


コンボボックスで都道府県を選んだ後に、選んだ都道府県下の市町村を絞りこんで表示させたい。

Rails3になって、observe_fieldというフィールドの更新を検知する関数がなくなったり、Ajax周りの関数が変更になっていたりで、そのまま利用できるサンプルは少なかった。

最も参考になったのは、下記のブログ記事。

[Rails3]jQueryでobserve_field的なことを。

まずは、都道府県と市町村、その他住所クラスのscaffoldを作成する。(実行結果は省略。)
rails g scaffold todofuken name:string
rails g scaffold shichoson todofuken:references name:string
rails g scaffold jusho todofuken:references shichoson:references sonotajusho:string
scaffoldで作成された_form.html.erbは、TodofukenとShichosonがテキストフィールドになっているので、下記のようにコンボボックスに変更する。
<%= form_for(@jusho) do |f| %>
  <% if @jusho.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@jusho.errors.count, "error") %> prohibited this jusho from being saved:</h2>

      <ul>
      <% @jusho.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :todofuken %><br />
    <%= f.collection_select(:todofuken_id, Todofuken.find(:all), :id, :name) %>
  </div>
  <div class="field">
    <%= f.label :shichoson %><br />
    <%= f.collection_select(:shichoson_id, Shichoson.find(:all), :id, :name) %>
  </div>
  <div class="field">
    <%= f.label :sonotajusho %><br />
    <%= f.text_field :sonotajusho %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

次に、市町村の部分のhtmlをAjaxで変更できるように外出しにする。

_todoufuken_select.html.erb
<%= f.select(:shichoson_id, @shichoson) %>
_form.html.erbからは、下記のようにrenderで呼び出す。
<%= render 'todoufuken_select', {:f => f} %>
{:f => f}と指定しておくと、_form.html.erbのFormの変数(:shichoson)が、_todoufuken_select.html.erb内でも使えるようになる。

次に、都道府県のコンボボックスの変更を検知するために、JavaScriptを記述する。

application.js
jQuery(function(){
  $('#jusho_todofuken_id').change(function(){
  var todofuken_id = $("#jusho_todofuken_id").val();
  $.get("todoufuken_select.js?todofuken_id=" + todofuken_id);
});
todoufuken_select.js の部分は、URLとしては/jushos/todoufuken_select.js になる。
scaffoldを実行した直後だと、/jushos/XXXX のリクエストは、すべてコントローラーのshowメソッドで処理されてしまうので、ルーティングを下記のように追加する。

routes.rb
Mytest::Application.routes.draw do

  match '/jushos/todoufuken_select'
  resources :jushos
match~の部分をresources :jushosの先に記述する必要がある。 ※showメソッドの/jushos/XXXXのルーティングの方が範囲が広いので、先にヒットしてしまう。
次に、コントローラーを修正する。

jushos_controller.rb
  # GET /jushos/new
  # GET /jushos/new.json
  def new
    @jusho = Jusho.new
    @shichoson = [["都道府県を選択してください。", "0"]]

    respond_to do |format|
      format.html # new.html.erb
      format.json { render json: @jusho }
    end
  end
  # GET /jushos/todoufuken_select
  def todoufuken_select
    @shichoson = Shichoson.find_all_by_todofuken_id(params[:todofuken_id])
    render
  end
変更点は2箇所。
既存のnewメソッドに、市町村のコンボボックスの初期値を設定したことと、todoufuken_selectメソッドの新規追加。

次に、todoufuken_selectメソッドが呼び出すことになる_todoufuken_select.html.erbを作成する。
$("#jusho_shichoson_id").html("<%= escape_javascript(options_for_select(@shichoson.map{ |shichoson| [shichoson.name, shichoson.id] })) %>");
options_for_selectは、selectタグのoprionの部分を作成する関数だが、ここにDBの検索結果の配列(@shichoson)をそのまま渡してもうまく表示されないので、上記のように、DBの列を指定して渡すようにする。

これで完成。

初期状態

都道府県で愛知県を選択

愛知県の市町村が表示された。

2012年10月25日木曜日

Windows環境でのRuby on railsコンソールの日本語表示

Windows のコマンドプロンプトで、Ruby on rails のコンソールを動かすと、日本語の表示でエラーが発生してしまう。

具体的には、下記クラスのputsメソッドを実行すると、
class Jpmsg
  def self.prt
    puts "テスト"
  end
end
下記のようなエラーが発生する。
irb(main):001:0> Jpmsg.prt
SyntaxError: E:/Sites/mytest/app/models/jpmsg.rb:3: invalid multibyte char (US-A
SCII)
E:/Sites/mytest/app/models/jpmsg.rb:3: invalid multibyte char (US-ASCII)
E:/Sites/mytest/app/models/jpmsg.rb:3: syntax error, unexpected $end, expecting
keyword_end
    puts "���������"
            ^
(以下略)
Rubyでは内部的にはUTF-8を利用している。
一方で、WindowsのコマンドプロントはShift-JIS。

コマンドプロントの文字コードは、「chcp 65001」と入力することで変更できるのだが、変更後は自動的に欧文フォントになってしまう。

下記のブログでは、レジストリの値を変更して、UTF-8で日本語フォントを利用する方法が紹介されているものの、自分の環境ではうまくいかなかった。

コンソール(cmd.exe)の文字コードを UTF-8 に

試行錯誤した結果、コマンドプロンプトの問題ではなく、ソースコードで利用する文字コードを明示的に指定しなければならないという問題のようである。

下記のように、文字コードを指定する一文を追加したことでちゃんと表示されるようになった。
# coding: utf-8
class Jpmsg
  def self.prt
    puts "テスト"
  end
end
irb(main):002:0> Jpmsg.prt
テスト
=> nil
下記のブログが参考になった。情報公開に感謝。

OSとRubyスクリプトの文字コードが異なる場合の対策
http://pgnote.net/?p=211

2012年10月7日日曜日

Railsで1対多の関係を設定するサンプル~既存モデルの修正

以前の記事では、RteamクラスとRmemberクラスに1対多の関係を設定した。

今回は、Rteamのクラスの上位にRleagueというクラスを作成して、さらに1対多の関係を設定してみたい。


以前の記事の場合と異なり、外部クラスへの参照を最初から維持するクラスを新規に作成するわけではなく、既存のクラスに後から追加することになる。

まずは Rleagueクラスの scaffoldを作成する。(実行結果は省略。)
rails g scaffold rleague name:string
次に、Rteamクラスに Rleagueクラスへの参照を追加するための migarationファイルの generateを行う。
E:\Sites\mytest>rails g migration add_rleagueref_rteammodell
      invoke  active_record
      create    db/migrate/20121005010409_add_rleagueref_rteammodell.rb
作成された migarationファイルに、Rleagueへのreferenceのカラムを追加する。
class AddRleaguerefRteammodell < ActiveRecord::Migration
  def up
    add_column :rteams, :rleague, :reference
  end

  def down
    remove_column  :rteams, :rleague
  end
end
migration を実行する。
E:\Sites\mytest>rake db:migrate
==  AddRleaguerefRteammodell: migrating =======================================
-- add_column(:rteams, :rleague, :reference)
   -> 0.0010s
==  AddRleaguerefRteammodell: migrated (0.0030s) ==============================

==  CreateRleagues: migrating =================================================
-- create_table(:rleagues)
   -> 0.1100s
==  CreateRleagues: migrated (0.1100s) ========================================
しかしながら、db/schema.rb を確認すると、rteamsテーブルにはエラーが発生している。
"reference" という型は後付けでは追加できないようだ。

"reference" 型を利用する代わりに、参照先のクラスのIDをクラス内に保持することになるらしい。

そこで、RleagueクラスのIDのカラムを追加するために、migrationファイルの generateを再度実行する。 (実行結果は省略。)
rails g migration add_rleagueid_rteammodel
作成された migarationファイルに、rleague_id というカラムを追加する。
class AddRleagueidRteammodel < ActiveRecord::Migration
  def up
    add_column :rteams, :rleague_id, :integer
    add_index :rteams, :rleague_id
  end

  def down
    remove_index :rteams, :rleague_id
    remove_column :rteams, :rleague_id
  end
end
以前の記事でも、"reference" 型を指定して作成されたテーブルには、rteam_id という名前でinteger型のカラムが追加されていたので、"reference" 型というのは実体のある型ではなく、単なるシンタックスシュガーなのかもしれない。

migrartionを再度実行する。
E:\Sites\mytest>rake db:migrate
==  AddRleagueidRteammodel: migrating =========================================
-- add_column(:rteams, :rleague_id, :integer)
   -> 0.0020s
-- add_index(:rteams, :rleague_id)
   -> 0.0280s
==  AddRleagueidRteammodel: migrated (0.0320s) ================================
今回は問題なく作成できたようだ。

Rleagueクラスの has_many :rteams と Rteamクラスの belongs_to :rleague は手動で追加する。
class Rleague < ActiveRecord::Base
  has_many :rteams
end

class Rteam < ActiveRecord::Base
  has_many :rmembers
  belongs_to :rleague
end
これで model側の修正は完了。
view側も修正しておく。

▽変更後の_form.html.erb
<%= form_for(@rteam) do |f| %>
  <% if @rteam.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@rteam.errors.count, "error") %> prohibited this rteam from being saved:</h2>

      <ul>
      <% @rteam.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </div>
  <div class="field">
    <%= f.label :rleague %><br />
    <%= f.collection_select(:rleague_id, Rleague.find(:all), :id, :name) %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>
▽editの実行結果



▽変更後のshow.html.erb
<p id="notice"><%= notice %></p>

<p>
  <b>Name:</b>
  <%= @rteam.name %>
</p>

<p>
  <b>Member's name:</b><br />
  <% @rteam.rmembers.each do |rmember| %>
    <%= rmember.name %><br />
  <% end %>
</p>

<p>
  <b>League:</b>
  <%= @rteam.rleague.name %>
</p>

<%= link_to 'Edit', edit_rteam_path(@rteam) %> |
<%= link_to 'Back', rteams_path %>
▽showの実行結果


2012年10月4日木曜日

Railsで1対多の関係を設定するサンプル~画面表示


前回の記事で作成した Rteamクラスと Rmemberクラスの1対多の関係を画面から参照/更新できるようにしたい。

まずは、Rmemberクラスの Scaffoldで作成された画面から見てみる。

▽現状のindexページ


現状の indexページでは、Rteam のフィールドにインスタンスのハッシュ値が表示されているので、名前を表示するように変更する。

▽変更後のindex.html.erb
<h1>Listing rmembers</h1>

<table>
  <tr>
    <th>Name</th>
    <th>Rteam</th>
    <th></th>
    <th></th>
    <th></th>
  </tr>

<% @rmembers.each do |rmember| %>
  <tr>
    <td><%= rmember.name %></td>
    <td><%= rmember.rteam.name %></td>
    <td><%= link_to 'Show', rmember %></td>
    <td><%= link_to 'Edit', edit_rmember_path(rmember) %></td>
    <td><%= link_to 'Destroy', rmember, confirm: 'Are you sure?', method: :delete %></td>
  </tr>
<% end %>
</table>
▽変更後のindexページ


showページの方も同様にインスタンスのハッシュ値が表示されているので、名前を表示するように変更。

▽現状のshowページ


▽変更後のshow.html.erb
<p id="notice"><%= notice %></p>

<p>
  <b>Name:</b>
  <%= @rmember.name %>
</p>

<p>
  <b>Rteam:</b>
  <%= @rmember.rteam.name %>
</p>


<%= link_to 'Edit', edit_rmember_path(@rmember) %> |
<%= link_to 'Back', rmembers_path %>

▽変更後のshowページ


editページはテキストフィールドにインスタンスのハッシュ値が表示されていて何とも使いにくい。
どうやってインスタンスのハッシュ値を調べれば良いのか?

▽現状のeditページ


ハッシュ値の代わりに名前の一覧をコンボボックスで表示するように変更する。

▽現状のedit.html.erb
<h1>Editing rmember</h1>

<%= render 'form' %>

<%= link_to 'Show', @rmember %> |
<%= link_to 'Back', rmembers_path %>
edit.html.erb では、外部のファイルをレンダリングしているので、_form.html.erb の方を変更した。

▽変更後の_form.html.erb
<%= form_for(@rmember) do |f| %>
  <% if @rmember.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@rmember.errors.count, "error") %> prohibited this rmember from being saved:</h2>

      <ul>
      <% @rmember.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </div>
  <div class="field">
    <%= f.label :rteam %><br />
    <%= f.collection_select(:rteam_id, Rteam.find(:all), :id, :name) %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>
▽変更後のeditページ


editの実行も問題なく修了した。

▽editの実行後


newページでもeditと同じファイルをレンダリングしているので、既にnewページの方でもコンボボックスが表示されるようになっている。

▽変更後のnewページ



次に、Rteamクラスの方は、下記のように showページで Rmemberの一覧を表示するように変更する。

現状のshowページ


▽変更後のshow.html.erb
<p id="notice"><%= notice %></p>

<p>
  <b>Name:</b>
  <%= @rteam.name %>
</p>

<p>
  <b>Member's name:</b><br />
  <% @rteam.rmembers.each do |rmember| %>
    <%= rmember.name %><br />
  <% end %>
</p>


<%= link_to 'Edit', edit_rteam_path(@rteam) %> |
<%= link_to 'Back', rteams_path %>
変更後のshowページ


editページと newページに関しては、ページ内でRmemberのリストを表示し、リストへの追加/変更/削除を実装する必要があって少々複雑なので、今回は見送りとした。

Railsで1対多の関係を設定するサンプル

Rails の場合はSQLを直接使わないので、下図のような1対多の関係を設定するにはどうすれば良いのだろうか?


まずはRails付属のSQLiteのSQLでやってみる。

▽テーブルの作成
E:\Sites\mytest\db>sqlite3 development.sqlite3
SQLite version 3.7.3
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> create table team(id, name);
sqlite> create table member(id, team_id, name);
sqlite> insert into team values(1, 'team1');
sqlite> insert into member values(1, 1, 'member1');
sqlite> insert into member values(2, 1, 'member2');
▽データの取得
sqlite> select team.id, team.name, member.id, member.name from team, member wher
e team.id = member.team_id;
1|team1|1|member1
1|team1|2|member2
こんな感じで、簡単にできる。(外部キーは未設定。)

Rails の ActiveRecord では、references という型を指定することで、他のクラスへの外部参照を設定できるようである。

まずは sacffold を作成してみる。

先程作成したテーブルと同じ名前のテーブルは作成できないので、rteam と rmember で作成する。(長いので実行結果は省略。)
rails g scaffold rteam name:string
rails g scaffold rmember name:string rteam:references
Rmember のクラスには、"belongs_to :rteam" という属性が追加されている。
class Rmember < ActiveRecord::Base
  belongs_to :rteam
end
Rmember のクラスは何も変更されていない。
"has_many :rmember" を手動で追加する必要があるようだ。

▽修正前
class Rteam < ActiveRecord::Base
end
▽修正後
class Rteam < ActiveRecord::Base
   has_many :rmembers
end
修正後にデータベースの Migration を実行。
E:\Sites\mytest>rake db:migrate
==  AddThirdnameOnecolumnmodel: migrating =====================================
-- add_column(:onecolumnmodels, :thirdname, :string)
   -> 0.0020s
==  AddThirdnameOnecolumnmodel: migrated (0.0020s) ============================

==  CreateRteams: migrating ===================================================
-- create_table(:rteams)
   -> 0.1510s
==  CreateRteams: migrated (0.1520s) ==========================================

==  CreateRmembers: migrating =================================================
-- create_table(:rmembers)
   -> 0.0020s
-- add_index(:rmembers, :rteam_id)
   -> 0.0350s
==  CreateRmembers: migrated (0.0390s) ========================================
db/schema.rb を見ると、rteam_id が rmembers テーブルに追加されている。
index も追加されているようだ。
  create_table "rmembers", :force => true do |t|
    t.string   "name"
    t.integer  "rteam_id"
    t.datetime "created_at", :null => false
    t.datetime "updated_at", :null => false
  end

  add_index "rmembers", ["rteam_id"], :name => "index_rmembers_on_rteam_id"

  create_table "rteams", :force => true do |t|
    t.string   "name"
    t.datetime "created_at", :null => false
    t.datetime "updated_at", :null => false
  end
自動生成されたテストデータを読み込でおく。
E:\Sites\mytest>rake db:fixtures:load

rails consoleから、Rteamクラスのデータを確認してみる。
現時点では、Rteam に関連付けられた Rmember は空の状態。
E:\Sites\mytest>rails console
Loading development environment (Rails 3.2.1)
irb(main):001:0> rteam1=Rteam.find(980190962)
=> #<Rteam id: 980190962, name: "MyString", created_at: "2012-10-02 04:13:28", u
pdated_at: "2012-10-02 04:13:28">
irb(main):002:0> rteam1.rmembers
=> []
rmembers の配列に Rmember から値を抽出して追加。
irb(main):003:0> rteam1.rmembers << Rmember.find(980190962)
=> [#<Rmember id: 980190962, name: "MyString", rteam_id: 980190962, created_at:
"2012-10-02 04:13:28", updated_at: "2012-10-02 05:52:04">]
Rmember 側の rteam_id も自動で追加された。
irb(main):005:0> rteam1.rmembers
=> [#<Rmember id: 980190962, name: "MyString", rteam_id: 980190962, created_at:
"2012-10-02 04:13:28", updated_at: "2012-10-02 05:52:04">]

2012年10月1日月曜日

SVG画像を読み込んでProcessing.jsで操作するサンプル


Processing の描画機能は高機能なのだが、スクリプト上で複雑な画像をゼロから描くのは面倒である。
そこで、Illustrator などのツールで作成したSVG画像を読み込んで表示するサンプルを作成してみた。

▽作成したHTML
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
    <script src="processing-1.4.1.js" type="text/javascript"></script>
  </head>
  <body>
    <p>
    <canvas id="mycanvas"></canvas>
    </p>
    <button onClick="sketchProc2()">Move</ button>
<script type="text/processing" data-processing-target="mycanvas">
PShape s;
int cy;
boolean down;

void setup() {
  size(320, 320);
  noLoop();
  s = loadShape("helicopter.svg");
  cy = 10;
  down = true;
}

void draw() {
  background(255); // clear
  shape(s, 80, cy, 100, 52);
}

void moveSVG() {

  background(255); // clear
  if (cy <= 10) {
    down = true;
  } else if (250 <= cy) {
    down = false;
  }

  if (down) {
    cy += (random(10) + 10);
  } else {
    cy -= (random(10) + 10);
  }

  shape(s, 80, cy, 100, 52);
}
</script>

<script id="script1" type="text/javascript">

function sketchProc2() {
  var p2 = Processing.getInstanceById("mycanvas");
  p2.moveSVG();
}
</script>

  </body>
</html>
▽実行結果
Move! ボタンを押すとヘリコプターのSVG画像が上下に移動する。

※ヘリコプターのSVG画像は、下記のサイトのアイコンを利用。

The Noun Project
http://thenounproject.com/

テーブルにカラムの追加と削除を行う最小サンプル


一度作成したテーブルにカラムを追加してみる。

▽現状のSchema
  create_table "onecolumnmodels", :force => true do |t|
    t.string   "name"
    t.datetime "created_at", :null => false
    t.datetime "updated_at", :null => false
  end
まずは空の migration ファイルを作成する。
G:\Sites\mytest>rails g migration add_thirdname_onecolumnmodel
      invoke  active_record
      create    db/migrate/20120930223614_add_thirdname_onecolumnmodel.rb
"rails generate" は "rails g" で代用できるようだ。
作成されたmigrationファイルを開くと下記のようになっている。
class AddThirdnameOnecolumnmodel < ActiveRecord::Migration
  def up
  end

  def down
  end
end
def up のところに追加したい内容を、def down のところに元に戻す内容を追加する。
class AddThirdnameOnecolumnmodel < ActiveRecord::Migration
  def up
    add_column :onecolumnmodels, :thirdname, :string
  end

  def down
    remove_column  :onecolumnmodels, :thirdname
  end
end
この状態で、migration を実行。
G:\Sites\mytest>rake db:migrate
==  AddThirdnameOnecolumnmodel: migrating =====================================
-- add_column(:onecolumnmodels, :thirdname, :string)
   -> 0.0070s
==  AddThirdnameOnecolumnmodel: migrated (0.0080s) ============================
実行後に、db/schema.rb の内容を確認すると、thirdname が追加されている。
  create_table "onecolumnmodels", :force => true do |t|
    t.string   "name"
    t.datetime "created_at", :null => false
    t.datetime "updated_at", :null => false
    t.string   "thirdname"
  end
元に戻したい場合は、下記のように version を指定して migration を実行する。
rake db:migrate VERSION=1
ではどこに version が記載されているかというと、railsのデータベース内の schema_migrations というテーブルに記録されているようだ。


最後の行が直近で追加したバージョン番号(migrationファイルについているシリアル番号と同じ)なので、一つ前のバージョンを指定して migration してみる。
G:\Sites\mytest>rake db:migrate VERSION=20120928013904
==  AddThirdnameOnecolumnmodel: reverting =====================================
-- remove_column(:onecolumnmodels, :thirdname)
   -> 0.2440s
==  AddThirdnameOnecolumnmodel: reverted (0.2460s) ============================
実行後に、db/schema.rb の内容を確認すると、thirdname が削除されている。
  create_table "onecolumnmodels", :force => true do |t|
    t.string   "name"
    t.datetime "created_at", :null => false
    t.datetime "updated_at", :null => false
  end