I am fairly new to Rails, and I am following an online tutorial that has you build a book review app. Everything seems to work except for when I submit a review. When I do, I get this error:
undefined method `book_id=' for nil:NilClass
Here is my reviews controller:
class ReviewsController < ApplicationController
before_action :find_book
def new
#review = Review.new
end
def create
#reivew = Review.new(review_params)
#review.book_id = #book.id
#review.user_id = #current_user.id
if #review.save
redirect_to book_path(#book)
else
render "new"
end
end
private
def review_params
params.require(:review).permit(:rating, :comment)
end
def find_book
#book = Book.find(params[:book_id])
end
end
Here is my Review model:
class Review < ApplicationRecord
belongs_to :book
belongs_to :user
end
Here is my Book model:
class Book < ApplicationRecord
belongs_to :user
belongs_to :category
has_many :reviews
has_attached_file :book_img, styles: { book_index: "250x350>", book_show: "325x475>" }, default_url: "/images/:style/missing.png"
validates_attachment_content_type :book_img, content_type: /\Aimage\/.*\z/
end
I feel like I've been reading help forums for the past two hours. I'm stuck. Any help would be very appreciated!
There's a typo in review in your create action.
Change the following line
#reivew = Review.new(review_params)
to
#review = Review.new(review_params)
The reason for the error is that #review is nil and you can't call the method book_id on a nil object.
Related
I have issue when create nested model in Rails 6:
post.rb
class Post < ApplicationRecord
has_many :post_votes, dependent: :destroy
end
post_vote.rb
class PostVote < ApplicationRecord
belongs_to :posts
end
routes.rb
resources :posts do
resources :post_votes
end
views:
<%= button_to post_post_votes_path(post), method: :post, remote: true, form_class: "post_vote" do%>
<%= bootstrap_icon "arrow-up-circle", width: 20, height: 20, fill: "#333" %>
<%end%>
PostVost Controller
def create
#post = Post.find(params[:post_id])
#post_vote = PostVote.new
if already_voted?
# flash[:notice] = "You can't vote more than once"
redirect_to root_path
else
#post_vote = #post.post_votes.build(user_id: current_user.id)
end
# redirect_to post_path(#post)
respond_to do |format|
format.html {}
format.js
end
end
def already_voted?
PostVote.where(user_id: current_user.id, post_id: params[:post_id]).exists?
end
I check the log file, no record was update in database
Any one known why i can not create new post_vote model?
Thank you so much!
On this line:
#post_vote = #post.post_votes.build(user_id: current_user.id)
.build only creates the object in memory. It does not persist it to the database.
Try:
#post_vote = #post.post_votes.create(user_id: current_user.id)
or
#post_vote = #post.post_votes.create!(user_id: current_user.id)
if you want an exception to be thrown if persistence fails.
The problem is using belong_to without optional
class PostVote < ApplicationRecord
belongs_to :posts
end
After:
class PostVote < ApplicationRecord
belongs_to :posts, optional: true
end
I'm following a tutorial on how to implement reviews for my rails app, users can add reviews for posts. But when i click add new review i get 'NoMethodError in Reviews#new'
Reviews controller
class ReviewsController < ApplicationController
before_action :find_post
def new
#review = Review.new
end
def create
#review = Review.new(review_params)
#review.post_id = #post.id
#review.user_id = current_user.id
if #review.save
redirect_to post_path(#post)
else
render 'New'
end
end
private
def review_params
params.require(:review).permit(:rating, :comment)
end
def find_post
#post = Post.friendly.find(params[:post_id])
end
end
Review model
class Review < ApplicationRecord
belongs_to :post
belongs_to :user
end
Post model
class Post < ApplicationRecord
extend FriendlyId
belongs_to :user
friendly_id :title, use: :slugged
validates_presence_of :title , :description
end
It looks like you forgot has_many :reviews in your Post model.
So I have these files
deal.rb
class Deal < ApplicationRecord
has_many :images, as: :imageable, dependent: :destroy
#there is more code after this
end
image.rb
class Image < ApplicationRecord
belongs_to :imageable, polymorphic: true
belongs_to :deal
has_attached_file :attachment, styles: { thumb: "100x100!", medium: "200x200!" }
validates_attachment_content_type :attachment, content_type: /\Aimage\/.*\z/
end
deals_controller.rb
module Admins
class DealsController < BaseController
before_action :find_deal, only: [:edit, :update, :destroy]
def index
#deals = Deal.includes(:images)
end
def new
#deal = Deal.new
end
def edit
end
def create
#deal = Deal.new(deal_params.merge(created_by: current_user.id))
if #deal.save
flash[:success] = t('.success')
redirect_to admins_deals_url
else
flash.now[:warning] = t('.failure')
render :new
end
end
def update
if #deal.update(deal_params)
flash[:success] = t('.success')
redirect_to admins_deals_url
else
flash.now[:warning] = #deal.errors[:base].to_sentence
render :edit
end
end
def destroy
if #deal.destroy
flash[:success] = t('.success')
redirect_to admins_deals_url
else
flash.now[:warning] = t('.failure')
render :index
end
end
private
def deal_params
params.require(:deal).permit(:title, :description, :price, :discounted_price, :quantity, :publish_date, images_attributes: [:id, :attachment, :_destroy])
end
def find_deal
#deal = Deal.find_by(id: params[:id])
unless #deal
flash[:warning] = t('deals.not_found')
redirect_to admins_deals_path
end
end
end
end
application_controller.rb
class ApplicationController < ActionController::Base
helper_method :current_user, :current_cart
def current_user
#current_user ||= User.find_by(id: current_user_id)
end
def current_user_id
cookies.signed[:user_id] || session[:user_id]
end
def current_cart
#current_cart ||= (current_user.addressed_cart || current_user.cart) if current_user
end
end
EDIT:
Although I don't think application_controller has anything to do with the error
I am creating a deal with nested image attributes. I am using paperclip to upload the images. But I am getting these errors. I don't have any idea what the errors even mean. Here is an image to show the errors.
Here is the pastebin link
errors on terminal on creating deal
This appears to be a validation error. Try this for your validation:
validates_attachment_content_type :attachment, :content_type => /image/
Or for other variations you can see Validate Attachment Content Type Paperclip
UPDATE after testing your code seems this was a validation error because Paperclip creates an image but doesn't know about the belongs_to association. You can make it optional because by default rails 5 requires the belongs_to id field.
class Image < ApplicationRecord
belongs_to :imageable, polymorphic: true
belongs_to :deal, optional: true
has_attached_file :attachment, styles: { thumb: "100x100!", medium: "200x200!" }
validates_attachment_content_type :attachment, content_type: /\Aimage\/.*\z/
end
I'm trying to create a form with a series of checks to prevent duplicates during the simultaneous creation of three model records: one for the parent (assuming it doesn't exist), one for its child (assuming it doesn't exist), and one for a join table between the child and the User (to allow the User to have their own copy of the Song object).
In the current state of the code, The checks seemingly pass, but
the server logs show ROLLBACK, and nothing gets saved
to the database EXCEPT the parent object (artist).
When I try to use the ids of the object, I get the error undefined method id for nil:NilClass, or "couldn't find object without an ID".
The following code is in my controller:
class SongsController < ApplicationController
before_action :authenticate_user!
def create
#artist = Artist.find_by(name: params[:artist][:name].strip.titleize) #look for the artist
#song = Song.find_by(title: params[:artist][:songs_attributes]["0"][:title].strip.titleize)
if #artist.present? && #song.present?
#user_song = current_user.user_songs.find(#song_id)
if #user_song.present?
render html: "THIS SONG IS ALREADY IN YOUR PLAYLIST"
render action: :new
else
#user_song = UserSong.create(user_id: current_user.id, song_id: #song.id)
redirect_to root_path
end
elsif #artist.present? && !#song.present?
#song = #artist.songs.build(title: params[:artist][:songs_attributes]["0"][:title].strip.titleize, lyrics: params[:artist][:songs_attributes]["0"][:lyrics].strip)
#user_song = UserSong.create(user_id: current_user.id, song_id: #song.id)
redirect_to root_path
elsif !#artist.present?
#artist = Artist.create(name: params[:artist][:name].strip.titleize)
#song = #artist.songs.build(title: params[:artist][:songs_attributes]["0"][:title].strip.titleize, lyrics: params[:artist][:songs_attributes]["0"][:lyrics].strip)
#user_song = UserSong.create(user_id: current_user.id, song_id: #song.id)
redirect_to root_path
else
render html: "SOMETHING WENT WRONG. CONTACT ME TO LET ME KNOW IF YOU SEE THIS MESSAGE"
end
end
def index
#songs = Song.all
end
def new
#artist = Artist.new
#artist.songs.build
#user_song = UserSong.new(user_id: current_user.id, song_id: #song_id)
end
def show
#song_id = params["song_id"]
#song = Song.find(params[:id])
end
def destroy
UserSong.where(:song_id => params[:id]).first.destroy
flash[:success] = "The song has been from your playlist"
redirect_to root_path
end
def edit
#song = Song.find(params[:id])
#artist = Artist.find(#song.artist_id)
end
def update
end
private
def set_artist
#artist = Artist.find(params[:id])
end
def artist_params
params.require(:artist).permit(:name, songs_attributes: [:id, :title, :lyrics])
end
def set_song
#song = Song.find(params["song_id"])
end
end
The models:
class Artist < ApplicationRecord
has_many :songs
accepts_nested_attributes_for :songs, reject_if: proc { |attributes| attributes['lyrics'].blank? }
end
class Song < ApplicationRecord
belongs_to :artist
has_many :user_songs
has_many :users, :through => :user_songs
end
class UserSong < ApplicationRecord
belongs_to :song
belongs_to :user
end
Sorry if I haven't abstracted enough. Not really sure how, given that there's no error message, just a rollback (without any validations present in any of the controllers).
Thanks to #coreyward and his pointing out of the fat-model skinny-controller lemma (never knew that was a thing), I was able to cut the code down and arrive at a solution immediately. In my models, I used validates_uniqueness_of and scope in order to prevent duplication of records. In my controller, I used find_or_create_by to seal the deal.
To whom it may concern, the final code is as follows:
class SongsController < ApplicationController
before_action :authenticate_user!
def create
#artist = Artist.find_or_create_by(name: params[:artist][:name].strip.titleize)
#song = #artist.songs.find_or_create_by(title: params[:artist][:songs_attributes]["0"][:title].strip.titleize) do |song|
song.lyrics = params[:artist][:songs_attributes]["0"][:lyrics].strip
end
#user_song = current_user.user_songs.find_or_create_by(song_id: #song.id) do |user_id|
user_id.user_id = current_user.id
end
redirect_to root_path
end
class Song < ApplicationRecord
validates_uniqueness_of :title, scope: :artist_id
belongs_to :artist
has_many :user_songs
has_many :users, :through => :user_songs
end
class Artist < ApplicationRecord
validates_uniqueness_of :name
has_many :songs
accepts_nested_attributes_for :songs, reject_if: proc { |attributes| attributes['lyrics'].blank? }
end
class UserSong < ApplicationRecord
validates_uniqueness_of :song_id, scope: :user_id
belongs_to :song
belongs_to :user
end
New to stack! so Hello there ! I'm making a sample event booking app, that has event check out using stripe.
My set up is below
class Event < ActiveRecord::Base
belongs_to :user
has_many :tickets, :inverse_of => :event, dependent: :destroy
end
class Ticket < ActiveRecord::Base
belongs_to :event, :inverse_of => :tickets
end
class Booking < ActiveRecord::Base
belongs_to :event
belongs_to :ticket, :inverse_of => :bookings
has_one :sale, :inverse_of => :booking
end
class Sale < ActiveRecord::Base
belongs_to :booking, :inverse_of => :sale
belongs_to :ticket
end
class BookingsController < ApplicationController
before_filter :load_event
before_filter :load_ticket
def index
#bookings = #event.bookings
end
def new
#booking = Booking.new
end
private
def load_event
#event = Event.find(params[:event_id])
end
def load_ticket
#ticket = #event.tickets.find(params[:ticket_id])
end
def booking_params
params.require(:booking).permit(:buyer_name, :phone, :address, :order_quantity,:total_amount)
end
end
class TransactionsController < ApplicationController
before_filter :load_event
before_filter :load_booking
before_filter :load_ticket
def new
end
def pickup
#sale = Sale.find_by!(guid: params[:guid])
#booking = #sale.booking
end
def complete
#sale = Sale.find_by!(guid: params[:guid])
#booking = #sale.booking
end
if sale.save
StripeCharger.perform_async(sale.guid)
render json: { guid: sale.guid }
else
errors = sale.errors.full_messages
render json: {
error: errors.join(" ")
}, status: 400
end
end
def status
sale = Sale.find_by!(guid: params[:guid])
render json: { status: sale.state }
end
private
def load_event
#event = Event.find(params[:event_id])
end
def load_booking
#booking = #event.bookings.find(params[:booking_id])
end
def load_ticket
#ticket = #booking.ticket.find(params[:ticket_id])
end
end
#Stripe Checkout Routes
I left out a view minimal details within the models . But basically What I am trying to do is have a user enter Name, and quantity of the ticket and from submitin the booking redirect to the transaction new, in which I can carry out the sale model with Stripe Check out.
My ultimate goal of everything is to get the bookings quantity input multiplied with the ticket price to get a total amount to carry through Stripe. Do anyone have any suggestions on how to improve this break down. Of modeling a events, tickets, bookings to check out type of example. Sorry if how I'm breaking it down is noobish, I'm attempting to wrap my head around accomplishing this.
In transaction controller you don't need find on #booking.ticket
def load_ticket
#ticket = #booking.ticket.find(params[:ticket_id])
end
Since #booking has only one ticket, you just need #booking.ticket