Jakov
Jakov

Reputation: 151

How to define params with has_many through?

I'm using has_many through relationship and I don't really get what should I do else to make it work. I suppose there is something about parameters that I don't understand and omit. If so, please tell me where and how to write it, because I'm confused a little bit because of all these params. book.rb:

class Book < ActiveRecord::Base
  has_many :user_books
  has_many :users, through: :user_books
end

user.rb:

class User < ActiveRecord::Base
  devise :database_authenticatable, :registerable, :confirmable,
    :recoverable, :rememberable, :trackable, :validatable

  validates :full_name, presence: true

  has_many :user_books
  has_many :books, through: :user_books #books_users - book_id user_id

end

and books_controller.rb:

class BooksController < ApplicationController
  before_action :is_admin?, except: [:show_my_books, :book_params]
  before_filter :authenticate_user!
  expose(:book, attributes: :book_params)
  expose(:user_book)
  expose(:user_books)
  expose(:books){current_user.books}

  def create
    if book.save
      redirect_to(book)
    else
      render :new
    end
  end

  def update
    if book.save
      redirect_to(book)
    else
      render :edit
    end
  end

  def show
  end

  def is_admin?
    if current_user.admin?
      true
    else
      render :text => 'Who are you to doing this? :)'
    end
  end

  def book_params
    params.require(:book).permit(:name, :author, :anotation, user:[:name])
  end
end

When I create new book it gives me an error

Couldn't find Book with 'id'=27 [WHERE "user_books"."user_id" = ?]

<%=book.name%>

Sorry for a silly question, but I couldn't find a proper example to understand it myself that's why I ask you for help. Every help would be appreciated, thank you!

Upvotes: 1

Views: 1945

Answers (1)

max
max

Reputation: 102318

To setup a relation via a form you usually use a select or checkbox and pass the ID(s) of related item(s):

For a one to one relation the request would look like this:

POST /books  { book: { name: 'Siddharta', author: 'Herman Hesse', user_id: 1 } }

For many to many or one to many you can use _ids:

POST /books  { book: { name: 'Siddharta', author: 'Herman Hesse', user_ids: [1,2,3] } }

ActiveRecord creates a special relation_name_ids= setter and getter for has_many and HABTM relations. It lets you modify the relations of an object by passing an array of IDs.

You can create the form inputs like so:

<%= form_for(@book) do |f| %>
  <%= f.collection_select(:author_ids, User.all, :id, :name, multiple: true) %>

   OR

   <%= f.collection_checkboxes(:author_ids, User.all, :id, :name) %>
<% end %>

To whitelist the user_ids params which should permit an array of scalar values and not a nested hash we pass an empty array:

def book_params
  params.require(:book).permit(:name, :author, :anotation, user_ids: [])
end

On the other hand if you want to assign records to the current user it is better to get the user from the session or a token and avoid passing the param at all:

def create
  @book = current_user.books.new(book_params)
  # ...
end

This lets you avoid a pretty simple hack where a malicious user passes another users id or takes control of a resource by passing his own id.

As to your other error why it would try to create a strange query some sort of stack trace or log is needed.

However if you are new to Rails you might want to hold off a bit on the decent exposure gem. It obscures away a lot of important concepts in "magic" - and you'll spend more time figuring out how it works that might be better spent learning how good rails apps are built.

Upvotes: 3

Related Questions