Rails: How do you create a new nested resource? - ruby-on-rails

In my app, Users have Conversations, and Conversations have Messages. A Message belongs both to a User (as an author) and to a Conversation.
I want to create a new Message. This is the code I'm using right now in MessagesController.
def new
#user = current_user #currently logged in user
#conversation = Conversation.find(params[:id])
#message = #conversation.messages.build
end
def create
#conversation = Conversation.find(params[:conversation_id])
#message = #conversation.messages.build(params[:message])
if #message.save
redirect_to username_conversation(current_user, #message.conversation)
else
redirect_to root_url
end
end
params[:message] contains the message content ("content" => "I'm Spartacus").
This isn't working (probably because I'm not specifying the user/author when creating a new Message?). How do I make this work the Rails way?
Thanks.

You need to set the user manually. Only one property can be set using the short methods provided by Rails.
def new
#user = current_user #currently logged in user
#conversation = Conversation.find(params[:id])
#message = #conversation.messages.build
#message.user = user
end
def create
#conversation = Conversation.find(params[:conversation_id])
#message = #conversation.messages.build(params[:message])
#message.user = current_user
if #message.save
redirect_to username_conversation(current_user, #message.conversation)
else
redirect_to root_url
end
end

Related

Adding search function for gem mailboxer

I'm new to Rails, and I found this gem mailboxer for sending messages between the website users. I am unable to write the search functions for inbox and sent.
conversations controller
class ConversationsController < ApplicationController
before_action :authenticate_user!
def new
end
def create
recipients = User.where(id: conversation_params[:recipients])
conversation = current_user.send_message(recipients, conversation_params[:body], conversation_params[:subject]).conversation
flash[:success] = "Your message was successfully sent!"
redirect_to conversation_path(conversation)
end
def show
#receipts = conversation.receipts_for(current_user)
# mark conversation as read
conversation.mark_as_read(current_user)
end
def reply
current_user.reply_to_conversation(conversation, message_params[:body])
flash[:notice] = "Your reply message was successfully sent!"
redirect_to conversation_path(conversation)
end
def trash
conversation.move_to_trash(current_user)
redirect_to mailbox_inbox_path
end
def untrash
conversation.untrash(current_user)
redirect_to mailbox_inbox_path
end
private
def conversation_params
params.require(:conversation).permit(:subject, :body,recipients:[])
end
def message_params
params.require(:message).permit(:body, :subject)
end
end
What do I need to do to implement these?
I am not aware of the gem you are talking about. However, in general, you could do something like this in Rails.
Assuming you have a model Message, with name as a column, you could do this:
Message.where("name like ?", params[:name])
where params[:name] could be either "inbox" or "sent".

Prevent /new action being replaced in URL after failed validation

After a failed validation I would like the user's brower to display the /new action in the URL not the /create action (which replaces /new after every failed validation). Any way to do this with rails?
def new
#user = User.new
end
def create
#user = User.new(params[:user])
if #user.save
redirect_to success_path
else
render 'new'
end
end
You could do it with a redirect using the session instead:
def new
if session[:new_user_params].present?
#user = User.new(session[:new_user_params])
#user.valid?
else
#user = User.new
end
end
def create
#user = User.new(params[:user])
if #user.save
session.delete(:new_user_params)
redirect_to success_path
else
session[:new_user_params] = params[:user]
redirect_to action: :new
end
end

Use different variable depending on which view was used in Rails 4

I'm using Mailboxer so that users can reply to posts in my site. There are two types of post models in my site which users might reply to, Requests and Offers.
Here is my messages controller:
class MessagesController < ApplicationController
# GET /message/new
def new
#request = Request.find(params[:request])
#message = current_user.messages.new
#user = #request.user
end
def reply
#conversation ||= current_user.mailbox.conversations.find(params[:id])
end
# POST /message/create
def create
#user = User.find(params[:user])
#body = params[:body]
#subject = params[:subject]
current_user.send_message(#user, params[:body], params[:subject])
flash[:notice] = "Message has been sent!"
redirect_to :conversations
end
end
In the new action, I'm using #request, but if I reply to an Offer post, I'll need to use an #offer variable. Is there a way I can use an if statement to choose between an #offer and #request depending on the view the new action is called from? Is this the best way to go about this?
It's not the most graceful thing in the world but something as simple as this should work.
if params[:request]
#request = Request.find(params[:request])
else
#offer = Offer.find(params[:offer])
end

Updating a record via a action method

User signs up, is redirected to a page to be collected info, pretty straight forward
I for my life can't figure out how to do this
My controller for the user
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def additional_info
#user = User.find session[:user_id]
#user = User.update(user_addinfo)
redirect_to user_path
end
def create
#user = User.new(user_params)
if #user.save
#session[:user_id] = #user.id
#UserMailer.welcome_email(#user).deliver
sign_in #user
redirect_to additional_info_path
flash[:success] = "Welcome to InYourShoes!"
else
render'new'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
def user_addinfo
params.require(:user).permit(:year)
end
end
user_addinfo is the action method that i want to call updating my record on for my additional_info method.
the def create method has commented line that i'm unsure if necessary, particularly the session[:user_id] = #user.id. I was told that i need this in order to keep track of my session, but perhaps someone can debunk this for me, as im following michael hartl's tutorial.
as of right now with this code, rails is giving me a parameter missing in the
params.require(:user).permit(:year) line.
Much help is greatly appreciated. Ive been trying many different things, and cant seem to figure this out
Change your controller code as below:
def additional_info
#user = User.find params[:id] ## Set #user
end
def update
if #user.update(user_addinfo)
redirect_to user_path(#user), notice: 'User was successfully updated.'
else
render action: 'additional_info'
end
end
def create
#user = User.new(user_params)
if #user.save
#session[:user_id] = #user.id
#UserMailer.welcome_email(#user).deliver
sign_in #user
redirect_to additional_info_path(#user) ## Pass #user
flash[:success] = "Welcome to InYourShoes!"
else
render'new'
end
end
and in your routes.rb update the additional_info route as
get 'info/:id' => 'users#additional_info', :as => 'additional_info'
You additional_info action seems to be wrong. You need to pass in the id of the user for whom you are collecting additional information.
def additional_info
#user = User.find params[:id]
#user.update_attributes(user_addinfo)
redirect_to user_path(#user)
end
The line you have commented in your create method:
#session[:user_id] = #user.id
Is what is storing the user id to a session variable and not a param in the url.
You then have this line commented in your additional_info method
#user = User.find session[:user_id]
This is looking up the user by the id that you would have previously stored in the session variable.
At that point the user object would be stored in user
If you need it in your instance variable, make sure to modify the line to be
#user = User.find session[:user_id]
Your user would then be stored in #user and be able to be accessed in the view

Create or new in Rails controller in create action

In Rails, we define the create action 2 ways. What are the difference?
def create
#shop = Shop.new(params[:shop])
if #shop.save
flash[:success] = 'Thanks for adding new shop.'
redirect_to #shop
else
flash[:error] = 'Error adding review, please try again.'
redirect_to #shop
end
end
# or
def create
#shop = Shop.create(params[:shop])
if #shop.save
flash[:success] = 'Thanks for adding new shop.'
redirect_to #shop
else
flash[:error] = 'Error adding review, please try again.'
redirect_to #shop
end
end
Considering we already have:
def new
#shop = Shop.new
end
Which is more proper?
This def new action is just for the New view (the new action in your Shop controller would correspond to the app/views/shop/new.html.erb file) - it doesn't do any creation:
def new
#shop = Shop.new
end
There is no mention of params[:shop] in that action, because the parameters don't exist yet - that's what you're gathering in the New view.
Your def create action is the action that actually creates the database entry:
def create
#shop = Shop.new(params[:shop])
if #shop.save
flash[:success] = 'Thanks for adding new shop.'
redirect_to #shop
else
flash[:error] = 'Error adding review, please try again.'
redirect_to #shop
end
end
You're using .new instead of .create so that you can do validations. Also, the Shop.new call doesn't actually create the record - it's #shop.save that does that.
If you used Model.create, you don't need to save the object explicitly. The create method will do that for you.
If you used Model.new, you need to save the object by doing #object.save. The new
method does not do that for you.
Using Model.new:
def create
#shop = Shop.new(params[:shop])
if #shop.save
flash[:success] = 'Thanks for adding new shop.'
redirect_to #shop
else
flash[:error] = 'Error adding review, please try again.'
redirect_to #shop
end
end
Using Model.create:
def create
#shop = Shop.create(params[:shop])
# if #shop.save (This is not required)
if #shop
flash[:success] = 'Thanks for adding new shop.'
redirect_to #shop
else
flash[:error] = 'Error adding review, please try again.'
redirect_to #shop
end
end
The first way doesn't do what you expect:
def create
#shop = Shop.new(params[:shop]) # This won't create a new record on Shops table unless...
#show.save # ...you do this
end
def create
#shop = Shop.create(params[:shop]) # This will create a new record if everything is fine
if #shop.save # This is redundant
# ...
end
end
Calling create and then save is redundant. The create method will try to make a new record and fail silently if validations are not successful. In the other hand, save will try to make a new record, but returns nil if vaildations fail, so you can use it in a if/else block.
In create controller action, Shop.new is useless without following #shop.save. Usually it's being split into these two steps to handle validation errors. We init shop with data from the user. If data is ok, we save the shop. If there are validation errors, we tell user to try again.
Or we need to do some complex attribute initialisation before we save record to the database.
#user = User.new params[:user]
#user.reputation = fetch_reputation_from_stackoverflow
if #user.save
# the usual steps here
RE: question edit
Considering we already have:
def new
#shop = Shop.new
end
What is there in new action is completely irrelevant. If your validations might fail (or otherwise your model might not be created successfully), then use new + save pair. If you are sure that input data is ok and model will always save, then use only create (following save is redundant).

Resources