1

I've run into this situation many times, where I need to store something like a status persay, so it would be something like this:

class Order < ActiveRecord::Base

  INCOMPLETE = 'Incomplete'
  IN_PROGRESS = 'In progress'
  SHIPPED = 'Shipped'
  CANCELLED = 'Cancelled'
  ...
end

Order would have a status attribute, and when creating an order I would just use collection_select with [INCOMPLETE, IN_PROGRESS, SHIPPED, CANCELLED] as the options.

Is there a cleaner way of doing this without using hardcoded strings, like using a Status association or some sort of PORO? I feel like this would be a bit brittle, like if someone changed the INCOMPLETE = 'Incomplete' to INCOMPLETE = 'Incompletezzzzz' then all the statuses of my existing records would not match.

4
  • 4
    What version of Rails? I ask because Rails 4.1 has an enum attribute type that suits your purpose pretty well: robots.thoughtbot.com/… Commented Jul 10, 2014 at 15:31
  • (And for other versions of Rails, there's the simple_enum gem.) Commented Jul 10, 2014 at 15:32
  • Wow nice. I'm on 4.0.0. I guess I can upgrade to the latest Commented Jul 10, 2014 at 15:34
  • The attribute type is reserved and should not be used (except for Single Table Inheritance cases). Ruby on Rails relies on this type attribute to find from which Class comes from the record from the DB. Commented Jul 10, 2014 at 15:46

2 Answers 2

3

I would advise you to use another model in this situation, something like OrderStatus:

class OrderStatus < ActiveRecord::Base
  validates :internal_reference, presence: true, uniqueness: true
  has_many :orders

  def translated(locale = :en)
    I18n.t("activerecord.attributes.order_status.#{self.internal_reference}", locale: locale, default: self.internal_reference)
  end

class Order < ActiveRecord::Base
  belongs_to :order_status
  validates :order_status_id, presence: true

And the records will look like this:

OrderStatus.first
# => OrderStatus id: 1, internal_reference: canceled
Order.first
# => Order id: 3, order_status_id: 1 # etc.

In your views, it could be:

order.order_status.translated(I18n.locale) 
# looks for activerecord.attributes.order_status.canceled
# if nothing found, returns the internal_reference, here it would return `'canceled'`

This configuration is better than just constants :

  • You can create as many statuses as you want,
  • You can translate them directly (using internal_reference as key for I18n),
  • The statuses can be tested either if they are in english, french or whatever (thanks to internal_reference,
  • You can create statuses directly in your app, without rebooting it,
  • You can set an attribute, like status_code and makes ranges (kind of like HTTP requests statuses) and group them (ex: if status_code > 100)

You can also add an boolean attribute cant_be_deleted to prevent from deleting Statuses used in the code.

You might think it is overkill to do so, but I guarantee that the day you will want to translate / add / remove / change your Statuses, it will be much easier with Models rather than Constants. Trust me, I worked for an Online shop, handling Carts, Orders and Products, I know how painful it is to change from constants to models, everywhere in your already existing code ;-)

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks! And you nailed it, I do have to translate them eventually. What exactly is internal_reference though? Is that a special attribute?
Yes, it is a special attribute kind of acting like an ID, but for humans: It must be unique, must be present, and can be used directly in your code, whatever the Locale is (allows you to do calls like Order.includes(:order_status).where(order_statuses: { internal_reference: :canceled }).first instead of Order.where(status: Order::CANCELED).first)
1

I think enumerize gem can help

You can write your code like this

class Order < ActiveRecord:Base
  extend Enumerize

  enumerize :status, in: [:incomplete, :in_progress, :shipped, :cancelled]
end

It also works perfectly with I18n.

https://github.com/brainspec/enumerize

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.