I am checking to see if a value in a related table exists quite often in my view, in this case, expenses have approvals. If they have not been submitted, then there is no record of them in the approvals table.
I am ending up with some really awkward code
<% if !expense_item.expense_approval || expense_item.expense_approval.approval_status == 0 %>
Is there a way to do an if statement on a value, without having to check if it exists first? Or some default way to set it nil?
You could do the following in your ExpenseItem model:
delegate :approval_status, :to => :expense_approval, :allow_nil => true
This should allow you to simply do the following:
if expense_item.approval_status == 0
The try method above certainly works, but I like delegation better because I don't have to have .try(...) all over my code.
you can use try method which will return nil if method passed doesn't exist. so you could do something like:
expense_item.expense_approval.try(:approval_status) == 0
Related
I am creating a rails app that checks whether a user follows an artist on spotify. I have the following code to check if this is the case.
def personalise(acts)
acts_with_rank = acts.collect{|act|{:value => rank(act), :label => act.name}}
end
def rank(act)
spotify_user = RSpotify::User.new(request.env['omniauth.auth'])
artist = RSpotify::Artist.search(act.name).first
binding.remote_pry
if spotify_user.follows?(artist)
10
else
0
end
end
The problem is, every act ends up with 10 as its value in the hash, regardless of whether or not the user is actually following the artist. I am using remote-pry to check whether or not true or false is returned for each iteration of the if statement, and although it is correctly returning true or false depending on whether or not the user is following the artist, something else seems to make the if statements return 0. Any help would be appreciated on this as I'm sure I'm just looking at this for too long and can't see something stupid that I've done!
Just figured out what was going wrong
spotify_user.follows?(artist)
returns an array of booleans, to access the boolean of interest here, the simple fix was:
if spotify_user.follows?(artist)[0]
10
else
0
end
Say I have a User object, which has an email property, and I need the upper cased last letter of their email:
u = User.find(1)
letter = u.email.upcase.last
If u or email is nil in this chain, then I get a NoMethodError: undefined method 'blah' for nil:Nilclass. I should be able to work around it in most cases, but sometimes, a nil gets where it shouldn't or its hard to contain. One way would be verbose:
u = User.find(1)
letter = nil
if u && u.email
letter = u.email.upcase.last
end
But this gets annoying and hazardous in a view, or in a long chain of a.bunch.of.properties.down.a.hierarchy. I read about try method in Rails:
u = User.find(1)
letter = u.try(:email).try(:upcase).try(:last)
This is less verbose, but I feel icky writing all those tries. And once I put try in the chain, I have to use them all the way down. Is there a better way?
I like to use the Null Object Pattern. Avdi has a great post explaining this, but the basic idea is you have a little class that can stand in for an object and respond reasonably to the messages you might pass the original object. I've found these are useful not only for avoiding NoMethodErrors but also for setting default values/nice messages.
For instance, you could do:
class NilUser
def email
"(no email address)"
end
end
u = User.find(1) || NilUser.new
u.email.upcase.last # => No errors!
I just wanted to update this thread with one more option: Ruby now (as of 2.3) gives us a safe navigation operator, the &. syntax.
So:
u.email.upcase
Would become:
u.email&.upcase
Similarly to Rail's try method, the whole chain will return nil if it encounters NoMethodError on a nil.
User.find(1)
Will raise exception if user with id 1 not exist so you don't need to worry about nil here
u.email
If you have in your model
validates :email, presence: true
You don't need to worry about nil because User without email cant be in database
But I think you are asking about general way of handling nils in ruby code. Lately I'm using Null Object pattern
http://devblog.avdi.org/2011/05/30/null-objects-and-falsiness/
http://robots.thoughtbot.com/post/20907555103/rails-refactoring-example-introduce-null-object
Rails: replacing try with the Null Object Pattern
https://github.com/martinciu/nullobject
You could also map the result of find
[User.find(1)].map{|u| (u != nil ? u.mail : "no mail")}[0]
Here's a piece of code in my controller:
#post.increment!(:views_count) # => false, record not saved, views_count is 0
#post.errors # => is empty
#post.save! # => true, views_count magically incremented to 1
The problem is, that without #save! it doesn't really work: record is not updated, views_count is 0. Any ideas?
Have you tried reloading the record? I bet that you are looking at a cached version of the file. try:
#post.increment!(:views_count)
#post.reload
I don't think there is anything wrong with the way you are using the method.
The increment! returns false, when the record could not be saved.
I think one possible thing you could try would be to initialize the views_count attribute to 0. I think it might currently be nil instead of an integer value. And on nil value, you can't perform += operator.
To initialize value for an attribute, find the migration file for the attribute under db/migrate folder.
Modify the attribute code to something like this.
t.integer :views_count, default: 0
or if you added the column individually apart from table creation ...
add_column :appropriate_model, :views_count, :integer, default: 0
Note that increment! calls update_attribute, which in turn calls save(:validate => false). That skips validations, but the callback chain is still executed, and:
If any of the before_* callbacks return false the action is cancelled and save returns false.
Do you have any before_* callbacks defined in your model that could be returning false but not setting anything in errors?
I'd like to know if a Rails database query returns a row or not (I don't need to know whats in the row, just if a row is returned).
I can do this:
academic_year = AcademicYear.find_by_raw_input(year)
if academic_year
...
end
but if I mistype and do a find_all_by:
academic_year = AcademicYear.find_all_by_raw_input(year)
then an empty array is returned, which causes the if statement to be true.
I know, I should be careful and just avoid the all call, but is there a rails-esque call to see if the return result from any query (all or not) is nil?
As you said, find_by_... will return nil, and find_all_by_... will return []. I think what you're looking for is .blank?.
if !academic_year.blank?
#...
end
In console
> AcademicYear.find_by_raw_input(some_non_existent_year).blank?
=> true
> AcademicYear.find_all_by_raw_input(some_non_existent_year).blank?
=> true
def follows(follower, followed)
follow = Follows.where("follower = ? AND followed = ?", follower, followed)
if follow
true
else
false
end
end
Here is my view code:
<% if current_user.id == #user.id%>
<p>This is you!</p>
<% else %>
<% if follows(current_user.id, #user.id)%>
<p>You already follow <%= #user.username %>
<% else %>
<p><%= link_to "Follow!", follow_path(#user.id) %></p>
<% end %>
<% end %>
I want to check if a user follows another, so wrote this. It takes in two user ids, and queries the Database, and should return true when a match is found and false otherwise. But it always return true. Why is this?
Let's start with some style and design issues and end with the actual answer:
Models are singular by convention. Doing otherwise will only cause you more work. In this case, I would suggest Following as a suitable name, as in "a user has many followings".
Foreign keys should end with _id. Doing otherwise will only cause you more work. So follower_id and followed_id.
Methods that are intended to be used for their true/false nature ("query methods") should end with a ?, so follows? instead of follows,
Your if statement is redundant and can be safely removed once the condition does the right thing. In ruby, in the context of conditionals, we care more about whether things evaluate to true/false than whether they are literally true or false. This means that anything other than nil or false will be "truthy".
The fact that your method depends entirely on information known to User objects indicates that it would be better to hang it off of those objects, for instance current_user.follows? other_user.
You are duplicating behavior that would already be provided to you by using associations.
Finally, taking all of these things into consideration, the answer:
class User < ActiveRecord::Base
has_many :followings, :class_name => 'Following', :foreign_key => 'followed_id'
has_many :followers, :through => 'followings'
def follows?(other)
other.followed_by? self
end
def followed_by?(other)
followers.include? other
end
end
NB: The use of the followed_by? method here is a use of double dispatch that prevents the (minor) Law of Demeter violation of one user knowing directly about the state of another user's followers. Rather, the first user object asks the second user object a direct question ("Are you followed by me?") and bases the result off of the answer. (It is also likely to be a useful method in and of itself.)
The reason it always returns true is that, even when no records are found, where() is returning an empty array. An empty array is "true". In other news, the structure:
if (condition)
true
else
false
end
Can be replaced by:
condition
follow is actually an instance of ActiveRecord::Relation rather than the result set of your query. To work out find out if any rows would be returned by the query use follow.count. Eg.
if follow.count > 0
true
else
false
end
You can use present?. Your code should be
if follow.present?
true
else
false
end
#rein Heinrichs answer is superb. He gives you the best Rails way to solve it. But i would like to explain why what you wrote does not work, and how you should fix that.
Follows.where(...)
returns an array, the easy way to verify this yourself is to run that line in the rails console (type rails c in the console).
An array, even an empty one, is not nil and will always evaluate to true.
So to return a boolean depending on the fact whether or not any followers are found, just check for the amount of items inside the result of the where (use size > 0 or present?)
So your follows function could then have been rewritten as:
def follows(follower, followed)
Follows.where("follower = ? AND followed = ?", follower, followed).present?
end
and this is actually quite readable as well. Hope this helps.