Rails: if record exists then use this record - ruby-on-rails

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

Related

How to modify params before creating an object

I'm using nested attributes to create a Photo and a Comment object. I would like to set the author on the comment, which is nested inside the photo.
Here are the params:
photo: {
file: 'hi.jpg',
comments_params: [
{ content: "hello world!" }
]
}
But I would like to add the author to the comment.
# ...
comments_params: [
{ content: "hello world!", author: current_user }
]
# ...
What's easiest way to do this? My controller code looks like this.
#photo = Photo.new(photo_params)
#photo.save!
private
def photo_params
params.require(:photo).permit(:file, comments_attributes: [:content])
end
I can do it by manipulating the params after filtering them with strong_parameters (pseudo-code, but the idea stands), but I would rather not.
photo_params[:comments_attributes].each do |comment|
comment[:author] = current_user
end
But this feels a bit wrong.
Instead of messing with params, you could assign author to now-existing objects:
#photo = Photo.new(photo_params)
#photo.comments.select(&:new_record?).each {|c| c.author = current_user }
#photo.save!
I don't think there's anything wrong with the way you'd rather not do it.
You could also perhaps use standard Hash#merge or merge!, or ActiveSupport's deep_merge or deep_merge! in some way.
The fact that comments is an array of potentially many makes it hard to do that nicely though.
I think I would make a copy of the original params rather than editing them in place -- is that what seems wrong to you? ActiveSupport's deep_dup may be be helpful.
How about something like:
photo_params = photo_params.deep_dup
photo_params[:comments_attributes] = photo_params[:comments_attributes].collect {|c| c.merge(:author => :current_user)}
#photo = Photo.new(photo_params)
...
I'm not sure if that is really any better. But maybe it gives you an idea of some of the tools at your disposal.
You can add a hidden field to your comment form.
<%= f.hidden_field :user_id, :value => current_user.id %>

How to add exception handling to my before_action

I have a before_action method like this:
def current_user
#current_user ||= User.find(:id => session[:id])
end
And I call a method like this:
def get_food user
food = Food.find(:id => user.id)
end
This is fine, but I want to add exception handling.
When the user is nil I want to use #current_user:
def get_food user
food = Food.find(if user is nil i want to use #current_user.id)
end
Of course, I can write it like this:
def get_food user
if user.nil?
food = Food.find(#current_user.id)
else
food = Food.find(user.id)
end
Or, is this the best way?
def get_food user
food = Food.find(user == nil? #current_user.id : user.id)
end
I'm curious is there a better way than adding a simple if statement inside the param?
The shortest one lines I can think of are something like this:
Food.find((user || current_user).id)
Food.find(user.try(:id) || current_user.id)
Food.find(user ? user.id : current_user.id)
Not sure if this is really an impovement in readability. I would prefer something like this:
def get_food(user)
user ||= current_user
Food.find(user.id)
end
You can use ternary operator to make it one line:
user ? Food.find(user.id) : Food.find(#current_user.id)
How about arrays
food = Food.where(id: [#current_user.try(:id),user.id]).first
You can try this:
food = Food.find(user.nil? ? #current_user.id : user.id)
What about default parameters?
def get_food(user = #current_user)
food = Food.find(user.id)
end
It will work if you call it without the parameter
something.get_food # notice the method is called with no params
If you want it working also if you pass nil, you should also add:
def get_food(user = #current_user)
food = Food.find((user || #current_user).id)
end
However is strange that foods and users have the same ids...
Maybe the correct query is:
food = Food.find_by_user_id((user || #current_user).id)
or, if users have more than just one food:
foods = Food.where(user: (user || #current_user)) # rails 4, :user => (user || #current_user) for rails 3
Food.find(user.id rescue #current_user.id)

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 3 - Refactor ruby conditions

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))

Rails 3 displaying tasks from partials

My Tasks belongs to different models but are always assigned to a company and/or a user. I am trying to narrow what gets displayed by grouping them by there due_at date without doing to many queries.
Have a application helper
def current_tasks
if user_signed_in? && !current_company.blank?
#tasks = Task.where("assigned_company = ? OR assigned_to = ?", current_company, current_user)
#current_tasks = #tasks
else
#current_tasks = nil
end
end
Then in my Main view I have
<%= render :partial => "common/tasks_show", :locals => { :tasks => current_tasks }%>
My problem is that in my task class I have what you see below. I have the same as a scope just named due_today. when I try current_tasks.due_today it works if I try current_tasks.select_due_today I get a undefined method "select_due_tomorrow" for #<ActiveRecord::Relation:0x66a7ee8>
def select_due_today
self.to_a.select{|task|task.due_at < Time.now.midnight || !task.due_at.blank?}
end
If you want to call current_tasks.select_due_today then it'll have to be a class method, something like this (translating your Ruby into SQL):
def self.select_due_today
select( 'due_at < ? OR due_at IS NOT NULL', Time.now.midnight )
end
Or, you could have pretty much the same thing as a scope - but put it in a lambda so that Time.now.midnight is called when you call the scope, not when you define it.
[edited to switch IS NULL to IS NOT NULL - this mirrors the Ruby in the question, but makes no sense because it will negate the left of the ORs meaning]

Resources