2013年2月21日木曜日

Railsで表形式のデータを複数同時に更新するサンプル(新規追加対応版)


前回のサンプルで、更新に加えて削除もできるように改修を行った。
今回は、同じ画面から新規追加も行えるようにしてみたい。

scaffoldの内容は前回からクラスの名前を変えただけ。
rails g scaffold imp2_ramen name:string price:integer
rake db:migrate
新規追加のために、add_rowというルートを新たに追加。それ以外は前回から変更なし。
  match '/imp2_ramen/add_row' => 'imp2_ramen#add_row'
  match '/imp2_ramen/update_all' => 'imp2_ramen#update_all'
  match '/imp2_ramen/confirm_all' => 'imp2_ramen#confirm_all'
  match '/imp2_ramen/edit_all' => 'imp2_ramen#edit_all', as: 'edit_all_imp2_raman', :via => :get
  resources :imp2_ramen
controllerの確認画面(confirm_all)の箇所を下記のように編集。
パラメーターとしてIDが飛んで来なければ、新規追加と判断してクラスをnewしている。
  # GET /imp2_ramen/confirm_all
  def confirm_all
    @imp2_ramen = Array.new
    for imp2_raman in params[:imp2_ramen]
      unless imp2_raman["id"].blank?
        updated_imp2_raman = Imp2Raman.find(imp2_raman["id"])
        updated_imp2_raman.update_attributes(imp2_raman)
        @imp2_ramen << updated_imp2_raman
      else
        @imp2_ramen << Imp2Raman.new(imp2_raman)
      end
    end
    session[:imp2_ramen] = @imp2_ramen
  end
今回追加したadd_rowというルートのcontroller側の記述は下記の通り。
実は何もしていない(笑)。

クライアント側のJavaScriptだけで完結することも可能なのだが、Railsの<%~%>タグが使いたいので、一度サーバに飛ばすようにしている。
  # GET /imp2_ramen/add_row
  def add_row
    respond_to do |format|
      format.js { render :layout => false }
    end
  end
複数同時更新画面(edit_all)のviewは下記の通り。

「行追加」のボタンを追加したことと、新規の場合はチェックボックスの代わりに「新規」という文字を表示するようにしたことが変更点。
<h1>Editing all imp2_ramen</h1>

<%= form_tag :action => 'confirm_all' do %>

<table border="1">
  <tr>
    <th>新規/削除</th>
    <th>Name</th>
    <th>Price</th>
  </tr>
  <% for imp2_raman in @imp2_ramen %>
    <tr>
      <td>
        <% unless imp2_raman.id.blank? %>
          <%= hidden_field_tag("imp2_ramen[][id]", imp2_raman.id) %>
          <%= check_box_tag("imp2_ramen[][delete_check]", true, imp2_raman.delete_check) %>
        <% else %>
          <%= '新規' %>
        <% end %>
      </td>
      <td>
        <%= text_field_tag("imp2_ramen[][name]", imp2_raman.name) %>
      </td>
      <td>
        <%= number_field_tag("imp2_ramen[][price]", imp2_raman.price) %>
      </td>
    </tr>
  <% end %>
</table>
<%= submit_tag "入力確認" %>
<input id="add_row_button" type="button" value="行追加" />
<% end %>

<%= link_to 'Back', imp2_ramen_path %>
「行追加」のボタンがクリックされると、app/assets/javascript/imp2_ramen.js.coffeeで、下記の処理を実行。
クリックを検知して、ajaxとしてadd_row.jsのpathを読みに行くだけ。
jQuery ->
  jQuery('#add_row_button').click ->
    $.get("add_row.js")
add_row.jsは下記の内容となる。
tableの最後に空行を追加している。
$('tbody').append('<tr><td>新規</td><td><%= text_field_tag("imp2_ramen[][name]") %></td><td><%= number_field_tag("imp2_ramen[][price]") %></td></tr>')
このファイルではなく、前述のimp2_ramen.js.coffee内でinputタグを直接記述すれば同じことができるはずなのだが、Railsの場合はinputタグ内のidの付け方が独特なので、保守性を考慮してview側と同様の記述(<%~%>)を行うようにしている。

確認画面(confirm_all)のviewは下記の通り。
<h1>Editing all imp2_ramen</h1>

<table border="1">
  <thead>
    <tr>
      <th>新規/削除</th>
      <th>Name</th>
      <th>Price</th>
    </tr>
  </thead>
  <tbody>
  <% for imp2_raman in @imp2_ramen %>
    <tr>
      <td>
        <% if imp2_raman.delete_check %>
          <%= '削除' %>
        <% elsif imp2_raman.id.blank? %>
          <%= '新規' %>
        <% end %>
      </td>
      <td>
        <%= imp2_raman.name %>
      </td>
      <td>
        <%= imp2_raman.price %>
      </td>
    </tr>
  <% end %>
  </tbody>
</table>

<%= form_tag :action => 'update_all' do %>
<%= submit_tag "更新" %>
<% end %>

<%= link_to 'Back', edit_all_imp2_raman_path %>
IDが存在しない場合に、「新規」と表示している以外は前回から変更なし。
controller側で新規追加分も含めてImp2Ramanクラスの配列としてセッションに格納しているので、view側では同じ配列をループさせて値を取り出すのみ。

実際の画面遷移の例は以下のようになる。

一覧画面(index)

更新画面(edit_all)

更新画面(edit_all)で新規追加。

確認画面(confirm_all)

更新後の一覧画面(index)


今回作成したソースコードの参照はこちらから。

https://github.com/torafugu/mytest/blob/master/app/assets/javascripts/imp2_ramen.js.coffee
https://github.com/torafugu/mytest/tree/master/app/views/imp2_ramen
https://github.com/torafugu/mytest/blob/master/app/controllers/imp2_ramen_controller.rb

実際に動くサンプルはこちらから。(heroku上のサイト)

2013年2月19日火曜日

Railsで表形式のデータを複数同時に更新するサンプル(削除対応版)


前回作成したサンプルで、複数件の更新はひとまず対応できた。
今回は、選択した行のデータを削除できる機能を追加してみたい。

まずはscaffold。クラスの名前を変えただけで、フィールドは同じ。(nameとprice)
rails g scaffold imp_ramen name:string price:integer
rake db:migrate
次にroutes.rbの編集。これも前回と同様。画面遷移も同じになる。
  match '/imp_ramen/update_all' => 'imp_ramen#update_all'
  match '/imp_ramen/confirm_all' => 'imp_ramen#confirm_all'
  match '/imp_ramen/edit_all' => 'imp_ramen#edit_all', as: 'edit_all_imp_raman', :via => :get
  resources :imp_ramen
modelに新たにdelete_checkというフィールドを追加する。
class ImpRaman < ActiveRecord::Base
  attr_accessor :delete_check
end
次にcontroller。index、edit_all、confirm_allは前回から変更なし。
update_allを下記のように編集。
  # GET /imp_ramen/update_all
  def update_all
    @imp_ramen = session[:imp_ramen]
    for imp_raman in @imp_ramen
      if imp_raman.delete_check
        to_destory_raman = ImpRaman.find(imp_raman.id)
        to_destory_raman.destroy
      else
        imp_raman.save
      end
    end
    session[:imp_ramen] = nil
    respond_to do |format|
      format.html { redirect_to imp_ramen_url }
      format.json { head :no_content }
    end
  end
delete_checkがtrueの場合に、該当行のデータのdestroyを実行。

to_destory_ramanに代入し直しているのは、imp_ramanはループ中の配列の要素なので、ここでdestoryするとエラーが発生してしまうため。

DBに反映後は、セッションはもう不要なので、nilを代入してクリア。

indexのviewを指定してレンダリングすると変更が反映されない中途半端な状態で表示されてしまうので、indexのURLへリダイレクトしている。

複数同時更新画面(edit_all)のviewは下記の通り。
<h1>Editing all imp_ramen</h1>

<%= form_tag :action => 'confirm_all' do %>

<table border="1">
  <tr>
    <th>削除</th>
    <th>Name</th>
    <th>Price</th>
  </tr>
  <% for imp_raman in @imp_ramen %>
    <tr>
      <td>
        <%= hidden_field_tag("imp_ramen[][id]", imp_raman.id) %>
        <%= check_box_tag("imp_ramen[][delete_check]", true, imp_raman.delete_check) %>
      </td>
      <td>
        <%= text_field_tag("imp_ramen[][name]", imp_raman.name) %>
      </td>
      <td>
        <%= number_field_tag("imp_ramen[][price]", imp_raman.price) %>
      </td>
    </tr>
  <% end %>
</table>
<%= submit_tag "入力確認" %>
<% end %>

<%= link_to 'Back', imp_ramen_path %>
check_boxはチェックされている場合のみ値が送信されるので、チェックされていない行があると位置がズレてしまう。
hidden_fieldをcheck_boxの前に持ってくると、位置ズレを防ぐことができる。

check_box_tagでは、3番目の引数(チェックされた場合に送信される値)で明示的にtrueをしておかないと、デフォルトでは"on"という妙な値が送信されるので、controller側で編集のために余計なコードが必要になる。

確認画面(confirm_all)のviewは下記の通り。
<h1>Editing all imp_ramen</h1>

<table border="1">
  <tr>
    <th>削除</th>
    <th>Name</th>
    <th>Price</th>
  </tr>
  <% for imp_raman in @imp_ramen %>
    <tr>
      <td>
        <% if imp_raman.delete_check %>
          <%= '削除' %>
        <% end %>
      </td>
      <td>
        <%= imp_raman.name %>
      </td>
      <td>
        <%= imp_raman.price %>
      </td>
    </tr>
  <% end %>
</table>

<%= form_tag :action => 'update_all' do %>
<%= submit_tag "更新" %>
<% end %>

<%= link_to 'Back', edit_all_imp_raman_path %>
delete_checkの値をチェックして、trueであれば「削除」を表示している。

実際の画面遷移の例は以下のようになる。

一覧画面(index)

更新画面(edit_all)

確認画面(confirm_all)

更新後の一覧画面(index)


今回作成したソースコードの参照はこちらから。

https://github.com/torafugu/mytest/tree/master/app/views/imp_ramen
https://github.com/torafugu/mytest/blob/master/app/models/imp_raman.rb
https://github.com/torafugu/mytest/blob/master/app/controllers/imp_ramen_controller.rb

実際に動くサンプルはこちらから。(heroku上のサイト)


今回参考にさせていただいたブログの記事。


2013年2月18日月曜日

Railsで表形式のデータを複数同時に更新するサンプル


Railsのscaffoldで作成されるedit画面は、1件のデータしか更新できない。
データの数が多い場合は、1件1件edit画面を開いて更新するのは面倒である。
一覧形式で複数件同時に更新できる画面を作成してみる。

まずはscaffold。
rails g scaffold ramen name:string price:integer
rake db:migrate
ラーメンでramenというクラス名にしてみたのだが、menがmanの複数形として判断されるので、クラスの単数形はramanとなってしまった。

まぁこれはこれで識別しやすいのでこのまま利用する。

次にroutes.rbの編集。以下を追加した。
  match '/ramen/update_all' => 'ramen#update_all'
  match '/ramen/confirm_all' => 'ramen#confirm_all'
  match '/ramen/edit_all' => 'ramen#edit_all', as: 'edit_all_raman', :via => :get
  resources :ramen
画面遷移としては、


という流れになる。

次にcontrollerを編集する。
  # GET /ramen
  # GET /ramen.json
  def index
    @ramen = Raman.all
    session[:ramen] = @ramen

    respond_to do |format|
      format.html # index.html.erb
      format.json { render json: @ramen }
    end
  end
まず一覧画面(index)の検索結果をセッション(session[:ramen])に入れておく。
複数同時更新画面(edit_all)では、DBからではなく、セッションから取得する。
  # GET /ramen/edit_all
  def edit_all
    @ramen = session[:ramen]
  end
セッションを使う理由は、確認画面(confirm_all)から更新画面(edit_all)に戻った場合に変更部分を維持するため。
確認画面(confirm_all)では、画面から送られたパラメータの値でRamanクラスを編集して再度セッションに格納している。
  # GET /ramen/confirm_all
  def confirm_all
    @ramen = Array.new
    for raman in params[:ramen]
      updated_raman = Raman.find(raman["id"])
      updated_raman.update_attributes(raman)
      @ramen << updated_raman
    end
    session[:ramen] = @ramen
  end
@ramenはRamanクラスの配列で、モデルで定義している型と同じではないために、パラメータの値をそのままクラスとして利用することができずに、ちょっと面倒なことをしている。

更新処理(update_all)では、セッションから値を取得してDBにsaveするのみ。
  # GET /ramen/update_all
  def update_all
    @ramen = session[:ramen]
    for raman in @ramen
      raman.save
    end
    render action: "index"
  end
複数同時更新画面(edit_all)のviewは下記の通り。
<h1>Editing all ramen</h1>

<%= form_tag :action => 'confirm_all' do %>

<table border="1">
  <tr>
    <th>ID</th>
    <th>Name</th>
    <th>Price</th>
  </tr>
  <% for raman in @ramen %>
    <tr>
      <td>
        <%= raman.id %>
        <%= hidden_field_tag("ramen[][id]", raman.id) %>
      </td>
      <td>
        <%= text_field_tag("ramen[][name]", raman.name) %>
      </td>
      <td>
        <%= number_field_tag("ramen[][price]", raman.price) %>
      </td>
    </tr>
  <% end %>
</table>
<%= submit_tag "入力確認" %>
<% end %>

<%= link_to 'Back', ramen_path %>
@ramenがRamanクラスの配列なので、ループを回して一つづつ取り出して、inputタグを作成している。
"ramen[][XXXX]"と記述すると、ramenという名前のパラメータの配列の項目名として扱ってくれるようだ。

確認画面(confirm_all)のviewは下記の通り。
<h1>Editing all ramen</h1>

<table border="1">
  <tr>
    <th>ID</th>
    <th>Name</th>
    <th>Price</th>
  </tr>
  <% for raman in @ramen %>
    <tr>
      <td>
        <%= raman.id %>
      </td>
      <td>
        <%= raman.name %>
      </td>
      <td>
        <%= raman.price %>
      </td>
    </tr>
  <% end %>
</table>

<%= form_tag :action => 'update_all' do %>
<%= submit_tag "更新" %>
<% end %>

<%= link_to 'Back', edit_all_raman_path %>
controllerが既にセッションに値を確認しているので、ここでは単純に表示するだけ。

実際の画面遷移の例は以下のようになる。

一覧画面(index)

更新画面(edit_all)
確認画面(confirm_all)
更新後の一覧画面(index)
今回作成したソースコードの参照はこちらから。

https://github.com/torafugu/mytest/tree/master/app/views/ramen
https://github.com/torafugu/mytest/blob/master/app/models/raman.rb
https://github.com/torafugu/mytest/blob/master/app/controllers/ramen_controller.rb

実際に動くサンプルはこちらから。(heroku上のサイト)

http://ancient-savannah-6605.herokuapp.com/ramen

今回参考にさせていただいたブログの記事。

Rails のフォームで配列形式のデータを扱う方法
http://webos-goodies.jp/archives/51387214.html

Ruby on Railsあれこれ/複数従業員情報の一括更新
http://winter-tail.sakura.ne.jp/pukiwiki/index.php?Ruby%20on%20Rails%A4%A2%A4%EC%A4%B3%A4%EC%2F%CA%A3%BF%F4%BD%BE%B6%C8%B0%F7%BE%F0%CA%F3%A4%CE%B0%EC%B3%E7%B9%B9%BF%B7

2013年2月16日土曜日

RailsでIDだけのテーブルを作ってみる

Railsでシーケンス番号を取得するためのテーブルを使いたくなったので、ID以外に列のないmodelを作成してみた。

まずはmodelのgenerate。
G:\Sites\mytest>rails generate model idonly
      invoke  active_record
      create    db/migrate/20130214223210_create_idonlies.rb
      create    app/models/idonly.rb
      invoke    test_unit
      create      test/unit/idonly_test.rb
      create      test/fixtures/idonlies.yml
次にdbのmigration。
G:\Sites\mytest>rake db:migrate
==  CreateRamen: migrating ====================================================
-- create_table(:ramen)
   -> 0.1920s
==  CreateRamen: migrated (0.1940s) ===========================================

==  CreateIdonlies: migrating =================================================
-- create_table(:idonlies)
   -> 0.0070s
==  CreateIdonlies: migrated (0.0100s) ========================================
特に問題なく作成できた。
G:\Sites\mytest>rails c
Loading development environment (Rails 3.2.1)
irb(main):001:0> id = Idonly.new
=> #<Idonly id: nil, created_at: nil, updated_at: nil>
irb(main):002:0> id.save
=> true
irb(main):003:0> Idonly.all
=> [#<Idonly id: 1, created_at: "2013-02-14 22:37:25", updated_at: "2013-02-14 2
2:37:25">]
modelのnewもできるし検索もできる。
IDだけのmodelでも普通に使えるようである。