Sig
Sig

Reputation: 5996

Create dynamic scopes based on other model

In a Rails (5.0) app, I have the following

class Batch < ApplicationRecord
  belongs_to :zone, optional: false
end

class Zone < ApplicationRecord
  scope :lines, -> { where(kind: 'line') }
end

Now I need to define in Batch a scope for each Zone which is a line. Something like the code below works.

  Zone.lines.map(&:name).each do |name|
    scope "manufactured_on_#{name}".to_sym, -> { joins(:zone).where("zones.name = '#{name}'") }
  end

The issue is that the code above is evaluated when the app boots and at that time the scopes are created. If I add a newZone of kind line, the scope is not created.

Is there a way to solve this issue?

Upvotes: 0

Views: 365

Answers (3)

Gokul
Gokul

Reputation: 3251

If you really need the scope name to be dynamic you can use method_missing as below:

class Batch < ApplicationRecord
  belongs_to :zone, optional: false

  def self.method_missing(name, *args)
    method_name = name.to_s

    if method_name.start_with?('manufactured_on_')
      zone_name = method_name.split('manufactured_on_')[1]
      joins(:zone).where(zones: { name: zone_name } )
    else
      super
    end
  end

  def self.respond_to_missing?(name, include_private = false)
    name.to_s.start_with?('manufactured_on_') || super
  end
end

Now, you can call this scope as normal:

Batch.manufactured_on_zone_a

Upvotes: 0

Michał Szajbe
Michał Szajbe

Reputation: 9002

You can look into documentation and search for method_missing.

But this does not seem a good approach to me.

Instead, define scope in following way:

class Batch < ApplicationRecord
  scope :manufactured_on, ->(line) { joins(:zone).where("zones.name = '#{name}'") }
end

Then simply use

Batch.manufactured_on(zone.name)

Upvotes: 0

Samy Kacimi
Samy Kacimi

Reputation: 1238

You could pass the zone's name as a scope param

scope :manufactured_on, -> (name) { joins(:zone).where(zones: { name: name } ) }

Upvotes: 3

Related Questions