How to let active model serializer automatically convert enum attribute to integer - ruby-on-rails

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/

Related

Rails - ArgumentError in using ActiveRecord::Enum

I have created a model Tester with integer column tester_type and declared enum variable in the model.
class Tester < ApplicationRecord
enum tester_type: { junior: 0, senior: 1, group: 2 }
end
I am getting below error while trying to create / initialize an object for that model:
ArgumentError: You tried to define an enum named "tester_type" on the model "Tester", but this will generate a class method "group", which is already defined by Active Record.
So, I tried changing tester_type to type_of_tester but it throws same error:
ArgumentError: You tried to define an enum named "type_of_tester" on the model "Tester", but this will generate a class method "group", which is already defined by Active Record.
I have searched for the solution and I found this error was a constant ENUM_CONFLICT_MESSAGE in ActiveRecord::Enum class but, cannot able to find the cause of this problem.
Please help me.
Thanks.
In this case, if you want to use enum, you are probably better off renaming your label to something else. This is not unique to enums – a lot of Active Record features generates methods for you and usually there aren't ways to opt-out of those generated methods.
then change group to another_name
OR you should follow this also
enum :kind, [:junior, :senior, :group], prefix: :kind
band.kind_group?
You can use the :_prefix or :_suffix options when you need to define multiple enums with same values or in your case, to avoid conflict with already defined methods. If the passed value is true, the methods are prefixed/suffixed with the name of the enum. It is also possible to supply a custom value:
class Conversation < ActiveRecord::Base
enum status: [:active, :archived], _suffix: true
enum comments_status: [:active, :inactive], _prefix: :comments
end
With the above example, the bang and predicate methods along with the associated scopes are now prefixed and/or suffixed accordingly:
conversation.active_status!
conversation.archived_status? # => false
conversation.comments_inactive!
conversation.comments_active? # => false
For your case, my suggestion would be using something like:
class Tester < ApplicationRecord
enum tester_type: { junior: 0, senior: 1, group: 2 }, _prefix: :type
end
Then you can use these scopes as:
tester.type_group!
tester.type_group? # => true
Tester.type_group # SELECT "testers".* FROM "testers" WHERE "testers"."tester_type" = $1 [["tester_type", 2]]
# or,
Tester.where(tester_type: :group) # SELECT "testers".* FROM "testers" WHERE "testers"."tester_type" = $1 [["tester_type", 2]]
check this out. it is the option group you are having a problem with. You can use prefix option as mentioned in this post
enum options
Specifying a prefix option worked for me.
# models/tester.rb
enum tester_type: { junior: 0, senior: 1, group: 2 }, _prefix: true
And then to use it:
Tester.first.tester_type
=> nil
Tester.first.tester_type_junior!
=> true
Tester.first.tester_type
=> 0
Note that the enum values can be given explicit string values instead of integers, with the same notation provided in the question. Which makes the saved db values more human readable.
enum tester_type: { junior: 'junior', senior: 'senior', group: 'group' }, _prefix: true
Tester.first.tester_type_senior!
=> true
Tester.first.tester_type

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.

Default value of enum if it's not present

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

How to handle enum values in rails 4

So I have a subscription table in my database.
I would like to have a state column which will have any one of the following values
Valid
Invalid
Cancelled
In Trial
Non Renewing
Future
Can someone explain how to use these values as enum values in rails 4?
Credit to: https://hackhands.com/ruby-on-enums-queries-and-rails-4-1/
Declare an enum attribute where the values map to integers in the database, but can be queried by name. Example:
class Conversation < ActiveRecord::Base
enum status: [ :active, :archived ]
end
# conversation.update! status: 0
conversation.active!
conversation.active? # => true
conversation.status # => "active"
# conversation.update! status: 1
conversation.archived!
conversation.archived? # => true
conversation.status # => "archived"
# conversation.update! status: 1
conversation.status = "archived"
# conversation.update! status: nil
conversation.status = nil
conversation.status.nil? # => true
conversation.status # => nil
Scopes based on the allowed values of the enum field will be provided as well. With the above example, it will create an active and archived scope.
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.
Finally, it's also possible to explicitly map the relation between attribute and database integer with a Hash:
class Conversation < ActiveRecord::Base
enum status: { active: 0, archived: 1 }
end
Note that when an Array is used, the implicit mapping from the values to database integers is derived from the order the values appear in the array. In the example, :active is mapped to 0 as it's the first element, and :archived is mapped to 1. In general, the i-th element is mapped to i-1 in the database.
Therefore, once a value is added to the enum array, its position in the array must be maintained, and new values should only be added to the end of the array. To remove unused values, the explicit Hash syntax should be used.
In rare circumstances you might need to access the mapping directly. The mappings are exposed through a class method with the pluralized attribute name:
Conversation.statuses # => { "active" => 0, "archived" => 1 }
Use that class method when you need to know the ordinal value of an enum:
Conversation.where("status <> ?", Conversation.statuses[:archived])
Where conditions on an enum attribute must use the ordinal value of an enum.
More info: http://api.rubyonrails.org/v4.1.0/classes/ActiveRecord/Enum.html
You can use gem called Workflow. It enables you to use custom statuses and gracefully handle transitions between states. I've used it on many Rails3 and Rails 4 apps.
Example from the documentation.
class Article
include Workflow
workflow do
state :new do
event :submit, :transitions_to => :awaiting_review
end
state :awaiting_review do
event :review, :transitions_to => :being_reviewed
end
state :being_reviewed do
event :accept, :transitions_to => :accepted
event :reject, :transitions_to => :rejected
end
state :accepted
state :rejected
end
end
And later:
article = Article.new
article.accepted? # => false
article.new? # => true
Edit: My answer here does not use ActiveRecord magic enums. What I suggest below works for any DB library (like Sequel), and I happen to think it slightly more robust. For example, it does not rely on automatic ordering of values, and it uses foreign key constraints in the RDBMS to ensure that your value is valid (unlike the ActiveRecord solution). However, it is not the Rails idiomatic methodology. See Giri's answer (a copy/paste of the documentation page) for the idiomatic answer.
Create a states table in your database with these string values populated along with a unique ID, e.g.
states
id | name
---+--------------
1 | Valid
2 | Invalid
3 | Cancelled
4 | In Trial
5 | Non Renewing
6 | Future
Use a standard foreign-key association in your subscription table referencing an entry from states.
In your models, create a State class with constants matching the IDs, e.g.:
class State < ActiveRecord::Base
VALID = 1
INVALID = 2
# etc.
end
Now you are a) guaranteed that your table data is valid, and b) you can use convenient references such as asking for subscriptions
Subscription.where( state_id: State::VALID )

How to create a enum type and default to a specific value for new objects

I have a model
class Transaction < ActiveRecord::Base
end
I have a transaction_type column which is an integer.
How can I create an enumeration that I could map values to names like:
one_time = 1
monthly = 2
annually = 3
So in the db column, the values would be 1, 2 or 3.
Also, whenever I create a new instance, or save a model and the field wasn't set like:
#transaction = Transaction.new(params)
It should default to 1 (on_time).
I'm not sure how I can do this?
basically the same answer as Amit, slight variation
class TransactionType
TYPES = {
:one_time => 1,
:monthly => 2,
:annually => 3
}
# use to bind to select helpers in UI as needed
def self.options
TYPES.map { |item| [item[0], item[1].to_s.titleize] }
end
def self.default
TYPES[:one_time]
end
end
one way to control the default value
class Transaction < ActiveRecord::Base
before_create :set_default_for_type
def set_default_for_type
type = TransactionType.default unless type.present?
end
end
but - best way is to just apply the defaults on your database column and let ActiveRecord get it from there automatically
NOTE: it might also make sense to just have a TransactionType ActiveRecord object instead of above, depends on your situation, i.e.
# on Transaction with type_id:integer
belongs_to :type, class_name: "TransactionType"
You can map the values by creating a constant either in the same Transaction model or by creating a new module and place it inside that as explained by #KepaniHaole
In Transaction model, you can do it like :
class Transaction < ActiveRecord::Base
TRANSACTION_TYPES = { 'one_time' => 1, 'monthly' => 2, 'monthly' => 3 }
end
You can access these values by accessing the constant as
Transaction::TRANSACTION_TYPES['one_time'] # => 1
Transaction::TRANSACTION_TYPES['monthly'] # => 2
Transaction::TRANSACTION_TYPES['monthly'] # => 3
To add a default value to transaction_type column just create a new migration with :
def up
change_column :transactions, :transaction_type, :default => Transaction::TRANSACTION_TYPES['one_time']
end
With this, every time you create a Transaction object without passing transaction_type, the default value 1 with be stored in it.
Maybe you could try something like this? Ruby doesn't really support c-style enums..
module TransactionType
ONCE = 1
MONTHLY = 2
ANUALLY = 3
end
then you could access their values like so:
#transaction = Transaction.new(TransactionType::ONCE)

Resources