How do I submit a boolean parameter in Rails? - ruby-on-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

Related

How to update a specific field in Mongodb knowing it's name?

I have the following code
def update_field(db_entity, field_name)
db_entity_value = db_entity.attributes[field_name]
if db_entity_value == false
db_entity.update(field_name: true) # db_entity.update(person_name: true) works!
end
end
it takes two vars: db_entity and field_name(it's a string value like "age" or "weight"). I want to update the value of field_name.
I can get its value by using db_entity.attributes[field_name] and then I want to set the value to true if it was false, but I don't know how to update it because field_name it's a variable which holds a field name that I want to update. Please, help.
def update_field(db_entity, field_name)
db_entity_value = db_entity.attributes[field_name]
if db_entity_value == false
db_entity.update(field_name => true)
end
end
In Ruby when you construct hashes with the hashrocket (=>) syntax the keys can be variables.
You can also use update_attribute(name, value).

Validate params based on condition in rails-grape

I am developing APIs for a web app. In one specific case I am facing a situation where I require param2 only when value of param1 is false. I was able to use conditional statements to achieve it. I would like to know is there any inbuilt methods in grape for this type of validation. Something on the lines below
params do
requires :model_name, type: Hash do
optional :params1
if params1 == false
require :params2
end
end
end
You probably need mutually_exclusive validation that ensures given params aren't present at the same time in a request
params do
requires :model_name, type: Hash do
optional :params1
optional :params2
mutually_exclusive :params1, :params2
end
end
Grape supports it starting from version 0.8.0
There are also other options like exactly_one_of, all_or_none_of, at_least_one_of. Read more
Also for more complex conditions, you can express this relationship through the given method
given params1: -> (params1) { params1 == false } do
require :params2
end

Rails: Is it possible to check if a named scope is valid for a given active record?

I'm Using Ruby 1.8.7-p374 and Rails 2.3.18. (Yeah, I know, we're working on it.)
I'm considering dynamically passing a named scope to be used as a condition for an ActiveRecord. Is it possible to check to see if a passed string is a valid named scope for the record?
For Example:
If I have a named scope called :red defined for Car
named_scope :red, :condition => ["color = ?", "red"]
Then is there some function where I could do
Car.some_function("red") # returns true
Car.some_function("blue") # returns false
Thanks in advanced.
You can use .respond_to?(:method) (documentation here)
In your case :
Car.respond_to?(:red) # => true
Car.respond_to?(:blue) # => false
But you said:
I'm considering dynamically passing a named scope to be used as a condition for an ActiveRecord
I hope you will not use something like this:
# url
/cars?filter=red
# controller
def index
#cars = Car.send(params[:filter]) if params[:filter].present? && Car.respond_to?(params[:filter])
#cars ||= Car.find(:all)
Guess what woud happen if I use this URL?
/cars?filter=destroy_all
The Car model responds to the method .destroy_all, so Ruby calls it on the Car model. BOOM, all cars are destroyed!
Klass.scopes will return a hash of all scopes for that class. You can see if it's in there - the names are stored as symbols. eg
if Car.scopes[:red]
...
This will return the scope itself (truthy) or nil (falsy), which is fine for passing/failing an if test. If you literally want either true or false back then you can do !! on it to convert it to a boolean.
a_bool = !!Car.scopes[:red]

For a hash of objects in ruby, hash.keys.index(obj) works but hash.has_key?(obj) does not. Why?

Here's my Rails class
class SkinnyEmployee
include ActiveModel::Validations
attr_accessor :uid, :name
validates :uid, :presence => true
def initialize(id, name)
#uid = id
#name = name
end
def ==(other)
puts "Calling =="
raise ArgumentError.new("other is nil or bad in "+self.to_s) if other.nil? or !other.instance_of?(SkinnyEmployee)
return (self.class == other.class && self.uid == other.uid)
end
alias :eql? :==
end
I have a hash of SkinnyEmployee objects. E.g.,
skinny_hash = {SkinnyEmployee.new("123", "xyz") => 1, SkinnyEmployee.new("456", "abc") => 2}
I have another SkinnyEmployee object that I want to look up. E.g.,
entry = SkinnyEmployee.new("456", "abc")
When I do
skinny_hash.keys.index(entry)
I get 1, as expected. But when I do
skinny_hash.has_key?(entry)
I get false.
Why is that? Doesn't has_key? also use == or eql? to find whether a key exists in a hash?
Thanks much for the help!
First, this drove me nuts. What you're doing looked absolutely correct to me, and, as you already know, doesn't work.
I can take you part of the way to a solution:
http://ruby-doc.org/core-2.0.0/Object.html#method-i-hash
quoting:
Generates a Fixnum hash value for this object. This function must have the property that a.eql?(b) implies a.hash == b.hash.
The hash value is used along with eql? by the Hash class to determine if two objects reference the same hash key. Any hash value that exceeds the capacity of a Fixnum will be truncated before being used.
I added:
def hash
1
end
to your SkinnyEmployee Class, and has_key? started returning true. Obviously that's not a solution, but I'm thinking it at least puts you on the path to one.
You have overwritten the eql? method used by Array#index but not the hash method used by Hash#has_key?.
From Ruby docs for Object#hash
Generates a Fixnum hash value for this object. This function must have the property that a.eql?(b) implies a.hash == b.hash.
The Object#hash and Object#eql? methods return equal if and only if the objects occupy the same space in memory. Some classes like Array overwrite both methods to return true if the compared array's have same elements.
For your case you can define the hash method like:
def hash
"#{self.class}_#{self.uid}".hash
end
This would satisfy the docs criteria for hash method given above.
That is happening because the object you are using as a key and they one you are using to search the key are different.
Every time you call SkinnyEmployee.new it will create a new, different, object. For example
employee_1 = SkinnyEmployee.new("123", "xyz")
employee_2 = SkinnyEmployee.new("123", "xyz")
employee_1 == employee_1 #=> true
employee_2 == employee_2 #=> true
employee_2 == employee_1 #=> false
If you call object_id on both employee_1 and employee_2 you will notice that it gives you different id's.
Using has_key? will check for the exact same object, and that won't be the case if you use SkinnyEmployee.new("456", "abc").
You would need a way to retrieve the exact same object, store it in a variable or in the DB, you are using as a key and use it as an attribute for has_key? for it to work.
Hope this can help you.

Rails 3 check if attribute changed

Need to check if a block of attributes has changed before update in Rails 3.
street1, street2, city, state, zipcode
I know I could use something like
if #user.street1 != params[:user][:street1]
then do something....
end
But that piece of code will be REALLY long. Is there a cleaner way?
Check out ActiveModel::Dirty (available on all models by default). The documentation is really good, but it lets you do things such as:
#user.street1_changed? # => true/false
This is how I solved the problem of checking for changes in multiple attributes.
attrs = ["street1", "street2", "city", "state", "zipcode"]
if (#user.changed & attrs).any?
then do something....
end
The changed method returns an array of the attributes changed for that object.
Both #user.changed and attrs are arrays so I can get the intersection (see ary & other ary method). The result of the intersection is an array. By calling any? on the array, I get true if there is at least one intersection.
Also very useful, the changed_attributes method returns a hash of the attributes with their original values and the changes returns a hash of the attributes with their original and new values (in an array).
You can check APIDock for which versions supported these methods.
http://apidock.com/rails/ActiveModel/Dirty
For rails 5.1+ callbacks
As of Ruby on Rails 5.1, the attribute_changed? and attribute_was ActiveRecord methods will be deprecated
Use saved_change_to_attribute? instead of attribute_changed?
#user.saved_change_to_street1? # => true/false
More examples here
ActiveModel::Dirty didn't work for me because the #model.update_attributes() hid the changes. So this is how I detected changes it in an update method in a controller:
def update
#model = Model.find(params[:id])
detect_changes
if #model.update_attributes(params[:model])
do_stuff if attr_changed?
end
end
private
def detect_changes
#changed = []
#changed << :attr if #model.attr != params[:model][:attr]
end
def attr_changed?
#changed.include :attr
end
If you're trying to detect a lot of attribute changes it could get messy though. Probably shouldn't do this in a controller, but meh.
Above answers are better but yet for knowledge we have another approch as well,
Lets 'catagory' column value changed for an object (#design),
#design.changes.has_key?('catagory')
The .changes will return a hash with key as column's name and values as a array with two values [old_value, new_value] for each columns. For example catagory for above is changed from 'ABC' to 'XYZ' of #design,
#design.changes # => {}
#design.catagory = 'XYZ'
#design.changes # => { 'catagory' => ['ABC', 'XYZ'] }
For references change in ROR

Resources