Rails 4 | Properly creating and saving in controller - ruby-on-rails

I'm creating a messaging system of sorts. It's part of a bigger overall system in Rails 4.
Anyway, to setup my question briefly I have users who can both send and receive messages. So in my "new" users' view I send a parameter [:receiver_id] to the message controller when creating a new message.
My question is how do I get the #send_to variable down to the create action properly. I had to send it into the messages#new action via the receiver_id from the users' view. Do I store it as a message parameter somehow or make it a hidden parameter?
Here is my messages controller
class MessagesController < ApplicationController
def new
#message = Message.new
#this :receiver_id is being sent from the show view in the user controller / view
#it is being used to pass a parameter
#send_to = User.find(params[:receiver_id])
#message.sender = current_user
#message.receiver = #send_to
#the form_for should save the message with the RESTful action of create method in this controller
end
def create
debugger
#messasge = Message.create!(message_params)
redirect_to users_path
end
def index
#messages = current_user.messages
end
def sent
#messages = current_user.messages
end
def message_params
params.require(:message).permit!
end
end
I know from rails routing mostly about RESTful actions, and yes I have read over the routing guide again before writing this.
EDIT:
I have moved these two lines down into the create function. The #send_to needs the :receiver_id though.
#message.sender = current_user
#message.receiver = #send_to

You should implement an asynchronous request for sending messages, cause it might be annoying for system users to redirect to another page just to send a message. From the above snippets it is not known (the logic).
Having an asynchronous request, which would serve a dialog with new message form, next step is just to send form data to messages#create action. I assume that user, while fulfilling form data, can select somehow message recipient.
Action new does not need to know who is the message receiver.
Don't use Message.create! (with !) because it will raise an exception if creation fails. Use save instead and handle case if it would fail,
e.g:
def create
#message = Message.new params[:message]
#message.save
respond_to do |f|
f.json do
render :json => {:success => #message.persisted?, :message => #message}.to_json
end
end
end

Related

Issues Looping through an array in Active Mailer

I am trying to implement a feature which allows user to send one email to multiple recipients. I split the recipient email params by calling the .split method which turns it into an array. Then I loop through the array with the each method which should apply the mail method to each element in the array. The controller and mailer code is given below.
controller code
def create
#scoreboard = Scoreboard.find(params[:scoreboard_id])
#invitation = #scoreboard.sent_invitations.build(invitation_params)
if #invitation.save
UserMailer.registered_invitation_email(#scoreboard, #invitation).deliver_now
flash[:success] = "Invitation sent successfully"
redirect_to new_scoreboard_invitation_path
else
render 'new'
end
end
end
mailer code
def registered_invitation_email(scoreboard, invitation)
#scoreboard = scoreboard
#invitation = invitation
split_email = #invitation.recipient_email.split
split_email.each do |email|
mail(to: email, subject: "View scoreboard")
end
end
The problem is that it only sends the email to the last element in the array. For example, if the user types in "joe#example.com Mike#example.com" in the form it will only send an email to the last element in an array. In this case, Mike#exapmle.com. I am not sure why that is. I think I am looping through it correctly. I am not sure what's causing the problem. I am not sure if its the loop or maybe loops don't work in active mailer. Not exactly sure. Any help would be appreciated. Thanks!!
Try something like this
Create a model with fields
field :mail_to
field :subject
create a method in the same model
def deliver
UserMailer.send_invitation_mail(self).deliver
end
Mailer
def send_invitation_mail(email)
mail(to: email.mail_to, subject: email.subject)
email.destroy
end
Controller
def create
user_emails.each do |email|
email = Email.new(:mail_to => "xxx#yyy.com",:subject => "View Scoreboard") # Email is the model name.
email.deliver
sleep(1)
end
end
This way u can also log and also deliver all mails together in the model and also do various manipulations on the email.

Is there a way to get the visitors city and country in Rails controller?

In my current app, i use Geocoder gem to get the city and the country of the visitor. I use hidden fields in my view to get these details. When the login form is submitted, these details will be sent to the controller and the controller will save them to the database. When I try to get these details directly from the controller by using
request.location.city
It will assigning a blank value to the database. If I use hidden fields in the view, some one can temper with them right? So, how can I fix this?
You should store visitor information before you render any content:
class UsersController
def new
# I suspect that, for fast insert, you should probably use a NoSQL database
# to perform `store!` or even just write it to a log file
Visitor.store!(:city => request.location.city, :ip => request.ip)
end
def create
#user = User.build(params[:user].merge(:city => request.location.city))
if #user.valid?
#user.save
flash[:notice] = "You've been registered!"
redirect_to user_dashboard_path
else
flash[:notice] = "Couldn't register your account"
render action: "new"
end
end
end

Ruby on Rails controller design

When I look at examples of Rails controllers, I usually see something like this:
class WidgetController < ActionController::Base
def new
#widget = Widget.new
end
def create
#widget = Widget.new(params[:id])
if #widget.save
redirect_to #widget
else
render 'new'
end
end
end
This works, but there's a couple problems:
Routes
If I add widgets to my routes.rb file:
Example::Application.routes.draw do
resources :widgets
end
GET /widgets/new will route to new and POST /widgets will route to create.
If the user enters incorrect information on the new widget page and submits it, their browser will display a URL with /widgets, but the new template will be rendered. If the user bookmarks the page and returns later or refreshes the page, the index action will be called instead of the new action, which isn't what the user expects. If there's no index action or if the user doesn't have permission to view it, the response will be a 404.
Duplication of code
As a contrived example, let's say I had some tricky logic in my new method:
def new
#widget = Widget.new
do_something_tricky()
end
Using the current approach, I'd duplicate that logic in new and create. I could call new from create, but then I'd have to modify new to check if #widget is defined:
def new
#widget ||= Widget.new
do_something_tricky()
end
Plus, this feels wrong because it reduces the orthogonality of the controller actions.
What to do?
So what's the Rails way of resolving this problem? Should I redirect to new instead of rendering the new template? Should I call new inside of create? Should I just live with it? Is there a better way?
I don't think this is a problem in "the rails way" and there is no builtin functionality to allow this without getting your hands dirty. What does a user expects when bookmarking a form they just submitted and had errors? Users don't know better, and they shouldn't bookmark a failed form.
I think redirecting to new_widget_path is the cleanest solution. Yet, you should keep the errors and display them on the form. For this I recommend you keep the params in session (which I expect to be smaller than a serialized Widget object).
def new
#widget = widget_from_session || Widget.new
end
def widget_from_session
Widget.new(session.delete(:widget_params)) if session[:widget_params].present?
end
private :widget_from_session
# Before the redirect
session[:widget_params] = params
The code is self explanatory, Widget.new will only be called when widget_from_session returns nil, this is when session[:widget_params] is present. Calling delete on a hash will return de deleted value and delete it from the original hash.
UPDATE Option 2
What about submitting the form using ajax? Your controller could benefit from:
respond_to :html, :json
...
def create
#widget = Widget.new params[:widget]
#widget
respond_with #widget, location: nil
end
Based on the response code (which is set by Rails: 201 Created or 422 Unprocessable Entity), you could show the errors (available in the body of the response when validations fail) or redirect the user to #widget
This is how StackOverflow does it: https://stackoverflow.com/questions/ask. They submit the form asynchronously.
In general, I think the Rails way of solving the problem would be to put the tricky method onto the model or as a helper method, so the controller stays "thin" and you don't have to make sure to add custom behavior to both #new and #create.
EDIT: For further reading, I'd recommend the "Rails AntiPatterns" book, as they go through a lot of these common design issues and give potential solutions.
you put do_something_tricky() in its own method and call it inside the create action (but only when you're rendering the new template, ie when validation fails).
As for the bookmark issue, I don't know a good way to prevent that but to modify the routes and set the create action to the new action but using POST
get '/users/new' => 'users#new'
post '/users/new' => 'users#create'
UPDATE: using resources
resources :platos, except: :create do
post '/new' => 'plates#create', on: :collection, as: :create
end
then you can use create_platos_path in your forms
You don't need to write same function in two action , use before_filter instead.
If you want to have "widget_new_url" after incorrect submission then in your form add url of new widget path something like :url => widget_new_path .
Rails takes the url from Form .
I have this problem before, so I use edit action instead.
Here is my code.
Routes:
resources :wines do
collection do
get :create_wine, as: :create_wine
end
end
Controller:
def create_wine
#wine = Wine.find_uncomplete_or_create_without_validation(current_user)
redirect_to edit_wine_path(#wine)
end
def edit
#wine = Wine.find(params[:id])
end
def update
#wine = Wine.find(params[:id])
if #wine.update_attributes(params[:wine])
redirect_to #wine, notice: "#{#wine.name} updated"
else
render :edit
end
end
Model:
def self.find_uncomplete_or_create_without_validation(user)
wine = user.wines.uncomplete.first || self.create_without_validation(user)
end
def self.create_without_validation(user)
wine = user.wines.build
wine.save(validate: false)
wine
end
View:
= simple_form_for #wine, html: { class: 'form-horizontal' } do |f|
= f.input :complete, as: :hidden, input_html: { value: 'true' }
What I did is create a new action 'create_wine' with get action.
If user request 'create_wine', it will create a new wine without validation and redirect to edit action with a update form for attributes and a hidden field for compele .
If user has create before but gave up saving the wine it will return the last uncompleted wine.
Which means whether use save it or not, the url will be the same to /wines/:id.
Not really good for RESTful design, but solve my problem. If there is any better solution please let me know.

Implementing a specific action for a model that respond_with(#object)

I have a Subscription model that users can create. Users needs to validate a PIN sent to them to confirm subscription after is created. I'm having a bit of trouble trying to figure it out the best way to implement this.
I implemented a confirms controller with two new and create actions.
def new
#confirm = Subscription.new
end
def create
#keyword = Keyword.joins(:shortcode).where("shortcodes.shortcode = ? and shortcodes.country = ?",params[:subscription][:shortcode],params[:subscription] [:country]).find_or_create_by_keyword(params[:subscription][:keyword])
if #confirm = Subscription.where(:phone => params[:subscription][:phone], :country => params[:subscription][:country], :keyword_id => #keyword.id).last
#confirm.check_subscription_pin(params[:subscription][:pin])
respond_with(#confirm)
elsif #confirm && #confirm.errors.any?
flash[:notice] = #confirm.errors
render :action => :new
else
flash[:notice] = "Subscription not found."
render :action => :new
end
end
This solution doesn't look very convincing since I would like to always respond_with(#confirm) to allow REST POST done via JSON.
There's no params[:subscription] received when you make your curl call. Phone and Pin are nested within subscription, hence the error.
<pre>undefined method `[]' for nil:NilClass</pre>
I don't know how to make a curl call with nested params by the way.

How to pass value in a link_to inside a mail of Rails 3.1?

This is the way I am approving certain events in my site.
The idea is:
With every new event, an email is send to "admin#example.com". The email contains certain stuff and a link to a method that changes the 'approved' boolean.
Everything works apart from the url generated in the email.
It generates, http://example.com/approve_event . And I get the error 'Could not find an event with id nil'
How should I do to get a url that points to something like http://example.com/approve_event?=23
AdminNotifier
def approve_event(event)
#event = event
mail(:to => "admin#example.com", :subject => "Event pending of approval")
end
email view html
= link_to "Approve", approve_event_url(:id=> #event.id)
EventController
def approved
#event = Event.find(params[:id])
#event.approved = true
#event.save
end
You need to make sure that the #event variable is defined correctly in the mailer method (approve_event in this case), to make sure that it will be available in the view.
More info on the Rails guides

Resources