New to rails and running into some issues around best practice error handling.
I have the following code in helper in the /lib folder which is used to apply a coupon to some pricing.
As you can see from code below i'm trying a few different ways to return any errors. Specifically, model.errors.add and flash[:error].
The code below is supposed to return a new price once a valid coupon has been applied. What i'm not sure about is what to return in the case of an error? I can't return false as I am below because this method should really be returning a numeric value, not a boolean. What is the best method to bubble up an error to the view from the lib/helper and stop execution?
def calc_coupon transaction_price, discount_code, current_tenant
coupon = Coupon.where(code: discount_code, tenant_id: current_tenant.id).first
if coupon.nil?
current_tenant.errors.add :base, "No such coupon exists"
return false
else
if coupon.maxed? || coupon.expired?
flash[:error] = "This coupon is no longer valid."
return false
else
transaction_price = coupon.calculate(transaction_price)
end
end
return transaction_price
end
I think you are on the right track but you are struggling because you are mixing view logic with business logic.
Forget the helper class and add the functionality to your model.
By adding the validations to the model rather than a helper class and adding the errors to base your controller action will be able to handle the errors when it tries to save/create the record in the normal way, thus "bubbling up the errors"
e.g.
class MyModel < ActiveRecord ...
validate :validate_coupon
protected
def validate_coupon
# add your validations here - call your boolean check which adds errors to the model
# if validations are ok then calculate your transaction price here
end
end
If you have your checks as public methods on your model then they can be called from anywhere, i.e. from within a view
so a public transaction_price method on your model that first checks for errors and sets the price could be used in a view like so
<%= #some_object.transaction_price %>
As I stated earlier because the validations are added in the model then your controller action just needs to check the value of the call to save and flash the errors as normal if it returns false so no need to do anything custom in your controller actions at all. Just the default scaffolded behaviour.
There are a mountain of validation options available to you. Have a look at
http://guides.rubyonrails.org/active_record_validations_callbacks.html
Hope that makes sense.
UPDATE IN RESPONSE TO COMMENT
1) You have that pretty much spot on. It is merely a suggestion that you could calculate the transaction price as part of the validation. If that doesn't suit your requirement then that's fine. I was just trying to give you the best fit for your question. The thing is that you should have two seperate instance methods then you can call them from wherever you wish which makes it a lot simpler for experimentation and moving stuff around. Go with your suggested solution It's good :)
2) Have a close look at a scaffolded controller and views. You will see how the saves/updates fail (They will fail automatically if you add errors as part of your validation process) using an if statement and you will see how the _form partial displays the errors
This is an example of an form that deals with engines in an app I am working on.
<%= form_for(#engine) do |f| %>
<% if #engine.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#engine.errors.count, "error") %> prohibited this engine from being saved:</h2>
<ul>
<% #engine.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
Basically it is checking for errors and looping through them all if any exist to display them.
To handle flash messages you should utilise the applications main layout
The following code will display flash messages inside a div called "info" which you can style however you want
<% unless flash.empty? %>
<div class="info">
<%- flash.each do |name, msg| -%>
<%= content_tag :div, msg, :id => "flash_#{name}" %>
<%- end -%>
</div>
<% end %>
By placing that before the main yield in your application.html.erb in your views/layouts folder you can be sure that whatever page is rendered, flash messages will always be shown to the user
Of course if you are displaying flash messages then maybe you don;t need to display the errors. Sometimes both is the right thing to do and sometimes just one or the other is fine. That's up to you to decide on a case by case basis.
The main thing is that the receiving action for the HTTP POST or HTTP PUT (create or update action) handles the failure to save with or without a flash as you see fit
e.g.
A create action
def create
#engine = Engine.new(params[:engine])
respond_to do |format|
if #engine.save
format.html { redirect_to #engine, notice: 'Engine was successfully created.' }
format.json { render json: #engine, status: :created, location: #engine }
else
flash.now[:notice] = "Failed to save!" # flash.now because we are rendering. flash[:notice] if using a redirect
format.html { render action: "new" }
format.json { render json: #engine.errors, status: :unprocessable_entity }
end
end
end
Hope that clears things up
UPDATE 2
Forgot to mention that flash is just a hash and uses the session cookie so you can set any key you like
e.g.
flash.now[:fred]
This would enable you to style and handle a specific fred notification in a specific view if you wanted a different styling from that supplied by your application layout by adding the code to handle the :fred key (or name of your choice) in a specific view
Related
Just wondering if there is a way to make the following code more concise
<% #users.each do |user| %>
<%= render partial: "#{user.state}_user", locals: { user: user } %>
<% end %>
Each user has a state (either active or inactive) and, for each user, there correct partial (_active_user.hmtl.erb or _inactive_user.html.erb needs to be rendered).
Looking for using collection but I can't find any sample.
You could override ActiveModel::Conversion#to_partial_path which is how render looks up the partial implicitly:
class User < ApplicationRecord
# ...
def to_partial_path
"users/#{state}_user"
end
end
But that will override how its rendered implicitly everywhere which may not be desirable. Otherwise I would question if you really need to make this more succinct - what you are doing is off the rails and its better to have code that clearly shows its intent instead of something overly clever / force DRYied.
The reason you haven't found any examples using collection is that its not going to work. It iterates through the collection and calls #to_partial_path on each member.
Messages are displayed green if sent by the current user, and blue otherwise. Following Rails convention, where does that logic belong?
Introdution
The user will visit /group/:id to see the list of messages, so the corresponding view is views/groups/show.html.erb and the corresponding controller is controllers/groups_controller.rb.
The message we want to display are in an array in #group, as #group.messages. The array is sorted by timestamp.
The code to style the color of the message is not important, but for simplicity purposes we will say there are two class selectors (one for from and one for to) and we can simply add a class attribute to the div that a message is within to change its color.
Both the user's sent and received messages are held in the array #group.messages.
If we have an individual message stored in message, we can test if it was sent by the current user with:
if session[:user_id] == message.user_id
Problem
Messages are ordered by timestamp and will need to be displayed in that order. For this reason, I can't see any clean way of handling the logic in the controller.
I would like to keep as much logic as possible out of the views and especially out of the partials, but after considering the options for rendering sent and received messages in different ways, the cleanest option I've found is to put the logic in the message partial.
Handling the logic in the message partial:
<% if message.user.id == session[:user_id] %>
<div class="to">
<p> <%= message.body %> </p>
</div>
<% else %>
<div class="from">
<p> <%= message.body %> </p>
</div>
<% end %>
Pros:
This method handles the logic with one if statement that is clean and simple
It allows us to make the code DRY because we won't have to use the logic anywhere else if we want it on other pages
Since every message only has a body, we don't have to make another partial to display messages without this formatting
Cons:
The logic is in the partial! I think people I'm working with or other programmers or even myself would first look in the controller then in the view then in the partial to make any changes or see the code
This doesn't feel like normal Rails convention
Handling the logic in the view:
Possibly two clean solutions -
1) Style the messages inside the logic or
2) Render a different partial for sent/received messages
Styling inside the logic:
<% #group.messages.each do |message| %>
<% if message.user.id == session[:user_id] %>
<div class="to">
<p> message.body </p>
</div>
<% else %>
<div class="from">
<p> message.body </p>
</div>
<% end %>
<% end %>
Rendering different partials:
<% #group.messages.each do |message| %>
<% if message.user.id == session[:user_id] %>
<%= render :partial => '/messages/sent_message', :message => message %>
<% else %>
<%= render :partial => '/messages/received_message', :message => message %>
<% end %>
<% end %>
Pros:
Either view solution keeps the logic out of the partial
It makes sense that showing something as one color or another is decided in the view
The view solution using two partials is clean and allows us to avoid styling within logic which also means that we can change the style within the partials and affect the look of messages everywhere.
Cons:
Both view options mean that our code is no longer DRY. Using these methods will mean that if we want the same functionality on 3 other pages, we will have to write the same code 3 more times
It makes sense that a view shouldn't be deciding anything
The view solution using two partials means that we will crowd the views/messages folder with partials, and still not have a default partial for rendering messages
Both of the view solutions just feel dirty in my opinion
My main points about my solutions -
No option allows for the logic to be held within the controller
Placing the logic inside the view means that to provide the same functionality on multiple pages, the same code will be written in more than one place
The option that looks the cleanest and makes the most sense to me means putting logic inside a partial, and there must be a better way.. right?
None of the solutions seem like they follow Rails convention
Which of the three options I coded best follow Rails convention?
Is it possible to place the logic in the controller?
Is there a better way to design this so that there is a clear solution following Rails convention?
What you probably have realized is that each of the three versions you described is either not DRY or not scalable. You've done a great job analyzing pros and cons of each option, so there is very little for me to add there. :)
To add presentation functionality to your models, Rails community uses Presenters. There is a great article on Presenters here that explains more about them.
Basically, you'll want to have one partial for message:
<div class=<%=#presenter.css_class%>>
<p> <%= message.body %> </p>
</div>
Then Presenter:
class MessagesPresenter
def initialize(message, current_user)
#message = message
#current_user = current_user
end
def css_class
message.user == current_user ? 'to' : 'from'
end
private
attr_reader :message, :current_user
end
And controller:
#presenter = MessagesPresenter.new(#message, current_user)
Voila! The presenter is available in both views and partials and is a great place to stash all presentation logic.
Since the only difference in these examples in the CSS class, you're repeating yourself quite a bit. Can't you add or remove a class on the tag depending on whether the tag belongs to the current_user or not?
This is really a presentation issue, and you can handle this simple logic for displaying the correct CSS tag using decorators (http://johnotander.com/rails/2014/03/07/decorators-on-rails/). I recommend using Draper (https://github.com/drapergem/draper).
First, for simplicity, add a current_user helper method to application_controller.rb to return the authenticated user.
Add a Decorator:
MessageDecorator.rb
def recipient_class
user_id == current_user.id ? "to" : "from" # (user_id delegates to message object)
end
Now your views can have much cleaner logic
Views
Message Partial:
<div class="<%= message.recipient_class %>">
<p><%= message.body %></p>
</div>
collection partial in the main view:
<%= render partial: "message", collection: #messages, as: :message %>
Finally, call decorate on messages in your controller action:
#messages = #group.messages.decorate
EDIT
You can also use a simple helper method rather than a decorator:
def css_class_for_message(message)
message.user_id == current_user.id ? "to" : "from"
end
the f.error_messages form builder helper has been depreciated in Rails 3.
How do I get the error messages for a form with the opening tag:
<%= form_for Model.new %>
(I'm using Model.new because I want to be able to load an undefined multiple number of these forms onto a single page)
You can't. Not with a form builder like this one.
Basically, error messages are stored inside the object you use to build a form. If you build a new one every time, you get a clean object without errors.
What you need to do is persist the object filled in by the user between requests. Typically this is done by creating a new object in controller:
#model = Model.new
The essence of this is, that new view uses #model to render a form. And the trick is to have a possibility to render the same view in other actions that also provide #model. That said, if you do something like this in create:
#model = Model.new(model_params)
if #model.save
# success
else
render :new
end
It can render new view, because it assigns #model too; in this case, it will contain errors with messages and other stuff. All this is inside #model.errors – which is always empty in new action.
It's not that different for multiple forms, bear in mind that you always submit only one. You may switch to rendering an array of forms, in that case you could have #models array:
#models = [form1, form2, form3]
In that case, if saving fails, assign that array again and either replace the form the user tried to fill in (if you can identify it), ot prepend/append that form with errors to that array.
#models[index_of_submitted_form] = form_from_user #replace
#models << form_from_user # append
#models.unshift form_from_user # prepend
In Rails 3, you can access errors using #model.errors
<%= form_for #model = Model.new do |f| %>
<% #model.errors.full_messages.each do |msg| %>
<p><%= msg %></p>
<% end %>
# ...
# ...
# ...
<% end %>
I have a form in a view of my project and
Im doing
redirect_to root_path(#locale), alert: #client.errors
and getting the errors in my view iterating with
flash[:alert].full_messages.each
but when there are a lot of errors rails launch the error
ActionDispatch::Cookies::CookieOverflow
what is the correct way to pass a lot of error descriptions in RoR?
Normally, the alert is a set length string - not something that can grow this long.
render :new, alert: "There has been an error"
When you render you can look for the errors in an instance object - in this case #client.errors.
The rails way is something like this in the view...
<% if #client.errors %>
<ul>
<% #client.errors.full_messages.each do |m| %>
<li><%= m %></li>
<% end %>
</ul>
<% end %>
Forgive my erb - I've been writing haml too long.
The main thing to take away from this is to render instead of redirect. Usually the page that's rendered is the same one that had the form. So, in the create method, you render new on error. In the update method, you render edit.
Personally, I think that the most correct way to pass error is to use a redirect_to only if you pass the validation, and a render if you don't (a simple test on the value of #object.update_attributes(hash)).
This way, you can directly access your object errors from inside your view (through #object.errors) without loading anything in the user cookies.
In my app, I've got a little box that appears on every page, checking on the status of requests made by the user. If a request is accepted at any time, then the user should automatically be taken to a certain page. This is my code so far:
<% offersMade.each do |w| %>
<% if w.accepted == true %>
<% redirect_to offer_path(:email => "email#gmail.com") %>
<% end %>
<% end %>
But I'm getting this error:
undefined method `redirect_to' for #<ActionView::Base:0x1042a9770>
Is it not possible to user redirect_to in a view? If not, is there something else I can use? Thanks for reading.
redirect_to is a method of ActionController::Base Class so you can not use it in ActionView.
You can try following
<% if w.accepted == true %>
<script type="text/javascript">
window.location.href="/logins/sign_up" // put your correct path in a string here
</script>
<% end %>
Edited for the email parameter
window.location.href="/logins/sign_up?email=<%= w.email %>"
Sorry i don't know if there is anything in ruby for that.
If you want to use redirect_to from view , do this method:
syntax : <%controller.redirect_to path %>
Example:<% controller.redirect_to users_profile_path %>
The code in the view could be moved into the controller which would make redirect_to available.
class ApplicationController < ActionController::Base
before_action :check_for_accepted_offer
def check_for_accepted_offer
if Offer.any? { |o| o.accepted }
redirect_to offer_path(:email => "email#gmail.com")
end
end
end
If the OP wanted to immediately change the URL when an offer is accepted then none of the Ruby code shown in the answers would help because it is only evaluated when the page is loaded. The OP would need to setup some kind of polling or push strategy to alert the browser when an offer has been accepted and then use the JavaScript redirect scheme posted in another answer:
window.location.href="/logins/sign_up?email=<%= w.email %>"
Although, this does not answer the question: "How can I use redirect_to in a view", I think this answer would ultimately have been more useful to the OP. I stumbled across someone using this answer to redirect to another page, when the redirect should have been performed in the controller.
redirect_to is not a method of ActionView. Its a method of ActionController. You can probably use Javascript window.location.href on page load or some other event to take your user to another page.
Yes, you can call controller.redirect_to from your view to get what you want without having to render the whole response and then use javascript on the client to make a new request.
In your example, this would look like:
<% offersMade.each do |w| %>
<% if w.accepted == true %>
<% controller.redirect_to offer_path(:email => "email#gmail.com") %>
<% end %>
<% end %>
Note that this controller.redirect_to will not break out of your loop, so you will probably want to break, and you'll probably want to make sure that the rest of your view is only conditionally rendered if you didn't redirect.
(Disclaimer: I don't necessarily condone this technique. You would be better off doing this in your controller or helper, as others have mentioned.)