【问题标题】:Should I save a Model calculation as an attribute?我应该将模型计算保存为属性吗?
【发布时间】:2015-04-15 11:28:39
【问题描述】:

我有一个应用程序,用户可以使用该应用程序创建一个新房间,使用表单输入名称、描述、长度和宽度。创建的每个房间都会成为一条新记录,应用程序会将房间的“大小”计算为“长度”*“宽度”。这是一个简单的应用程序,我正在使用它来学习 Rails,但我可能会更进一步,将一组房间组成一个房子,每个房子都有一些总“大小”。
我的问题与“大小”值以及如何将其集成到应用程序中有关。我最初认为用户应该立即在表单上看到“大小”的值,但是一旦出现可能需要 Ajax 就搁置了。我将“大小”方法计算从视图移到模型以符合“胖模型,瘦控制器”概念,现在我在“索引”视图中显示“大小”,留下“新”视图纯粹用于输入数据.

我最初将模型设置为包括长度、宽度和大小。查看 Room 模型的迁移:

20150118183743_create_rooms.rb

class CreateRooms < ActiveRecord::Migration
  def change
    create_table :rooms do |t|
      t.string :name
      t.text :description
      t.integer :length
      t.integer :width
      t.integer :size

      t.timestamps null: false
    end
  end
end

我应该将每条记录的“大小”保存到数据库吗?我已经读过,没有必要将计算保存为模型的属性。据推测,应用程序应该处理这个?思考这个问题的正确方法是什么?
我的“索引”视图计算并返回最大“长度”和“宽度”,但是当我尝试计算最大“尺寸”时遇到错误。我在模型中有一个计算(即方法),但它似乎是错误的。有什么建议? 以下是相关代码:

room.rb

class Room < ActiveRecord::Base
    validates :name,    presence: true, length: { maximum: 30 },
                                        uniqueness: { case_sensitive: false }
    validates :length, :width,  presence: true,
                                numericality: { only_integer: true, 
                                less_than_or_equal_to: 1000,
                                greater_than_or_equal_to: 1 }

    def size
        size = length * width
    end

    def max_room
        size.max
    end

end

rooms_controller.rb

class RoomsController < ApplicationController

  def show
    @room = Room.find(params[:id])
  end

  def new
    @room = Room.new
  end

    def index
    @rooms = Room.all
  end

  def create
        @room = Room.new(user_params)  
        if @room.save                   #a boolean, if able to save the instance
            flash[:success] = "You created a new Room!!"
            redirect_to @room   #we send the user to the room
        else
            render 'new'            #so we want to render the new template 
        end 
  end

    private
        def user_params
            params.require(:room).permit(:name, :description, :length,
                                         :width, :size)
        end


end

index.html.erb

<% provide(:title, 'All Rooms') %>

<h1>All rooms</h1>


<div class="container">
    <div class="row column-md-7">
        <table class="table table-hover">
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Description</th>
                    <th class="text-right">Length (ft.) </th>
                    <th class="text-right">Width (ft.) </th>
                    <th class="text-right">Size (sq.ft.) </th>
                    <th class="text-center">Delete? </th>
                </tr>
            </thead>

            <tbody>

              <% @rooms.each do |room| %>
                <tr>
                  <td> <%= link_to room.name, room %> </td>
                  <td> <%= room.description %> </td>
                  <td class="text-right"> <%= room.length %> </td>
                  <td class="text-right"> <%= room.width %> </td>
                  <td class="text-right"> <%= room.size %> </td>
                  <td class="text-center"> <%= link_to "delete", room, method: :delete, 
                        data: { confirm: "You sure?" } %> </td>
                </tr>
              <% end %>
            </tbody>
        </table>

        <div class="alert alert-info">
      The model contains <%= pluralize(Room.count, "room") %> in total.  
      The max length is <%= Room.maximum('length') %>.
      The max width is <%= Room.maximum('width') %>.

    </div>

    </div>
</div>

我尝试通过添加来显示“大小”

The max size is <%= Room.max_room %>

但返回错误。

new.html.erb

<% provide(:title, "New Room")  %>
<h1>The Rooms page </h1>

<div class="row">
    <div class="col-md-6 col-md-offset-3">
        <%= form_for(@room) do |f| %>
            <%= render 'shared/error_messages' %>

            <%= f.label :name %>    
            <%= f.text_field :name %>

            <%= f.label :description %>
            <%= f.text_area :description %>

            <%= f.label :length, "Length (ft.)" %>
            <%= f.number_field :length %>

            <%= f.label :width, "Width (ft.)" %>
            <%= f.number_field :width %>

            <%= f.submit "Create my room", class: "btn btn-primary" %>

        <% end %>

    </div>
</div>

show.html.erb

<% provide(:title, @room.name)  %>
<h1>The "<%= @room.name %>" page </h1>
<h2>This page contains the show action associated with the 
    Rooms page </h2>
<br>
<br>

<div class="container">
    <div class="col-md-6 col-md-offset-3"> 
        <table class="table table-bordered">
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Description</th>
                    <th class="text-right">Length (ft.) </th>
                    <th class="text-right">Width (ft.) </th>
                    <th class="text-right">Size (sq.ft.) </th>
                </tr>
            </thead>

            <tbody>
          <tr>
            <td> <%= @room.name %> </td>
            <td> <%= @room.description %> </td>
            <td class="text-right"> <%= @room.length %> </td>
            <td class="text-right"> <%= @room.width %> </td>
            <td class="text-right"> <%= @room.size %> </td>
          </tr>
        </tbody>

        </table>
    </div>
</div>

<hr>

<%= link_to "Create a new room", new_room_path, class: "btn btn btn-primary" %>

routes.rb

Rails.application.routes.draw do

  root                  'static_pages#home'
  get 'home'            =>  'static_pages#home'
  get 'calculations'    => 'static_pages#calculations'
  get 'help'            =>  'static_pages#help'
  get 'about'           => 'static_pages#about'
  get 'new_room'        =>  'rooms#new'
  get 'rooms'           =>  'rooms#index'
  resources :rooms
end

我计划使用需要大量数值计算的应用程序,因此我希望正确掌握这些基础知识。我不希望应用程序的数据库在应该(也许)在虚拟环境中完成时保存太多计算。

所以,回顾一下......

  1. 应将应用计算作为新记录的属性保存到数据库中吗?
  2. “大小”的正确计算/方法可能是什么?
  3. 如果我想对计算值执行计算,是否必须首先将该值保存为属性?

【问题讨论】:

  • 欢迎来到 Stack Overflow。永远不要发布带有“返回错误”的问题。始终引用确切的错误消息(但首先删除任何私人文本。)

标签: ruby-on-rails ruby design-patterns model-view-controller model


【解决方案1】:

您对max_room 的实现是错误的,因为size 值只是一个数字,max 方法没有在数字上定义,而是应该在Enumerable 的值上调用。 所以Room应该这样实现:

class Room < ActiveRecord::Base
    validates :name,    presence: true, length: { maximum: 30 },
                                        uniqueness: { case_sensitive:    false }
    validates :length, :width,  presence: true,
                                numericality: { only_integer: true, 
                                less_than_or_equal_to: 1000,
                                greater_than_or_equal_to: 1 }

    def size
        size = length * width
    end

    class << self
      # This is a class method, since it depends on all the rooms
      # not on a specific room 
      def max_size
          # This will delegate the calculation to the database
          select('MAX(length * width) AS max')[0]['max'];
      end
      # But, this will instantiate the records on memory before it makes the calculation
      # def max_room
      #    all.max{ |room| room.length * room.width }
      # end
      # This is a class method as well
      def max_room
        order('size DESC').first
      end
    end
end
  1. 应将应用计算作为新记录的属性保存到数据库中吗?

如果计算值所依赖的属性会经常变化,这种情况下不应该保存计算值,而是每次需要时计算。但是我可以看到房间的长度和宽度不会改变,所以计算值需要计算一次,并保存以在需要时使用(例如计算max_size),所以在这种情况下你需要创建一个属性size 并在您使用挂钩创建记录时计算它。

before_save :calculate_size

private
def calculate_size
   size = length * width
end

【讨论】:

  • 我认为你是正确的 - 长度和宽度不会改变,所以定义一个新属性似乎很有意义,特别是因为我将使用“大小”来表示其他功能的应用程序。从上面的“create_rooms.rb”迁移中可以看出,该属性是存在的。我只是还没有使用它。后续问题:在 room.rb 中,鉴于您建议使用“calculate_size”,我可以摆脱“size”方法吗?另外,我应该使用'self.size = length * width'吗?这会为特定的@room 实例创建并保存“大小”吗?
  • @TomShaw 是的,你可以去掉size 方法,现在已经没有意义了,@room.size 将从数据库中获取保存的值。赋值size = length * width会改变实例属性size但不会保存它。before_save钩子,它运行实例方法,会在实例持久化到数据库之前设置size属性,然后所有更改的属性都将在一个步骤中保存。
  • 我现在能够自动计算“大小”并将其保存到数据库中。伟大的!我仍在努力检索具有最大“大小”值的房间。当我在 Room 类上调用 max_room 方法时出现“未定义方法”错误。我会继续努力的。
  • 哦,不好意思,max_sizemax_room 应该是类方法,我放在注释里但是我定义为实例方法。好的,我编辑了答案,你不应该得到那个undefined method error
  • 我添加了max_room方法检查答案。
猜你喜欢
  • 1970-01-01
  • 2018-10-11
  • 2022-08-16
  • 1970-01-01
  • 2013-08-02
  • 2014-03-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多