0

I have a form with four input fields where a user creates an item. In the form one of the four input fields is for picture which is used to create a user_item at the same time on form submission. I am running into several problems.

  1. When validation fails on the item I have the controller render :new but when this happens the picture input field is not visible.

  2. Validation on presence of picture is not working.

  3. I need a way to set the user_id attribute to current_user on the user_item when it is created.

item.rb

  validates :name, presence: true, uniqueness: true
  validates :description, presence: true
  has_many :tags
  has_many :user_items
  has_many :users, -> { uniq }, through: :user_items
  belongs_to :user

  accepts_nested_attributes_for :user_items
  validates_associated :user_items

user_item.rb

  belongs_to :user
  belongs_to :item
  mount_uploader :picture, PictureUploader
  validates_presence_of :picture
  validate :picture_size

items_controller.rb

  def new
    @item = Item.new
    @item.user_items.build
  end

  def create
    @item = item.new item_params

    if @item.save
      redirect_to items_path, notice: "Thank you for your item request!"
    else
      render :new
    end
  end

  private

    def item_params
      params.require(:item).permit(:name, :description, :tag_list, user_items_attributes: [:picture]).merge(created_by: current_user.id)
    end

new.html.erb

<%= simple_form_for @item, html: { class: "create-item-form" } do |item_builder| %>
  <div class="well">
  <%= item_builder.input :name, required: false, error: false, label: "Item name" %>
  <%= item_builder.input :description, as: :text, required: false, error: false, label: "Description of item" %>
  <%= item_builder.input :tag_list, required: false, label: "Tags (these will help users find your item)" %>
  <%= item_builder.simple_fields_for :user_items do |user_item_builder| %>
    <%= user_item_builder.input :picture, as: :file, required: false, label: "Picture of you with this item" %>
  <% end %>
  </div>
  <div class="clearfix">
    <%= item_builder.submit 'Submit new item request', class: "btn btn-primary pull-right inherit-width" %>
  </div>
<% end %>

1 Answer 1

1

I need a way to set the user_id

The simplest way to add the user_id to the user_item is to include a hidden_field in your fields_for. Not the most secure, but should work:

#app/views/items/new.html.erb
...
<%= item_builder.simple_fields_for :user_items do |user_item_builder| %>
    <%= user_item_builder.input :picture, as: :file, required: false, label: "Picture of you with this item" %>
    <%= user_item_builder.input :user_id, as: :hidden, input_html: { value: current_user.id } %>
<% end %>

#app/controllers/items_controller.rb
...
def item_params
   params.require(:item).permit(:name, :description, :tag_list, user_items_attributes: [:picture, :user_id]).merge(created_by: current_user.id)
end

the picture input field is not visible

According to this answer: Nested Input Disappears When Form Reloads, you need to rebuild your picture objects:

def create
   if @item.save
     ...
   else
     @item.user_items.build
     render :new
   end
end

file_field inputs are particularly interesting. Because your OS cannot guarantee your files will be exactly the same as they were, so the file_field is not populated.


Validation on presence of picture is not working.

You should use inverse_of to make sure the two objects can talk to each other:

#app/models/user.rb
class User < ActiveRecord::Base
  has_many :user_items, inverse_of: :user
end

#app/models/user_item.rb
class UserItem < ActiveRecord::Base
  belongs_to :user, inverse_of: :user_items
  validates :picture, presence: true
end

Update

If you wanted to pass the user_id through the backend, not having a hidden field, you'd be able to do something like this:

#app/controllers/items_controller.rb
class ItemsController < ApplicationController
  def new
    @user = current_user
    @user.user_items.build.build_item
  end

  def create
    @user = current_user.update user_params
  end 

  private

  def user_params
     params.require(:user).permit(user_items_attributes: [:picture, item_attributes: [:name, :description, :tag_list])
  end
end

This would have to be accompanied with the following change to your items#new view:

<%= simple_form_for @user, url: items_path, html: { class: "create-item-form" } do |f| %>
  <%= f.fields_for :user_items do |user_item_builder|
    <%= user_item_builder.input :picture, as: :file, required: false, label: "Picture of you with this item" %>
    <%= user_item_builder.fields_for :item do |item_builder| %>
        <%= item_builder.input :name, required: false, error: false, label: "Item name" %>
        <%= item_builder.input :description, as: :text, required: false, error: false, label: "Description of item" %>
        <%= item_builder.input :tag_list, required: false, label: "Tags (these will help users find your item)" %>
    <% end %>
  <% end %>
  <%= f.submit 'Submit new item request', class: "btn btn-primary pull-right inherit-width" %>
<% end %>

You'll also need to pass the attributes through the respective models:

#app/models/user.rb
class User < ActiveRecord::Base
  has_many :user_items
  has_many :items, through: :user_items

  accepts_nested_attributes_for :user_items
end

#app/models/user_item.rb
class UserItem < ActiveRecord::Base
  belongs_to :user
  belongs_to :item

  accepts_nested_attributes_for :item
end
Sign up to request clarification or add additional context in comments.

7 Comments

Thanks for the answer. Just a couple questions. If using a hidden field is "not the most secure" why is it still better than an explicit attribute declaration in the controller? Also it turns out that once I added the hidden field, I no longer needed to rebuild the pictures object in the if statement. Finally, it's obvious now my original question was a mess, aside from writing code and asking questions on here, do you recommend anything else for me to improve my ruby on rails coding (resources, books, etc.)?
It's tricky to declare the value in the controller as it is nested. I suppose you could try merge but would need tweaking. It's not "secure" because if it's in HTML, people could change it.
In regard resources, apart from practice, you need to find someone who's very very very very good; work alongside them. It's like if you go the gym, if you work out with someone who's dedicated, you'll ultimately adopt their habits, much more than if you worked alone. Software is somewhat similar; very very very very good devs are the ones who'll consistently push the boundaries & alight your ideas. I'd personally stay away from boring tutorials and focus on getting shit done. Do work for free, make a gem, talk about cool ideas. Make a game.
In my edit from this question I had the line @item.user_items.first.user_id = current_user.id in the controller. Do you think a hidden field is the better way to do this?
What happens if you want more than one user_item? (Considering you're calling user_items.first)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.