Best_In_Place inline edits with nested attributes

2019-06-21 19:39发布

问题:

I am currently trying to use the best_in_place gem in order to do inline editing within an HTML table. I am showing a cart in cart's show view. Within the cart's show view, I have the ability to add lineItems. When an LineItem is created, a new Available record is also created with a lineItem_id and then it's shown in the cart with its lineitem. Both the Cart and LineItem tables come from an external database and because of that, I can't add columns to so that is why I can't just add an available boolean attribute to the LineItem.

**cart.rb
class Cart << AR::Base
 has many LineItems
end

**line_item.rb
class LineItems <<AR::Base
 belongs_to Cart
 has_one :available 
 accepts_nested_attributes_for :available 
end

**available.rb
class Available<<AR::Base
 belongs_to LineItems
end


**views/cart/show.html.erb
@cart.lineitems.each do |line_items|
    <td><%= line_item.price %></td>
    <td><%=line_item.name %></td>
    <td><%= best_in_place line_item.available.boolean, :boolean, :path => line_items_path, :type =>  type: :checkbox, collection: %w[No Yes] %></td>  
end

I want to be able to edit the line_item.available.boolean within the html table which is on the cart show view using best_in_place but I am not having any luck.. Any help would be AMAZING! =] I know after reading around that it's not possible using nested attributes, but If I could get rid off the available model somehow and have a field in the show table that I can edit for a line_item to see whether or not the lineItem is available, that would also be great. I am open to any ideas!

回答1:

Firstly, there are a few syntax issues in your code that we need to fix:

@cart.lineitems.each do |line_item| # changed "line_items" to "line_item"
  <td><%= line_item.price %></td>
  <td><%=line_item.name %></td>
  <td><%= best_in_place line_item.available, :boolean, :path => line_items_path, type: :checkbox, collection: %w[No Yes] %></td>  # changed "line_item.available.boolean" to "line_item.available" and ":type =>  type: :checkbox" to "type: :checkbox"
end

Now, the answer:

As I explain in this Github issue, you need to pass a param option and a url option (used to be path but that was deprecated in v3.0.0) to best_in_place.

The url option

The default url is the update action of the first argument to best_in_place. Since your code begins with best_in_place line_item.available, this would default to url_for(line_item.available.id). However, you want it to PATCH the update action of the LineItemsController, i.e. url_for(line_item)

The param option

By default, the param option assumes you are PATCH'ing to the AvailablesController, so here are the parameters that Rails conventions require in order to update available.boolean to the value "1":

{"available"=>{"boolean"=>"1"}}

The ID of the Available is in the URL already, so the only extra param you need to pass is the new value for boolean.

In your case, however, you are PATCH'ing to the LineItemsController and the available model accepts nested attributes. This requires two adjustments:

  1. The ID of the LineItem is in the URL already, but the ID of the Available is not. We have two choices here: Either put the ID of the Available into the param option, or make the ID unnecessary by passing update_only: true to accepts_nested_attributes in the model. The update_only approach may not work for you depending on your use case, but I find that it is the simplest approach the vast majority of the time and adds an extra layer of security for free.

  2. The boolean option needs to be nested properly, i.e.:

    line_items[available_attributes][boolean]
    

    That way, when it gets to the server, the params will be:

    {"line_item"=>{"available_attributes"=>{"id"=>"99","boolean"=>"1"}}}
    # "99" is just an example of line_item.available.id
    

    Note that you will need to permit these attributes in the controller, i.e.:

    line_item.update(params.require(:line_item).permit(
      available_attributes: [:id, :boolean]))
    # You can remove `:id` if you are using the `update_only` option
    

Putting it all together, here's your best_in_place method:

best_in_place line_item.available, :boolean, 
  type: :checkbox, 
  url: line_item_path(line_item.id),
  param: "line_item[available_attributes][id]=#{line_item.available.id}&line_item[available_attributes]"

However, if at all possible, use the update_only option.

# line_item.rb
accepts_nested_attributes_for :available, update_only: true

Look how much simpler it becomes now:

best_in_place line_item.available, :boolean, 
  type: :checkbox, 
  url: line_item_path(line_item.id),
  # Note: `url: line_item` might also work.
  # If someone can confirm this in a comment, I can update this answer
  param: "line_item[available_attributes]"