今回は営業の人が顧客だったり、商品だったり、会社だったりのモデルに対して共有できるメモを取るというニーズを想定し、SalesNoteというモデル名を使うことにする。
モデル生成
どのモデルにでも紐付けられるようにPolymorphic Associationで作る。
rails g model SalesNote target_id:integer target_type:string content:text
して、生成したMigrationファイルにインデックスを張る命令だけ付け加える。
add_index :sales_notes, [:target_id, :target_type]
結果、以下のMigrationファイルが出来上がる。
class CreateSalesNotes < ActiveRecord::Migration
def change
create_table :sales_notes do |t|
t.integer :target_id
t.string :target_type
t.text :content
t.timestamps
end
add_index :sales_notes, [:target_id, :target_type]
end
end
モデル
モデルは自動で生成されるはずだが、ここではvalidationだけ軽くつけておくことにする。
class SalesNote < ActiveRecord::Base
belongs_to :target, polymorphic: true, inverse_of: :notes
attr_accessible :content
validates_presence_of :target_id, :target_type
end
Noteをつける側のモデル
module SalesNoteTakableConcern
extend ActiveSupport::Concern
included do
has_one :sales_note, as: :target, inverse_of: :target
end
end
を作って、
class User
include SalesNoteTakableConcern
end
というように使う。
ルート
namespace :admin do
resources :sales_notes, only: [:create, :update]
end
コントローラー
class Admin::SalesNotesController < Admin::BaseController
before_filter :find_note # before_action
def create
create_or_update
end
def update
create_or_update
end
private
def find_note
@sales_note = SalesNote.where(target_id: params[:sales_note][:target_id], target_type: params[:sales_note][:target_type]).first_or_initialize
end
def create_or_update
params[:sales_note].delete(:target_id)
params[:sales_note].delete(:target_type)
respond_to do |format|
if @sales_note.update_attributes(params[:sales_note])
format.html { redirect_to :back, notice: "ノートが更新されました" }
format.js { head :ok }
else
format.html { redirect_to :back, alert: "ノートの更新に失敗しました" }
format.js { head :not_acceptable } # 406
end
end
end
end
ビュー
一般ユーザーには見えないようにAdmin用のViewで行う。
Admin::BaseController
にUserがAdminかの判定がある想定。
.sales_memo
%h3 メモ
- sales_note = target_model.sales_note || target_model.build_sales_note
= form_for [:admin, sales_note], remote: true, html: { id: "sales_note_form" } do |f|
= f.hidden_field :target_id
= f.hidden_field :target_type
= f.text_area :content, style: "height: 60px; width: 290px;"
= f.submit 'save', id: 'sales_note_save', class: "btn blue", style: "width: 100px; height: 30px; padding: 0", disabled: :disabled
%span#sales_note_save_notification{ style: "margin: 0 10px; font-size: 20px; display: hidden; color: red;"} saved!
:javascript
var memoContent = $('#sales_note_content').val();
$('#sales_note_content').on('input propertychange', function() {
if ($(this).val() == memoContent) $('#sales_note_save').attr('disabled', 'disabled');
else $('#sales_note_save').attr('disabled', null);
});
$('#sales_note_form').on('ajax:success', function(event, data, status, xhr) {
memoContent = $('#sales_note_content').val();
$('#sales_note_save').attr('disabled', 'disabled');
$('#sales_note_save_notification').show('slow').delay(1000).hide('fast');
});
$(function(){
$('#sales_note_content').autosize();
});
とPartialViewを作っておいて、
= render 'sales_note_form', target_model: @user
や
= render 'admin/base/sales_note_form', target_model: @user if current_user.admin?
の用に使う。
Javascriptの部分は、メモが編集されている時のみ、ボタンがEnableされて保存することができるようにしているコードが大半。ココらへんは最悪必要ないし、適当でいいと思う。
テキストは適宜大きくなるように、jqueryのautosizeをつけている。(http://www.jacklmoore.com/autosize/)
autosizeはもともと有名だったAutoResizeがメンテされなくなって使われるようになった。AutoResizeよりちょっと英語が変だけど気にしない。