Saving values from a http post from a nested resource? - ruby-on-rails

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.

Related

Can not create or build nested model in Rails 6

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

How to structure a Conversation between two models?

I have a Store object that has an email_address attribute. Using the logic from and How To Build A Form and Handling Inbound Email Parsing with Rails, I'm trying to figure out how to structure a Conversation where a visitor can email the Store, and the Store can reply through email - their replies would post a Message to the Conversation.
When a visitor inquires to the store (via form), I create a Reservation record with their name and email, and start a Conversation like this:
#conversation = Conversation.create(sender_id: self.id, recipient_id: self.store_id)
I wanted to model the notifications similar to this, where everyone but the sender receives an email, but I'm stumped on how to map the User, since it's two different objects (Reservation and Store):
def send_notifications!
(forum_thread.users.uniq - [user]).each do |user|
UserMailer.new_post(user, self).deliver_now
end
end
The Conversation model looks like this, may be wrong, any guidance on what I could use to make the messages unique and structure the notifications?
class Conversation < ApplicationRecord
belongs_to :sender, :foreign_key => :sender_id, class_name: "Reservation"
belongs_to :recipient, :foreign_key => :recipient_id, class_name: "Store"
belongs_to :reservation
has_many :messages, dependent: :destroy
end
The simplest and most flexible way would be to set this up as a many-to-many association:
class User < ApplicationRecord
has_many :messages
has_many :conversations, through: :messages
end
class Message < ApplicationRecord
belongs_to :user
belongs_to :conversation
end
class Conversation < ApplicationRecord
has_many :messages
has_many :users, through: :messages
end
Here Message actually works as the join table that ties it together. Conversion is the recipient. When sending an initial message to a user you would POST to /users/:user_id/messages:
<%= form_with(model: [#user, #message || Message.new]) do |f| %>
# ...
<% end %>
module Users
class MessagesController < ApplicationController
# POST /users/:user_id/messages
def create
#user = User.find(params[:user_id])
#conversation = Conversation.joins(:users)
.where(users: { id: [current_user, #user]})
.first_or_create
#message = #conversation.messages.new(message_params.merge(user: current_user))
if #message.save
redirect_to #coversation
else
render :new
end
end
end
end
And then you would handle the views and controllers (such as a chat window) for conversations in a separate controller:
<%= form_with(model: [#conversation, #message || #conversation.messages.new]) do |f| %>
# ...
<% end %>
module Conversations
class MessagesController < ApplicationController
# POST /conversations/:conversation_id/messages
def create
#conversation = Conversation.find(params[:conversation_id])
#message = #conversation.messages.new(message_params.merge(user: current_user))
if #message.save
redirect_to #coversation
else
render :new
end
end
end
end

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.

Rails Setting Devise Username as attribute of comment

I'm new to rails and still figuring out which things belong in the model and which in the controller. I'm creating a simple comment model that belongs to articles. I have a attribute :commenter which is a string. I would like to get the username from the current_user (I'm using devise for my login feature) Would I do this in the create method of my controller?
Something like
def create
#post = Post.find(params[:post_id])
#comment.commenter = current_user.username
#comment = #post.comments.create(comment_params)
redirect_to post_path(#post)
end
class User < ActiveRecord::Base
has_many :posts
has_many :comments
end
class Post < ActiveRecord::Base
belongs_to :user
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :user #should have user_id: integer in Comment
belongs_to :post #should have post_id: integer in comment
delegate :username, to: :user, allow_nil: true
end
In posts controller: -
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.new(comment_params)
#comment.user = current_user
if #comment.save
flash[:success] = "Comment saved successfully!"
redirect_to post_path(#post)
else
flash[:error] = #comment.errors.full_messages.to_sentence
redirect_to post_path(#post)
end
end
After that you can get all user details of any comment:-
comment = Comment.find(#id_of_comment)
comment.username => #will return username because of delegation
Reference for delegation

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

Resources