arraysruby-on-railsformshashactioncontroller

Ruby on Rails 6: form submits an array of hashes via hidden field but gets blocked by <ActionController::Parameters ..... permitted: false>


on the posts form errors it says 'User Must Exist'.

The correct parameters are being received including the user_id, but all the parameters are still rejected. I suspect its something to do with the array of hashes as they come from another controller but via a form.

So when I inspect the Post params it says:

<ActionController::Parameters {"email"=>"test@test.com", "user_id"=>"2", "items_bought"=>"[#<LineItem id: 3, product_id: 1, cart_id: 3, quantity: 1>, #<LineItem id: 4, product_id: 2, cart_id: 3, quantity: 4>]"} permitted: false>

Simple_form_for @post

<%= simple_form_for @post, :url => user_posts_path do |f| %>

  <%= f.error_notification %>

  <ul>

    <%= @post.errors.full_messages.each do |message| %>
      <li><%= message %></li>
    <%end%>

  </ul>

   <%= f.input :email, :input_html => { value: "#{current_user.email}" } %>
   <%= f.input :user_id, :as => :hidden, :input_html => { value: "#{current_user.id}" } %>
   <%= f.input :items_bought, :as => :hidden, :input_html => { value: "#{current_cart.line_items.to_a}" } %>

   <%= f.error :base %>
   <%= f.button :submit %>

<% end %>

Posts controller

class PostsController < ApplicationController

 def new
    @user = current_user
    @post = Post.new
 end

 def create
    @user = current_user
    @post = current_user.posts.build(params[:post_params])

  if @post.save 
    render plain:
    params[:post].inspect
  else
    render 'new'
  end

  end

  private

  def post_params
    params.require(:post).permit(:email, :user_id, items_bought: [:LineItem_id [], :product_id[], :cart_id[], :quantity[]])
  end
end

Post Model

class Post < ApplicationRecord

  belongs_to :user, dependent: :destroy


end

any help would be greatly appreciated


Solution

  • You are not using your post_params method, change this:

    @post = current_user.posts.build(params[:post_params])
    

    to this:

    @post = current_user.posts.build(post_params)
    

    EDIT: note that passing the user_id as a parameter is a security risk, someone can change the hidden field value. Use the current_user id instead of receiving the id from the form.

    Actually, all the hidden info is retrieved from the current_user, you don't really need to use hidden fields since if you have access to the current_user you already have access to all that (id, cart items)

    EDIT 2: if you want to have an array of hashes inside the :items_bought parameter, you need to use multiple hidden fields with specific names so it's parsed as hashes by rails.

    Let's say you have this structure:

    [
      {
        id: 1,
        name: 'Foo',
        some_attr: 'Lorem'
      },
      {
        id: 2,
        name: 'Bar',
        some_attr: 'Impsum'
      }
    ]
    

    If you send that as parameters you need multiple hidden fields:

    hidden_field_tag 'items_bought[1][id]', 1
    hidden_field_tag 'items_bought[1][name]', 'Foo'
    hidden_field_tag 'items_bought[1][some_attr]', 'Lorem'
    hidden_field_tag 'items_bought[2][id]', 2
    hidden_field_tag 'items_bought[2][name]', 'Bar'
    hidden_field_tag 'items_bought[2][some_attr]', 'Impsum'
    

    When submitted, params[:items_bought] will be:

    {
      '1' => {
        id: '1',
        name: 'Foo',
        some_attr: 'Lorem'
      },
      '2' => {
        id: '2',
        name: 'Bar',
        some_attr: 'Impsum'
      }
    }
    

    Note it's not the same, it's a hash of hashes and you have the ID if each hash as the key (that's the first part of the hidden field name items_bought[1] and items_bought[2], and the ids are strings, not numbers.

    You could also serialize the hash as a string:

    hidden_field_tag :items_bougth, my_hash.to_json
    

    and then on the controller do

    param_hash = JSON.parse(params[:items_bought]
    

    but I woudn't recommend this, it's a sign of something done more complex than it needs to be, you need to parse the parameters, use an extra library, etc.

    Note you shouldn't do any of this with complex objects like LineItem as is, you talk about an array of hashes, an array of LineItems is not an array of hashes, you have to convert the objects to hashes first for both methods to work consistently.