Andrey Deineko
Andrey Deineko

Reputation: 52357

form_with does not hit the controller's action

I tried both form_tag and form_with - the result is the same, controller's action never gets triggered.

# routes.rb
resources :products do
  member do
    patch :add_comment
  end
end

# products_controller.rb
def add_comment
  # !!! damn form_with never gets here!!!
  product.add_comment!(params[:comment_id])
  redirect_back(fallback_location: products_path)
end

# view
<%= form_with(url: add_comment_product_path, local: true) do |form| %>
  <%= form.text_field :comment_id %>
  <%= form.submit 'Add comment' %>
<% end %>

Actual logs:

Started PATCH "/products/1"
Processing by ProductsController#update as HTML
Parameters: {
  "utf8"=>"✓",
  "authenticity_token"=>"token",
  "products"=>{a_lot: :of_stuff},
  "comment_id"=>"2",
  "commit"=>"Add comment",
  "id"=>"1"
}

Expected logs:

Started PATCH "/products/1/add_comment?comment_id=2"
Processing by ProductsController#add_comment as HTML
Parameters: {
  "utf8"=>"✓",
  "authenticity_token"=>"token",
  "comment_id"=>"2",
  "id"=>"1"
}

Edit:

I think it has something to do with the fact that this form_with is nested into bigger form and it looks when I hit Add comment it triggers the outer submit

Upvotes: 3

Views: 4177

Answers (4)

Andrey Deineko
Andrey Deineko

Reputation: 52357

I simply had to move the nested form out of the bigger form to make it work.

Upvotes: 0

max
max

Reputation: 102218

The Rails way to handle this would be as a seperate but nested resource - as you´re really creating a new resource (a comment) and not modifying the product itself.

This also keeps your code in line with the Single Responsibility Principle (SRP) as each controller only handles CRUD'ing a single type of resource.

You can nest resources by nesting the calls to resources:

resources :products do
  resources :comments, shallow: true
end

Then setup a CommentsController to handle CRUD'ing comments:

class CommentsController < ApplicationController
  before_action :set_comment, only: [:index, :new, :create]

  # GET /products/:product_id/comments
  def index
    @comments = @product.comments
  end

  # GET /products/:product_id/comments/new
  def new
    @comment = @product.comments.new
  end

  # POST /products/:product_id/comments
  def create
    @comment = @product.comments.new(comment_params)
    if @comment.save
      redirect_to @product, success: 'Comment created'
    else
      render :new
    end
  end

  # ...

  private
  def set_product
    @product = Product.find(params[:product_id])
  end

  def comment_params
    params.require(:comment)
          .permit(:foo, :bar)
  end
end

To set the form action attribute to point to a nested route you simply use an array or the named product_comments(product_id: @product.to_param) route helper.

<%= form_with(model: @comment, url: [@comment.product, @comment], local: true) do |form| %>
  <%= form.submit 'Add comment' %>
<% end %>

As the product id is passed through the URI there is no need to pass it via a hidden input.

I think it has something to do with the fact that this form_with is nested into bigger form and it looks when I hit Add comment it triggers the outer submit

You should note that the HTML standards (both HTML5 and older (x)HTML standards) do not allow nested form elements and the behaviour can be very unpredictable as its not specified if the browser should use the action attribute of the nested form or bubble the event to the parent form element which is most likely happening in your case. see: http://w3.org/TR/html5/forms.html

Upvotes: 2

Sahidur Rahman Suman
Sahidur Rahman Suman

Reputation: 638

Yon try this -

# products_controller.rb
def add_comment
  # You need add permitted for get parameters
  params.permit(:comment_id)
  product.add_comment!(params[:comment_id])
  redirect_back(fallback_location: products_path)
end

# You can place this form anywhere in your application, but you need to specify product object and comment_id
<%- @product = Product.find(1) %>
<%= form_with(url: add_comment_product_path(@product, comment_id: 2), local: true, method: :patch) do |form| %>
  <%= form.text_field :comment_id %>
  <%= form.submit 'Add comment' %>
<% end %>

Started PATCH "/products/1/add_comment?comment_id=2" for 127.0.0.1 at 2018-10-05 22:01:37 +0600
Processing by ProductsController#add_comment as HTML

Parameters: {"utf8"=>"✓", "authenticity_token"=>"token", "comment_id"=>"2", "commit"=>"Add comment", "id"=>"1"}

Upvotes: -1

Pavan
Pavan

Reputation: 33542

add_comment_product PATCH  /products/:id/add_comment(.:format)    products#add_comment

You have declared it as a member route, but I don't see you are passing any value for the :id the path helper.

Try changing it to

<%= form_with(url: add_comment_product_path(product), local: true) do |form| %>

where product is the Product instance.

Upvotes: 0

Related Questions