Jakov Jakov - 7 months ago 20
Ruby Question

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!

max max
Answer

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.