Although
Although

Reputation: 37

Ruby 'return' a method from another method?

In a Rails project because of some history reasons, a piece of codes in Tiger and Elephant are same.

I don't like the repetition, but if I create a new method in AnimalController class and move these codes into it, I can't return the walk or running method from the new method.

I think return from another method may not a good practice, but I really hate the duplications, can someone help me refactoring?

class AnimalController
  # I want create a new method here
  #def all_in
  #end
end

class TigerController < AnimalController
  def running  # This is an Action
    some_different_codes...

    if arm.blank?
      render_not_found
      return      # <- how can I return `running` from the new method?
    end
    if lag.nil?
      invalid_id
      return      # <- 
    end

    some_different_codes...
  end
end

class ElephantController < AnimalController
  def walk     # This is an Action
    some_different_codes...

    if arm.blank?
      render_not_found
      return
    end
    if lag.nil?
      invalid_id
      return
    end

    some_different_codes...
  end
end

Upvotes: 2

Views: 2214

Answers (2)

Richard Peck
Richard Peck

Reputation: 76784

Might be a good turn to look up about callbacks and superclassing:


Callbacks basically allow you to run login in code based on the response of another function.

Since we have them all over Rails, not many people actually appreciate what they do. If you've ever implemented them in JS, you'll know all about them!

-

Superclassing is where you inherit from an existing class - allowing you to use (& extend) the functions the class has. This is where the super command comes from.


I'd do this (looks like Sergio's answer actually, which is reassuring):

#app/controllers/animals_controller.rb
class AnimalsController < ApplicationController

   private 

   def all_in?
       if invalid_id
          return false
       end
       if lag.nil?
          invalid_id
          return false
       end
       true #-> ruby automatically returns the last line 
   end
end

The above is what you'd call a callback -- you'll be able to call all_in (in an instance) and receive a response of either true or false.

This will give you the ability to call that method (if you're superclassing, the method will be available down the chain:

#app/controllers/elephants_controller.rb
class ElephantController < AnimalController
  def walk     # This is an Action
    some_different_codes...

    if all_in?
       some_different_codes...
    end
  end
end

Now, there's something you must be aware of.

This type of behavior should not be put in the controller of your app - it should be in your models:

#app/models/animal.rb
class Animal < ActiveRecord::Base
   def walk
   end
end

#app/models/animals/elephant.rb
class Elephant < Animal
   def walk
      super ...
   end
end

#app/models/animals/tiger.rb
class Tiger < Animal
end

The above is known as STI (Single Table Inheritance). It's basically a way to subclass against a main model with other "dependent" models & their methods.

Since ruby is object orientated, you should be calling object-specific methods on the objects themselves;

#config/routes.rb
resources :tigers, :elephants, controller: :animals

#url.com/tigers/
#url.com/elephants/

#app/controllers/animals_controller.rb
class AnimalsController < ApplicationController
   def show
     @tiger = Tiger.find params[:id]
     @tiger.walk
   end
end

This is more or less epitomized with a state machine:

class Vehicle
  attr_accessor :seatbelt_on, :time_used, :auto_shop_busy

  state_machine :state, :initial => :parked do
    before_transition :parked => any - :parked, :do => :put_on_seatbelt

    after_transition :on => :crash, :do => :tow
    after_transition :on => :repair, :do => :fix
    after_transition any => :parked do |vehicle, transition|
      vehicle.seatbelt_on = false
    end

...

Upvotes: 0

Sergio Tulentsev
Sergio Tulentsev

Reputation: 230531

A method can't make its caller return, if it doesn't want to. So this new method will perform checks (while rendering something) and it will return result of the checks. Caller method analyzes the return value and decides what to do. Something along these lines:

class AnimalController
  def all_in
    if invalid_id
      render_not_found
      return false
    end

    if lag.nil?
      invalid_id
      return false
    end

    true
  end
end

class TigerController < AnimalController
  def running  # This is an Action
    some_different_codes...

    return unless all_in

    some_different_codes...
  end
end

Upvotes: 2

Related Questions