How to deal with boolean value from form submit? - ruby-on-rails

we use checkbox to submit a boolean data in a form.
In rails, when submit the form, a string with "1" or "0" will be submitted to the controller.
In phoenix, when submit the form, a string with "true" or "false" will be submitted to the controller.
It's fine if we directly create object in the database. Either of value will be store as boolean correctly.
But if we need to use the boolean value in our logic, what's the best way to do it?
Convert to boolean:
# Ruby code
def create
admin = ActiveModel::Type::Boolean.new.cast(param[:admin])
if admin
....
end
end
Directly use as string:
# Elixir code
def create(conn, params) do
case params[:admin] do
"true" -> do something
_ -> do others
end
end
Other better ways?

String.to_existing_atom/1 is your friend.
Both true and false are atoms, namely a syntactic sugar for :true and :false. That is because in erlang they are atoms.
:true == true
#⇒ true
:false == false
#⇒ true
String.to_existing_atom "true"
#⇒ true

Related

How to add form tag for a checbox attribute that is stored as a datetime in the database?

Let's say that I have a table called advertisements. I need to add a field for it so people can flag it as active or inactive. I can add a boolean field called inactive and that's a straight forward approach.
There is another approach where I can create a field inactive with datatype of datetime. This not only tells me whether an advertisement is active/no, but also tells when it was deactivated. So if the value is null, it's active and if it's a timestamp, it was deactivated at that time in the past.
The problem, however, is adding it to the form helper.
For a checkbox implementation, Rails suggests following:
= f.check_box :inactive, { class: 'something' }
This works fine with the datatype as boolean in db.
How do I make this work with datetime value?
Thanks!
Assuming we have a method for permitting parameters
def advertisment_params
params.require(:advertisement).permit(:inactive)
end
By altering the method to be
def advertisment_params
params[:advertisement][:inactive] = params[:advertisement][:inactive] == "0" ? nil : #advertisement.nil? ? DateTime.now.to_s(:db) : #advertisement.inactive
params.require(:advertisement).permit(:inactive)
end
Will do the trick.
Answering my own question. So with the check_box helper you can specify when a checkbox should be displayed as checked and you can also specify the value to assign when someone checks the box.
I did this
= f.check_box :inactive, { class: 'something', checked: f.object.inactive.present? }, f.object.inactive.nil? ? DateTime.now : f.object.inactive
The last part DateTime.now is the value that gets assigned when a user checks the box. This way you can save a DateTime value using a checkbox implementation. The presence of value helps you determine whether the checkbox should be checked or not when you display the form.
The only caveat with this approach is the accuracy of the value. User might submit the form after few mins/hours of opening the form. If it's important for you to capture the right value, you can use JS to assign the exact value or have the logic in the controller.
Try
= f.check_box :inactive, checked: form.object.inactive.present?, { class: 'something' }
To track deactivate time
Add a column deactivated_at datetime
add_column :advertisements, :deactivated_at, :datetime
in form
<%= form.text_field :deactivated_at, class: "add-datetimepicker" %>
add a datetimepicker on the field
bootstrap-datetimepicker
datetimepicker
Here is my take on implementing support for a boolean param to datetime transformation done at the controller level. This addresses the issue in the accepted answer where the timestamp is stale due to the datetime being generated in the edit view instead at the time of PUT/PATCH.
Code is written to work with Rails 6. YMMV on other versions.
First you'll want the timestamp column on your table. Let's call the model Resource and the table resources. Add the datetime field inactive_at in a migration.
def change
add_column :resources, :inactive_at, :datetime
end
I am assuming in the controller, the resource is instantiated and assigned to #resource.
In the view form, you'll want the following check box line inside your form helper block.
f.check_box :inactive, checked: #resource.inactive_at?
The checked: #resource.inactive_at? sets the default value of the check box. We are going to use the param inactive to host the boolean information of whether to update inactive_at.
In your controller, you'll want the following. For brevity, I excluded details if the controller is inheriting a parent class, the actions, and the existence of other params.
class ResourceController
private
def resource_params
translate_inactive_param
params.require(:resource).permit(:inactive_at)
end
def translate_inactive_param
return if params[:resource][:inactive_at].present?
return if changing_inactive_at_is_not_requested?
return if touching_inactive_at_is_requested_and_inactive_at_is_already_present?
return if removing_inactive_at_is_requested_and_inactive_at_is_already_blank?
params[:resource][:inactive_at] = cast_to_boolean(inactive_param) ? Time.current : nil
end
def changing_inactive_at_is_not_requested?
inactive_param.nil?
end
def touching_inactive_at_is_requested_and_inactive_is_already_present?
cast_to_boolean(inactive_param) && #resource.inactive_at?
end
def removing_inactive_at_is_requested_and_inactive_at_is_already_blank?
!cast_to_boolean(inactive_param) && #resource.inactive_at.blank?
end
def inactive_param
params.dig(:resource, :inactive)
end
def cast_to_boolean(value)
ActiveRecord::Type::Boolean.new.cast(value)
end
end
I will go over each method.
def resource_params
transform_inactive_param
params.require(:resource).permit(:inactive_at)
end
This is the method with strong parameters.
Before returning the strong parameters, the inactive param with boolean'ish value will be translated into a datetime object for inactive_at.
For the boolean'ish value of inactive, a Rail's form helper will output "0" for false and "1" for true.
For typecasting in Rails, the following are considered falsey: false, 0, "0", :"0", "f", :f, "F", :F, "false", :false, "FALSE", :FALSE, "off", :off, "OFF", :OFF
For truthy, it is any value other than nil.
For this example, we will assume an undefined inactive in the params or assigned nil value to mean no change to inactive_at is requested. This is a reasonable assumption given if the request omits inactive from the request payload.
def translate_inactive_param
return if params[:resource][:inactive_at].present?
return if changing_published_at_is_not_requested?
return if touching_inactive_at_is_requested_and_inactive_at_is_already_present?
return if removing_inactive_at_is_requested_and_inactive_at_is_already_blank?
params[:resource][:inactive_at] = cast_to_boolean(inactive_param) ? Time.current : nil
end
This method translates the inactive param to inactive_at param.
The guard clause pattern is used here. There are a few conditions we want the translation to skip.
params[:resource][:inactive_at].present? — We prioritize the already present inactive_at param over inactive. Useful if resource_params is being called multiple times in the controller.
changing_published_at_is_not_requested? — If the #resource.inactive_at is already present and inactive param is truthy, then we don't want to touch the field. I made the assumption it isn't a requirement to update the timestamp.
removing_inactive_at_is_requested_and_inactive_at_is_already_blank? — If #resource.inactive_at is already blank and inactive param is falsey, then there is no need to translate to inactive_at param.
If none of these conditions are met, then the translation will proceed.
A ternary is used to determine if the translated value to be assigned to the inactive_at params will be a timestamp or nil. Casting is used to translate the inactive_param into a boolean.
def changing_inactive_at_is_not_requested?
inactive_param.nil?
end
Self explanatory by method name. Pulled into a method to give clarity on what it does by giving it a descriptive method name.
def touching_inactive_at_is_requested_and_inactive_is_already_present?
cast_to_boolean(inactive_param) && #resource.inactive_at?
end
Self explanatory by method name. Pulled into a method to give clarity on what it does by giving it a descriptive method name.
def removing_inactive_at_is_requested_and_inactive_at_is_already_blank?
!cast_to_boolean(inactive_param) && #resource.inactive_at.blank?
end
Self explanatory by method name. Pulled into a method to give clarity on what it does by giving it a descriptive method name.
def inactive_param
params.dig(:resource, :inactive)
end
Access the value of inactive param from in params. Since this is used a few times I extracted it out into a method.
def cast_to_boolean(value)
ActiveRecord::Type::Boolean.new.cast(value)
end
Cast "boolean" values, in particular from Rails form helper or any request done via HTTP. Rails has built in classes which typecast various objects. The ActiveRecord::Type::Boolean is the one we are interested in.
Edit:
As an added bonus, here is a request test made in RSpec which tests the functionality. A controller test may be more appropriate 🤷🏻‍♂️
I will not go line-by-line to explain it, so I'll leave it up to the reader to interpret the test.
RSpec::Matchers.define_negated_matcher(:not_change, :change)
RSpec.describe 'PATCH/PUT /resources/:id/edit', type: :request do
include ActiveSupport::Testing::TimeHelpers
shared_examples 'update `inactive_at` using parameter `inactive`' do
describe '`inactive` parameter' do
let(:resource) { Resource.create(inactive_at: nil) }
let(:params) { { resource: { inactive: nil } } }
subject { -> { resource.reload } }
it { is_expected.to(not_change { resource.inactive_at }) }
context 'when `inactive` key is form value falsey "0"' do
let(:params) { { resource: { inactive: '0' } } }
it { is_expected.to(not_change { resource.inactive_at }) }
end
context 'when `inactive` key is form value truthy "1"' do
let(:params) { { resource: { inactive: '1' } } }
around { |example| freeze_time { example.run } }
it { is_expected.to(change { resource.inactive_at }.to(Time.current)) }
end
context 'when `inactive_at` is present in the resource' do
let(:resource) { Resource.create(inactive_at: 1.year.ago) }
it { is_expected.to(not_change { resource.inactive_at }) }
context 'and when `inactive` key is form value falsey "0"' do
let(:params) { { resource: { inactive: '0' } } }
it { is_expected.to(change { resource.inactive_at }.to(nil)) }
end
context 'and when `inactive` key is form value truthy "1"' do
let(:params) { { resource: { inactive: '1' } } }
it { is_expected.to(not_change { resource.inactive_at }) }
end
end
end
end
describe 'PATCH' do
before do
patch resource_path(resource), params: params
end
include_examples 'update `inactive_at` using parameter `inactive`'
end
describe 'PUT' do
before do
put resource_path(resource), params: params
end
include_examples 'update `inactive_at` using parameter `inactive`'
end
end
Active Record provides query methods for each attribute in your model, they are named after attribute name ending with question mark (eg. for model.name attribute there is model.name?) that responded with false when attribute is empty and true otherwise. Additionally, when dealing with numeric values, a query method will return false if the value is zero.
AR query methods will give you a getter for your inactive? flag, you need to add setter to allow saving changes to inactive? attribute.

Validate Param Types in Rails

I've been looking all over the place and I'm wondering if I'm doing something wrong. And just to double check, I'll ask you guys!
So I'm receiving params in a Rails controller. One key, value pair is :status => true/false. However, I find that when I try to post status as a string like
:status => "THIS IS NOT A BOOLEAN"
and create my object in my controller, the :status attribute of my object becomes false.
Therefore, is there any clean way in rails to validate that my :status corresponds to a boolean?
Thanks!
This very strange method will to the trick
def is_boolean?(item)
!!item == item
end
params[:status] = 'some string'
is_boolean?(params[:status])
# => false
params[:status] = true
is_boolean?(params[:status])
# => true
A slightly more intuitive version would be
def is_boolean?(item)
item == false || item == true
end
Validation
The Rails way to do it is to validate in the model (from the docs):
#app/models/model.rb
Class Model < ActiveRecord::Base
validates :status, inclusion: { in: [true, false] }, message: "True / False Required!"
end
--
MVC
The reason for this is twofold:
DRY
MVC
If you want to keep your application DRY, you need to make sure you have only one reference to a validation throughout. Known as the "Single Source Of Truth", it means if you try and populate the model with other controllers / methods, you'll still invoke the same validation
Secondly, you need to consider the MVC (Model-View-Controller) pattern. MVC is a core aspect of Rails, and means you have to use your controller to collate data only - pulling & compiling data in the model. This is also true for validations -- always make sure you keep your validations with the data (IE in the model)
The above #Iceman solution is good if you are only doing it once place but you keep doing/repeating it in other places i suggest you to create to_bool method. i.e
class String
def to_bool
return true if self == true || self =~ (/(true|t|yes|y|1)$/i)
return false if self == false || self.blank? || self =~ (/(false|f|no|n|0)$/i)
raise ArgumentError.new("invalid value for Boolean: \"#{self}\"")
end
end
and put this method in intializer or in library. And, you can simply do this
Mymodel.new(status: params[:status].to_s.to_bool)
we are doing to_s just because to convert nil to '' incase the status key isn't in params .

Working with Boolean fields on Mongoid

I create a Model that has a Boolean field, but when catch the value it gave me 1 or 0. I discover that it's because BSON type for Boolean is "\x00" and "\x01".
So my question is, how can I get the "boolean" value of the field? Do I need to do a method on a model or a controller that returns me true if value is 1 or false if 0? Or will Mongoid do this for me?
Mongoid Version: 4.0.0 38de2e9
EDIT
Mongo Shell
db.feedbacks.find().limit(1).pretty()
{
"_id" : ObjectId("52290a2f56de969f8d000001"),
"like" : "1",
...
}
Explain:
I create a app with scaffold:
rails g scaffold Feedback like:Boolean
When I insert a new record, in Mongo the Document stay as I sad.
When I do Feedback.first, the field like in Model has the "0" or "1" value.
class Feedback
include Mongoid::Document
include Mongoid::Timestamps
field :comment, type: String
field :like, type: Boolean
def isLike?
like=="1"
end
end
This is the repo:
https://github.com/afucher/4kFeedback/blob/master/app/models/feedback.rb
Mongoid handles that in a transparent manner if you use the Boolean type. Checkout the documentation.
EDIT :
From the rails console (in an app with an Indicator model defining a field global of type Boolean) :
Indicator.first.global?
# => true
Indicator.first.global?.class
# => TrueClass
The equivalent from the mongo shell :
> db.indicators.find().limit(1).pretty()
{
"_id" : ObjectId("52319eeb56c02cc74200009c"),
...
"global" : true,
...
}
EDIT
The spec for the Boolean extension clearly shows that for any of true, "true", "t", "yes", "y", 1, 1.0 on the MongoDB side you'll get a TrueClass instance. Same for false.
I can resolve my problem, reading the check_box documentation:
http://apidock.com/rails/ActionView/Helpers/FormHelper/check_box
The default value of Check Box is "0" or "1". To change this value, is just pass the values that you want to the tag:
check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
So, I change to this:
<%= f.check_box :like,{}, "true", "false" %>
Thanks Jef for help me!!

Validation Hash fields using mongoid

I am working on mongoDB with Rails. So using gem mongoid, Anyone know how to validate Hash fields in model?
We have to write custom validation methods
Here explained how we are writing custom validation methods
Looking for a solution, I came to a custom validator that appears good to me and it can be used generically.
private
def fix_content(input_hash, valid_fields)
temphash = {}
input_hash.each do |k,v|
k=k.to_sym
if valid_fields.has_key? k
case valid_fields[k]
when 'integer'
v=v.to_i
when 'boolean'
v=(v=='true' || v==true)
when 'float'
v=v.to_f
when 'array'
v = "#{v.class}"=="Array" ? v : []
else
v=v.to_s
end
temphash[k]=v
end
end
temphash
end
Let's suppose we have this field:
field :fieldname, type: Hash, default: {hfield1: 0, hfield2: [], hfield3: false}
Actually, it's not a validator, it's a callback. It works like this:
before_save :fieldname_fix_content
Under private:
def fieldname_fix_content
# we show the callback what fields will be processed. All others will be disposed of
self.fieldname = fix_content(self.fieldname, {:hfield1=> 'integer', :hfield2=>'array', :hfield3=>'boolean'})
end

Does Rails have a way to convert checkboxes from "on" to true?

When a controller receives the params of a checked checkbox it comes back as "on" if the box was checked. Now in my case I'm trying to store that value as a boolean, which is typically what you want to with values from checkboxes. My question is, does rails have a way to automatically convert "on" (or even exists) to true/false or do I need to do the following?
value = params[my_checkbox] && params[my_checkbox] == "on" ? true : false
You can just use:
value = !params[:my_checkbox].nil?
as the checkbox would not return any value if not checked (implied by this forum)
The best way of doing this is to create a custom setter for the field in the database, something like this:
class Person < ActiveRecord::Base
def active=(value)
value = value == 'on' ? true : false
super(value)
end
end
That way you don't have to worry about it in the controller and it's the model that knows what value it is supposed to be. When you go to the view rails automatically checks a checkbox from a boolean field. Just in case that didn't work you could also define your own getter.
This can be then used for example in conjunction with store accessor something like this:
class Person < ActiveRecord::Base
validates :active, inclusion: {in: [true, false]}
validates :remember_password, inclusion: {in: [true, false]}
store :settings,
accessors: [:active, :remember_password],
coder: JSON
def active=(value)
value = value == 'on' ? true : false
super(value)
end
def remember_password=(value)
value = value == 'on' ? true : false
super(value)
end
end
Note that the setting field in the database has to be text so you can put more stuff in it.

Resources