Home > Blockchain >  Rails Concern class_macro with params from inherited class
Rails Concern class_macro with params from inherited class

Time:02-12

This is probably a really easy question but I'd like to have a concern which adds a validation whose parameters depend on methods in the derived class. For instance, I'd like to have a concern SlugHelpers as follows

module SlugHelpers
  extend ActiveSupport::Concern


  included do
     validates :slug, uniqueness: { case_sensitive: false, message: "Slug must be unique", scope: slug_scope }, presence: true,

  end

  class_methods do
    def slug_scope
      []
    end
  end

end

But then have a model Post which overrides slug_scope, e.g.,

class Post < ApplicationRecord
  include SlugHelpers

  def self.slug_scope
     [:stream_id, :stream_type]
  end

I want the slug_scope defined in Post to override the slug_scope used in the included do (though I might be calling that wrong, maybe I need self.class.slug_scope) but as written I think that this won't work (isn't the included do executed before the derived class defines its methods)?

Can I do this somehow using prepended do? Or is the way I wrote this roughly correct? I know that included modules are entered into inheritance chain before the derived class but I'm obviously kinda confused about when/where code in an included do block gets executed.

CodePudding user response:

As described by @mu_is_too_short you can just create a class method which adds the validations to the class when called:

module SlugHelpers
  extend ActiveSupport::Concern
  class_methods do
    def has_slug(name = :slug, scope: :default_value)
        validates_uniqueness_of name, 
           case_sensitive: false, 
           message: "Slug must be unique", 
           scope: scope
        validates_presence_of name
    end
  end
end

This is the generally preferred way in Ruby to make the behavior provided by modules configurable.

class Foo
  include SlugHelpers
  has_slug(scope: [:foo, :bar])
end 
class Bar
  include SlugHelpers
  has_slug(scope: [:baz, :bar])
end 

While you could actually call a method on the class when the module is included it would produce very strange results if you want to override the behavior in subclasses since it still would be evaluated just when the module was included.

  • Related