Default value of enum if it's not present - ruby-on-rails

I have a enum in a model. I want to get its integer value, or it's not set I want to get an integer value of a value which I choose to be a default one:
enum my_enum: [:val1, :val2, :val3]
def method1
int_val = self.read_attribute(:my_enum)
# what if my_enum hasn't been set?
unless int_val
int_val = ??? # how to get integer of :val2 ???
end
end

Default value for an enum is the value you set at the 0th index.
For ex. enum Animals{ Cat, Dog, Lion, Tiger} will have its default as 'Cat' which is Animals(0).
However if you define the enum to be enum Animals{ Cat=1, Dog=2, Lion=3, Tiger=0} then the default will be 'Tiger'
Considering that you can define your enum to fulfil your requirement

It's good practive to set the default value of a field in the database. Try using a database migration to change/add the default value to the required field. Example code:
class ChangeDefaultValueToModel < ActiveRecord::Migration
def change
change_column_default :model_name, :field_name, default_value
end
end
For more detail check the Ruby on Rails API for change_column_default

Related

How to let active model serializer automatically convert enum attribute to integer

class User < ApplicationRecord
enum status: [ :active, :inactive ]
end
By default the active model serializer serializes User object's status attribute to a string, either "active" or "inactive", but I would like it to be integer 0 or 1. To accomplish so, I have to do this manually:
class UserSerializer < ActiveModel::Serializer
attributes :status
def status
object.status_before_type_cast # get integer
# or User.statuses[object.status], the same thing
end
end
This is a bit ugly because I have to write code for each enum attribute for each active model class. Is there any option to do this once?
you can access the enum index value like a hash
User.statuses[:active]
=> 0
User.statuses[:inactive]
=> 1
I hope this is what you looking for
http://api.rubyonrails.org/v5.1/classes/ActiveRecord/Enum.html
enum status: { active: 0, inactive: 1 }
Model.statuses # Pluralized version of the enum attribute name
That returns a hash like:
=> {"active"=>0, "inactive"=>1}
You can then use the status value from an instance of the Model class to access the integer value for that instance:
my_model = Model.find(123)
Model.statuses[my_model.status] # Returns the integer value
https://www.sitepoint.com/enumerated-types-with-activerecord-and-postgresql/

How to get the int value from my Model enum when I have an record instance?

How can I convert a Model enum to the INT value?
I am trying to do this:
class User < ActiveRecord::Base
enum user_type: [:member, :super]
end
Now say I have a user record, now I want to use the user_type enum value in another query:
u = User.find_by_type(#user.user_type)
def self.find_by_user_type(user_type)
User.where(user_type: user_type).take
end
This doesn't work because the user.user_type is returning "member" or "super" and I need 0 or 1.
Is there a way to get the Int value from my #user instance?
(Rails 4.x)
To get integer equivalent of enum type:
User.user_types[self.user_type] # returns integer value
Replace self with another user instance if necessary.

Reset value of an attribute to default

I would like to know how I can set a models attribute and possible associations to its default value.
user = User.find_by(name: "Martin")
user.phone = 012345
user.save!
# some time later
user.phone = # set to default
user.save!
Few options to set a default value of a column:
Set the default value in migration (preferable)
Set the default value in before_* callback
To revert to default column's value you can use ActiveRecord::ConnectionAdapters::SchemaCache#columns_hash:
user.phone = user.class.columns_hash['phone'].default
You already set default in the migration.
:default => 'your_default'
It's better to use:
User.column_defaults["phone"]
instead of:
User.columns_hash['phone'].default
since columns_hash gets the raw default value defined at database level and skips defaults set in ActiveModel. See the following example:
class Order < ApplicationRecord
enum status: %i[open closed]
attribute :deliver_at, default: -> { Date.tomorrow }
end
Order.columns_hash['status'].default # => "0" ('0' if default value was defined in the database or 'nil' otherwise)
Order.columns_hash['deliver_at'].default # => NoMethodError (undefined method `default' for nil:NilClass) if it's a virtual attribute or 'nil' if the column exists in the database
Order.column_defaults['status'] # => "open"
Order.column_defaults['deliver_at'] # => Wed, 06 May 2020

Ruby on Rails 4.2 enum attributes

I'm trying to use new Enum type, everything works well except one issue. When writing functional tests I usually use structure:
order = Order.new(o_status: :one)
post :create, order: order.attributes
# Error message:
# ArgumentError: '0' is not a valid o_status
It's ok as long as I don't have Enum attribute. The problem with enums is that instead of String value .attributes returns it's Integer value which can't be posted as enum attribute value.
In above example model can look like this:
class Order < ActiveRecord::Base
enum o_status: [:one, :two]
end
I figured out that when I do:
order = Order.new(o_status: :one)
atts = order.attributes
atts[:o_status] = "one" # it must be string "one" not symbol or integer 0
post :create, order: order.attributes
It will work OK.
Is it normal or there is some better solution?
EDIT:
The only workaround which I found looks like this:
order = { o_status: :one.to_s }
post :create, order: order
pros: It is short and neat
cons: I cannot validate order with order.valid? before sending with post
This doesn't solve issue with order.attributes when there is Enum inside.
From the Enum documentation:
You can set the default value from the database declaration, like:
create_table :conversations do |t|
t.column :status, :integer, default: 0
end
Good practice is to let the first declared status be the default.
Best to follow that advice and avoid setting a value for an enum as part of create. Having a default value for a column does work in tests as well.

Why does Model.new in Rails 3 do an implicit conversion?

I'm facing a weird behavior in Rails 3 model instantiation.
So, I have a simple model :
class MyModel < ActiveRecord::Base
validates_format_of :val, :with => /^\d+$/, :message => 'Must be an integer value.'
end
Then a simple controller :
def create
#mod = MyModel.new(params[:my_model])
if #mod.save
...
end
end
first, params[:my_model].inspect returns :
{:val => 'coucou :)'}
But after calling #mod = MyModel.new(params[:my_model]) ...
Now, if I call #mod.val.inspect I will get :
0
Why am I not getting the original string ?
At the end the validates succeed because val is indeed an integer.
Is this because val is defined as an integer in the database ?
How do I avoid this behavior and let the validation do his job ?
If val is defined as an integer in your schema then calling #my_model.val will always return an integer because AR does typecasting. That's not new to rails 3, it's always worked that way. If you want the original string value assigned in the controller, try #my_model.val_before_type_cast. Note that validates_format_of performs its validation on this pre-typecast value, so you don't need to specify that there.
EDIT
Sorry I was wrong about the "performs its validation on this pre-typecast value" part. Looking at the code of the validation, it calls .to_s on the post-typecast value which in your case returns "0" and therefore passes validation.
I'd suggest not bothering with this validation to be honest. If 0 is not a valid value for this column then validate that directly, otherwise just rely on the typecasting. If the user enters 123 foo you'll end up with 123 in the database which is usually just fine.
There is also better fitting validator for your case:
http://guides.rubyonrails.org/active_record_validations_callbacks.html#validates_numericality_of

Resources