Ruby on Rails - unless multiple conditions - ruby-on-rails

I'm trying to substitute an expression unless the expression is one of two values.
def substitute_string (string)
string.gsub('abc', 'xyz') unless string == ('dabc' || 'eabc')
end
substitute_string('jjjjjabc')
=> 'jjjjjxyz'
substitute_string('dabc')
=> 'dabc'
substitute_string('eabc')
=> 'exyz'
I expected substitute_string('eabc') to return ('eabc') since I stated that in the unless block, which I passed two values.
I don't understand why this doesn't work, and what I can do to make 'eabc' return 'eabc'.

('dabc' || 'eabc') is a boolean expression that evaluates to true and returns 'dabc'.
Use two or's:
unless string == 'dabc' || string == 'eabc'
Or use =~ (regex pattern match)
unless string =~ /(dabc|eabc)/
Since you indicated you're using Rails, you can also use in? like this:
unless string.in? ['dabc', 'eabc']

It is because (1) 'dabc' || 'eabc' is equivalent to 'dabc', and nowhere in your code does 'eabc' appear in a meaningful way, and because (2) it only returns nil when the condition is met according to the way you used unless.
def substitute_string(string)
case string
when 'dabc', 'eabc' then string
else string.gsub('abc', 'xyz')
end
end

Apart from the fun of obscure technicalities about what is returned when and in what situations, I don't see a lot of merit in not being more explicit with the return. The very fact that this issue was brought and subsequently debated on SO is exactly why writing code (working code to be sure) in this obscure fashion will lead to confusion for developers interpreting this code, and leads to buggy software.
The only benefit I see to this is that it's on one line.
def substitute_string(string)
string.gsub('abc', 'xyz') unless ['dabc', 'eabc'].include?(string)
end
I personally would prefer the following as it makes it clear what your intentions are:
def substitute_string(string)
return string if ['dabc', 'eabc'].include?(string)
string.gsub('abc', 'xyz')
end

'dabc' || 'eabc' will always equal true since it just means condition or condition where condition is a string. Since a string is not nil or false it evaluates to true. You could check whether the string is in an array values instead:
def substitute_string(string)
string.gsub('abc', 'xyz') unless ['dabc', 'eabc'].include?(string)
end

Related

Better way to check 2 conditions based on 1 object

I commonly need to use this kind of methods where I have to check if an object exists and if this object returns a specific value or a behavior. Is that a better way to write this code?
def set_current_theme
if current_tenant && current_tenant.has_custom_domain?
#theme = Theme.last || Theme.create
end
end
At a first glance, I would just add one conditional: if current_tenant.has_custom_domain? and that should be enough. But the result is generally that there is no such method (in this case has_custom_domain?) for nil class.
Shorter (and i think better) way is to use &. (it's shorthand for try!) like this.
if current_tenant&.has_custom_domain?
#theme = Theme.last || Theme.create
end
What does &. (ampersand dot) mean in Ruby?
I would suggest early return (so called guard clause) instead of :if statement, because you don't have :else clause:
def set_current_theme
return unless current_tenant&.has_custom_domain?
#theme = Theme.last || Theme.create
end

Can't figure out what the error is with this method?

I'm passing a hash to this function that either a) has keys that are strings along with values that are ints OR b) it is an empty hash.
The point of the function is to return nil if the hash is empty and return the key associated with the lowest int.
def key_for_min_value(name_hash)
if name_hash == nil
return nil
else
lowest_value = nil
lowest_value_name = nil
name_hash.collect do |name, value|
if lowest_value > value
lowest_value = value
lowest_value_name = name
end
end
return lowest_value_name
end
end
The error I'm receiving is:
1) smallest hash value does not call the `#keys` method
Failure/Error: key_for_min_value(hash)
NoMethodError:
undefined method `>' for nil:NilClass`
You can't compare nil to anything using >, it's not allowed, so you either have to avoid that test or use tools like min_by to get the right value instead of this collect approach.
One way to make your unit test happy might be:
def key_for_min_value(name_hash)
return unless (name_hash)
name_hash.keys.min_by do |key|
name_hash[key]
end
end
Ruby leans very heavily on the Enumerable library, there's a tool in there for nearly every job, so when you have some free time have a look around there, lots of things to discover.
Now Ruby is very strict about comparisons, and in particular a nil value can't be "compared" (e.g. > or < and such) to other values. You'll need to populate that minimum with the first value by default, not nil, then the comparisons work out, but doing that completely is pretty ugly:
def key_for_min_value(name_hash)
return unless (name_hash)
min_key, min_value = name_hash.first
name_hash.each do |key, value|
next unless (value < min_value)
min_key = key
min_value = value
end
min_key
end
So that approach is really not worth it. Enumerable makes it way easier and as a bonus your intent is clear. One thing you'll come to appreciate is that in Ruby if your code looks like code then you're probably going about it the wrong way, over-complicating things.
Ruby is an unusually expressive language, and often there's a very minimal form to express just about anything.

Check string is a valid number or not in ruby

I want to check weather variable contains a valid number or not.
I can validate correctly for null and blank but can not validate text as a "Integer"...
I tried:
if(params[:paramA].blank? || (params[:paramA].is_a?(Integer)) )
I have also tried is_numeric, is_numeric(string), is_number? and other ways...
but did not get success...
I saw such patch:
class String
def is_number?
true if Float(self) rescue false
end
end
if (params[:paramA].blank? || !params[:paramA].is_number?)
Or without the patch:
if (params[:paramA].blank? || (false if Float(params[:paramA]) rescue true))
It supports 12, -12, 12.12, 1e-3 and so on.
If your parameter is for an ActiveRecord model, then you should probably use validates_numericality_of. Otherwise...
You only want integers, right? How about:
if (params[:paramA].blank? || params[:paramA] !~ /^[+-]?\d+$/)
That is, check whether the parameter consists of an optional + or -, followed by 1 or more digits, and nothing else.
If the thing you want to do is this:
I want to check weather variable contains a valid number or not.
You can get it with regex. See it here
s = 'abc123'
if s =~ /[-.0-9]+/ # Calling String's =~ method.
puts "The String #{s} has a number in it."
else
puts "The String #{s} does not have a number in it."
end
In rails you can use the numeric? method on a String or Integer or Float which does exactly what you need.
123.numeric?
# => true
123.45.numeric?
# => true
"123".numeric?
# => true
"123.45".numeric?
# => true
"a1213".numeric?
# => false
UPDATE
My bad, I had a dirty environment, the above works if mongoid version 3 and above is loaded.

Can I do this in Ruby?

Currently I have the following 2 lines of code
errors.add_to_base I18n.t :error_message if value != 1
return false if !errors.blank?
Is it possible to condense this into 1 line of code? I need to do this in multiple places with different error message and condition. Also, "return false" is to stop the flow of an ActiveRecord lifecycle.
Hmm. If you know errors.blank? will be true unless the first condition fires then:
(errors.add_to_base I18n.t :error_message; return) if value != 1
Update: Aha, you are willing to define a method. How about a Proc object? It's better than a method here in that if the Proc block returns then the invocation will return from the surrounding method.
test = Proc.new do |cond, msg|
errors.add_to_base I18n.t msg if cond
return unless errors.blank?
end
# ...
test.call value != 1, :error_message
Note that you don't need to return false as a plain return will return nil and that will be good enough unless some sadist is doing something like f().class == NilClass. :-)
You can take advantage of how the boolean logic operators work and do something like this:
value != 1 && errors.add_to_base I18n.t :error_message && return false
Not very clear, I wouldn't recommend using it. Also if errors.add_to_base returns something that's "falseish" the return false wouldn't happen.
Remember: "Always code as if the person who will maintain your code is a violent psychopath who knows where you live”

How do I submit a boolean parameter in Rails?

I'm submitting a parameter show_all with the value true. This value isn't associated with a model.
My controller is assigning this parameter to an instance variable:
#show_all = params[:show_all]
However, #show_all.is_a? String, and if #show_all == true always fails.
What values does Rails parse as booleans? How can I explicitly specify that my parameter is a boolean, and not a string?
UPDATE: Rails 5:
ActiveRecord::Type::Boolean.new.deserialize('0')
UPDATE: Rails 4.2 has public API for this:
ActiveRecord::Type::Boolean.new.type_cast_from_user("0") # false
PREVIOUS ANSWER:
ActiveRecord maintains a list of representations for true/false in https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/column.rb
2.0.0-p247 :005 > ActiveRecord::ConnectionAdapters::Column.value_to_boolean("ON")
2.0.0-p247 :006 > ActiveRecord::ConnectionAdapters::Column.value_to_boolean("F")
This is not part of Rails' public API, so I wrapped it into a helper method:
class ApplicationController < ActionController::Base
private
def parse_boolean(value)
ActiveRecord::ConnectionAdapters::Column.value_to_boolean(value)
end
end
and added a basic test:
class ApplicationControllerTest < ActionController::TestCase
test "parses boolean params" do
refute ApplicationController.new.send(:parse_boolean, "OFF")
assert ApplicationController.new.send(:parse_boolean, "T")
end
end
I wanted to comment on zetetic answer but as I can't do that yet I'll post this as an answer.
If you use
#show_all = params[:show_all] == "1"
then you can drop ? true : false because params[:show_all] == "1" statement itself will evaluate to true or false and thus ternary operator is not needed.
This question is rather old, but since I came across this issue a couple of times, and didn't like any of the solutions proposed, I hacked something myself which allows to use multiple strings for true such as 'yes', 'on', 't' and the opposite for false.
Monkey patch the class String, and add a method to convert them to boolean, and put this file in /config/initializers as suggested here: Monkey Patching in Rails 3
class String
def to_bool
return true if ['true', '1', 'yes', 'on', 't'].include? self
return false if ['false', '0', 'no', 'off', 'f'].include? self
return nil
end
end
Notice that if the value is none of the valid ones either for true or false, then it returns nil. It's not the same to search for ?paid=false (return all records not paid) than ?paid= (I don't specify if it has to be paid or not -- so discard this).
Then, following this example, the logic in your controller would look like this:
Something.where(:paid => params[:paid].to_bool) unless params[:paid].try(:to_bool).nil?
It's pretty neat, and helps to keep controllers/models clean.
#show_all = params[:show_all] == "1" ? true : false
This should work nicely if you're passing the value in from a checkbox -- a missing key in a hash generates nil, which evaluates to false in a conditional.
EDIT
As pointed out here, the ternary operator is not necessary, so this can just be:
#show_all = params[:show_all] == "1"
You could change your equality statement to:
#show_all == "true"
If you want it to be a boolean you could create a method on the string class to convert a string to a boolean.
I think the simplest solution is to test "boolean" parameters against their String representation.
#show_all = params[:show_all]
if #show_all.to_s == "true"
# do stuff
end
Regardless of whether Rails delivers the parameter as the String "true" or "false" or an actual TrueClass or FalseClass, this test will always work.
You could just do
#show_all = params[:show_all].downcase == 'true'
It's worth noting that if you're passing down a value to an ActiveModel in Rails > 5.2, the simpler solution is to use attribute,
class Model
include ActiveModel::Attributes
attribute :show_all, :boolean
end
Model.new(show_all: '0').show_all # => false
As can be seen here.
Before 5.2 I use:
class Model
include ActiveModel::Attributes
attribute_reader :show_all
def show_all=(value)
#show_all = ActiveModel::Type::Boolean.new.cast(value)
end
end
Model.new(show_all: '0').show_all # => false
Another approach is to pass only the key without a value. Although using ActiveRecord::Type::Boolean.new.type_cast_from_user(value) is pretty neat, there might be a situation when assigning a value to the param key is redundant.
Consider the following:
On my products index view by default I want to show only scoped collection of products (e.g. those that are in the stock). That is if I want to return all the products, I may send myapp.com/products?show_all=true and typecast the show_all parameter for a boolean value.
However the opposite option - myapp.com/products?show_all=false just makes no sense since it will return the same product collection as myapp.com/products would have returned.
An alternative:
if I want to return the whole unscoped collection, then I send myapp.com/products?all and in my controller define
private
def show_all?
params.key?(:all)
end
If the key is present in params, then regardless of its value, I will know that I need to return all products, no need to typecast value.
You can add the following to your model:
def show_all= value
#show_all = ActiveRecord::ConnectionAdapters::Column.value_to_boolean(value)
end
You could convert all your boolean params to real booleans like this:
%w(show_all, show_featured).each do |bool_param|
params[bool_param.to_sym] = params[bool_param.to_sym] == "true"
end
In this solution, nil parameters would become false.
While not explicitly what the question is about I feel this is appropriately related; If you're trying to pass true boolean variables in a rails test then you're going to want the following syntax.
post :update, params: { id: user.id }, body: { attribute: true }.to_json, as: :json
I arrived at this thread looking for exactly this syntax, so I hope it helps someone looking for this as well. Credit to Lukom

Resources