Can not create or build nested model in Rails 6 - ruby-on-rails

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

Related

has_many :through add extra param in join table in one call (object creation)

I have the following code letting a user to create a new album through a join table with an extra params (creator).
In order to do it, my controller does 2 requests (one for creating the album object and the collaboration object / the other to update the collaboration object with the extra params).
I would like to know if there is a way to do this call with only one request. (add the extra "creator" params in the same time than the album creation)
Thank you.
albums_controller.rb
class AlbumsController < ApplicationController
def new
#album = current_user.albums.build
end
def create
#album = current_user.albums.build(album_params)
if current_user.save
#album.collaborations.first.update_attribute :creator, true
redirect_to user_albums_path(current_user), notice: "Saved."
else
render :new
end
end
private
def album_params
params.require(:album).permit(:name)
end
end
Album.rb
class Album < ApplicationRecord
# Relations
has_many :collaborations
has_many :users, through: :collaborations
end
Collaboration.rb
class Collaboration < ApplicationRecord
belongs_to :album
belongs_to :user
end
User.rb
class User < ApplicationRecord
has_many :collaborations
has_many :albums, through: :collaborations
end
views/albums/new
= simple_form_for [:user, #album] do |f|
= f.input :name
= f.button :submit
You can just add associated objects on the new album instance:
#album = current_user.albums.new(album_params)
#album.collaborations.new(user: current_user, creator: true)
When you call #album.save ActiveRecord will automatically save the associated records in the same transaction.
class AlbumsController < ApplicationController
def new
#album = current_user.albums.new
end
def create
#album = current_user.albums.new(album_params)
#album.collaborations.new(user: current_user, creator: true)
if #album.save
redirect_to user_albums_path(current_user), notice: "Saved."
else
render :new
end
end
private
def album_params
params.require(:album).permit(:name)
end
end
You are also calling current_user.save and not #album.save. The former does work due to fact that it causes AR to save the associations but is not optimal since it triggers an unessicary update of the user model.

Saving values from a http post from a nested resource?

I am trying to use a nested resource in rails to create a new record. I am attempting to use of /events/id/entries/new where there is a
<%= form_for([#event, #entry]) do |f| %>
on the new page.
My models are:
class Entry < ApplicationRecord
belongs_to :user
belongs_to :event
class User < ApplicationRecord
has_many :entries, dependent: :destroy
class Event < ApplicationRecord
has_many :entries, dependent: :destroy
The controller is:
def new
#event = Event.find(params[:event_id])
#entry = #event.entries.build
end
def create
#user = current_user
#entry = Entry.new(user_id: #user.id, event_id: :event_id , course: :entry.course , siCard: :entry.siCard)
if #entry.save
redirect_to #user
flash.now[:info] = "Event Created"
else
render '/create'
flash.now[:danger] = "Somthing went wrong"
end
end
But the record does not save NoMethodError in EntriesController#createdespite it saying that the parameters passed are.
"utf8"=>"✓",
"entry"=>{"course"=>"Orange", "siCard"=>"23232323"},
"commit"=>"Enter",
"event_id"=>"2"}
How do I modify the create controller to save the record?
Give this a go:
#entry = Entry.new(
user_id: current_user.id,
event_id: params[:event_id],
course: params[:entry][:course],
siCard: params[:entry][:siCard]
)
You don't need to #user = current_user, just use current_user.id.
event_id: :event_id is just going to set event_id to a symbol. You want to access the params value, I am guessing. So:
event_id: params[:event_id]
Here:
course: :entry.course
you're trying to call the course method on the :entry symbol. What?!?
Instead:
course: params[:entry][:course]
Same thing with siCard.
BTW, siCard is not very Ruby-ish. Rather, you should do si_card.

Error getting user's name and email from the associated model in Ruby on Rails

I am creating a webiste where people can debate with each other. It has 4 main models - post, for_the_motion, against_the_motion, and user( added in the respective order). I ran a migration and made a association between for model and against model.
For each view in "for" model I want to show which user added that particular motion. But I am getting an error
undefined method `image_url' for nil:NilClass
Stuck from long time on this. This is how the models look
user.rb
class User < ApplicationRecord
has_many :posts
has_many :fors
has_many :againsts
class << self
def from_omniauth(auth_hash)
user = find_or_create_by(uid: auth_hash['uid'], provider: auth_hash['provider'])
user.name = auth_hash['info']['name']
user.image_url = auth_hash['info']['image']
user.url = auth_hash['info']['urls'][user.provider.capitalize]
user.save!
user
end
end
end
for.rb
class For < ApplicationRecord
belongs_to :post, optional: true
belongs_to :user,optional: true
end
post.rb
class Post < ApplicationRecord
has_many :fors, dependent: :destroy
has_many :againsts, dependent: :destroy
belongs_to :user, optional: true
end
against.rb
class Against < ApplicationRecord
belongs_to :post, optional: true
belongs_to :user, optional:true
end
CONTROLLERS
posts_controller.rb
class PostsController < ApplicationController
def index
#posts = Post.all
end
def land
end
def show
#post = Post.find(params[:id])
end
def new
#post = Post.new
end
def create
#post = Post.new(post_params)
#post.user = current_user
if #post.save
redirect_to #post
else
render 'new'
end
end
private
def post_params
params.require(:post).permit(:title)
end
end
fors_controller.rb
class ForsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#for = #post.fors.create(fors_params)
#for.user = current_user
redirect_to post_path(#post)
end
private
def fors_params
params.require(:for).permit(:content)
end
end
sessions_controller.rb
class SessionsController < ApplicationController
def create
begin
#user = User.from_omniauth(request.env['omniauth.auth'])
session[:user_id] = #user.id
# flash[:success] = "Welcome, #{#user.name}!"
rescue
# flash[:warning] = "There was an error while trying to authenticate you..."
end
redirect_to root_path
def destroy
if current_user
session.delete(:user_id)
# flash[:success] = 'See you!'
end
redirect_to root_path
end
end
end
This is where I am getting the error
<h1><%=#post.title%></h1>
<div class="fort">
<h3>For the motion</h3>
<%#post.fors.each do |f|%>
<p><%=f.content%></p>
<p><%=f.user.image_url%></p>/*This is where errors arise*/
<%end%>
<%= render "fors/form"%>
</div>
<div class="against">
<h3>Against the motion</h3>
<%#post.againsts.each do |f|%>
<p><%=f.content%></p>
<p><%= #post.user.name%></p>
<%end%>
<%= render "againsts/form"%>
</div>
Here is the github link for any other required information
https://github.com/sarfrazbaig/DebatingSociety2
Seems like you missed saving the .user on fors_controller.rb:
class ForsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#for = #post.fors.create(fors_params)
# .create above already will save a new For record in DB
# therefore your #for.user assignation will be only assigned in memory, but not yet in DB
#for.user = current_user
# you'll need to save it again afterwards:
#for.save
redirect_to post_path(#post)
end
# ...
end
Suggestion:
use .new instead of .create to not-yet-save into the DB, and only call save when everything that you need to assign is already assigned.
class ForsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#for = #post.fors.new(fors_params)
#for.user = current_user
#for.save
redirect_to post_path(#post)
end
# ...
end
Take note that you would still encounter that error even if you already updated your code with the above; this is because currently your For records in the DB all are missing the .user value. You'll have to manually assign and save the .user accordingly for each For record, and probably best that you'd write a...
class For < ApplicationRecord
validates :user, presence: true
end
... validation so that this error will be prevented in the future.
One of the #post.fors is lacking a user, which is permitted by the belongs_to :user, optional: true in your For model.
You can restrict your query to showing only fors that have an associated user:
#post.fors.joins(:users) or you can use the safe navigation operator to return nil when attempting to read the image_url for a non-existent user - f.user&.image_url

DB rolls back on create action

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

Rails: Same "Create" action conditional between two models

Making a site in with three main models: Users, Posts, and Gyms. Users should be able to post either from their own model (User.post), or, if they are the admin of a gym, from the Gym's model (Gym.post).
I'm using the same post controller and post form to post fro either the gym or the user, but the controller "Create" action can't distinguish between the two.
class PostsController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy]
before_action :correct_user, only: :destroy
def create
if (gym.gym_admin == current_user.id)
#post = gym.posts.build(post_params)
if #post.save
flash[:success] = "Post!"
redirect_to "/gyms/#{gym.id}"
else
#feed_items = []
render 'static_pages/home'
end
else
#post = current_user.posts.build(post_params)
if #post.save
flash[:success] = "Post!"
redirect_to root_url
else
#feed_items = []
render 'static_pages/home'
end
end
end
def destroy
#post.destroy
flash[:notice] = "Post deleted"
redirect_to request.referrer || root_url
end
private
def post_params
params.require(:post).permit(:post_type, :title, :content, :picture, :body_parts,
:duration, :equipment, :calories, :protein,
:fat, :carbs, :ingredients, :tag_list,
:postable_id, :postable_type)
end
def correct_user
#post = current_user.posts.find_by(id: params[:postable_id])
redirect_to root_url if #post.nil?
end
def gym
#gym = Gym.find_by(params[:id])
end
end
And the Models:
class Post < ApplicationRecord
belongs_to :user
belongs_to :gym
belongs_to :postable, polymorphic: true
class User < ApplicationRecord
has_many :posts, as: :postable, dependent: :destroy
has_many :gyms
class Gym < ApplicationRecord
has_many :posts, as: :postable, dependent: :destroy
belongs_to :user
Rught now, this create action only creates posts from the gym's model; if I remove the first half of the conditional, it will only post from the User model.
Any help is greatly appreciated, thank you
I would be curious what gym.gym_admin (and consequently the whole line below 'def create') evaluates to since I don't see in referenced anywhere else.
My suspicion is that you would want to change
if (gym.gym_admin == current_user.id)
to
if (gym.gym_admin.id == current_user.id)
or
if (gym.gym_admin == current_user)
once that relationship is working correctly.
Also, could post be built independently of whether a user is a gym admin and send the post params the gym_id if applicable. Then accessed either through:
/gym/:id
#posts = Post.where('gym_id = ?', params[:id])
or
/user/:id
#posts = Post.where('user_id = ?', params[:id])
I fixed it my removing the conditional logic altogether; I just made two separate custom actions in the controller and called them from different links. ie. :actions => create_gym / create_user

Resources