Rails4には他のフレームワーク同様、CRUD (Create, Read, Update, Delete) を行うアプリケーションの骨組みとなるファイル一式を自動で出力するScaffolding機能があります。
$ rails new myApp
としてアプリケーションを作成しておきます。そうではなく、既存のアプリケーションを利用する場合でかつ下記Scaffolding機能が自動生成するファイルが存在する場合、「HelloWorldを表示するまでに必要な設定および知識 (Rails4)」を参考にそれらをdestroyしておきましょう。
$ rails generate scaffold myModel field1:string field2:integer field3:date field4:boolean
(単数形とする。myModelsはダメです)
DBマイグレーションを行っておきましょう。
$ rake db:migrate
サーバーを起動して
$ rails s
アクセスしてみましょう → http://localhost:3000/my_models
config/routes.rb
MyApp::Application.routes.draw do
resources :my_models
...
という記述が自動追記されたことによって
$ rake routes
Prefix Verb URI Pattern Controller#Action
my_models GET /my_models(.:format) my_models#index
POST /my_models(.:format) my_models#create
new_my_model GET /my_models/new(.:format) my_models#new
edit_my_model GET /my_models/:id/edit(.:format) my_models#edit
my_model GET /my_models/:id(.:format) my_models#show
PATCH /my_models/:id(.:format) my_models#update
PUT /my_models/:id(.:format) my_models#update
DELETE /my_models/:id(.:format) my_models#destroy
資源myModelsを操作するために必要なルーティング一式が有効になっていることが分かります。"(.:format)" は省略時には ".html" が使用されます。上述の通りJSON形式のビューテンプレートも自動生成されているので、「http://localhost:3000/my_models.json」というアクセスも可能です。
'a'タグを出力するためのヘルパーで、例えば
<%= link_to 'リンク', 'http://example.com/', target: '_blank' %>
<%= link_to 'サイト内リンク', { controller: :my_models, action: :index }, id: :link %>
という記述で
<a target="_blank" href="http://example.com/">リンク</a>
<a href="/my_models" id="link">サイト内リンク</a>
というHTMLが生成されます。Scaffoldで生成されたファイルでもこのヘルパーは多用されており、例えば
app/views/my_models/index.html.erb
<%= link_to 'Show', my_model %>
<%= link_to 'Edit', edit_my_model_path(my_model) %>
<%= link_to 'Destroy', my_model, method: :delete, data: { confirm: 'Are you sure?' } %>
<%= link_to 'New My model', new_my_model_path %>
という記述があります。最初の例でURLを指定した箇所にモデルを指定すると、そのモデルのページへのリンクが生成されます。"edit_my_model_path" や "new_my_model_path" は、
$ rake routes
を実行して表示される一覧の "Prefix" に "_path" を付けたものです。また、"method:" や "data:" を指定することで
<a rel="nofollow" href="/my_models/1" data-method="delete" data-confirm="Are you sure?">Destroy</a>
といったHTMLが生成されます。"rake routes" における "Verb" がGET以外の場合には "method:" の指定が必要です。また、data-confirmによって確認ダイアログを表示できます。
ビュー内で
<%= render 'form' %>
と記述すると、その部分に
app/views/my_models/_form.html.erb
...
<%= form_for(@my_model) do |f| %>
...
<div class="field">
<%= f.label :field1 %><br>
<%= f.text_field :field1 %>
</div>
<div class="field">
<%= f.label :field2 %><br>
<%= f.number_field :field2 %>
</div>
<div class="field">
<%= f.label :field3 %><br>
<%= f.date_select :field3 %>
</div>
<div class="field">
<%= f.label :field4 %><br>
<%= f.check_box :field4 %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
を挿入することができます。新規登録画面と編集画面など、複数のビューで共通のものを一つにまとめる際に便利です。
複数のアクションメソッドに共通の処理は、一つのprivateメソッドにまとめておくと記述がシンプルになります。
app/controllers/my_models_controller.rb
class MyModelsController < ApplicationController
before_action :set_my_model, only: [:show, :edit, :update, :destroy]
...
private
# Use callbacks to share common setup or constraints between actions.
def set_my_model
@my_model = MyModel.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def my_model_params
params.require(:my_model).permit(:field1, :field2, :field3, :field4)
end
end
":id" の意味については "$ rake routes" の出力を確認してみましょう。また、before_actionで直接はよび出されない処理であっても、例えばPOSTデータをハッシュ形式
{
"field1" => 'somevalue'
"field2" => 'somevalue'
"field3" => 'somevalue'
"field4" => 'somevalue'
}
で返す定型句を "my_model_params" にまとめておくことで、アクションメソッドを簡略化できます。
app/controllers/my_models_controller.rb
class MyModelsController < ApplicationController
...
# POST /my_models
# POST /my_models.json
def create
@my_model = MyModel.new(my_model_params)
respond_to do |format|
if @my_model.save
format.html { redirect_to @my_model, notice: 'My model was successfully created.' }
format.json { render action: 'show', status: :created, location: @my_model }
else
format.html { render action: 'new' }
format.json { render json: @my_model.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /my_models/1
# PATCH/PUT /my_models/1.json
def update
respond_to do |format|
if @my_model.update(my_model_params)
format.html { redirect_to @my_model, notice: 'My model was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: @my_model.errors, status: :unprocessable_entity }
end
end
end
# DELETE /my_models/1
# DELETE /my_models/1.json
def destroy
@my_model.destroy
respond_to do |format|
format.html { redirect_to my_models_url }
format.json { head :no_content }
end
end
...
end
cnt = MyModel.where(field1: 'value').update_all(field1: 'value2') # シンボルで指定可能
# cnt: 更新した件数
cnt = MyModel.order(:field1).limit(10).update_all('field2 = field2 * 0.5') # SQL形式でも指定可能
# cnt: 更新した件数
MyModel.where('field1 <> ?', 'value').destroy_all
MyModel.destroy_all(['field1 <> ?', 'value'])
トランザクションを使用すると、一連の処理がすべて成功しない場合、一部の実行済みの処理をロールバックして、何も実行してない状態まで戻すことができます。具体的には、コントローラ内で以下のように記述します。
def new
MyModel.transaction do
my_model_1 = MyModel.new({field1: 'value'})
my_model_1.save! # 保存に失敗すると例外を返す (参考: .save は true/false を返す。トランザクションには不向き)
raise 'わざと例外を発生させています'
my_model_2 = MyModel.new({field1: 'value2'})
my_model_2.save!
end
# 例外が発生しなかった場合の処理
rescue => e
# 例外が発生した場合の処理
render :text => e.message
end
なお、トランザクションを使用するためには、データベース側が対応していることが必要です。MySQLの場合、ストレージエンジンとして有名なものに "MyISAM" と "InnoDB" があります。しかしながら "MyISAM" はトランザクションに未対応であるため、"InnoDB" 型としてテーブルを作成する必要があります。具体的には、マイグレーションファイル内で
class CreateMyModels < ActiveRecord::Migration
def change
create_table :my_models, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8' do |t|
...
end
end
end
と記述することでInnoDBを指定します。
"DELETE" や "UPDATE" といったデータベース処理の直前や直後にコールバック関数を指定できます。具体的な用途としては、削除対象のレコードを履歴として保存する関数を作成しておきコールバック関数として指定するというものが考えられます。これによって、コントローラ内の各所で同じ処理を記述する必要がなくなり、コードを共通化できます。
class MyModel < ActiveRecord::Base
after_destroy :callback_func
private #← プライベート関数として宣言しましょう
def callback_func
# 何か共通の処理
logger.info('deleted: ' + self.inspect)
end
end
ちなみに、destroyした "後" に "self" で内容を参照できているのは "DELETE" が発行される前に一度 "SELECT" が発行されているからです。Railsには "destroy" の他に "delete" メソッドがありますが、こちらの場合は "DELETE" だけが発行されるため、コールバック関数が登録できません。
MyModel.destroy(params[:id]) #→ SELECT → DELETE
MyModel.delete(params[:id]) #→ DELETEのみ