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

百般思念 提交于 2019-12-03 10:47:51

问题


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



来源:https://stackoverflow.com/questions/9037758/how-to-store-and-compare-symbols-in-an-activerecord-ruby-on-rails

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!