How can I set "If" statement with nil? - ruby-on-rails

country attribute's default value is nil.
In countries table, some record has image_url, and the rest of the record's country attributes are nil.
So I coded this in helper
def image(user)
if user.country.image_url
image_tag "flags/#{user.country.image_url}.png"
end
end
However, it returns error when image_url was nil
Something went wrong
How can I fix?

You'll need two conditions: The user has to have a country, and that country has to have an image_url. Only then will there be something to show. Luckily, it's a simple tweak:
def image(user)
if(user.country && user.country.image_url)
image_tag "flags/#{user.country.image_url}.png"
end
end
If you're paranoid, you should make sure that user isn't nil either.
Hope that helps!

While method chaining like that certainly works, your code will look a lot cleaner and become less coupled if you implement some method delegation.
Inside of your User model:
class User < ActiveRecord::Base
belongs_to :country
delegate :image_url, :to => :country, :prefix => true, :allow_nil => true
end
Now your helper becomes simply:
def image(user)
if user.country_image_url
image_tag "flags/#{user.country_image_url}.png"
end
end
Law of Demeter states:
Each unit should have only limited knowledge about other units: only units "closely" related to the current unit.
Also check out Rail Best Practices Law of Demeter; if nothing else you're saving yourself the extra clause in your if statement & your code looks pretty.

Related

How to run validations of sub-class in Single Table Inheritance?

In my application, I have a class called Budget. The budget can be of many types.. For instance, let's say that there are two budgets: FlatRateBudget and HourlyRateBudget. Both inherit from the class Budget.
This is what I get so far:
class Budget < ActiveRecord::Base
validates_presence_of :price
end
class FlatRateBudget < Budget
end
class HourlyRateBudget < Budget
validates_presence_of :quantity
end
In the console, if I do:
b = HourlyRateBudget.new(:price => 10)
b.valid?
=> false
b.errors.full_messages
=> ["Quantity can't be blank"]
As, expected.
The problem is that the "type" field, on STI, comes from params.. So i need to do something like:
b = Budget.new(:type => "HourlyRateBudget", :price => 10)
b.valid?
=> true
Which means that rails is running validations in the super-class instead of instantiating the sub class after I set up the type.
I know that is the expected behaviour, since I'm instantiating a class that dosen't need the quantity field, but I wonder if there is anyway to tell rails to run the validations for the subclass instead of the super.
You could probably solve this with a custom validator, similar to the answer on this question: Two models, one STI and a Validation However, if you can simply instantiate the intended sub-type to begin with, you would avoid the need for a custom validator altogether in this case.
As you've noticed, setting the type field alone doesn't magically change an instance from one type to another. While ActiveRecord will use the type field to instantiate the proper class upon reading the object from the database, doing it the other way around (instantiating the superclass, then changing the type field manually) doesn't have the effect of changing the object's type while your app is running - it just doesn't work that way.
The custom validation method, on the other hand, could check the type field independently, instantiate a copy of the appropriate type (based on the value of the type field), and then run .valid? on that object, resulting in the validations on the sub-class being run in a way that appears to be dynamic, even though it's actually creating an instance of the appropriate sub-class in the process.
I've done something similar.
Adapting it to your problem:
class Budget < ActiveRecord::Base
validates_presence_of :price
validates_presence_of :quantity, if: :hourly_rate?
def hourly_rate?
self.class.name == 'HourlyRateBudget'
end
end
For anyone looking for example code, here's how I implemented the first answer:
validate :subclass_validations
def subclass_validations
# Typecast into subclass to check those validations
if self.class.descends_from_active_record?
subclass = self.becomes(self.type.classify.constantize)
self.errors.add(:base, "subclass validations are failing.") unless subclass.valid?
end
end
Instead of setting the type directly set the type like that... Instead, try:
new_type = params.fetch(:type)
class_type = case new_type
when "HourlyRateBudget"
HourlyRateBudget
when "FlatRateBudget"
FlatRateBudget
else
raise StandardError.new "unknown budget type: #{new_type}"
end
class_type.new(:price => 10)
You could even transform the string into its class by:
new_type.classify.constantize but if it's coming in from params, that seems a bit dangerous.
If you do this, then you'll get a class of HourlyRateBudget, otherwise it'll just be Budget.
Better yet, use type.constantize.new("10"), however this depends on that the type from params must be correct string identical to HourlyRateBudget.class.to_s
I also required the same and with the help of Bryce answer i did this:
class ActiveRecord::Base
validate :subclass_validations, :if => Proc.new{ is_sti_supported_table? }
def is_sti_supported_table?
self.class.columns_hash.include? (self.class.inheritance_column)
end
def subclass_validations
subclass = self.class.send(:compute_type, self.type)
unless subclass == self.class
subclass_obj= self.becomes(subclass)
self.errors.add(:base, subclass_obj.errors.full_messages.join(', ')) unless subclass_obj.valid?
end
end
end
Along the lines of #franzlorenzon's answer, but using duck typing to avoid referencing class type in the super class:
class Budget < ActiveRecord::Base
validates_presence_of :price
validates_presence_of :quantity, if: :hourly_rate?
def hourly_rate?
false
end
end
class HourlyRateBudget < Budget
def hourly_rate?
true
end
end

ruby/rails: what is the right way to refer to some_user.user_profile.name if user might not have a user_profile yet?

What is the correct way to refer to current_user.user_profile.name when there may or may not be a user_profile record for a particular user?
I have two tables, User (:email) and UserProfile (:name, :company).
User has_one user_profile, and UserProfile belongs_to User
A profile may or may not not exist. (It's optional for users)
In lot of my code and views I want to display the user's name and company, for example current_user.user_profile.name
Of course, if the user has not created their profile yet, current_user.user_profile is nil, so referencing current_user.user_profile.name throws an error.
I know how to do it the wrong way (check for nil before referencing the .name field EVERY time I need the name).
The best I can think of is create a User method called name that does the nil checking so current_user.name returns the name (or "" if there is no profile). But that feels wrong, too, since I have to write a method for every single field I add to user_profile.
I agree with the Law of Demeter answers, but Rails has a convenient way for your User class to delegate :name and other methods to its user_profile:
class User < ActiveRecord::Base
has_one :user_profile
delegate :name, :email, :phone, :height, :etc,
to: :user_profile, allow_nil: true, prefix: :profile
end
Now these:
user.profile_name
user.profile_email
user.profile_phone
will return nil if user.user_profile is nil, and otherwise return user.user_profile.name, user.user_profile.email, and user.user_profile.phone, respectively. But they hide that fact from callers, who only interact with User. You could leave off the prefix option if you'd rather have this:
user.name
user.email
user.phone
In general I'd shy away from strategies that involve exceptions or rescue, unless a nil user_profile is truly something that should not happen. And I'd hate to use andand or try because they push the responsibility of managing the user_profile to callers.
You could do something like this that should work with all attributes of the UserProfile:
# User.rb
def try_this(attribute)
self.user_profile ? self.user_profile.send(attribute) : "Not Available"
end
Then you'd just call
current_user.try_this(:name)
Edit
Dylan's try method also works:
def try_this(attribute)
self.user_profile.try(attribute) || "Not Available"
end
You could use try:
current_user.user_profile.try(:name) || "Not Available"
When you use rescue, there's a risk that you may be rescuing from a different type of error than NoMethodError as a result of user_profile being nil.
current_user.andand.user_profile.name || "Not Available"
The "andand" gem implements null-safe chaining.
As you suggest, creating a User.name method may be preferable (and Demeter would be proud).
You are correct. The best way, according to the Law of Demeter, is to add a method on User with this logic. Whatever needs the user's name shouldn't need to know about user_profile.
Each unit should have only limited knowledge about other units: only units "closely" related to the current unit.
So...
class User < ActiveRecord::Base
def user_name
user_profile ? user_profile.name : ""
end
[...]
end
current_user.user_profile.name rescue "Not Available"

How do I get Index-tank to do conditional indexing?

After a long conversation with the fine people at IndexTank, I was not really sure on how to fix my problem, and I was wondering if anyone could help me.
I have an article model that belongs to a user model. This article model also has a boolean attribute called anonymous, which, if set to true gives the user the option to post the article without his name being shown.
Article
belongs_to :user
attr_accesible :anonymous, :user_id
User
has_many :articles
My problem is that if the article is posted as anonymous. I don't want tanker to search within the author name field, but I want it searching every other field. I tried to do this with an if else statement where I would normally put the "tankit" block, but that does not work.
Is there a way I could put the tankit block into a model method and use a validation call back like this?
def anon_index
if self.anonymous
tankit 'my_index' do
indexes VARIABLES ETC BUT NOT the user_ attributes
end
else # if anonymous is false
tankit 'my_index' do
indexes :title
indexes :body
indexes :user_penname
indexes :user_firstname
indexes :user_lastname
end
end
end
I was thinking either this or putting an if else statement where the "tankit" block declaration goes, but neither of those seem to, unless I'm doing something wrong.
how does this look?:
class Article < ActiveRecord::Base
tankit 'my_index' do
indexes :title
indexes :body
indexes :custom_penname
indexes :custom_firstname
indexes :custom_lastname
end
def custom_penname
if self.anonymous
'anonymous'
else
self.user_penname
end
end
def custom_firstname
#same for first name
end
def custom_lastname
#same for last name
end
end
Same approach, different scenario:
https://github.com/adrnai/rails-3-tanker-demo/blob/master/app/models/comment.rb

How do I handle nils in views?

I have the following models set up:
class Contact < ActiveRecord::Base
belongs_to :band
belongs_to :mode
validates_presence_of :call, :mode
validates_associated :mode, :band
validates_presence_of :band, :if => :no_freq?
validates_presence_of :freq, :if => :no_band?
protected
def no_freq?
freq.nil?
end
def no_band?
band.nil?
end
end
class Band < ActiveRecord::Base
has_many :logs
end
class Mode < ActiveRecord::Base
has_many :logs
end
When I enter a frequency on my new view it allows for no band to be specified if a freq is entered. This creates a problem in my other views though because band is now nil. How do I allow for band not to be specified and just show up as empty on my index and show views, and then in the edit view allow one to be specified at a later point in time.
I have been able to get my index to display a blank by doing:
contact.band && contact.band.name
But I'm not sure if this is a best approach, and I'm unsure of how to apply a similar solution to my other views.
Many thanks from a rails newb!
In my views, I use the following for potentially nil objects in my views:
<%= #contact.band.name unless #contact.band.blank? %>
if your object is an array or hash, you can use the empty? function instead.
<%= unless #contacts.empty? %>
..some code
<% end %>
Hope this helps!
D
A couple years old but still a top Google result for "rails view handle nil" so I'll add my suggestion for use with Rails 3.2.3 and Ruby 1.9.3p0.
In application_helper.rb, add this:
def blank_to_nbsp(value)
value.blank? ? " ".html_safe : value
end
Then to display a value in a view, write something like this:
<%= blank_to_nbsp contact.band %>
Benefits:
"blank" catches both nil values and empty strings (details).
Simply omitting a nil object, or using an empty string, may cause formatting issues. pushes a non-breaking space into the web page and preserves formatting.
With the "if" and "unless" suggestions in other answers, you have to type each object name twice. By using a helper, you only have to type each object name once.
<%= #contact.try(:band).try(:name) %>
This will return nil if band or name do not exist as methods on their respective objects.
You can use Object#andand for this:
<%= #contact.band.andand.name %>
<%= #contact.band if #contact.band %> also works

Checkbox for terms and conditions, without column in database

I need a "I accept terms of service" checkbox on a page, it has to be checked in order for the order to proceed. It seems hence illogical to have a column in the database to match this (whether user has accepted or declined terms).
I am using the form helper like this in my view:
<%= check_box("client", "terms") %>
And in my model:
validates_acceptance_of :terms
At the moment it is not working at all.
This seems like a really common piece of code, yet I can't find it used anywhere without having the terms in the model. Else I could use javascript to validate it, but would prefer to keep it all the in model.
This should work fine, without a database column or attr_accessor:
http://guides.rubyonrails.org/active_record_validations.html#acceptance
I would be inclined to check your params hash is as it should be i.e. that the 'terms' attribute is being passed within the 'client' hash, perhaps try adding raise params.inspect on your controller create action to help you debug?
What about having an attr_accessor :terms in your Client model?
I had this working with these settings:
In the controller, I have added :terms_of_service as a permitted field:
def application_params
params.require(:application).permit(. . . , :terms_of_service)
end
In the model:
attr_accessor :terms_of_service
validates :terms_of_service, :acceptance => true
In the view:
<%= f.check_box("terms_of_service", :checked => false) %>
attr_accessor :terms will do the trick nicely.
Either go with #neutrino's solution, or to reset :terms to "not accepted" if you need to redisplay the form (because validation may fail), use this:
def terms
nil
end
def terms=(val)
# do nothing
end

Resources