0

I have a module that defines some methods and which I include in some Rails models. I want the models where the module is included to have some specific methods defined (as if I want them to implement an interface).

However the problem arises when the I want the models to have a method that turns out to be an attribute that active record already defined.

My code is like as follows:

Module Mod
  included do
    # some code...
    # require some methods to be implemented by including class
    def do_x
      raise('not implemented, please override me')
    end

    def name
      raise('not implemented, please override me')
    end
  end
end

class Supplier < ApplicationRecord
  # already has a name attribute
  include Mod

  # this overrides do_x of mod
  def do_x
    # some code...
  end
end

The name method of the Supplier class actually get overridden by that of the module Mod, which is not the behavior I want.

1 Answers1

0
Module Mod
  # some code...  (within an included block)
  # require some methods to be implemented by including class
  def do_x
    raise('not implemented, please override me')
  end

  def name
    raise('not implemented, please override me')
  end
end

class Supplier < ApplicationRecord
  include Mod

  # this overrides do_x of mod
  def do_x
    # some code...
  end
end

removing those from the included block seemed to work for me.

https://api.rubyonrails.org/classes/ActiveSupport/Concern.html#method-i-included explains "Evaluate given block in context of base class, so that you can write class macros here. When you define more than one included block, it raises an exception." It is typically used for defining relations, scopes, validations

A related and helpful Stackoverflow question and answer: Ruby modules - included do end block , specifically, https://stackoverflow.com/a/28009847/2526423

=======

Working example, locally:

irb(main):070:0> Author.create!
   (0.1ms)  SELECT sqlite_version(*)
   (0.1ms)  begin transaction
  Author Create (0.5ms)  INSERT INTO "authors" ("created_at", "updated_at") VALUES (?, ?)  [["created_at", "2020-03-29 22:06:28.202124"], ["updated_at", "2020-03-29 22:06:28.202124"]]
   (0.8ms)  commit transaction
=> #<Author id: 2, name: nil, year: nil, created_at: "2020-03-29 22:06:28", updated_at: "2020-03-29 22:06:28", uid: nil>
irb(main):071:0> author.name
=> nil
irb(main):072:0> author.do_x
class: do_x
=> nil

app/models/author.rb

require 'do_x_mod'

class Author < ApplicationRecord
  include DoXMod

  def do_x
    puts "class: do_x"
  end
end

lib/do_x_mod.rb

module DoXMod
  # included do
    # some code...
    # require some methods to be implemented by including class
    def do_x
      puts "module: do_x"
      raise('not implemented, please override me')
    end

    def name
      puts "module: name"
      raise('not implemented, please override me')
    end
  # end
end
avimoondra
  • 886
  • 5
  • 9
  • Putting `do_x` and `name` outside the included block didn't solve the problem for me. The `name` method still raises the exception when I try to call it on a `Supplier` instance. – Abdelrahman Abdelnabi Mar 29 '20 at 23:19
  • @AbdelrahmanAbdelnabi could you provide the Supplier model and migration? And the exact stack trace? What version of Rails are you on? Do you agree that the example provided (albeit different names), has the expected behavior? – avimoondra Mar 30 '20 at 04:06