I have a show page for my Users and each attribute should only be visible on that page, if it is not nil and not an empty string. Below I have my controller and it is quite annoying having to write the same line of code #user.city != nil && #user.city != "" for every variable. I am not too familiar with creating my own methods, but can I somehow create a shortcut to do something like this: #city = check_attr(#user.city)? Or is there a better way to shorten this procedure?
users_controller.rb
def show
#city = #user.city != nil && #user.city != ""
#state = #user.state != nil && #user.state != ""
#bio = #user.bio != nil && #user.bio != ""
#contact = #user.contact != nil && #user.contact != ""
#twitter = #user.twitter != nil && #user.twitter != ""
#mail = #user.mail != nil && #user.mail != ""
end
There's a method that does this for you:
def show
#city = #user.city.present?
end
The present? method tests for not-nil plus has content. Empty strings, strings consisting of spaces or tabs, are considered not present.
Since this pattern is so common there's even a shortcut in ActiveRecord:
def show
#city = #user.city?
end
This is roughly equivalent.
As a note, testing vs nil is almost always redundant. There are only two logically false values in Ruby: nil and false. Unless it's possible for a variable to be literal false, this would be sufficient:
if (variable)
# ...
end
This is preferable to the usual if (!variable.nil?) or if (variable != nil) stuff that shows up occasionally. Ruby tends to wards a more reductionist type of expression.
One reason you'd want to compare vs. nil is if you have a tri-state variable that can be true, false or nil and you need to distinguish between the last two states.
You can use .present? which comes included with ActiveSupport.
#city = #user.city.present?
# etc ...
You could even write it like this
def show
%w(city state bio contact twitter mail).each do |attr|
instance_variable_set "##{attr}", #user[attr].present?
end
end
It's worth noting that if you want to test if something is blank, you can use .blank? (this is the opposite of .present?)
Also, don't use foo == nil. Use foo.nil? instead.
Related
Im using virtual attributes to concat and form a address before i save the user. So when they click edit user i would like to populate the fields in the form again. Every time i try to assign them they come back nil?
This is what i call from devise registrations controller before_action edit:
def test
resource.populate_address_attributes
end
and here is the method im trying to work with:
def populate_address_attributes
if address == nil || address == ""
return false
else
attributes = address.split(",")
[self.number, self.street_name, self.area, self.postcode, self.state].each { |x| x = attributes.delete_at[0]}
end
end
all i'm getting is this:
=> [nil, nil, nil, nil, nil]
maybe i'm trying to make it to complicated?
When you are passing [self.number, self.street_name] etc you are passing the value of those attributes (which are nil and hence immutable).
Try this
def populate_address_attributes
if address == nil || address == ""
return false
else
attributes = address.split(",")
[:number, :street_name, :area, :postcode, :state].each_with_index do |field, index|
self.public_send("#{field}=", attributes[index])
end
end
end
row = {"joining_date"=>"18/07/2015", "name"=>" Joe Doe ", "company"=>" Google", "location"=>" New York ", "role"=>"developer", "email"=>"joe#doe.com", "mobile"=>"11-(640)123-45674", "address"=>"4 XYZ Road", "validity"=>"true"}
row is invalid only if any one of the fields(joining_date, name, company, location, email, address) is nil or not present.
def is_valid?
valid = true
if row[:name] == nil || row[:joining_date] == nil || row[:address] == nil || row[:email] == nil || row[:company] == nil || row[:location] == nil
valid = false
end
valid
end
Is there any way that I can simplify and refactor the above method in rails to find it more efficient using regex?
Probably, but I wouldn't use a regex as it's in a hash. As you're using rails you can use present? or blank?.
row.values.any?(&:blank?)
Would return true if any are blank
for your case
def is valid?
row.values.all?(&:present?)
end
I've tried reading some tutorials on refactoring and I am struggling with conditionals. I don't want to use a ternary operator but maybe this should be extracted in a method? Or is there a smart way to use map?
detail.stated = if value[:stated].blank?
nil
elsif value[:stated] == "Incomplete"
nil
elsif value[:is_ratio] == "true"
value[:stated] == "true"
else
apply_currency_increment_for_save(value[:stated])
end
If you move this logic into a method, it can be made a lot cleaner thanks to early return (and keyword arguments):
def stated?(stated:, is_ratio: nil, **)
return if stated.blank? || stated == "Incomplete"
return stated == "true" if is_ratio == "true"
apply_currency_increment_for_save(stated)
end
Then...
detail.stated = stated?(value)
stated = value[:stated]
detail.stated = case
when stated.blank? || stated == "Incomplete"
nil
when value[:is_ratio] == "true"
value[:stated] == "true"
else
apply_currency_increment_for_save stated
end
What's happening: when case is used without an expression, it becomes the civilized equivalent of an if ... elsif ... else ... fi.
You can use its result, too, just like with if...end.
Move the code into apply_currency_increment_for_save
and do:
def apply_currency_increment_for_save(value)
return if value.nil? || value == "Incomplete"
return "true" if value == "true"
# rest of the code. Or move into another function if its too complex
end
The logic is encapsulated and it takes 2 lines only
I like #Jordan's suggestion. However, it seems the call is incomplete -- the 'is_ratio' parameter is also selected from value but not supplied.
Just for the sake of argument I'll suggest that you could go one step further and provide a class that is very narrowly focused on evaluating a "stated" value. This might seem extreme but it fits with the notion of single responsibility (the responsibility is evaluating "value" for stated -- while the 'detail' object might be focused on something else and merely makes use of the evaluation).
It'd look something like this:
class StatedEvaluator
attr_reader :value, :is_ratio
def initialize(value = {})
#value = ActiveSupport::StringInquirer.new(value.fetch(:stated, ''))
#is_ratio = ActiveSupport::StringInquirer.new(value.fetch(:is_ratio, ''))
end
def stated
return nil if value.blank? || value.Incomplete?
return value.true? if is_ratio.true?
apply_currency_increment_for_save(value)
end
end
detail.stated = StatedEvaluator.new(value).stated
Note that this makes use of Rails' StringInquirer class.
So in my rails form there are several rows of 2 textfields. For the form to save ok, at least one of the pair of textfields needs to be filled out.
So
nil nil
10 20
nil nil
nil nil
is valid.
This:
nil nil
nil nil
nil nil
nil nil
is invalid
This:
nil 10
nil nil
nil nil
nil nil
is invalid
Here is the method I am using to check all the fields (note that single_field and aggregate_field are strings and are the field names):
def no_values_present?(single_field, aggregate_field)
self.lo_item.lo_line_items.each do |item|
return false if "!item.#{single_field}".nil? && "!item.#{aggregate_field}".nil?
end
true
end
But I guess this doesn't work as it will return true or false several times and will determine that a row is invalid even though a previous row may have been valid.
I need an overall true or false.
How can this be achieved?
Try leveraging any? or none? from the Enumerable module.
Your code could be rewritten as
def no_values_present?(single_field, aggregate_field)
self.lo_item.lo_line_items.none? { |item|
!(item.send(single_field).nil?) && !(item.send(aggregate_field).nil?)
}
end
although I think that it would be clearer to have the condition be positive and to return true when there is a match found. I would write
def any_pairs_present?(single_field, aggregate_field)
self.lo_item.lo_line_items.any? { |item|
!(item.send(single_field).nil?) && !(item.send(aggregate_field).nil?)
}
end
Note that "!item.#{single_field}" will never be nil because it will always be a string! If you want to access instance fields dynamically then one way to do that is with send, but for other options you could look here which suggests the alternatives of instance_eval and instance_variable_get.
The function looks ok, but there seems to be syntax errors, I'd also make a few amendments:
def form_valid?(single_field, aggregate_field)
self.lo_item.lo_line_items.each do |item|
return true if !item.send(single_field).nil? && !item.send(aggregate_field)
end
false
end
I have a module:
module Voteable
def has_up_vote_of user
return ! self.votes.select{|v| v.user.id == user.id && v.value == 1}.empty?
end
def has_down_vote_of user
return ! self.votes.select{|v| v.user.id == user.id && v.value == -1}.empty?
end
end
Which is mixed into a model:
class Comment < ActiveRecord::Base
include Voteable
end
In a controller code, there is a check:
has_up_vote = #voteable.has_up_vote_of #user
has_down_vote = #voteable.has_down_vote_of #user
#voteable and #user are existing model items, found in a DB.
Suppose, voteable item has up-vote of user. After executing the code, has_up_vote will be equal to true, and has_down_vote will be nil.
Why nil, instead of false ?
I have used several variations of methods, but the problem is the same. Even this gives me the same effect:
def has_up_vote_of user
has = self.votes.select{|v| v.user.id == user.id && v.value == 1}.empty?
return !has.nil? && has
end
Posssible, i'm misunderstanding something, but this behavior is strange
Update
I've noticed very strange behaviour.
When i change methods to trivial:
def has_up_vote_of user
return false
end
def has_down_vote_of user
return false
end
They both returns nil, when i debug the app.
But, from console, they returns false.
It's more stange, because i cannot do anything with these results. These code is not working:
has_up_vote = false if has_up_vote.nil?
has_down_vote = false if has_down_vote.nil?
I think that the debugging environment you're running in is interfering with the actual value of has_down_votes. The select method should never return nil as defined.
Instead of !{}.empty? you could use {}.present?
Its more readable and the output will always be true/false only
I know this doesn't get to the root cause of your strange problem, but it should give you the results you want. Instead of
return ! self.votes.select{|v| v.user.id == user.id && v.value == -1}.empty?
try
return !!self.votes.select{|v| v.user.id == user.id && v.value == -1}.any?
The double exclamation point is intentional -- it will cause nil to become false. (!arr.empty? is equivalent to arr.any? which is equivalent to !!arr.any? -- except the last one converts the nil to false)