Rails - ArgumentError in using ActiveRecord::Enum - ruby-on-rails

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

Related

Rails doesn't allow to create new enum - instance method already defined by another enum

Trying to add enum to User class:
enum vehicle: { car: 1, bus: 2, other: 3 }, _prefix: true
and rails throws an error
You tried to define an enum named "vehicle" on the model "Spree::User", but this will generate an instance method "vehicle_car?", which is already defined by another enum.
I had tried with another name, suffix and prefix options, and made ack in gems folder. I suppose that can be Zeitwerk problem but I don't have idea how to fix that.
Working on Rails version: 6
You could give a custom prefix/suffix to make it differ from vehicle_car?
enum vehicle: { car: 1, bus: 2, other: 3 }, _prefix: :vehicle_type
model_instance.vehicle_type_car!
model_instance.vehicle_type_car? # => false
For reference
https://api.rubyonrails.org/v5.2.4.1/classes/ActiveRecord/Enum.html
If you have validates call referencing the enum, make sure you are typing the plural form, vehicles.keys, not vehicle.keys, otherwise you will get this error.
class User < ApplicationRecord
enum vehicle: { car: 1, bus: 2, other: 3 }
validates :vehicle, inclusion: { in: vehicles.keys }
end
That could happen if you have a method or column called vehicle_car, because enum method will try to create a helper for you with that name. Is that the case?

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/

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.

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)

Rails3: Defining enum and using it as a custom type for db column

New to rails so not sure what the best approach is here. I want to define a simple c++ style enum which can then be used as a custom type in my db. The enum can be simulated with an array or a custom module but how do I go about turning that into a custom type for my table?
Here's a pattern I follow in rails:
In my model class, I add a module to hold the possible values of the column. Then I put them into an array and define validation against the array of possible values.
Imagine I have a column/attribute called status and it can be three possible values. I'd do this:
class MyModel < ActiveRecord::Base
# This validates that status can't be null
validates :status, :presence => true
# Define a module with all possible values
module Status
IN_DEVELOPMENT = 'in development'
DISABLED = 'disabled'
ACTIVE = 'active'
end
# Now create an array of possible status values, and add a validation
STATUSES = [ Status::DISABLED, Status::ACTIVE, Status::IN_DEVELOPMENT]
validates :status, :inclusion => { :in => STATUSES, :message => "%{value} is not a valid status value" }
end
Have you considered using the built-in enumeration support in your database? Lots of common RDMBSes have enum support, such as Postgres (see http://www.postgresql.org/docs/9.1/static/datatype-enum.html) and MySQL (see http://dev.mysql.com/doc/refman/5.5/en/enum.html). With that, you can directly create the type in your data store and then use it via one of the ActiveRecord plugins (such as enum_type for Postgres: https://github.com/riscfuture/enum_type).
Alternatively, you could use something like active_enum to structure the enumeration as you described and store fields as integers in the database.
Depending on how you plan to utilize this enum type in your code I've found that using scopes accomplishes close to the same thing along with an enum type in the database to ensure only specific values are set.
Example:
scope :trial, :conditions => { :utype => 'TRIAL' }
scope :registered, :conditions => { :utype => 'REGISTERED' }
scope :active, :conditions => { :status => 'ACTIVE' }
scope :abuse, :conditions => { :status => 'ABUSE' }

Resources