Rails 3 - Refactor ruby conditions - ruby-on-rails

I'd like to know if there is a simpler way to do these 2 conditions in ruby :
if params[:action] == 'index' || params[:action] == 'show'
and
if !(comment = (session[:my_params].include?(:comment) rescue nil)).nil?
Thanks in advance

For the first one, you could do:
if %w(index show).include?(params[:action])
The second should really be re-factored into two lines: assignment within a condition check is a code smell; there's never a reason for it.
If you're using Rails / ActiveSupport, you can take advantage of Object#try
comment = session[:my_params].try(:include?, :comment)
if comment
# ... comment is in scope
end
Otherwise, you're left with something slightly clunkier:
comment = session[:my_params].include?(:comment) rescue nil
if comment
# etc

1:
if ['index','show'].include? params[:action]
2:
if (comment = (session[:my_params].include?(:comment) rescue nil))
! and .nil? in second condition are redundant
But, really, you should not try to make everything as short as possible, the first thing to care about is how clear your code would be for other people. The second condition should look like:
if ( comment = (session[:my_params] && session[:my_params].include?(:comment) )
or even
comment = session[:my_params] && session[:my_params].include?(:comment)
if comment

First one can be refractored like this:
if ['index', 'show'].include? params[:action]
or
if %w(index show).include? params[:action]

This should be faster than using an array and include?:
case params[:action]; when 'index', 'show'
...
end
The second one:
if comment = (session.fetch(:my_params, {}).include?(:comment))

Related

Rails: if record exists then use this record

How do I use the result of an if condition in Rails? Something like:
if #edits.where(:article_id => a.id).first
THIS.body.html_safe
else
a.body.html_safe
end
How on earth do I access the result of that condition? That being the THIS record?
Thanks!
You can do the assignment within the if statement.
if edit = #edits.where(:article_id => a.id).first
edit.body.html_safe
else
a.body.html_safe
end
You could write in one line:
(#edits.where(:article_id => a.id).first || a).body.html_safe
Putting such logic in view or helper is very ugly. It's not View's job to judge these.
Better alternative:
# Article model
def default_edit
edits.first
end
# Articles Controller
def show
article = Article.find(params[:article])
#article = article.default_edit || article
end
# view: no need to do anything, just plain obj
<%= #article.body %>
You can use find_by or find_by_* to avoid the nasty .where().first
if edit = Edit.find_by(article_id: article.id)
edit.body.html_safe
else
article.body.html_safe
end

Check if record exists from controller in Rails

In my app a User can create a Business. When they trigger the index action in my BusinessesController I want to check if a Business is related to the current_user.id:
If yes: display the business.
If no: redirect to the new action.
I was trying to use this:
if Business.where(:user_id => current_user.id) == nil
# no business found
end
But it always returns true even when the business doesn't exist...
How can I test if a record exists in my database?
Why your code does not work?
The where method returns an ActiveRecord::Relation object (acts like an array which contains the results of the where), it can be empty but it will never be nil.
Business.where(id: -1)
#=> returns an empty ActiveRecord::Relation ( similar to an array )
Business.where(id: -1).nil? # ( similar to == nil? )
#=> returns false
Business.where(id: -1).empty? # test if the array is empty ( similar to .blank? )
#=> returns true
How to test if at least one record exists?
Option 1: Using .exists?
if Business.exists?(user_id: current_user.id)
# same as Business.where(user_id: current_user.id).exists?
# ...
else
# ...
end
Option 2: Using .present? (or .blank?, the opposite of .present?)
if Business.where(:user_id => current_user.id).present?
# less efficiant than using .exists? (see generated SQL for .exists? vs .present?)
else
# ...
end
Option 3: Variable assignment in the if statement
if business = Business.where(:user_id => current_user.id).first
business.do_some_stuff
else
# do something else
end
This option can be considered a code smell by some linters (Rubocop for example).
Option 3b: Variable assignment
business = Business.where(user_id: current_user.id).first
if business
# ...
else
# ...
end
You can also use .find_by_user_id(current_user.id) instead of .where(...).first
Best option:
If you don't use the Business object(s): Option 1
If you need to use the Business object(s): Option 3
In this case I like to use the exists? method provided by ActiveRecord:
Business.exists? user_id: current_user.id
with 'exists?':
Business.exists? user_id: current_user.id #=> 1 or nil
with 'any?':
Business.where(:user_id => current_user.id).any? #=> true or false
If you use something with .where, be sure to avoid trouble with scopes and better use
.unscoped
Business.unscoped.where(:user_id => current_user.id).any?
ActiveRecord#where will return an ActiveRecord::Relation object (which will never be nil). Try using .empty? on the relation to test if it will return any records.
When you call Business.where(:user_id => current_user.id) you will get an array. This Array may have no objects or one or many objects in it, but it won't be null. Thus the check == nil will never be true.
You can try the following:
if Business.where(:user_id => current_user.id).count == 0
So you check the number of elements in the array and compare them to zero.
or you can try:
if Business.find_by_user_id(current_user.id).nil?
this will return one or nil.
business = Business.where(:user_id => current_user.id).first
if business.nil?
# no business found
else
# business.ceo = "me"
end
I would do it this way if you needed an instance variable of the object to work with:
if #business = Business.where(:user_id => current_user.id).first
#Do stuff
else
#Do stuff
end
Something new to try (:
Assign a variable or return
return unless #business = Business.where(user_id: current_user.id).first
Method would exit at this point if there are no businesses found with current user's ID, or assigns instance variable #business to the first business object.

Is there a more ruby way of doing this

Ok so i have this helper
def current_company_title
(Company.find_by_id(params["company_id"]).name rescue nil) || (#companies.first.name rescue nil) current_user.company.name
end
Basically what I am achieving with this is the following ...
If the param["company_id"] exists then try to get the company and if not then
if #companies exists grab the first company name and if not then get the current users company name
This works but the rescues seem like a hack...any idea on another way to achieve this
Indeed rescue is kind of a hack, id' probably split it up into two methods and then use try to fetch the name if available: http://api.rubyonrails.org/classes/Object.html#method-i-try
def current_company
#current_company ||= Company.find_by_id(params[:company_id]) || #companies.try(:first) || current_user.try(:company)
end
def current_company_name
current_company.try(:name)
end
Company.find_by_id(params["company_id"]).name`
find and its derivates are meant to be used when you're sure-ish you'll have a positive result, and only in some cases (row was deleted, etc) errors. That's why it raises an exception. In your case, you're assuming it's gonna fail, so a regular where, which would return nil if no rows was found, would do better, and remove the first rescue
#companies.first.name rescue nil
could be replaced by
#companies.first.try(:name)
I'll let you check the api for more on the topic of try. It's not regular ruby, it's a Rails addition.
Less "magic", simple code, simple to read:
def current_company_title
company = Company.where(id: params["company_id"]).presence
company ||= #companies.try(:first)
company ||= current_user.company
company.name
end
Ps. Not a big fan of Rails' try method, but it solves the problem.
def current_company_title
if params["company_id"]
return Company.find_by_id(params["company_id"]).name
elsif #companies
return #companies.first.name
else
return current_user.company.name
end
end
The rescues are a hack, and will obscure other errors if they occur.
Try this:
(Company.find_by_id(params["company_id"].name if Company.exists?(params["company_id"]) ||
(#companies.first.name if #companies && #companies.first) ||
current_user.company.name
then you can extract each of the bracketed conditions to their own methods to make it more readable, and easier to tweak the conditions:
company_name_from_id(params["company_id"]) || name_from_first_in_collection(#companies) || current_user_company_name
def company_name_from_id(company_id)
company=Company.find_by_id(company_id)
company.name if company
end
def name_from_first_in_collection(companies)
companies.first.name if companies && companies.first
end
def current_user_company_name
current_user.company.name if current_user.company
end
[Company.find_by_id(params["company_id"]),
#companies.to_a.first,
current_user.company
].compact.first.name

Rails redirect_to not working

I have a filter_before filter that checks to see if a param is true. If it is my app does this. However, the && return seems to be causing some problems. When I take it out, it'll redirect form every page to the desired countdown page--but then it loops or double renders.
redirect_to :countdown && return if #online == 1
Is there a way to wrap this in an if statement?
Something like:
if current_page(:countdown)
redirect_to :countdown if #online == 1
end
You'd better exclude the countdown action from your before_filter to avoid infinite loop.
something like:
before_filter :check_countdown, :except => :countdown
That said, what you did is valid:
redirect_to :countdown and return if #online == 1
You need to use and instead of &&.
From the Rails Guide:
Make sure you use and return and not && return because while the
former will work, the latter will not due to operator precedence in
the Ruby Language.
http://guides.rubyonrails.org/layouts_and_rendering.html
It might be work for you
return redirect_to :countdown if #online == 1

Ruby on Rails: how do I set a variable where the variable being changed can change?

i want to do
current_user.allow_????? = true
where ????? could be whatever I wanted it to be
I've seen it done before.. just don't remember where, or what the thing is called.
foo = "bar"
current_user.send("allow_#{foo}=", true)
EDIT:
what you're asking for in the comment is another thing. If you want to grab a constant, you should use for instance
role = "admin"
User.const_get(role)
That's a "magic method" and you implement the method_missing on your current_user object. Example from Design Patterns
#example method passed into computer builder class
builder.add_dvd_and_harddisk
#or
builder.add_turbo_and_dvd_dvd_and_harddisk
def method_missing(name, *args)
words = name.to_s.split("_")
return super(name, *args) unless words.shift == 'add'
words.each do |word|
#next is same as continue in for loop in C#
next if word == 'and'
#each of the following method calls are a part of the builder class
add_cd if word == 'cd'
add_dvd if word == 'dvd'
add_hard_disk(100000) if word == 'harddisk'
turbo if word == 'turbo'
end
end

Resources