Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Rails find_or_create_by with form objects

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

like image 643
neallred Avatar asked Oct 19 '22 15:10

neallred


1 Answers

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
like image 64
lacostenycoder Avatar answered Oct 21 '22 04:10

lacostenycoder