Why does this always return true? Rails - ruby-on-rails

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.

Related

Checking if a Spotify user follows an artist

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

Rails - Always have to check exists, and then value?

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

Rail3 'Return False Unless XYZ' Query Not Working

In my rails3.1 application, I'm trying to apply the following logic in one of my order model.
def digital?
line_items.map { |line_item| return false unless line_item.variant_id = '102586070' }
end
I've created a separate variant called prepaid_voucher which has id = 102586070. Despite this, the result is false...
Order has many line_items
LineItem belongs to order and variant
Variant has many line_items
Is this the best way to perform such a task and how can I fix?
First of all I think you want a double == here line_item.variant_id = '102586070', then I rather go for something like that (If I understand what you want)
def digital?
line_items.select{|line_item| line_item.variant_id == '102586070'}.any?
end
But it's hard to understand what you really want, what is the expected behavior if the id is not found?

Which is the best way to test if a model instance is "empty" in Ruby on Rails?

I want to implement a method that checks if a model's instance has only nil or empty attributes, except from its id or timestamps.
I've made use of an auxiliary method that removes a key from Hash and return the remaining hash ( question 6227600)
class ActiveRecord::Base
def blank?
self.attributes.remove("id","created_at","updated_at").reject{|attr| self[attr].blank?}.empty?
end
end
I guess that there may be much simpler, efficient or safer way to do this. Any suggestion?
def blank?
self.attributes.all?{|k,v| v.blank? || %w(id created_at updated_at).include?(k)}
end
My response is almost the same that tadman gave, but expressed in a more concise way.
Be careful with two situations:
- **blank?** is not a good choice as name, since if you call **object_a.object_b.blank?** trying to know if there is or not a object_b inside object_a, you'll get true event if the object exists. **empty?** seems a better name
- If databases sets defaults values, it can be tricky.
EDIT: Since build an array every iteration is slow (thanks tadman), a beter solution is:
def empty?
ignored_attrs = {'id' => 1, 'created_at' => 1, 'updated_at' => 1}
self.attributes.all?{|k,v| v.blank? || ignored_attrs[k]}
end
You could just check that all the properties in the attributes hash are not present, or the converse:
class ActiveRecord::Base
def blank?
!self.attributes.find do |key, value|
case (key)
when 'id', 'created_at', 'updated_at'
false
else
value.present?
end
end
end
end
Unfortunately this will not account for things that are set with a default in your database, if any relationship keys are assigned, among other things. You will have to add those as exceptions, or compare the values to a known default state of some sort.
This sort of thing is probably best implemented on a case by case basis.

How do I find the .max of an attribute value among a group of different Models?

everyone: I am also open to just straight-up refactoring what I'm finding to be pretty repetitive, but to give a baseline of how it's working....
I have for every contact a Campaign, which has_many of three types of Models: Email, Call, and Letter.
When an Email (Call or Letter) has been executed for a specific contact, I have a Contact_Email(_or_Call_or_Letter) which belongs to both the Contact and the Model (Email_or_Call_or_Letter).
Each Contact_Email for example pairing has a :date_sent attribute. So does each Contact_Call and Contact_Letter.
How do I find the latest of all of them?
Here is the code I wrote that can find the latest Email and my finding retyping similar code for Call and Letter, but then stuck on how to do a .max on all of them:
def last_email(contact)
#get campaign the contact belongs to
#campaign = Campaign.find_by_id(contact.campaign_id)
#last_email = ContactEmail.find(:last,
:conditions => "contact_id = #{contact.id}",
:order => "date_sent DESC")
#last_call = ContactCall.find(:last,
:conditions => "contact_id = #{contact.id}",
:order => "date_sent DESC")
#last_letter = ContactLetter.find(:last,
:conditions => "contact_id = #{contact.id}",
:order => "date_sent DESC")
# how do I get the latest of all of these to display?
#email_template = Email.find_by_id(#last_email.email_id)
if #last_email.nil?
return "no email sent"
else
return #last_email.date_sent.to_s(:long) + link_to('email was sent', #email_template)
end
end
Question 1: With what I have, how can I find effectively #last_event given I can find the last Email, last Call, and last Letter for every contact?
Question 2: How can I remove the repetitive code that I have to write for each Model?
Do you have has_many associations setup in Contact referring to the other models? Something like:
class Contact < ActiveRecord::Base
has_many :contact_emails
has_many :contact_calls
has_many :contact_letters
end
If so, you can then create a last_event method on the Contact model:
def latest_event
[contact_emails, contact_calls, contact_letters].map do |assoc|
assoc.first(:order => 'date_sent DESC')
end.compact.sort_by { |e| e.date_sent }.last
end
Handling nil
When using the latest_event method you will get nil if there are no associated records. There are a couple of ways you can workaround this. The first is to check for nil first with something like:
contact.latest_event && contact.latest_event.date_sent
On late versions of Rails/Ruby you can also use Object#try which will call the method if it exists:
contact.latest_event.try(:date_sent)
I prefer not to use this as it doesn't check for nil but only if the object can respond to a method. This has cause some interesting errors if you expect nil if the object is nil but are calling a method which nil itself responds to.
Finally, my preferred method for the simple case is to use the andand gem which provides Object#andand. This greatly shortens the safe case above and saves calling of latest_event multiple times:
contact.latest_event.andand.date_sent
date_sent, nil and You.
For your example usage of calling to_s(:long), you could either use && or andand:
contact.latest_event.andand.date_sent.andand.to_s(:long)
or
contact.latest_event && contact.latest_event.date_sent.to_s(:long)
The first is safer if date_sent itself may be nil. Without using andand this could be written as:
contact.latest_event &&
contact.latest_event.date_sent &&
contact.latest_event.date_sent.to_s(:long)
which is rather complex and unwieldily in my opinion. I would recommend looking into andand
For question 1:
Just do
#last_event = [#last_letter, #last_email, #last_call].sort_by{|m| m.date_sent}.first
For question 2:
Well this is more interesting. This kind of depends on how exactly do your models look, but you might want to consider Single Table Inheritance for this type of scenario.

Resources