简体   繁体   中英

How to store and compare :symbols in an ActiveRecord (Ruby on Rails)

I thought it would be good to populate a status field in an activeRecord table using constants. However, when it comes to checking if this status has a particular status, I'm having trouble.

If I do the following,

e = Mytable.new
e.status = :cancelled
e.save

then refind the record and try and compare my status to the symbol, the check fails. I have some output from the console to show this.

irb(main):060:0> e.status.eql?("cancelled")
=> true
irb(main):061:0> e.status.eql?(:cancelled)
=> false
irb(main):062:0> e.status == :cancelled
=> false
irb(main):063:0> e.status == "cancelled"
=> true
irb(main):064:0> e.status == :cancelled.to_s
=> true

Is there a better way of holding a status in a record? Is there a way of testing if a current field value is equal to the :symbol without converting the :symbol to a string? I'm thinking there may be an operator I'm not aware of.

With Rails 4.1.0, you'd probably want to use Active Record enums.

To quote the official release notes :

class Conversation < ActiveRecord::Base
  enum status: [ :active, :archived ]
end
 
conversation.archived!
conversation.active? # => false
conversation.status  # => "archived"
 
Conversation.archived # => Relation for all archived Conversations
 
Conversation.statuses # => { "active" => 0, "archived" => 1 }

This is kind of late, but may help someone else.

If you have classes with different statuses, you might consider an approach using constants along with scopes like this:

class Account < ActiveRecord::Base
  #-------------------------------------------------------------------------------
  # Configuration
  #-------------------------------------------------------------------------------

  # STATUS is used to denote what state the account is in.
  STATUS = { :active => 1, :suspended => 2, :closed => 3 }

  # Scopes
  scope :active, where(:status => Account::STATUS[:active])
  scope :suspended, where(:status => Account::STATUS[:suspended])
  scope :closed, where(:status => Account::STATUS[:closed])
  ...
end

Then you can easily find records based on status, like so:

# get all active accounts
active_accounts = Consumer.active.all
# get 50 suspended accounts
suspended_accounts = Consumer.suspended.limit(50)
# get accounts that are closed and [some search criteria]
closed_accounts = Consumer.closed.where([some search criteria])

Hope this helps someone else!

EDIT: If you're more into using gems, the simple_enum gem looks like an excellent alternative.

As of Rails 4.1, Active Record now supports enums

From the release notes :

2.5 Active Record enums

Declare an enum attribute where the values map to integers in the database, but can be queried by name.

class Conversation < ActiveRecord::Base
  enum status: [ :active, :archived ]
end

conversation.archived!
conversation.active? # => false
conversation.status  # => "archived"

Conversation.archived # => Relation for all archived Conversations

Conversation.statuses # => { "active" => 0, "archived" => 1 }

Additional documentation here: http://api.rubyonrails.org/v4.1.0/classes/ActiveRecord/Enum.html

If I remember well symbols in ActiveRecord are stored in yaml format, some kind of conversion must be done because there is no such thing as a symbol in relational db (which I'm aware of, at least). When you read it it's then a string which will not match your symbol and not even the string of the symbol, in fact it should be something like:

:x # => "--- :x\n"

I think this plugin can solve your problem, but I haven't used it honestly. https://github.com/zargony/activerecord_symbolize

* EDIT *

I leave the above because I remember that was the situation I had and I can be corrected if I'm wrong, nonetheless I'm trying this right now and the stored value (Rails 3.1.3) is a simple string with the value of the symbol, so the following should be enough.

class Example < ActiveRecord::Base

  def aaa
    super.to_sym
  end

  def aaa=(value)
    super(value.to_sym)
    aaa
  end

end

This of course will force the value to always be a symbol

** EDIT AFTER AGES ** I think it's fine in this situation as it's clear that in the db it's a string and the logic is simple, but I strongly discourage overriding db attribute methods to add more complex logic.

At the request of ecoologic, here is my comment as an answer:

ecoologic has a good solution for you, but I would recommend steering away from this and making a class with constants in it. That you can do things like e.status = Statuses::CANCELLED. And internally that could be a string and it doesn't matter. You're still using constants, and it will error out if that constant doesn't exist, and it's cleaner that way.

Also you can overwrite the reader method:

def status
  read_attribute(:status).to_sym
end

From Programming Ruby 1.9 , regarding the == operator in the Symbol class (p. 729):

Returns true only if sym and obj are symbols with the same object_id.

Whatever you have stored in the DB will always have different object_id than the fixed object_id of the symbol (a pointer to a string literal, in this case).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM