QA@IT

Railsでモデルのread_attributeとform_forでのシンボル指定での値取得の違い

3821 PV

お世話になります。
別の質問投稿のコメントでお聞きしていたのですが、情報を補足したく新たに質問させていただきます。

環境

  • rails 4.0.2
  • ruby 2.0
  • db: sqlite

概要

decimal型のフォームに小数第2位以下を入力した場合に、バリデーションエラーにするには?
上記の質問を拝見し、DBの設計で、通貨などの小数点表示をしたい項目をintegerで定義してアプリを作っておりました。

しかし、フォームでデータを呼び出した際に、モデルで設定しているgetter
が効いていないような現象となりましたので質問いたしました。

  def quantity
    read_attribute(:quantity).to_f / 100
  end

詳細の例

以下、kenn様のアドバイスでミニマルで再確認。

  • 新しくアプリを作る: rails new app
  • Postモデルを作る:rails g scaffold posts name:string body:text quantity:integer
    • quantity のinteger項目が使いたいだけです
  • routes.rbでルートを変える

モデルにsetter/getterをセットする

class Post < ActiveRecord::Base
  def quantity
    read_attribute(:quantity).to_f / 100
  end

  def quantity=(value)
    write_attribute(:quantity, (value.to_f * 100).to_i)
  end
end
  • rails serverで動作確認

POSTしてみる

quantity項目に100を入力。

showで見ると100.0で表示されている。
DBを除くと、10000という値。
editで編集をすると、10000で表示される。

おかしいなと思っているのはこの部分で、editで呼び出した際に、
form_for内で、f.number_field :quantityとして表示しているのですが、ここで100.0と表示されてほしいのです。
この部分でモデルに定義したgetterを参照するはずと考えているのですが。。

以下、補足でコードを載せます。
よろしくお願いいたします。

その他のコード

scaffoldで、モデルしか変更していないのですが、

View

<%= form_for(@post) do |f| %>
  <div class="field">
    <%= f.label :quantity %><br>
    <%= f.number_field :quantity %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

Controller

class PostsController < ApplicationController
  before_action :set_post, only: [:show, :edit, :update, :destroy]

  # GET /posts
  # GET /posts.json
  def index
    @posts = Post.all
  end

  # GET /posts/1
  # GET /posts/1.json
  def show
  end

  # GET /posts/new
  def new
    @post = Post.new
  end

  # GET /posts/1/edit
  def edit
  end

  # POST /posts
  # POST /posts.json
  def create
    @post = Post.new(post_params)

    respond_to do |format|
      if @post.save
        format.html { redirect_to @post, notice: 'Post was successfully created.' }
        format.json { render action: 'show', status: :created, location: @post }
      else
        format.html { render action: 'new' }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /posts/1
  # PATCH/PUT /posts/1.json
  def update
    respond_to do |format|
      if @post.update(post_params)
        format.html { redirect_to @post, notice: 'Post was successfully updated.' }
        format.json { head :no_content }
      else
        format.html { render action: 'edit' }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /posts/1
  # DELETE /posts/1.json
  def destroy
    @post.destroy
    respond_to do |format|
      format.html { redirect_to posts_url }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_post
      @post = Post.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def post_params
      params.require(:post).permit(:name, :body, :quantity)
    end
end

回答

あぁ、setはできるけどform上でgetする場合の話だったのですね。

その場合には、quantity_before_type_castが定義されてしまっているのでPost#quantityが呼ばれるパスを通らないため(該当箇所)、

alias :quantity_before_type_cast :quantity

とするか

= f.number_field :quantity, value: @post.quantity

とするしかなさそうです。なんかいまいちですね。。。

編集 履歴 (0)
  • そうなんです。説明不足ですみません。そうですか、後者でやってみます。ありがとうございました! -
ウォッチ

この質問への回答やコメントをメールでお知らせします。