How to store and compare :symbols in an ActiveReco

2019-03-14 09:53发布

问题:

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.

回答1:

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.



回答2:

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 }


回答3:

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.



回答4:

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.



回答5:

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



回答6:

Also you can overwrite the reader method:

def status
  read_attribute(:status).to_sym
end


回答7:

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).