Project

General

Profile

Actions

その他のアクション(詳細表示、編集フォーム、更新、削除、プレビュー)

それでは残った 5 つのアクションを一気に片付けていきましょう。

  • 詳細表示( show )
  • 削除( destroy )
  • 編集フォーム( edit )
  • 更新( update )
  • プレビュー( preview )

Foo オブジェクト(@foo)の取得

show, destroy, edit, update のアクションでは params[:id] に入っている id から対象となる Foo のオブジェクトを取得する必要があります。
3 つのメソッドで同じことを行います。こういうときには before_filter の出番です。ただし、 index や new, create, preview では必要ありません。 before_filter では :only オプションで実行する対象を指定することも、 :except オプションで実行しない対象を指定することも出来ます。サンプルでは 3 つのアクションだけですが、チケットのようにコピーや移動などのアクションを追加したくなるかもれません。そこで :except で index と new, create, preview を指定することにします。

find_project と同じように find_foo メソッドを追加します。

class FoosController < ApplicationController
  unloadable
  menu_item :standard
  before_filter :find_project, :authorize
  before_filter :find_foo, :except => [:index, :new, :create, :preview]

  # :

private
  def find_project
    @project = Project.find(params[:project_id])
  rescue ActiveRecord::RecordNotFound
    render_404
  end

  def find_foo
    @foo = Foo.find_by_id(params[:id])
    render_404    unless @foo
  end
end

ActiveRecord::Base クラスでは ruby のメタプログラミングを利用して find_by_XXX という名前のクラスメソッドで XXX というメンバをキーとしてテーブルを検索することが出来ます。ここでは Foo.find_by_id で id の値が一致するオブジェクトを検索しています。
ただし、 Project.find と違って見つからなかった場合は nil を返すだけで例外は発生しません。

詳細表示( show アクション )

詳細表示のコントローラでは、 Foo オブジェクトを取得してビューを生成するだけなので、 show メソッドはスケルトン作成時のままで何も追加することはありません。

  def show
  end

ビュー(show.html.erb) で @foo オブジェクトの中身を表示することになります。
表示は Redmine で定義されている CSS クラスを使って、ちょっとチケット風にしています。

<h2><%=h l(:label_foo) %>#<%= @foo.id %></h2>

<div class="issue">      

  <div class="subject">
    <h3><%= @foo.subject %></h3>
  </div>

<% unless @foo.description.blank? %>
  <p><strong><%=l(:field_description)%></strong></p>
  <div class="wiki">
    <%= textilizable @foo.description %>
  </div>
<% end %>

</div>

説明のパラメータは CSS クラスを wiki にし、 textilizable メソッドを使うことによって、wiki を解析した結果を出力しています。

また、詳細表示ページでは右上に 編集削除 リンクをつけます。

<div class="contextual">
<%= link_to_if_authorized(l(:button_edit),
                          {:action => 'edit', :project_id => @project, :id => @foo.id},
              :class => 'icon icon-edit') %>
<%= link_to_if_authorized(l(:button_delete),
                          {:action => 'destroy', :project_id => @project, :id => @foo.id},
               :confirm => l(:text_are_you_sure), :method => :delete,
               :class => 'icon icon-del') %>
</div>

削除用リンクには :confirm=>l(:text_are_you_sure) オプションをつけています。これによりリンク実行前に確認ダイアログを表示されるようになります。なお、そのままでは GET メソッドがよばれるため DELETE メソッド に変更しています。

この詳細表示ページの実行結果は次のようになります。

削除( destroy アクション )

destroy はコントローラのスケルトンで作成していませんでした。そこで中身だけでなく、定義からコントローラ(foos_controller.rb)に記述することになります。

  def destroy
    @foo.destroy
    redirect_to project_foos_path(@project)
  end

Foo オブジェクトの destroy メソッドを使ってデータベースからデータを削除しています。
その後、そのままだと destroy.html.erb のビューを表示しようとしますので、 redirect_to を使って一覧表示ページを表示しています。

編集( edit アクション )

編集のコントローラでは、 Foo オブジェクトを取得してビューを生成するだけなので、 edit メソッドはスケルトン作成時のままで何も追加することはありません。

  def edit
  end

ビュー(edit.html.erb) は次のようになります。

<h2><%=h l(:label_foo) %>#<%= @foo.id %></h2>
<%= labelled_form_for :foo, @foo,
                  :url => project_foo_path(@project),
                  :html => {:multipart => true, :id => 'foo-form'} do |f| %>
    <%= render :partial => 'foos/form', :locals => {:form => f} %>
    <%= f.submit l(:button_edit) %>
    <%= preview_link( preview_project_foo_path(@project), 'foo-form' ) %>
<% end %>
<div id="preview" class="wiki"></div>

labelled_form_for の :url の指定は new では :id のパラメータを省略していましたが、 edit では :id のパラメータを指定する必要があります。
プレビュー用のリンクを追加していますが、後はラベルなどの表示を変えているだけで new とほぼ同じです。

更新( update アクション )

update はコントローラのスケルトンで作成していませんでした。そこで中身だけでなく、定義からコントローラ(foos_controller.rb)に記述することになります。

update アクションはフォームの [編集] ボタンから呼ばれます。
create アクションと同じような感じで作成します。
コントローラは次のようになります。

  def update
    @foo.attributes = params[:foo]
    if @foo.save
      flash[:notice] = l(:notice_successful_update)
      redirect_to project_foo_path(@project, @foo.id)
    end
  rescue ActiveRecord::StaleObjectError
    flash.now[:error] = l(:notice_locking_conflict)
  end

create の場合と同様に save メソッドを使ってデータベースに保存しています。 create との違いは次の 3 点です。

  • id 番号は同じにしなければならないので、 create の引数ではなく attributes でパラメータのみ変更
  • 同時に編集している人がいるとエラーで例外が発生するので、その場合にはエラーメッセージを表示
  • POST リクエストではなく PUT リクエスト

その後、そのままだと update.html.erb のビューを表示しようとしますので、 redirect_to を使って詳細表示ページを表示しています。

プレビュー( preview アクション )

preview もコントローラのスケルトンで作成していませんでした。同様に定義からコントローラ(foos_controller.rb)に記述します。

  def preview
    @text = params[:foo][:description]
    render :partial => 'common/preview'
  end

これが edit アクションの preview_link から呼ばれることになります。
ここでは、Redmine のヘルパーメソッドの preview 機能を呼び出してプレビュー表示を実現しています。

レイアウト

最後に html ページのタイトルをつけましょう。

他の文書やチケットを参考にタイトルの付け方を次のようにします。

ページ タイトル
一覧表示、新規作成 (プロジェクト名) - 標準 - Redmine
詳細表示、編集 (プロジェクト名) - (題名) - Redmine

html のタイトルを付けるには html_title メソッドをビュー内で呼び出します。
このとき前後のプロジェクト名と Redmine の文字は Redmine が自動的に付けます。

例えば、 index.html.erb に次のコードを追加します。

<% html_title(l(:label_standard)) %>

このときプロジェクト名が "デモ" とするとタイトルは次のようになります。

デモ - 標準 - Redmine

それでは早速、他のビューにもこの文をコピペしてと ....
もう何がいいたいかお分かりですね。 繰り返しを避ける方法があります。

コントローラの before_filter のようにビューすべてにコードを追加したい場合には レイアウト というものを使用します。

レイアウト用の html.erb ファイルは app/views/layouts ディレクトリにおきます。
スケルトンでは layouts は作成されていませんので、 layouts ディレクトリを作って app/views/layouts/standard.html.erb ファイルを作成します。このときファイル名は何でもかまいません。

<% html_title((@foo && !@foo.subject.blank?) ? @foo.subject : l(:label_standard)) %>
<%= render :file => "layouts/base" %>

html_title の引数は決めたタイトルになるように条件で文字列を変えています。

最後の行でデフォルトのレイアウトを出力しています。
この layouts/base は Redmine の app/views/layouts/base.html.erb ファイルを指しています。このファイルはレイアウトを指定しない場合に使用されているものです。レイアウトに standard.html.erb を指定すると使用するレイアウトはそちらに置き換わってしまいます。 タイトルの指定だけの追加とするためには standard.html.erb 内で再度 base のレイアウトを呼び出す必要があります。

レイアウトの指定はコントローラのクラス定義内に次のコードを追加します。

  layout 'standard'

これでどのアクションに対しても layouts/standard.html.erb の内容がビューのページに付けられてタイトルが変更されるようになります。

これでサンプルプラグインは完成です!!

最終的なコントローラのファイル(foos_controller.rb)は以下のようになります。

class FoosController < ApplicationController
  unloadable
  menu_item :standard
  before_filter :find_project, :authorize
  before_filter :find_foo, :except => [:index, :new, :create, :preview]

  layout 'standard'

  def index
    @foos = Foo.find(:all, :conditions => ["project_id = #{@project.id} "])
  end

  def new
    @foo = Foo.new()
  end

  def create
    @foo = Foo.new(params[:foo])
    @foo.project_id = @project.id

    if @foo.save    
      flash[:notice] = l(:notice_successful_create)
      redirect_to project_foo_path(@project, @foo.id)
    end
  end

  def show
  end

  def update
    @foo.attributes = params[:foo]
    if @foo.save
      flash[:notice] = l(:notice_successful_update)
      redirect_to project_foo_path(@project, @foo.id)
    end
  rescue ActiveRecord::StaleObjectError
    flash.now[:error] = l(:notice_locking_conflict)
  end

  def edit
  end

  def destroy
    @foo.destroy
    redirect_to project_foos_path(@project)
  end

  def preview
    @text = params[:foo][:description]
    render :partial => 'common/preview'
  end

private
  def find_project
    @project = Project.find(params[:project_id])
  rescue ActiveRecord::RecordNotFound
    render_404
  end

  def find_foo
    @foo = Foo.find_by_id(params[:id])
    render_404    unless @foo
  end
end

^ << >>

Updated by NAITOH Jun over 6 years ago · 5 revisions