まず普通のを作ってみる。

参加してるMLの小江戸らぐの人たちで集まって毎月Railsの勉強会をやっていて、
そこで講師みたいな事させて頂いたりしているのですが、
8月の時にそこでデモンストレーションしたAjaxを使ったサンプルサイトの解説を書こうかと。*1


RailsとAjaxって結構親和性高いよーって話をしました。
超適当な名簿管理システムを作ります。

こんなサイトになります。

名前登録した後、項目をクリックすれば編集したり、
削除を押してデータを削除させたり、
D&Dで並び替えできるようなサイトです。

以下、開発手順とか。*2

途中、順番にやってるので手順が多くなってますが、
面倒な人は1〜4と、11〜を実行してください。
完成します。

1.Railsでプログラムを作ります。

$ rails ajax

つらつらーっとファイルが出来上がりますね。

      create
      create  app/controllers
      create  app/helpers
      create  app/models
      create  app/views/layouts
      create  config/environments
      create  components
      create  db
      create  doc
      create  lib
      create  lib/tasks
      create  log
      create  public/images
      create  public/javascripts
      create  public/stylesheets
      create  script/performance
      create  script/process
      create  test/fixtures
      create  test/functional
      create  test/integration
      create  test/mocks/development
      create  test/mocks/test
      create  test/unit
      create  vendor
      create  vendor/plugins
      create  tmp/sessions
      create  tmp/sockets
      create  tmp/cache
      create  tmp/pids
      create  Rakefile
      create  README
      create  app/controllers/application.rb
      create  app/helpers/application_helper.rb
      create  test/test_helper.rb
      create  config/database.yml
      create  config/routes.rb
      create  public/.htaccess
      create  config/boot.rb
      create  config/environment.rb
      create  config/environments/production.rb
      create  config/environments/development.rb
      create  config/environments/test.rb
      create  script/about
      create  script/breakpointer
      create  script/console
      create  script/destroy
      create  script/generate
      create  script/performance/benchmarker
      create  script/performance/profiler
      create  script/process/reaper
      create  script/process/spawner
      create  script/process/inspector
      create  script/runner
      create  script/server
      create  script/plugin
      create  public/dispatch.rb
      create  public/dispatch.cgi
      create  public/dispatch.fcgi
      create  public/404.html
      create  public/500.html
      create  public/index.html
      create  public/favicon.ico
      create  public/robots.txt
      create  public/images/rails.png
      create  public/javascripts/prototype.js
      create  public/javascripts/effects.js
      create  public/javascripts/dragdrop.js
      create  public/javascripts/controls.js
      create  public/javascripts/application.js
      create  doc/README_FOR_APP
      create  log/server.log
      create  log/production.log
      create  log/development.log
      create  log/test.log


2.DBを作成します。

$ mysql -u ユーザー名
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 66 to server version: 5.0.22

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql> create database ajax_development character set utf8;

大体めんどくさいので、いつもアプリ名_環境でDBを作ってます。
今回はajax_developmentです。
この辺の、DB環境は、自分の作ったrailsの環境に合わせて適当に書き換えてください。*3


3.名簿用のモデルを作成します。
generateを実行。

$ ruby script/generate model Member name:string atitude:string position:integer

これでマイグレートファイルとかモデルが出来上がりました。*4

      exists  app/models/
      exists  test/unit/
      exists  test/fixtures/
      create  app/models/member.rb
      create  test/unit/member_test.rb
      create  test/fixtures/members.yml
      exists  db/migrate
      create  db/migrate/001_create_members.rb

db:migrateを実行します。

rake migrate

テーブル出来上がれば成功です。

== CreateMembers: migrating ===================================================
-- create_table(:members)
   -> 0.0633s
== CreateMembers: migrated (0.0650s) ==========================================

4.コントローラを作成します。
generateを実行*5

ruby script/generate controller Test list

必要なファイルが出来上がります。

      exists  app/controllers/
      exists  app/helpers/
      create  app/views/test
      exists  test/functional/
      create  app/controllers/test_controller.rb
      create  test/functional/test_controller_test.rb
      create  app/helpers/test_helper.rb
      create  app/views/test/list.rhtml


5.まずは一覧表を作ってみる。
まずは普通に、list と 追加だけ作ります。
4で作成された app/controllers/test_controller.rb を編集します。

class TestController < ApplicationController
  def list
    @members = Member.find(:all)
  end

  def add
    @member = Member.new(params[:member])
    @member.save! unless @member.name.blank?

    redirect_to :action=>'list'
  end
end
<html>
  <head>
    <title>
      名簿管理アプリケーション
    </title>
  </head>
  <body>
    <hr>
    <ul id="members">
      <% for @member in @members %>
      <li id="member_<%=@member.id %>" >
         名前 : <%= h @member.name  %> /
         どんな人 : <%= h @member.atitude %>
      </li>
      <% end %>
    </ul>

    <hr>
    <%= form_tag :action => 'add' , :id => @member %>
      <p>名前 <%= text_field 'member','name' %>どんな人 <%= text_field 'member','atitude' %><%= submit_tag '登録' %></p>
    <%= end_form_tag %>
  </body>
</html>

これで、名簿に名前を追加していくだけのアプリケーションが出来上がりました。

まだ、全然Ajaxっぽくないので、ちょっとずつこれにカスタマイズしていきたいと思います。

*1:一ヶ月半遅れ。どれだけ遅れてるんだ……。

*2:linuxで開発やっているので、windowsの人は適度に読み替えてください。

*3:日本語を使う人はconfig/environment.rbに$KCODE='u'とかの記載もよろしくお願いします。

*4:generaterは便利すぎます。

*5:list.rhtmlを作らなきゃいけないので今のうちに指定してます。

次に、in_place_editor_fieldで同一画面編集出来る様にする。

今のままじゃ登録しか出来ないので、編集出来るようにします。
ここからはAjaxぽく、マウスクリックでデータを書きかえれる様にします。
(削除は次の段階でやりますよ。)


6.javascriptのファイルを設定する。
Ajaxを利用するので、Ajax用のjavascriptファイルを読み込む様にします。
app/views/test/list.rhtmlに、<%= javascript_include_tag :defaults %>
と言う一行を追加します。(上から五行目)

<html>
  <head>
    <title>
      名簿管理アプリケーション
    </title>
    <%= javascript_include_tag :defaults %>
  </head>
  <body>
    <hr>
    <ul id="members">
      <% for @member in @members %>
      <li id="member_<%=@member.id %>" >
         名前 : <%= h @member.name  %> /
         どんな人 : <%= h @member.atitude %>
      </li>
      <% end %>
    </ul>

    <hr>
    <%= form_tag :action => 'add' , :id => @member %>
      <p>名前 <%= text_field 'member','name' %>どんな人 <%= text_field 'member','atitude' %><%= submit_tag '登録' %></p>
    <%= end_form_tag %>
  </body>
</html>


7.in_place_editor_fieldを登録する。
in_place_editor_fieldと言うrailsのヘルパーを使います。
クリックするとその場所を編集出来るajaxを提供してくれる優れ物です。
なんと1行で済むんですね……。

中盤の名前やどんな人の項目を、 hでの表示からin_place_editor_fieldに置き換えます。
保存するときのボタンの名前をsave_textで指定してます。
取り消しの際のリンクの名前はcancel_textで。

<html>
  <head>
    <title>
      名簿管理アプリケーション
    </title>
    <%= javascript_include_tag :defaults %>
  </head>
  <body>
    <hr>
    <ul id="members">
      <% for @member in @members %>

      <li id="member_<%=@member.id %>" >
         名前 : <%= in_place_editor_field :member, 'name'     , {}, { :save_text=>"保存", :cancel_text=>"取消" } %> /
         どんな人 :  <%= in_place_editor_field :member, 'atitude'     , {}, { :save_text=>"保存", :cancel_text=>"取消" } %>
      </li>

      <% end %>
    </ul>

    <hr>
    <%= form_tag :action => 'add' , :id => @member %>
      <p>名前 <%= text_field 'member','name' %>どんな人 <%= text_field 'member','atitude' %><%= submit_tag '登録' %></p>
    <%= end_form_tag %>
  </body>
</html>


8.Webサーバ側の受け皿を作る
今のままだとブラウザ上で編集出来る様に見えるのですが、実際にはDBに
登録するWeb側の受け皿がないので、コントローラーにそれを作ります。
やっぱりこちらもrailsの機能を使うと1項目あたり1行ですみます。
in_place_edit_for を記載します。

コントローラーこんな感じになります。
今回は2項目メンテ画面用意してるので、2行目、3行目を追加しています。

class TestController < ApplicationController
  in_place_edit_for :member, :name
  in_place_edit_for :member, :atitude

  def list
    @members = Member.find(:all)
  end

  def add
    @member = Member.new(params[:member])
    @member.save! unless @member.name.blank?

    redirect_to :action=>'list'
  end
end

これでクリックでの編集は完成です。

削除機能をrjsで実装してみる。

次は、Ajaxっぽく、行削除を実装します。
RailsのRJS機能を利用します。


9.link_to_remoteでリクエスト!!
Ajaxへリクエストを投げる為にAタグを利用してイベントを作りますが、
普通にa href="URL"だと画面が切り替わります。
a href="#" onclick="new Ajax.Request ... と、Ajax系のリクエストを
生成するイベントを記載する必要がありますが、やはりRailsの link_to_remote を
使うと楽にかけます。

link_to_remote("リンク名", :url => { URLとオプション })

こんなんでOKです。

今まで作ってたapp/views/test/list.rhtmlのリスト表示部分に、削除へのリンクを
作ってみます。

<html>
  <head>
    <title>
      名簿管理アプリケーション
    </title>
    <%= javascript_include_tag :defaults %>
  </head>
  <body>
    <hr>

    <ul id="members">
      <% for @member in @members %>
      <li id="member_<%=@member.id %>" >
         名前 : <%= in_place_editor_field :member, 'name'     , {}, { :save_text=>"保存", :cancel_text=>"取消" } %> /
         どんな人 : <%= in_place_editor_field :member, 'atitude'     , {}, { :save_text=>"保存", :cancel_text=>"取消" } %> /
        <%= link_to_remote("削除", :url => { :action => :del,:id => @member.id }) %>
      </li>
      <% end %>
    </ul id="members">

    <hr>
    <%= form_tag :action => 'add' , :id => @member %>
      <p>名前 <%= text_field 'member','name' %>どんな人 <%= text_field 'member','atitude' %><%= submit_tag '登録' %></p>
    <%= end_form_tag %>
  </body>
</html>


10.削除するメソッドを作成
アップデートと同様、削除の受け皿も必要になります。
と言うわけでdelメソッドを作成します。
今回は、送られてきたパラメータのDBを削除するだけなので、そのまま書く事に。*1

class TestController < ApplicationController
  in_place_edit_for :member, :name
  in_place_edit_for :member, :atitude

  def list
    @members = Member.find(:all , :order => "position")
  end

  def add
    @member = Member.new(params[:member])
    @member.save! unless @member.name.blank?

    redirect_to :action=>'list'
  end

  def del
    Member.find(params[:id]).destroy
  end

end

11.RJSを実装する。
RJSで、選択された行を削除するメソッドを実装します。
app/views/test/del.rjs と言うファイルを作成します。
と言っても1行です。
member_idの行を削除しろーって命令です。

page.remove "member_" + params[:id]

これで、削除ボタンを押すと画面上からもDB上からもその行を削除出来る様になりました。

*1:例外処理は無視してます。めんどうなので・・・・・【何】

D&Dでソート出来る様にする。

sortable_elementにより、ソート処理を実装します。
相変わらず、ヘルパー使いまくりです。

12.sortable_elementをrhtmlに配置。
sortable_elementヘルパを利用してソート順を弄る様に追加します。

<html>
  <head>
    <title>
      名簿管理アプリケーション
    </title>
    <%= javascript_include_tag :defaults %>
  </head>
  <body>
    <hr>

    <ul id="members">
      <% for @member in @members %>
      <li id="member_<%=@member.id %>" >
         名前 : <%= in_place_editor_field :member, 'name'     , {}, { :save_text=>"保存", :cancel_text=>"取消" } %> /
         どんな人 : <%= in_place_editor_field :member, 'atitude'     , {}, { :save_text=>"保存", :cancel_text=>"取消" } %> /
        <%= link_to_remote("削除", :url => { :action => :del,:id => @member.id }) %>
      </li>
      <% end %>
    </ul id="members">

    <hr>
    <%= form_tag :action => 'add' , :id => @member %>
      <p>名前 <%= text_field 'member','name' %>どんな人 <%= text_field 'member','atitude' %><%= submit_tag '登録' %></p>
    <%= end_form_tag %>
    <%= sortable_element 'members',:url => { :action => "sort" } , :complete => visual_effect(:highlight, 'members',:duration => 0.5) %>
  </body>
</html>

13.毎度のごとく受け皿を・・・。
ソート順を保存するsortメソッドを実装します。

class TestController < ApplicationController
  in_place_edit_for :member, :name
  in_place_edit_for :member, :atitude

  def list
    @members = Member.find(:all , :order => "position")
  end

  def add
    @member = Member.new(params[:member])
    @member.save! unless @member.name.blank?

    redirect_to :action=>'list'
  end

  def del
    Member.find(params[:id]).destroy
  end


  def sort
     params[:members].each_with_index do | member, idx |
       sort_member = Member.find(member)
       sort_member.position = idx + 1
       sort_member.save
     end
     render :nothing => true
  end
end

これで完成です。
送られてきたパラメータを元に、ソート順を保存してます。

最終的に作ったファイル

app/contorllers/test_controller.rb

class TestController < ApplicationController
  in_place_edit_for :member, :name
  in_place_edit_for :member, :atitude

  def list
    @members = Member.find(:all , :order => "position")
  end

  def add
    @member = Member.new(params[:member])
    @member.save! unless @member.name.blank?

    redirect_to :action=>'list'
  end

  def del
    Member.find(params[:id]).destroy
  end


  def sort
     params[:members].each_with_index do | member, idx |
       sort_member = Member.find(member)
       sort_member.position = idx + 1
       sort_member.save
     end
     render :nothing => true
  end
end

app/views/test/list.rhtml

<html>
  <head>
    <title>
      名簿管理アプリケーション
    </title>
    <%= javascript_include_tag :defaults %>
  </head>
  <body>
    <hr>

    <ul id="members">
      <% for @member in @members %>
      <li id="member_<%=@member.id %>" >
         名前 : <%= in_place_editor_field :member, 'name'     , {}, { :save_text=>"保存", :cancel_text=>"取消" } %> /
         どんな人 : <%= in_place_editor_field :member, 'atitude'     , {}, { :save_text=>"保存", :cancel_text=>"取消" } %> /
        <%= link_to_remote("削除", :url => { :action => :del,:id => @member.id }) %>
      </li>
      <% end %>
    </ul id="members">

    <hr>
    <%= form_tag :action => 'add' , :id => @member %>
      <p>名前 <%= text_field 'member','name' %>どんな人 <%= text_field 'member','atitude' %><%= submit_tag '登録' %></p>
    <%= end_form_tag %>
    <%= sortable_element 'members',:url => { :action => "sort" } , :complete => visual_effect(:highlight, 'members',:duration => 0.5) %>
  </body>
</html>

app/views/test/del.rjs

page.remove "member_" + params[:id]

app/models/member.rb

class Member < ActiveRecord::Base
end

db/migrate/001_create_members.rb

class CreateMembers < ActiveRecord::Migration
  def self.up
    create_table :members do |t|
      t.column :name , :string
      t.column :atitude , :string
      t.column :position , :integer
    end
  end

  def self.down
    drop_table :members
  end
end

これで、サイトが作れます。
ちなみにこのプログラム自体は、勉強会の前日に1時間ぐらいかけて調べつつつくってました。