stevec
stevec

Reputation: 52967

How and where to define a method for use in the controller?

I wish to use a method in the controller:

class Hash
  def sort_by_array a; Hash[sort_by{|k, _| a.index(k) || length}] end
end

But after placing the code in the controller, I receive an error: class definition in method body

I tried removing the class Hash, and second end, and have also tried

class Hash
  def self.class.sort_by_array a; Hash[sort_by{|k, _| a.index(k) || length}] end
end

But I still can't get it to stop erroring

For reference, here is the controller:

class StaticPagesController < ApplicationController

  def main

  class Hash
    def self.class.sort_by_array a; Hash[sort_by{|k, _| a.index(k) || length}] end
  end

   @languages = Listing.group_by(&:language)
   @languages.sort_by_array(@languages)

  end

end

Upvotes: 0

Views: 85

Answers (2)

Jay-Ar Polidario
Jay-Ar Polidario

Reputation: 6603

That error occurs when you define a class inside the method of another class. i.e. you were probably doing something like below:

class SomeClass
  def some_method
    class Hash
      def sort_by_array(a)
      end
    end
  end
end

Assuming you want to extend the functionality of Hash objects by adding a method sort_by_array, then you can do monkey-patching like below:

Solution (Simple):

  • you can only define "instance" methods. If you want to define also "class" methods, see "Advanced" solution below.

lib/extensions/hash.rb

module Extensions
  module Hash
    def sort_by_array(a)
      sort_by do |k, _|
        a.index(k) || length
      end
    end
  end
end

i.e. let's say another class you want to extend functionality:

lib/extensions/active_record/base.rb

module Extensions    
  module ActiveRecord
    module Base
      def say_hello_world
        puts 'Hello World!'
      end
    end
  end
end

config/initializers/extensions.rb

Hash.include Extensions::Hash
ActiveRecord::Base.include Extensions::ActiveRecord::Base

Usage:

# rails console
some_array = [:a, :c, :b]
some_hash = { a: 1, b: 2, c: 3 }

some_hash.sort_by_array(some_array)
# => [[:a, 1], [:c, 3], [:b, 2]]


user = User.find(1)
user.say_hello_world
# => 'Hello World!'

Solution (Advanced):

  • now allows both "class" and "instance" methods to be defined:

lib/extensions/hash.rb

module Extensions
  module Hash
    def self.included(base)
      base.extend ClassMethods
      base.include InstanceMethods
    end

    # define your Hash "class methods" here inside ClassMethods
    module ClassMethods
      # commented out because not yet fully working (check update later)
      # # feel free to remove this part (see P.S. for details)
      # def self.extended(base)
      #   instance_methods.each do |method_name|
      #     raise NameError, "#{method_name} method already defined!" if (base.singleton_methods - instance_methods).include? method_name
      #   end
      # end
    end

    # define your Hash "instance methods" here inside InstanceMethods
    module InstanceMethods
      # commented out because not yet fully working (check update later)
      # # feel free to remove this part (see P.S. for details)
      # def self.included(base)
      #   instance_methods.each do |method_name|
      #     raise NameError, "#{method_name} method already defined!" if (base.instance_methods - instance_methods).include? method_name
      #   end
      # end

      def sort_by_array(a)
        sort_by do |k, _|
          a.index(k) || length
        end
      end
    end
  end
end

i.e. let's say another class you want to extend functionality:

lib/extensions/active_record/base.rb

module Extensions    
  module ActiveRecord
    module Base
      def self.included(base)
        base.extend ClassMethods
        base.include InstanceMethods
      end

      module ClassMethods
        # commented out because not yet fully working (check update later)
        # # feel free to remove this part (see P.S. for details)
        # def self.extended(base)
        #   instance_methods.each do |method_name|
        #     raise NameError, "#{method_name} method already defined!" if (base.singleton_methods - instance_methods).include? method_name
        #   end
        # end

        def say_hello_mars
          puts 'Hello Mars!'
        end
      end

      module InstanceMethods
        # commented out because not yet fully working (check update later)
        # # feel free to remove this part (see P.S. for details)
        # def self.included(base)
        #   instance_methods.each do |method_name|
        #     raise NameError, "#{method_name} method already defined!" if (base.instance_methods - instance_methods).include? method_name
        #   end
        # end

        def say_hello_world
          puts 'Hello World!'
        end
      end
    end
  end
end

config/initializers/extensions.rb

Hash.include Extensions::Hash
ActiveRecord::Base.include Extensions::ActiveRecord::Base

Usage:

# rails console
some_array = [:a, :c, :b]
some_hash = { a: 1, b: 2, c: 3 }

some_hash.sort_by_array(some_array)
# => [[:a, 1], [:c, 3], [:b, 2]]


user = User.find(1)
user.say_hello_world
# => 'Hello World!'
ActiveRecord::Base.say_hello_mars
# => 'Hello Mars!'

P.S, arguably you won't need to raise an error if a method is already defined, but this is just my personal taste to prevent "bugs" (i.e. if for example some "gems" you used also defined same exact methods name but having different functionality, of which you have no control of). Feel free to remove them though.

Upvotes: 2

Gene D.
Gene D.

Reputation: 311

Place it in a separate file. This would extend the base class Hash and would allow you to use in the whole application.

The easiest would be putting the code into config/initializers/hash.rb and restarting the server.

Or, better, similarly to how Rails does it (e.g. https://github.com/rails/rails/tree/master/activesupport/lib/active_support/core_ext):

Put your code here: lib/core_ext/hash/sort_by_array.rb

And then either add this path to autoload, or require it manually from where you'd like to use it, like this:

require "core_ext/hash/sort_by_array".

Upvotes: 1

Related Questions