【问题标题】:Updating a form, updates all records of a particular instance更新表单,更新特定实例的所有记录
【发布时间】:2018-03-07 14:31:21
【问题描述】:

所以我正在开发一个食谱书导轨应用程序。用户可以创建配方、查看配方和更新配方。但是,当我更新特定成分的数量(即 Pasta "2 Cups" )时,它会将包含意大利面的所有其他食谱更改为该新数量(“2 Cups”)。我可以在我的 rails 控制台和服务器中看到它可以识别更改和更新,但是当我显示视图时,它看起来好像显示了成分的第一个实例,而不是我刚刚更新的那个。我有一种强烈的感觉,这是我在配料中的数量方法的错误,但我不知道如何解决它。

配方模型

class Recipe < ApplicationRecord
    belongs_to :user, required: false
    has_many :recipe_ingredients
    has_many :ingredients, through: :recipe_ingredients

    validates :name, presence: true
    validates :instructions, presence: true
    validates :cooktime, presence: true

    def self.alphabetize
     self.order(name: :asc)
    end

    def ingredients_attributes=(ingredients_attributes)
      self.ingredients = []
      ingredients_attributes.values.each do |ingredients_attribute|
      if !ingredients_attribute[:name].empty?
          new_ingredient = Ingredient.find_or_create_by(name: 
           ingredients_attribute[:name])
          self.recipe_ingredients.build(ingredient_id: new_ingredient.id, 
          quantity: ingredients_attribute[:quantity])
          end
       end
     end
    end

配料模型

   class Ingredient < ApplicationRecord
      has_many :recipe_ingredients
      has_many :recipes, through: :recipe_ingredients

      def self.alphabetize
        self.order(name: :asc)
      end

      def quantity
        recipe_ingredient = RecipeIngredient.find_by(recipe_id: 
         self.recipes.first.id, ingredient_id: self.id)
        recipe_ingredient.quantity
      end
     end

食谱展示、编辑和更新操作:

  def show
    @recipe = Recipe.find(params[:id])
    @ingredients = @recipe.ingredients.alphabetize
  end

  def edit
    @recipe = Recipe.find(params[:id])
  end

  def update
     @recipe = Recipe.find(params[:id])
     if @recipe.user = current_user
       if @recipe.update(recipe_params)
        redirect_to @recipe
       else
         render :edit
       end
    end
  end

查看/食谱/展示(成分 - 数量)列表:

  <% @recipe.ingredients.each do |ingredient|%>
    <li><%=ingredient.name %> - <%=ingredient.quantity%></li>
  <%end%>

【问题讨论】:

    标签: ruby-on-rails ruby database activerecord


    【解决方案1】:

    只是对 abax 的回答稍作重复。我想我会做类似的事情:

    class Ingredient < ApplicationRecord
      has_many :recipe_ingredients
      has_many :recipes, through: :recipe_ingredients
    
      def self.alphabetize
        self.order(name: :asc)
      end
    
      def quantity_for(recipe)
        recipe_ingredients.where(recipe: recipe).first.quantity
      end
    
    end
    

    你会使用类似的东西:

    <% @recipe.ingredients.each do |ingredient|%>
      <li>
        <%= ingredient.name %> - <%= ingredient.quantity_for(@recipe) %>
      </li>
    <%end%>
    

    IMO,ingredient.quantity_for(@recipe) 非常清楚地表明正在发生的事情:ingredient 正在返回 quantity_for 一个特定的 @recipe

    顺便说一句,这个:

    def quantity
      recipe_ingredient = RecipeIngredient.find_by(
        recipe_id: self.recipes.first.id, 
        ingredient_id: self.id
      )
      recipe_ingredient.quantity
    end
    

    都是错的。除了abax识别的问题,不需要做

    RecipeIngredient.find_by(ingredient_id: self.id)
    

    这就是你这样做的原因has_many recipe_ingredients!这样你就可以简单地做:

    recipe_ingredients.where(recipe: recipe)
    

    并且,请注意,当您进行关联时,您不必说recipe_id: recipe.id。 Rails 允许您简单地执行recipe: recipe。少打字。更少的错别字。

    最后,这个作业:

    recipe_ingredient = RecipeIngredient.find_by(...)
    

    没必要。你可以这样做:

    RecipeIngredient.find_by(
      recipe:     recipe,
      ingredient: self
    ).quantity
    

    当然,你永远不会这样做。因为,如上所述,这样做要好得多:

    recipe_ingredients.where(recipe: recipe).first.quantity
    

    【讨论】:

    • 也许我对架构的想法是错误的。您能否解释一下使用Ingredient#quantity_for 而不是直接从保存该特定配方数量值的recipe_ingredients 表中读取背后的思考过程?我想你必须在某个时候访问这两个模型,一个是名称,另一个是数量,所以也许它正在分裂头发。只是想知道。
    • IMO,您希望您的视图尽可能少地了解您的模型。例如,您的视图不需要知道有一个名为receipt_ingredient 的东西并且recipe_ingredient 包含quantity。另外,我相信recipe_ingredient 不包含name 的值。所以,你不能像在你的解决方案中那样做recipe_ingredient.name。你必须做recipe_ingredient.ingredient.name。现在,您的视图需要大量了解您的模型是如何组织的。通过ingredient.quantity_for(@recipe),您的视图需要更少地了解模型的组织方式。
    • 我同意你的观点变得过于复杂。总的来说,我仍然喜欢recipe_ingredient 的想法,我想我会通过坚持使用recipe_ingredient.namedelegate :name, to: :ingredient 来解决视图问题
    • 我听到了。而且,我确实认为这里涉及到相当多的偏好。如果您稍后决定将ingredient.name 更改为ingredient.long_name(并添加ingredient.short_name)会怎样?在这种情况下,您必须记住在recipe_ingredient 中更改您的delegate 指令。就个人而言,我尝试尽可能少地耦合。但是,再一次,我觉得这是个人喜好的问题。您的解决方案效果很好。
    • 好点。在维护方面,我肯定还在学习。感谢您回覆,我很感激交流。
    【解决方案2】:

    看起来这是问题区域:

    class Ingredient < ApplicationRecord
    
    def quantity
      recipe_ingredient = RecipeIngredient.find_by(recipe_id: 
      self.recipes.first.id, ingredient_id: self.id)
      recipe_ingredient.quantity
    end
    

    &lt;%=ingredient.quantity%&gt; 将返回第一个配方的数量以使用该成分,无论配方如何。我认为您应该改为访问recipe_ingerdient.quantity

    <% @recipe.recipe_ingredients.each do |recipe_ingredient|%>
      <li><%=recipe_ingredient.name %> - <%=recipe_ingredient.quantity%></li>
    <%end%>
    

    因为看起来每个配方的数量都保存在 recipe_ingredient 连接表中。

    编辑:这种方法需要将delegate :name, to: :ingredient 添加到class RecipeIngredient

    【讨论】:

    • 您可以考虑将允许receipt_ingredient.name 的委托添加到您的答案中,以便 OP 清楚。
    【解决方案3】:

    我会从一个更温和的目标开始 - 只是让用户创建嵌套的 RecipeIngedients:

    <%= form_for(@recipe) do |f| %>
      <%= fields_for(:recipe_ingredients) do |ri| %>
        <%= ri.collection_select(:ingredient_id, Ingredient.all, :id, :name) %>
        <%= ri.text_field(:quantity) %>
      <% end %>
    <% end %> 
    

    class Recipe < ApplicationRecord
      # ...
      accepts_nested_attributes_for :recipe_ingredients
    end
    

    def recipe_attributes
      params.require(:recipe).permit(:foo, :bar, recipe_ingredient_attributes: [:ingredient_id, :quantity])
    end
    

    然后您可以通过使用额外级别的嵌套属性来扩展它:

    <%= form_for(@recipe) do |f| %>
      <%= f.fields_for(:recipe_ingredients) do |ri| %>
        <div class="recipe-ingredient">
          <%= ri.collection_select(:ingredient_id, Ingredient.all, :id, :name) %>
          <%= ri.fields_for(:ingredient) |ingredient| %>
            <%= ingredient.text_field :name %>
          <% end %>
        </div>
      <% end %>
    <% end %>
    

    class RecipeIngredient < ApplicationRecord
      # ...
      accepts_nested_attributes_for :ingredient, reject_if: [:ingredient_exists?, 
    
      private
    
      def ingredient_exists?(attributes)
        Ingredient.exists?(name: attributes[:name])
      end
    
      def ingredient_set?(attributes)
        self.ingredient.nil?
      end
    end
    

    def recipe_attributes
      params.require(:recipe)
            .permit(:foo, :bar, 
              recipe_ingredients_attributes: [:ingredient_id, :quantity, { ingredient_attributes: [:name] }]
            )
    end
    

    但是使用多于一层的嵌套属性通常会变成一团糟,因为您将这么多的功能塞进一个控制器中。从编程和用户体验的角度来看,一个更好的选择可能是使用 ajax 来设置自动完成并通过 POST'ing 到 /ingredients 即时创建记录。

    【讨论】:

      猜你喜欢
      • 2013-03-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多