I have a code where I would need to exit (php version of die), if certain events occur
Snippet in trace_controller.rb
def show
rule=Rule.new
#order,#order_error=rule.get_order(#order_external_id)
#order_items, #order_items_error=rule.get_order_items(#order)
#order_item_units, #order_item_units_error=rule.get_order_item_units(#order_items)
#outbound_messages, #outbound_messages_error = rule.check_outbound_messages(#order_external_id)
#inbound_messages, #inbound_messages_error = rule.check_inbound_messages(#outbound_message)
......
In show.html.erb
<% unless #order_error.blank? %>
<%= #order_error.html_safe %>
<% else %>
<%= render "trace/display_tabular_data", :data => #order %>
<% end %>
.....
.....
<% unless #order_items_error.blank? %>
<%= #order_items_error.html_safe %>
<% else %>
<% #order_items.each do |order_item| %>
<h5>Order Item</h5>
<%= render "trace/display_tabular_data", :data => order_item %>
<% end %>
<% end %>
......
Most of my functions are dependent on outcome of previous functions. Now take get_order_items function which is dependent on orders. If the order does not exist, there is no need to calculate get_order function as it won't exist either. Additionally, it fires up an error, as it says I am passing it a NIL object when I perform operations on orders inside get_order_item.
Additionally in the show.html.erb - #order_items, and #order_items_error should not even exist if the order does not exist. I just wanna render the function till the order_error, and then stop.
Now, coming from PHP background, I forgot that rails does not have die. So is there an alternate of die? Abort isn't it. I need it to exit disgracefully. Or is my best shot using conditionals if, unless etc...But it will look ugly as the page will become full of them. How would you about it?
To cut off the current action and render the view, you can use return. This will crash with errors if you try to use an unitialized instance variable in the view though.
You can also use render nothing: true which stops the current action and renders nothing.
Perhaps consider redirecting back as well:
flash[:error] = 'There was no order!'
redirect_to :back
I am not sure what exactly you want to do. But this should meet with what you are asking for.
def show
rule=Rule.new
#order,#order_error=rule.get_order(#order_external_id)
unless #order.blank?
#order_items, #order_items_error=rule.get_order_items(#order)
#order_item_units, #order_item_units_error=rule.get_order_item_units(#order_items) unless #order_items.blank?
end
#outbound_messages, #outbound_messages_error = rule.check_outbound_messages(#order_external_id)
#inbound_messages, #inbound_messages_error = rule.check_inbound_messages(#outbound_message) unless #outbound_messages.blank?
end
Similarly, you can add conditions in your view file:
<% unless #order.blank? %>
<% unless #order_error.blank? %>
<%= #order_error.html_safe %>
<% else %>
<%= render "trace/display_tabular_data", :data => #order %>
<% end %>
.....
.....
<% unless #order_items.blank? %>
<% unless #order_items_error.blank? %>
<%= #order_items_error.html_safe %>
<% else %>
<% #order_items.each do |order_item| %>
<h5>Order Item</h5>
<%= render "trace/display_tabular_data", :data => order_item %>
<% end %>
<% end %>
<% end %>
.......
.......
<% end %>
It is just a basic idea what I get from your example, though it was not clear enough to show what you want to achieve. So, whole idea is to check the variable before using it!
One more point I would like to mention, avoid using unless - else, unless should be used alone, if you need to put some logic in else block, why not use if - else. That probably makes more sense than unless - else.
Related
I'm trying to create a helper method that will display {user.name} has no submitted posts." on the profile show view of user if they haven't yet submitted any posts and display the number posts they have . currently on my show view i have <%= render #user.posts %> which displays nothing when there are 0 posts submitted.
the partial for post is :
<div class="media">
<%= render partial: 'votes/voter', locals: { post: post } %>
<div class="media-body">
<h4 class="media-heading">
<%= link_to post.title, topic_post_path(post.topic, post) %>
<%= render partial: "labels/list", locals: { labels: post.labels } %>
</h4>
<small>
submitted <%= time_ago_in_words(post.created_at) %> ago by <%= post.user.name %> <br>
<%= post.comments.count %> Comments
</small>
</div>
</div>
ive tried :
def no_post_submitted?(user)
user.post.count(0)
"{user.name} has not submitted any posts yet."
end
on my user show view :
<%= if no_post_submitted?(#user) %>
<%= render #user.posts %>
which im more than sure is wrong but i have no idea how to implement this method .
Where you are using render #user.posts you can just add a simple conditional:
<% if #user.posts.empty? %>
<p><%= #user.name %> has no submitted posts</p>
<% else %>
<%= render #user.posts %>
<% end %>
There wouldn't be much point creating a helper for this unless you need to use it in multiple places.
Render collection returns nil if the collection is empty so you can use the || operator:
<%= render #user.posts || "{#user.name} has not submitted any posts yet." %>
Or if there is more code render another partial:
<%= render #user.posts || render 'no_posts' %>
In Ruby methods automatically return the last value so this method:
def no_post_submitted?(user)
user.post.count(0)
"{user.name} has not submitted any posts yet."
end
Will always return a string - if you use a string literal in a condition it will be evaluated as true with the warning warning: string literal in condition. Also that is not how you use count - passing 0 will cause it to query on column 0 or just error.
http://apidock.com/rails/ActiveRecord/Calculations/ClassMethods/count
So to fix the method you would do:
def no_post_submitted?(user)
user.posts.empty?
end
However that conditional is so simple that it does not really warrant a helper method. Instead you would just write:
<%= if user.post.any? %>
<%= render #user.posts %>
<% else %>
<%= "{user.name} has not submitted any posts yet." %>
<% end %>
There are a couple of problems with your solution. Remember, rails is more about convention over configuration.
Your method no_post_submitted? should actually return true/false since its a method ending with ?. Also it should be named no_posts_submitted? for clarity. It should look something like this:
def no_post_submitted?(user)
user.posts.count > 0
end
Then, there should be another helper method that will print your required message, Something like:
def no_posts_message(user)
"{user.name} has not submitted any posts yet."
end
And eventually you can all plug it in like this:
<% if no_posts_submitted?(user) %>
<%= no_posts_message(user) %>
<% else>
<%= render #user.posts %>
<% end %>
As per the docs:
In the event that the collection is empty, render will return nil, so it should be fairly simple to provide alternative content.
<h1>Products</h1>
<%= render(#products) || "There are no products available." %>
--
So...
<%= render(#user.posts) || "#{#user.name} has not submitted any posts yet." %>
Say I have an instance variable #n, and I'm calling <%= #n.title %> in my view.
If #n equals a valid record, then this will print normally. But if #n is blank or invalid, then the entire page will show an error message, because of this one little line.
Is there a way to get #n.title to just print nil if #n is nil or invalid?
I'm looking for a way to do this without conditional statements. For example, if I wanted to print
<%= #v1.title %>,<%= #v2.title %>,<%= #v3.title %>,<%= #v4.title %>,
if I wanted to use conditionals to print without errors, it would require 12 lines of code:
<% if #v1 %>
<%= #v1.title %>,
<% end %>
<% if #v2 %>
<%= #v2.title %>,
<% end %>
<% elsif #v3 %>
<%= #v3.title %>,
<% end %>
<% elsif #v4 %>
<%= #v4.title %>,
<% end %>
It seems a shame to use 12 lines on this. It would be nice to be able to accomplish the error-handling right when printing.
You can totally do this easily with the try() method. I use it all the time.
<%= #n.try( :title ) %>
That will return nil if #n is nil or if the title method doesn't exist on #n.
You can also chain them together like this:
#n.try( :title ).try( :to_s )
Or even use it on a hash:
#n.try( :[], 'name' ) # Which is the same as #n['name']
See http://api.rubyonrails.org/classes/Object.html#method-i-try
EDIT (Jan 11, 2016)
You can now use the "safe navigation operator" as of Ruby 2.3.0.
#n&.title&.to_s
As well as the Array#dig and Hash#dig methods introduced in Ruby 2.3.0.
hash = { 'name' => 'bob' }
hash.dig( 'name' ) # Which is the safe way to do hash['name']
You can add some logic to your view that differentiates between development (where some errors can be ignored) and production environments (where errors should cause your app to fail in an obvious and ugly manner). Ruby's nil has a "falsey" nature, so you can use that concept to your benefit as well.
<% if Rails.env.development? %>
<% if #n %>
<%= #n.title %>
<% else %>
<%= nil %>
<% end %>
<% else %>
<%= #n.title %>
<% end %>
I have this condition to show the correct nav bar:
<% if current_user %>
<% if current_user.quality? %>
<%= render 'shared/quality_nav' %>
<% else %>
<%= render 'shared/nav' %>
<% end %>
<% end %>
On my production server everything is fine. On my staging server, I see the quality nav bar for 1 second before the proper nav bar appears - I've never seen this happen before, what could be wrong?
EDIT
how quality is defined:
def quality?
self.role == 'Quality' ? true : false
end
My only possible guess is the staging server is 1gb and no fast enough, but I find that odd. Changing the code to this makes it go away, but I would still like to know why the original code caused that behaviour.
<% if current_user && current_user.quality? %>
<%= render 'shared/quality_nav' %>
<% elsif current_user %>
<%= render 'shared/nav' %>
<% end %>
I want to make some styling changes if user has voted for a photo, and I used this code (acts_as_votable docs):
<% if current_user.voted_for? #photo %>
<%= link_to like_photo_path(#photo), method: :put do %>
<button>
¡Liked!
</button>
<% end %>
<% else %>
You dont like it yet
<% end %>
But this wont work because it will show "Liked" all the time, even if I didn't click the Like button.
photos controller
def upvote
#photo = Photo.friendly.find(params[:id])
#photo.liked_by current_user
redirect_to user_photo_path(#photo.user,#photo)
end
What can it be wrong?
Add an additional condition in your if statement
<% if current_user.voted_for? #photo && #photo.liked_by current_user %>
# different text
<% elsif current_user.voted_for? #photo %>
<%= link_to like_photo_path(#photo), method: :put do %>
<button>
¡Liked!
</button>
<% end %>
<% else %>
You dont like it yet
<% end %>
This is a pretty common design pattern of basically falling through to the next logical default.
Note that if you find yourself nesting "if" statements, like so
if condition_one
if condition_two
if condition_three
# do something
else
# do something else
end
This is the same as
if condition_one && condition_two && condition_three
# do something
else
# do something else
end
If you find yourself falling into the nested ifs pattern then rethink what you're doing. You may need to decompose the code into a helper method, etc.
So I have an interesting problem I'm working on. I am trying to create multiple objects of the same model in one view. I would like to display all the possible objects in my view, check boxes to select which ones to create, then submit and create all the corresponding objects.
Now the objects to select are gotten using an API request and returned in JSON format. The JSON is then displayed on the view for the user to select, then an array containing all the selected objects is sent back to the controller for creation.
Here is the relevant code that I've tried so far.
objects_controller.rb
def new
#possible_objects = <api call to get objs>
#objects = []
end
def create
params[:objects].each do |obj|
# create and save obj
end
end
objects/new.html.erb
<% form_for #objects do |f| %>
<% #possible_objects.each do |api_obj| %>
<%= check_box_tag(api_obj["name"])%>
<%= api_obj["name"] %>
<% end %>
<%= f.submit %>
<% end %>
This is definitely not the right approach, as the form will not accept an empty array as a parameter. I'm not sure where else to go with this, any pointers in the right direction would be great. Thanks.
Thanks to MrYoshiji for pointing me in the right direction, this is what ended up working
objects_controller.rb
def
#possible_objects = <api call to get objs>
end
def create
params[:objects].each do |object|
new_obj = Object_Model.new( <params> )
new_obj.save
if !new_obj.save
redirect_to <path>, alert: new_obj.errors.full_messages and return
end
end
redirect_to <path>, notice: 'Successfully created.'
end
objects/new.html.erb
<%= form_tag objects_path(method: :post) do %>
<% #possible_objects.each do |api_obj| %>
<%= check_box_tag 'objects[]', api_obj %>
<%= possible_object["name"] %>
<% end %>
<%= submit_tag 'Create'%>
<% end %>
Can you try the following?
# view
<% form_tag my_objects_path(method: :post) do |f| %>
<% #possible_objects.each do |api_obj| %>
<%= check_box_tag 'objects[names][]', api_obj["name"] %>
<%= api_obj["name"] %>
<% end %>
<%= f.submit %>
<% end %>
# controller
def create
params[:objects][:names].each do |obj_name|
YourModelForObject.create(name: obj_name)
end
end
See this comment on the documentation of check_box_tag: http://apidock.com/rails/ActionView/Helpers/FormTagHelper/check_box_tag#64-Pass-id-collections-with-check-box-tags