Monti
Monti

Reputation: 653

Incrementing enums in Ruby?

Say I have enum stage [:one, :two, :three, :four] in a model.

When the controller action next_stage is called (button clicked by the user to send it to the next stage), I want to go incrementally from stage X to Y. What's the easiest way to do this? I currently use a big, gross case statement, but I feel like I can do it better. I'll provide my use-case:

Class MyController
  def next_stage
    # @my_controller.stage => "two"
    @my_controller.stage.value++ unless @my_controller.stage.four?
    # @my_controller.stage => "three"
  end
end

Upvotes: 2

Views: 1053

Answers (6)

ShallmentMo
ShallmentMo

Reputation: 449

I don't know whether I get this right. Maybe you can try this:

    Class MyController
      def next_stage
        @my_controller.stage.increment! :value rescue nil
      end
    end

Upvotes: 0

Cyril Duchon-Doris
Cyril Duchon-Doris

Reputation: 13949

There exists a gem simple_enum which lets you use more powerful enums.

The enum will let you use both integers and/or symbols, then you can do something like

@my_controller.stage.value_cd += 1 unless @my_controller.stage.last_stage?

You can use it with ActiveRecord/Mongoid, but also any other object.

For an ORM like Mongoid, your model could look like this

class Project
  as_enum :status, {
        idea: 0, 
        need_estimate: 1,
        in_progress: 2,
        finished: 3,
        paid: 4,
        field: { type: Integer, default: 0 }

  def next_step!
    self.status_cd += 1 unless self.paid?
  end
end

project = Project.new
project.status # => :idea
project.need_estimate? #=> false
project.next_step!
project.need_estimate? #=> true

Upvotes: 0

Anko
Anko

Reputation: 1332

Honestly, if you're trying to store state that moves in a certain order, you should use a state machine. https://github.com/aasm/aasm supports using enums to store the state. You could do something like this;

aasm :column => :stage, :enum => true do
  state :stage1, :initial => true
  state :stage2
  state :stage3
  state :stage4

  event :increment_stage do
    transitions from: :stage1, to: :stage2
    transitions from: :stage2, to: :stage3
    transitions from: :stage3, to: :stage4
  end
end

it not only cleans up your logic, but the tests will be simpler, and you can do all sorts of callbacks on different events. It's really good for any sort of workflow as well (say moving a post from review to approved etc.)

EDIT: Can I also suggest that if you don't want to use a state machine then you at least move this state shifting logic into your model? Controllers are about access control, models are about state.

Upvotes: 4

Beartech
Beartech

Reputation: 6411

NEW ANSWER

Ok, I think I better understand what you are looking for. How about this:

In your model set up your Enum with a hash rather than an array:

class MyClass < ActiveRecord::Base
  enum stage: {one: 1, two: 2, three: 3, four: 4} #just makes more sense when talking about stages
end

Then you can use the current status' index:

Class MyController
  def next_stage
    # @my_controller.stage => "two"
    current_stage = MyClass.stages[@my_controller.stage] # returns => 1
    current_stage += 1 unless current_stage == 4   # returns => 2
    @my_controller.update! stage: current_stage 
    # @my_controller.stage => "three"
  end
end

If I understand the docs correctly, this may also work:

Class MyController
  def next_stage
    # @my_controller.stage => "two"
    @my_controller.update! stage: MyClass.stages[@my_controller.stage] + 1 unless @my_controller.stage == :four 
    # @my_controller.stage => "three"
  end
end

this is off the cuff and could probably be cleaned up in some ways. I can't experiment very much because I don't have something setup in a rails app with an enum to mess around with.

old answer left for archival purposes.

def next_stage
  self.next
end

edit I saw enums and thought you were shortening enumerable to enum (as in x.to_enum). Not so sure this won't work for you in some form. You asked for something other than an ugly case statement. You could use an enumerator that takes the current enum from that column to set the starting point, and the end point is 4 (or :four depending on how you write it) and have the rescue at the end of the enumerator return your max value.

Upvotes: -1

mrodrigues
mrodrigues

Reputation: 1082

Your code seems a bit strange, what is @my_controller? What is stage?

Besides that, if I understand correctly, what you want is this:

@my_controller[:stage] += 1 unless @my_controller.four?

Enumerations are stored as integers in the database, and they start at 0, with increments of 1. So, simply access the raw attribute data and increment it.

Upvotes: 0

Jared
Jared

Reputation: 1184

Sort of hackish but the only way to get an integer out of an enum that I have found is doing model.read_attribute('foo') So you could try to do

def next_stage
  @my_controller.update_column(:stage, @my_controller.read_attribute('stage')+1) unless @my_controller.four?
end

Upvotes: 3

Related Questions