Coder Social home page Coder Social logo

classy_enum's Introduction

ClassyEnum

Build Status Gem Version Code Climate Dependency Status

ClassyEnum is a Ruby on Rails gem that adds class-based enumerator functionality to Active Record attributes.

This README is also available in a user-friendly DocumentUp format.

Rails & Ruby Versions Supported

Rails: 3.2.x - 4.2.x

Ruby: 1.9.3, 2.0.0, 2.1.x, 2.2.x, and 2.3.x

Installation

The gem is hosted at rubygems.org

Despite RailsGuides claiming that all directories under app will be autoloaded, I've had reports of this not being the case with newer versions of Ruby and Rails.

You may need to add the enums path to config/application.rb:

# Make sure classy_enum enums get loaded
config.autoload_paths += %W(#{config.root}/app/enums)

Upgrading?

See the wiki for notes about upgrading from previous versions.

Getting Started & Example Usage

The most common use for ClassyEnum is to replace database lookup tables where the content and behavior is mostly static and has multiple "types". Please see the Wiki for a short discussion on use cases comparing ClassyEnum to other gems.

In this example, I have an Active Record model called Alarm with an attribute called priority. Priority is stored as a string (VARCHAR) type in the database and is converted to an enum value when requested.

1. Generate the Enum

The fastest way to get up and running with ClassyEnum is to use the built-in Rails generator like so:

rails generate classy_enum Priority low medium high

NOTE: You may destroy/revoke an enum by using the rails destroy command:

rails destroy classy_enum Priority

A new enum template file will be created at app/enums/priority.rb that will look like:

class Priority < ClassyEnum::Base
end

class Priority::Low < Priority
end

class Priority::Medium < Priority
end

class Priority::High < Priority
end

NOTE: The class order is important because it defines the enum member ordering as well as additional ClassyEnum behavior described below.

2. Customize the Enum

The generator creates a default setup, but each enum member can be changed to fit your needs.

I have defined three priority levels: low, medium, and high. Each priority level can have different properties and methods associated with it.

I would like to add a method called #send_email? that all member subclasses respond to. By default this method will return false, but will be overridden for high priority alarms to return true.

class Priority < ClassyEnum::Base
  def send_email?
    false
  end
end

class Priority::Low < Priority
end

class Priority::Medium < Priority
end

class Priority::High < Priority
  def send_email?
    true
  end
end

3. Setup the Active Record model

My Active Record Alarm model needs a text field that will store a string representing the enum member. An example model schema might look something like:

create_table "alarms", force: true do |t|
  t.string   "priority"
  t.boolean  "enabled"
end

NOTE: Alternatively, you may use an enum type if your database supports it. See this issue for more information.

Then in my model I've included ClassyEnum::ActiveRecord and added a line that calls classy_enum_attr with a single argument representing the enum I want to associate with my model. I am also delegating the #send_email? method to my Priority enum class.

class Alarm < ActiveRecord::Base
  include ClassyEnum::ActiveRecord

  classy_enum_attr :priority

  delegate :send_email?, to: :priority
end

With this setup, I can now do the following:

@alarm = Alarm.create(priority: :medium)

@alarm.priority  # => Priority::Medium
@alarm.priority.medium? # => true
@alarm.priority.high? # => false
@alarm.priority.to_s # => 'medium'

# Should this alarm send an email?
@alarm.send_email? # => false
@alarm.priority = :high
@alarm.send_email? # => true

The enum field works like any other model attribute. It can be mass-assigned using #update_attributes.

What if your enum class name is not the same as your model's attribute name?

Just provide an optional class_name argument to declare the enum's class name. In this case, the model's attribute is called alarm_priority.

class Alarm < ActiveRecord::Base
  include ClassyEnum::ActiveRecord

  classy_enum_attr :alarm_priority, class_name: 'Priority'
end

@alarm = Alarm.create(alarm_priority: :medium)
@alarm.alarm_priority  # => Priority::Medium

Internationalization

ClassyEnum provides built-in support for translations using Ruby's I18n library. The translated values are provided via a #text method on each enum object. Translations are automatically applied when a key is found at locale.classy_enum.enum_parent_class.enum_value, or a default value is used that is equivalent to #to_s.titleize.

Given the following file config/locales/es.yml

es:
  classy_enum:
    priority:
      low: 'Bajo'
      medium: 'Medio'
      high: 'Alto'

You can now do the following:

@alarm.priority = :low
@alarm.priority.text # => 'Low'

I18n.locale = :es

@alarm.priority.text # => 'Bajo'

Using Enum as a Collection

ClassyEnum::Base extends the Enumerable module which provides several traversal and searching methods. This can be useful for situations where you are working with the collection, as opposed to the attributes on an Active Record object.

# Find the priority based on string or symbol:
Priority.find(:low) # => Priority::Low.new
Priority.find('medium') # => Priority::Medium.new

# Test if a priority is valid:
Priority.include?(:low) # => true
Priority.include?(:lower) # => false

# List priorities base strings:
Priority.map { |p| p.to_s } # => ["low", "medium", "high"]

# Find the lowest priority that can send email:
Priority.find(&:send_email?) # => Priority::High.new

# Find the priorities that are lower than Priority::High
Priority.select {|p| p < :high } # => [Priority::Low.new, Priority::Medium.new]

# Iterate over each priority:
Priority.each do |priority|
  puts priority.send_email?
end

Default Enum Value

As with any Active Record attribute, default values can be specified in the database table and will propagate to new instances. However, there may be times when you can't or don't want to set the default value in the database. For these occasions, a default value can be specified like so:

class Alarm < ActiveRecord::Base
  include ClassyEnum::ActiveRecord

  classy_enum_attr :priority, default: 'medium'
end

Alarm.new.priority # => Priority::Medium

You may also use a Proc object to set the default value. The enum class is yielded to the block and can be used to determine the default at runtime.

class Alarm < ActiveRecord::Base
  include ClassyEnum::ActiveRecord

  classy_enum_attr :priority, default: ->(enum){ enum.max }
end

Alarm.new.priority # => Priority::High

Back Reference to Owning Object

In some cases you may want an enum class to reference the owning object (an instance of the Active Record model). Think of it as a belongs_to relationship, where the enum belongs to the model.

By default, the back reference can be called using #owner. If you want to refer to the owner by a different name, you must explicitly declare the owner name in the classy_enum parent class using the .owner class method.

Example using the default #owner method:

class Priority < ClassyEnum::Base
end

# low and medium subclasses omitted

class Priority::High < Priority
  def send_email?
    owner.enabled?
  end
end

Example where the owner reference is explicitly declared:

class Priority < ClassyEnum::Base
  owner :alarm
end

# low and medium subclasses omitted

class Priority::High < Priority
  def send_email?
    alarm.enabled?
  end
end

In the above examples, high priority alarms are only emailed if the owning alarm is enabled.

@alarm = Alarm.create(priority: :high, enabled: true)

# Should this alarm send an email?
@alarm.send_email? # => true
@alarm.enabled = false
@alarm.send_email? # => false

Model Validation

An Active Record validator validates_inclusion_of :field, in: ENUM is automatically added to your model when you use classy_enum_attr.

If your enum only has members low, medium, and high, then the following validation behavior would be expected:

@alarm = Alarm.new(priority: :really_high)
@alarm.valid? # => false
@alarm.priority = :high
@alarm.valid? # => true

To allow nil or blank values, you can pass in :allow_nil and :allow_blank as options to classy_enum_attr:

class Alarm < ActiveRecord::Base
  include ClassyEnum::ActiveRecord

  classy_enum_attr :priority, allow_nil: true
end

@alarm = Alarm.new(priority: nil)
@alarm.valid? # => true

Active Record Querying

Classy Enum classes are plain Ruby objects, and Active Record does not know how to typecast them to strings when querying. Therefore you must explicitly convert the object to a string or symbol for the query to be valid.

Classy Enum versions before 4.0.0 supported querying directly with the enum objects. Suppport for this was removed in 4.0.0 because it depended on ARel internals, and maintaining backwards compatibility with older version of Rails was not possible.

Any of these are valid:

Alarm.where(priority: 'high')

Alarm.where(priority: Priority[:high].to_s)

Alarm.where(priority: Priority::High.new.to_s)

Note If you get an error like Cannot visit <Enum>, it means that ActiveRecord - or more accurately ARel - has received the Classy Enum class or instance directly, rather than a string representing corresponding to the database value. To resolve this issue, you need to convert the object to a string.

Form Usage

ClassyEnum includes a select_options helper method to generate an array of enum options that can be used by Rails' form builders such as SimpleForm and Formtastic.

# SimpleForm

<%= simple_form_for @alarm do |f| %>
  <%= f.input :priority, as: :select, collection: Priority.select_options %>
  <%= f.button :submit %>
<% end %>

Testing Enums

ClassyEnums can be tested by creating new instances of the ClassyEnum and testing expectations. For example:

class TestPriorityHigh < Minitest::Test
  def setup
    @priority_high_enum = Priority::High.new
  end

  def test_send_email_enabled
    assert @priority_high_enum.send_email?
  end
end

If the ClassyEnum method implementations rely upon the owner, the ClassyEnum#build method can be used with the owner option. For example:

class TestPriorityHigh < Minitest::Test
  def setup
    @alarm = Alarm.create
    @priority_high_enum = Priority::High.build(:high, owner: @alarm)
  end

  def test_send_email_enabled
    assert_equal @priority_high_enum.owner, @alarm
  end
end

Copyright

Copyright (c) 2010-2016 Peter Brown. See LICENSE for details.

classy_enum's People

Contributors

beerlington avatar fotanus avatar indrekj avatar inopinatus avatar jordpo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

classy_enum's Issues

Uninitialized Constant when Loading Classy Enum in 4.0.0

I added a 4.0.0 classy_enum to an active record like so (ruby: 2.1.4, rails: 4.2.0):

class Membership < ActiveRecord::Base
  include ClassyEnum::ActiveRecord
  classy_enum_attr :status, class_name: 'MembershipStatus'
  ...
end

This active record joins two other tables (User and Group), and defaults the enum value to "pending".

Attempting to load the enum from the database yields:

NameError: uninitialized constant MembershipStatus

For example, with:

Group.find(2).memberships

Downgrading to 3.5.0 without any code changes fixes the problem:

Group.find(2).memberships[0].status.text
 => "Pending"

Accessing enum in model returns String, not Enum?

Hi there,

I hope I am not missing something real simple, but I'm having an issue that makes classy_enum unusable!

Basically, my enum getters return String objects, rather than the enum class, as expected.

Here's a console excerpt that illustrates the issue:

irb(main):014:0> t = Task.new
=> #<Task id: nil, priority: nil, created_at: nil, updated_at: nil>
irb(main):015:0> t.priority = Priority::Low
=> Priority::Low
irb(main):016:0> t.save
   (0.1ms)  begin transaction
   (0.1ms)  rollback transaction
=> false
irb(main):017:0> t.errors
=> #<ActiveModel::Errors:0x007f889c033e38 @base=#<Task id: nil, priority: "Priority::Low", created_at: nil, updated_at: nil>, @messages={:priority=>["is not included in the list"]}>
irb(main):018:0> t.priority.class
=> String
irb(main):019:0> t.priority
=> "Priority::Low"

I've configured the enum just like in the example in the README... nothing fancy. At first I thought one of my other gems may have been interfering, but I've isolated this in an empty rails app (Rails 3.2.11).

enums/priority.rb:

class Priority < ClassyEnum::Base
end

class Priority::Low < Priority
end

class Priority::Medium < Priority
end

class Priority::High < Priority
end

task.rb:

class Task < ActiveRecord::Base
  classy_enum_attr :priority
end

migration:

class CreateTasks < ActiveRecord::Migration
  def change
    create_table :tasks do |t|
      t.string :priority

      t.timestamps
    end
  end
end

Best practice for name-spacing or subclassing a Classy Enum?

Imagine I had several different models having a status, say Order with statuses "open", "invoiced", "paid", and `Product' with statuses "in_stock", "ordered", "discontinued".

Currently I am appending the class name, so in app/enums I have OrderStatus and ProductStatus, then in the model, use the :enum option, like

class Product << ActiveRecord::Base
  classy_enum_attr :status, enum: 'ProductStatus'
  # and so on
end

It works (even to my delight, in my I18n files). The doc suggests use of the :enum option is a special case. Is there a better way?

And, as an aside... I have been doing Rails for years, and have come up with a number of ugly patterns that solve some of these issues. I wish I had known about your gem -- it's a thing of beauty. Thanks!

Cannot visit Priority::Medium

I have an scope like this:

  class << self
    def my_scope
      where(:priority => Priority[:medium])
    end
  end

When I call only the scope, that's ok. But when I use count appended to the scope, it gives me an error like this: Cannot visit Priority::Medium.

user.tasks.my_scope # works
user.tasks.my_scope.count # Cannot visit Priority::Medium

But if I change the scope to where(:priority => Priority[:medium].to_s) it works with both queries. I don't know why it's not being interpreted as a String, any clue?

Thanks.

validation failed: ... not included in the list (Rails 4)

In Rails 4, with this model

class Player < ActiveRecord::Base
  classy_enum_attr :strategy
end

results in this error

$ ModelName.create!
(0.1ms) begin transaction
(0.1ms) rollback transaction
ActiveRecord::RecordInvalid: Validation failed: Strategy is not included in the list

However, this works fine when we specify a parameter:

class Player < ActiveRecord::Base
  classy_enum_attr :strategy, default: 'offensive'   #or serialize_as_json: true
end

:(

Rails 4.1 enums

Hi,

I have some questions for discussion:

  1. What are the advantages of keep using classy_enum after rails 4.1 introduced enums?
  2. How easy would be to migrate?
  3. Should we stop using class enum if working with rails 4.1+? Why not?

Maybe the readme should answer this questions from now on.

Thanks for the great work you did on this gem so far!

Question: Is this the right fit for my usecase?

So I have two places where I think this would work well. We have integrations for Twitter, Facebook and Instagram. Currently we're storing these things in an STI-ish table... but we also have a table of "types" that we update when we add a new type.

For programatic things, it's strange to store that kind of thing in a DB table, since it would essentially break if the table didn't have an entry.

Another instance is with "types" of fields... like we're going to have people choose between "image, video, color, text", and depending on the type, when you go to edit, it would display a different kind of field.

Are these good usecases for classy_enum? I'm trying to figure out the dividing line between STI and enums.

Rails 3.1.0

classy_enum is not compatible with Rails 3.1.0.

undefined method `is?' for portrait:OrientationTypePortrait

destroyer destroy folder when not empty

Thanks for the wonderful gem. Can't image myself living without it right now.

The title says it all. You can see the following output:

# All enums are there
$ ls app/enums
delivery_method.rb  job_state.rb  service_kind.rb

# Creating a new enum
$ rails generate classy_enum PaymentProcessor bank_deposit
       exist  app/enums
      create  app/enums/payment_processor.rb
      invoke  rspec
       exist    spec/enums
      create    spec/enums/payment_processor_spec.rb

# Ops, typo, let me destroy it
$ rails destroy classy_enum PaymentProcessor bank_deposit
      remove  app/enums
      remove  app/enums/payment_processor.rb
      invoke  rspec
      remove    spec/enums
      remove    spec/enums/payment_processor_spec.rb

# omg, where are my enums?!?!
$ ls app/enums
ls: cannot access app/enums: No such file or directory

Add Option to Store Integer Instead of String

I would like to be able to order my classy_enums by index in my database queries. Would it make sense to add an option to specify that classy_enums should be persisted as integers? For example:

classy_enum_attr :priority, :persist_as => :integer

How to update many records at once?

I'm using your gem and trying to do this:

 jobs.update_all job_state: JobState::WaitingTranslation

but it throws an error. is possible to make it works to update multiple records?

Problem with default value

When run in rails console, It's normal do happen this?

Point.new
NoMethodError: super: no superclass method `transaction_type=' for #Point:0x00000101913578

My Point definition

class Point < ActiveRecord::Base
    include ClassyEnum::ActiveRecord
  classy_enum_attr :transaction_type, default: 'issuance'
    delegate :do, to: :transaction_type
  belongs_to :loyalty
end
}
class TransactionType < ClassyEnum::Base
    owner :point
end

class TransactionType::Issuance < TransactionType
    def do
        point.loyalt.balance+=point.val
    end 
end

class TransactionType::Redemption < TransactionType
    def do
        point.loyalt.balance-=point.val
    end 
end

I'm following the README file and this should work..

May someone help me?

Changing what's returned by #select_options

I have an enum for programming languages, and one of the options is C++; I call it Cpp since + cannot be in a class name. Calling #select_options in a view gives a select option shown to the user as Cpp, but I'd prefer if it could instead return C++.

A solution I found for this was to modify my config/locales/en.yml file to make cpp be associated with the string "C++", but I'm not sure if that's the "correct" way to do it. I'm not terribly familiar with i18n or the inner workings of classy_enum, so forgive me if this is a dumb question.

Stack overflow when using classy_enum in the render function

Hi, I'm seeing some weird behavior that I can't explain and it seems to be related to classy_enum.
In my model I have:

class Message < ActiveRecord::Base
  classy_enum_attr :message_type

And I have the required enums and everything and it seems to work great. (I've used classy_enum in the past, so it's not my first enum. Other times didn't render a model with enums though)

The problem is that when I want to render a @message in the controller I get a stack overflow and sometimes (but not always) a complete server crash with class_enum in the stack

Users/ran/.rvm/gems/ruby-1.9.2-p290/gems/classy_enum-1.3.0/lib/classy_enum/class_methods.rb:58:in `const_get': uninitialized constant MessageTypeUser (NameError)
    from /Users/ran/.rvm/gems/ruby-1.9.2-p290/gems/classy_enum-1.3.0/lib/classy_enum/class_methods.rb:58:in `build'
    from /Users/ran/.rvm/gems/ruby-1.9.2-p290/gems/classy_enum-1.3.0/lib/classy_enum/attributes.rb:39:in `block (2 levels) in classy_enum_attr'
    from /Users/ran/dev/invi-backend/app/models/message.rb:16:in `to_s'
    from /Users/ran/dev/invi-backend/lib/messaging.rb:22:in `block in send'
    from /Users/ran/.rvm/gems/ruby-1.9.2-p290/gems/eventmachine-0.12.10/lib/em/deferrable.rb:141:in `call'
    from /Users/ran/.rvm/gems/ruby-1.9.2-p290/gems/eventmachine-0.12.10/lib/em/deferrable.rb:141:in `set_deferred_status'
    from /Users/ran/.rvm/gems/ruby-1.9.2-p290/gems/eventmachine-0.12.10/lib/em/deferrable.rb:180:in `fail'
    from /Users/ran/.rvm/gems/ruby-1.9.2-p290/gems/pusher-0.8.3/lib/pusher/channel.rb:51:in `block in trigger_async'
    from /Users/ran/.rvm/gems/ruby-1.9.2-p290/gems/eventmachine-0.12.10/lib/em/deferrable.rb:141:in `call'
    from /Users/ran/.rvm/gems/ruby-1.9.2-p290/gems/eventmachine-0.12.10/lib/em/deferrable.rb:141:in `set_deferred_status'
    from /Users/ran/.rvm/gems/ruby-1.9.2-p290/gems/eventmachine-0.12.10/lib/em/deferrable.rb:180:in `fail'
    from /Users/ran/.rvm/gems/ruby-1.9.2-p290/gems/em-http-request-0.3.0/lib/em-http/client.rb:310:in `unbind'
    from /Users/ran/.rvm/gems/ruby-1.9.2-p290/gems/eventmachine-0.12.10/lib/eventmachine.rb:1417:in `event_callback'
    from /Users/ran/.rvm/gems/ruby-1.9.2-p290/gems/eventmachine-0.12.10/lib/eventmachine.rb:256:in `run_machine'
    from /Users/ran/.rvm/gems/ruby-1.9.2-p290/gems/eventmachine-0.12.10/lib/eventmachine.rb:256:in `run'
    from /Users/ran/.rvm/gems/ruby-1.9.2-p290/gems/thin-1.2.11/lib/thin/backends/base.rb:61:in `start'
    from /Users/ran/.rvm/gems/ruby-1.9.2-p290/gems/thin-1.2.11/lib/thin/server.rb:159:in `start'
    from /Users/ran/.rvm/gems/ruby-1.9.2-p290/gems/rack-1.2.3/lib/rack/handler/thin.rb:14:in `run'
    from /Users/ran/.rvm/gems/ruby-1.9.2-p290/gems/rack-1.2.3/lib/rack/server.rb:217:in `start'
    from /Users/ran/.rvm/gems/ruby-1.9.2-p290/gems/railties-3.0.10/lib/rails/commands/server.rb:65:in `start'
    from /Users/ran/.rvm/gems/ruby-1.9.2-p290/gems/railties-3.0.10/lib/rails/commands.rb:30:in `block in <top (required)>'
    from /Users/ran/.rvm/gems/ruby-1.9.2-p290/gems/railties-3.0.10/lib/rails/commands.rb:27:in `tap'
    from /Users/ran/.rvm/gems/ruby-1.9.2-p290/gems/railties-3.0.10/lib/rails/commands.rb:27:in `<top (required)>'
    from script/rails:6:in `require'
    from script/rails:6:in `<main>'

It's hard to say what exactly is causing this.... but when I remove the classy enum definition in the model the crash doesn't happen.

Does this ring a bell? Have you any idea why is this or how to solve that?

thanks!

CircularReferenceError with rails 'to_json' method

Hi,

I added an enum to my (working) class, and now i get CircularReferenceError when i try to convert my instance into JSON.

By default class is serialized with the enum, which is serialized with the owner... which is serialized with the enum :-)

I think the generator (or the base class) should override as_json to except the owner attribute.

I did this into by base enum class:
def as_json(options = {})
{:sym => to_s, :name => name}
end

Update the readme to include a step about autoload_paths

I followed the steps in the readme to create an enum for Gender, however I kept getting the following error after updating my model:

NameError: uninitialized constant Gender

I ended up adding the following line to application.rb:

config.autoload_paths += %W(#{config.root}/app/enums)

For the record, I'm using Rails 4.1.6 and Ruby 2.2.0.

Should this step be in the Readme or is it assumed that we should know to update the paths?

Issue on 3.1.3 with loading of class constants and subdirectories

Hey,

Originally, I had my enums all in one file. However, this caused a problem in dev env because classes would be reloaded but not the subclasses. I think this is because rails only reloads based upon file names and doesn't know there are more classes defined in that file.

So I moved the sub enums into app/models/job_types/. JobType is my enum class and added app/models/job_types to the autoload directory in application.rb

However, this still causes more problems because the subclasses are still not loaded at all. However, the class constants are there because of the behavior in class_methods.rb#enum_classes methods where a class constant is assigned.

So my theory is that when rails goes to load the file, it sees an existing class constant for the file and skips loading of the file.

Interestingly, if I namespace the enum to the name of the directory, job_type...so classes are defined as JobType::JobTypeWebsite < JobType, and I access it JobType::JobTypeWebsite, it works.

The assigning of the class constant should optional or the behavior should change whether you explicitly define subclasses or not.

I'm going to fork this repo and work on it and get back to you.

Wierd failure when using with jruby

Hello,

I'm using gem version 3.4.0 and on ruby 2.0 it works well, but with jruby 1.7.11 fails.

Here is my app/enums/status.rb:

class Status < ClassyEnum::Base
end

class Status::Started < Status
end

class Status::Running < Status
end

class Status::Failed < Status
end

class Status::Complete < Status
end

Here is a stack trace.

NoMethodError: super: no superclass method `visit_Status_Started'
              inherited at /home/craw/.rvm/gems/jruby-1.7.11/gems/classy_enum-3.4.0/lib/classy_enum/base.rb:72
                 (root) at /home/craw/app/app/enums/status.rb:4
                require at org/jruby/RubyKernel.java:1085
                require at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:251
        load_dependency at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:236
                require at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:251
                 (root) at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:1
        require_or_load at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:359
  load_missing_constant at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:502
                   each at org/jruby/RubyArray.java:1613
          const_missing at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:192
          const_missing at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:190
                   each at org/jruby/RubyArray.java:1613
            constantize at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/inflector/methods.rb:230
            constantize at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/inflector/methods.rb:229
            constantize at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/core_ext/string/inflections.rb:54
       classy_enum_attr at /home/craw/.rvm/gems/jruby-1.7.11/gems/classy_enum-3.4.0/lib/classy_enum/active_record.rb:56
                  Batch at /home/craw/app/app/models/batch.rb:3
                require at org/jruby/RubyKernel.java:1085
                require at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:251
        load_dependency at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:236
                require at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:251
                 (root) at /home/craw/app/app/models/batch.rb:1
                 (root) at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:1
        require_or_load at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:359
                   each at org/jruby/RubyArray.java:1613
  load_missing_constant at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:502
          const_missing at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:192
                   each at org/jruby/RubyArray.java:1613
          const_missing at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:190
            constantize at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/inflector/methods.rb:230
            constantize at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/inflector/methods.rb:229
       safe_constantize at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/inflector/methods.rb:260
       safe_constantize at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/core_ext/string/inflections.rb:66
    _default_wrap_model at /home/craw/.rvm/gems/jruby-1.7.11/gems/actionpack-3.2.16/lib/action_controller/metal/params_wrapper.rb:152
  _set_wrapper_defaults at /home/craw/.rvm/gems/jruby-1.7.11/gems/actionpack-3.2.16/lib/action_controller/metal/params_wrapper.rb:169
              inherited at /home/craw/.rvm/gems/jruby-1.7.11/gems/actionpack-3.2.16/lib/action_controller/metal/params_wrapper.rb:133
              inherited at /home/craw/.rvm/gems/jruby-1.7.11/gems/actionpack-3.2.16/lib/abstract_controller/railties/routes_helpers.rb:7
                require at org/jruby/RubyKernel.java:1085
                require at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:251
        load_dependency at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:236
                require at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:251
              inherited at /home/craw/.rvm/gems/jruby-1.7.11/gems/actionpack-3.2.16/lib/action_controller/railties/paths.rb:7
                 (root) at /home/craw/app/app/controllers/batch_controller.rb:1
                 (root) at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:1
        require_or_load at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:359
                   each at org/jruby/RubyArray.java:1613
              depend_on at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:313
                   each at org/jruby/RubyArray.java:1613
     require_dependency at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:225
            eager_load! at /home/craw/.rvm/gems/jruby-1.7.11/gems/railties-3.2.16/lib/rails/engine.rb:444
          instance_exec at org/jruby/RubyBasicObject.java:1565
                    run at /home/craw/.rvm/gems/jruby-1.7.11/gems/railties-3.2.16/lib/rails/initializable.rb:30
            eager_load! at /home/craw/.rvm/gems/jruby-1.7.11/gems/railties-3.2.16/lib/rails/engine.rb:443
                   each at org/jruby/RubyArray.java:1613
            eager_load! at /home/craw/.rvm/gems/jruby-1.7.11/gems/railties-3.2.16/lib/rails/engine.rb:441
               Finisher at /home/craw/.rvm/gems/jruby-1.7.11/gems/railties-3.2.16/lib/rails/application/finisher.rb:53
       run_initializers at /home/craw/.rvm/gems/jruby-1.7.11/gems/railties-3.2.16/lib/rails/initializable.rb:55
       run_initializers at /home/craw/.rvm/gems/jruby-1.7.11/gems/railties-3.2.16/lib/rails/initializable.rb:54
                require at org/jruby/RubyKernel.java:1085
                require at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:251
        load_dependency at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:236
                require at /home/craw/.rvm/gems/jruby-1.7.11/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:251
            initialize! at /home/craw/.rvm/gems/jruby-1.7.11/gems/railties-3.2.16/lib/rails/application.rb:136
         method_missing at /home/craw/.rvm/gems/jruby-1.7.11/gems/railties-3.2.16/lib/rails/railtie/configurable.rb:30
                require at org/jruby/RubyKernel.java:1085
                 (root) at script/rails:6

I see you're supporting only CRuby, but probably can help.

#build --> #find?

It seems #find is a significantly better name for what #build is actually doing. Why the change from #find to #build?

"rails destroy classy_enum EnumName" deletes all enums

This command is not in the documentation but I decided to give it a try anyway and at first glance I thought it had worked. It turned out afterwards, however, that it had deleted all enums.

$ rails destroy classy_enum Level
remove app/enums
remove app/enums/level.rb
invoke rspec
remove spec/enums
remove spec/enums/level_spec.rb

It would be nice if it behaved as expected and only deleted the one whose name was provided in the end of the command, following the pattern of "rails destroy controller controler_name" etc.

Clean up specs

The specs haven't been refactored in many years, time for an overhaul.

Clean up how code is injected into Active Record

Need to figure out what the best practices are these days for gems that extend Active Record. Injecting it in all models feels dirty. Maybe it's time to require users to explicitly include it in models.

Rails 5.x update

Hello there 😄

I was wondering if there are any plans on updating the gem to Rails 5.x and Ruby 2.4.x? 🤔
Or is it compatible already?

Also, what is the status of this gem, it's still being maintained?

I found the concept interesting for state machines and general enums (used in two projects some time ago, one from real life and one pet project) 😆

Mongoid support

I really like this gem, but it works only with ActiveRecord. Mongoid support would be excellent :)

Adding subclasses at runtime

Hi there,

To save myself some tedious typing, I decided to do a little bit of metaprogramming to generate my subclasses, like so:

[0.13, 0.25, 0.5, 0.75, 1, 1.5, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30].each do |num|
    num_str = num.to_s.sub(/\./, '_')
    cls = Class.new(ContaminantDepth) do
        define_method :value do
            num
        end
    end
    ContaminantDepth.const_set("I#{num_str}", cls)
end

I do realize it may seem a little crazy to validate a range of numbers this way, but it is truly an enum, and I need special behaviour for some of these value, so I think this fits.

So it seems to work fine at first glance. However, doing a "ContamimantDepth.all" tells me that these dynamically-generated classes are not added to the enum list, and thus they do now validate in the active record column.

I think it may come down to this here in base.rb:

class << self
      def inherited(klass)
        return if klass.anonymous? # THIS ONE

        if self == ClassyEnum::Base
          klass.base_class = klass
        else

Can you point me in the right direction on this? It seems this was put in as a bug fix, but it may be blocking a legitimate use.

Thanks!

Classy_enum with actual enum fields?

Hey man, long time user of classy_enum, awesome work!

I'm working in rails 4.2 and pg, and I have a field that I want to just call an enum right at the database level since such a thing is possible now. Could you maybe write a quite how-to in the readme about how to use that instead of a string field?

Feature request: Better Mongoid support :)

class Property
  include Mongoid::Document

  classy_enum_attr        :type, :enum => 'PropertyType'
  classy_enum_attr        :furnished

super: no superclass method `type=' for #Property:0x007fb8fb25cb30

I have to declare the fields first! I thought it would be logical if it declares the String fields as a sideeffect?

  field :type,            type: String
  field :furnished,       type: String

  classy_enum_attr        :type, :enum => 'PropertyType'    
  classy_enum_attr        :furnished

Alternatively this should be possible with Mongoid using custom fields:

  field :type,   type: Enum, :enum => 'PropertyType'

Really cool :)

BTW: I still think macros are nice when you simply want a list of strings that are allowed and not a class with functions. You can always convert it into a real class if the need arises...

As a final note: In the *-mongoid gem, it looks real bad that the Mongoid::Document includes an ActiveRecord module. Why not extract the small AR part from Base into a separate module. Then call this module to do post config for that Orm if needed. Something like the following should work:

class Base
  def self.inherited(base)
     # stuff...

     orms = ClassyEnum::Orms.orms || []
     orms.each do |orm|
       "ClassyEnum::#{orm.to_s.camelize}::Config".constantize.config klass
     end

    # more stuff
  end
end

class ClassyEnum::ActiveRecord::Config
  def self.config klass
    Arel::Visitors::ToSql.class_eval do
      define_method "visit_#{klass.name.split('::').join('_')}", lambda {|value| quote(value.to_s) }
    end
  end
end

class ClassyEnum::Orms
   # orms that require special config
   def self.orms
      [:active_record] if defined?(Arel::Visitors::ToSql)
   end
end

Drop support for Rails 3.x

Rails 4.2 is right around the corner, no need to support this many versions of Rails. Not sure if 3.2 needs to be supported, but it's almost 3 years old.

ClassyEnum build method performance

I was profiling some code in my app and found that build method is based on find method to search the Enum that comes from the database.

In my case, one of the enums has 14 different types, and for a specific use case where I instantiate about 2000 records from the database, each one with several classy_enum fields the current implementation is noticeable slow.

I implemented an additional find method only to use it in a benchmark. I'm not suggesting replacing the current implementation with this. It´s only used as POC.

class ClassyEnum::Base
  def self.find_fast(key = nil)
    @@found ||= Hash.new
    @@found[key] ||= begin
                       if block_given?
                         find(key, &block)
                       else
                         find { |e| e.to_s == key.to_s }
                       end
                     end
  end
end

And then run a benchmark:

Warming up --------------------------------------
find          5.548k i/100ms
find_fast   247.291k i/100ms
Calculating -------------------------------------
find          57.654k (± 1.6%) i/s -    288.496k in   5.005099s
find_fast   2.509M (± 3.9%) i/s -     12.612M in   5.036393s

Comparison:
find_fast:  2508639.9 i/s
find:    57654.4 i/s - 43.51x  (± 0.00) slower

I think that something like this is a huge improvement and also saves a lot of cpu cycles.

Also, in the current implementation, I think there is a map call that can be removed

      def find(key=nil)
        if block_given?
          super
        elsif map(&:to_s).include? key.to_s
          super { |e| e.to_s == key.to_s }
        end
      end

This is the same, no need to map and test for include?, or maybe I don't know something

      def find(key=nil)
        if block_given?
          super
        else
          super { |e| e.to_s == key.to_s }
        end
      end

Thanks for this great gem! 😃

Undocumented breaking change (Cannot visit <Enum>)

I noticed that 963ac91 removed some visitor support and changed the specs. I upgraded to the 4.0 gem version and am seeing that lines similar to Dog.where(:breed => Breed.build('golden_retriever')) now error. I didn't see any mention of that change in the CHANGELOG, but maybe I'm missing something?

Question: Using classy enum to query with AR?

I'm a bit irritated on how to use classy enum correctly in some cases.

Given the following enum definitions:

class House::PictureType < ClassyEnum::Base; end

class House::PictureType::Summer < House::PictureType; end
class House::PictureType::Winter < House::PictureType; end

I cannot use classy enum in AR queries like this:

Pictures.where(kind: House::PictureTypes::Summer)

Do I understand correctly that the right way to do this is to instantiate a enum and call #to_s?

Pictures.where(kind: House::PictureTypes::Summer.new.to_s)

This feels very wired, mainly because I think of enums as constant types (value objects) and it makes no sense to instantiate them. Also, is there a "better" way to get the enums value representation then calling #to_s? Why can't I call House::PictureTypes::Summer.to_s directly as every instance of it will always return summer (AFAIK)

2 tests failing - extra id parameter in AR?

Clone, bundle, rspec. Two tests failed?

There's an id extra. Isn't that how AR should behave? adding id automatically? (I don't know much about AR, so correct me if I'm wrong)

bundle exec rspec                                                      !352
-- create_table(:dogs, {:force=>true})
   -> 0.0137s
-- create_table(:other_dogs, {:force=>true})
   -> 0.0006s
-- create_table(:allow_blank_breed_dogs, {:force=>true})
   -> 0.0005s
-- create_table(:allow_nil_breed_dogs, {:force=>true})
   -> 0.0005s
-- create_table(:active_dogs, {:force=>true})
   -> 0.0006s
-- create_table(:cats, {:force=>true})
   -> 0.0005s
-- create_table(:other_cats, {:force=>true})
   -> 0.0005s
-- initialize_schema_migrations_table()
   -> 0.0006s
-- assume_migrated_upto_version(1, ["db/migrate"])
   -> 0.0002s
...........................FF.....................................

Failures:

  1) Cat should correctly serialize without the owner reference
     Failure/Error: abyssian.to_json.should == "{\"cat\":{\"breed\":\"abyssian\"}}"
       expected: "{\"cat\":{\"breed\":\"abyssian\"}}"
            got: "{\"cat\":{\"breed\":\"abyssian\",\"id\":null}}" (using ==)
     # ./spec/classy_enum_owner_reference_spec.rb:34:in `block (2 levels) in <top (required)>'

  2) Cat should convert the enum to a string when serializing
     Failure/Error: persian.to_json.should == "{\"other_cat\":{\"breed\":{}}}"
       expected: "{\"other_cat\":{\"breed\":{}}}"
            got: "{\"other_cat\":{\"breed\":{},\"id\":null}}" (using ==)
     # ./spec/classy_enum_owner_reference_spec.rb:38:in `block (2 levels) in <top (required)>'

Finished in 0.23622 seconds
66 examples, 2 failures

Failed examples:

rspec ./spec/classy_enum_owner_reference_spec.rb:33 # Cat should correctly serialize without the owner reference
rspec ./spec/classy_enum_owner_reference_spec.rb:37 # Cat should convert the enum to a string when serializing

Add support for validation :on and :if options

As it stands, classy_enum_attr macro isn't very flexible, and if I want to customize validation a bit with if and on options, I have to monkey_patch method and delete validation defined by the gem. I guess something like validate_on: and validate_if options for this method would be pretty useful and easy to add, I could prepare a PR if you don't mind.

TypeError for classy_enum after upgrading Rails from 3.0.1 to 3.0.7

I've just upgraded rails on my development mac from 3.0.1 to 3.0.7. Tried to run rspec (2.6.0) (with factory_girl_rails 1.0.1) and got errors in all tests where models use classy_enum (0.9.1) gem for status implementation. Rails itself runs without problems, but rspec tests fails. Have no idea what causes the error.

Posted to stackoverflow with code examples:
http://stackoverflow.com/questions/6464221/typeerror-for-classy-enum-after-upgrading-rails-from-3-0-1-to-3-0-7

Enum by default are empty in DB?

I'm using classy_enum on my model, like this:

classy_enum_attr :state, enum: 'MyModelState', default: :open

However when I do:

m = MyModel.create

If i search on the DB the field is NULL, not open.

If I do

m.state # #<MyModelState::Open:0x007fb46ac77bf0

But on DB is null, the issue with this is that this is not working

MyModel.where(state: [:open, :error_submitting])

validates_uniqueness_of in Rails 3.1

I know you've listed this as a known issue, but it's now a major blocker for me :(

triggering validations on my model that uses validates_uniqueness_of for an enum column causes this error:

TypeError: Cannot visit ProviderTwitter
from /Users/bradrobertson/.rvm/gems/ruby-1.9.3-p0@influitive/gems/arel-2.2.1/lib/arel/visitors/visitor.rb:25:in `rescue in visit'

Eager loading with inludes() returns String, not Enum

Here my three classes :

class Bicycle < ActiveRecord::Base
  has_many :ratings, class_name: "BicycleRatings"
  has_many :parts, through :ratings
end
class BicycleRating < ActiveRecord::Base
  belongs_to :bicycle
  belongs_to :part, class_name: "BicyclePart", foreign_key: :bicycle_part_id
  classy_enum_attr :condition, default: :usable
end
class BicyclePart < ActiveRecord::Base
  has_many :ratings, class_name: "BicycleRatings"
  has_many :bicycles, through: :ratings
end

Here what I have when I try to get the condition of a bicycle part by eager loading the ratings :

pry(main)> Bicycle.first.parts.includes(:ratings).select( "bicycle_ratings.condition" ).first.condition
  Bicycle Load (0.4ms)  SELECT "bicycles".* FROM "bicycles" ORDER BY "bicycles"."id" ASC LIMIT 1
  BicyclePart Load (0.3ms)  SELECT bicycle_ratings.condition FROM "bicycle_parts" INNER JOIN "bicycle_ratings" ON "bicycle_parts"."id" = "bicycle_ratings"."bicycle_part_id" WHERE "bicycle_ratings"."bicycle_id" = ? ORDER BY "bicycle_parts"."id" ASC LIMIT 1  [["bicycle_id", 1]]
=> "usable"
pry(main)> condition.class
=> String

Condition::Usable is expected. I don’t know how to get it :-)

Docs on Owner

It appears that the docs are incorrect regarding owner. I have two models using the same classy_enum. Without defining owner I am able to call owner on the object and get the parent class. So it appears you do not have to explicitly define the owner.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.