Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Including a module in a Rails 5 Application Record class

I am using Application Record to simplify shared logic throughout an application.

Here's an example that writes a scope for a boolean and its inverse. This works well:

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  def self.boolean_scope(attr, opposite = nil)
    scope(attr, -> { where("#{attr}": true) })
    scope(opposite, -> { where("#{attr}": false) }) if opposite.present?
  end
end

class User < ApplicationRecord
  boolean_scope :verified, :unverified
end

class Message < ApplicationRecord
  boolean_scope :sent, :pending
end

My Application Record class got long enough it made sense for me to break it up into individual modules and load those as needed.

Here's my attempted solution:

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
  include ScopeHelpers
end

module ScopeHelpers
  def self.boolean_scope(attr, opposite = nil)
    scope(attr, -> { where("#{attr}": true) })
    scope(opposite, -> { where("#{attr}": false) }) if opposite.present?
  end
end

class User < ApplicationRecord
  boolean_scope :verified, :unverified
end

class Message < ApplicationRecord
  boolean_scope :sent, :pending
end

In this case, I don't get a load error, but boolean_scope is then undefined on User and Message.

Is there a way to ensure the included modules are loaded at the appropriate time and available to Application Record and its inheriting models?


I've also attempted to have the models include the modules directly and that did not fix the issue.

module ScopeHelpers
  def self.boolean_scope(attr, opposite = nil)
    scope(attr, -> { where("#{attr}": true) })
    scope(opposite, -> { where("#{attr}": false) }) if opposite.present?
  end
end

class User < ApplicationRecord
  include ScopeHelpers
  boolean_scope :verified, :unverified
end

class Message < ApplicationRecord
  include ScopeHelpers
  boolean_scope :sent, :pending
end
like image 497
seancdavis Avatar asked Dec 23 '22 17:12

seancdavis


2 Answers

As an alternative to @Pavan's answer, you can do this:

module ScopeHelpers
  extend ActiveSupport::Concern # to handle ClassMethods submodule

  module ClassMethods
    def boolean_scope(attr, opposite = nil)
      scope(attr, -> { where(attr => true) })
      scope(opposite, -> { where(attr => false) }) if opposite.present?
    end
  end
end

# then use it as usual
class ApplicationRecord < ActiveRecord::Base
  include ScopeHelpers
  ...
end
like image 182
Sergio Tulentsev Avatar answered Jan 18 '23 13:01

Sergio Tulentsev


In this case, I don't get a load error, but boolean_scope is then undefined on User and Message

The problem is include add methods on an instance of a class. You need to use extend

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
  extend ScopeHelpers
end

Now you can call it like User.boolean_scope. Below is the example for include vs extend

module Foo
  def foo
    puts 'heyyyyoooo!'
  end
end

class Bar
  include Foo
end

Bar.new.foo # heyyyyoooo!
Bar.foo # NoMethodError: undefined method ‘foo’ for Bar:Class

class Baz
  extend Foo
end

Baz.foo # heyyyyoooo!
Baz.new.foo # NoMethodError: undefined method ‘foo’ for #<Baz:0x1e708>
like image 32
Pavan Avatar answered Jan 18 '23 13:01

Pavan