I am creating a Rails library app where a genre has_many
books and a book belongs_to
one genre. I need to use form objects because my data model will eventually have multiple many to many relationships, and because I want to learn how to use form objects. I've based my form object off Rails Cast #416 Form Objects. My form, form object, models, and controller all seem to work. New books are created and they are associated with genres, but they are all of the genre "genre". I am using find_or_create_by
. I think the problem lies in the book_form.rb
where @genre ||= Genre.find_by(name: :name)
is not actually passing in the Genre information from the form. My relevant code is as follows:
Model book.rb
class Book < ActiveRecord::Base
belongs_to :genre
before_save :convert_isbn
.
.
.
end
Model genre.rb
class Genre < ActiveRecord::Base
has_many :books, dependent: :destroy
validates_associated :books
end
book_form.rb
class BookForm
include ActiveModel::Model
def self.model_name
ActiveModel::Name.new(self, nil, "Book")
end
.
.
.
validates :name, presence: true
delegate :name, to: :genre
delegate :title, :year, :pages, :isbn, :summary, :volume_a, :volume_b, to: :book
def genre
@genre ||= Genre.find_by(name: :name)
end
def book
@book ||= genre.books.build
end
def submit(params)
genre.attributes = params.slice(:name).permit(:name)
book.attributes = params.slice(:title, :year, :pages, :isbn, :summary, :volume_a,:volume_b).permit(:title, :year, :pages, :isbn, :summary, :volume_a,:volume_b)
if valid?
genre.save!
book.save!
true
else
false
end
end
end
books_controller.rb
class BooksController < ApplicationController
before_action :admin_user, only: [:new, :edit, :destroy]
def new
@book_form = BookForm.new
@genres = Genre.all
end
def create
@book_form = BookForm.new
@genres = Genre.all
if @book_form.submit(params[:book])
flash[:success] = "You've added a new book to the library!"
redirect_to @book_form
else
render 'new'
end
end
View new.html.erb
<%= form_for @book_form do |f| %>
<%=render 'shared/error_messages', object: f.object %>
<%= f.label :title %>
<%= f.text_field :title, class: 'form-control' %>
.
.
.
<%= f.label :genre %><br>
<%= collection_select(:genre, :name, Genre.all, :name, :name) %>
<br>
<br>
<%= f.submit "Add book to library", class: "btn btn-primary" %>
<% end %>
Using the Pry gem, I get this from the server on creating a book:
Started POST "/books" for ::1 at 2016-02-22 14:19:20 -0700
Processing by BooksController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"bPusgyl9n+p07eQsEAe9CpSsithtkg33HMifj8KTsidv3GDLuhjibOC7d2mm5boC4w7ZUne64R4n4OMQotDE4g==",
"book"=>{"title"=>"test",
"year"=>"2016",
"pages"=>"222",
"isbn"=>"9780-xx-xx-xx",
"summary"=>"fake summary",
"volume_a"=>"1",
"volume_b"=>"2"},
"genre"=>{"name"=>"Mystery"},
"commit"=>"Add book to library"}
From: /home/nathaniel/rails_apps/allredlib/app/forms/book_form.rb @ line 38 BookForm#submit:
37: def submit(params)
=> 38: binding.pry
39: genre.attributes = params.slice(:name).permit(:name)
40: book.attributes = params.slice(:title, :year, :pages, :isbn, :summary, :volume_a,:volume_b).permit(:title, :year, :pages, :isbn, :summary, :volume_a,:volume_b)
41: if valid?
42: genre.save!
43: book.save!
44: true
45: else
46: false
47: end
48: end
So book genre is getting passed in the parameters, but I'm accessing it the wrong way in my book form. If I comment out binding.pry
, the form creates a new book with genre "name" instead of genre "Mystery" like I want it to do.
When I type @genre
into rails while using binding.pry, I get
[1] pry(#<BookForm>)> @genre
=> #<Genre:0x007f19554e1220
id: 20,
name: "name",
book_id: nil,
created_at: Thu, 25 Feb 2016 20:28:45 UTC +00:00,
updated_at: Thu, 25 Feb 2016 20:28:45 UTC +00:00>
latest binding.pry results 27: def genre => 28: binding.pry 29: @genre ||= Genre.find_or_initialize_by(name: :name) 30: #@genre ||= Genre.find_or_initialize_by(name: params[:genre][:name]) 31: end
If you're going to look up the Genre by it's name then you need to pass in the name attribute in the method's options hash.
def genre
@genre ||= Genre.find_or_create_by(name: :name)
end
UPDATE
change
genre.attributes = params.slice(:name).permit(:name)
to
genre.attributes = { :name => params[:genre][:name] }
Also throw binding.pry before and after genre.save! and see what you get if you type @genre
because your genre method should have created that instance variable which your just sending params to with genre.attributes
which accepts the hash.
I would consider the same on book params but if slice is working than fine.
Also, since you're calling Genre.all
in your form view, you probably don't need to do this in the controller actions, but if you do, then consider using the object @genres
inside your form view collection_select
instead because you're making the all request twice on the model.
@genres = Genre.all
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With