slhck
slhck

Reputation: 38691

Defining factories that know their own class and look up the values

I have a validator class that defines valid events for objects of type Foo and Bar. Each Foo object has attributes alert_events, window_events and user_events, and each Bar object has state_events and user_events.

class EventListConformanceValidator
  VALID_EVENTS = {
    foo: {
      alert_events: {
        onAlertLoad: String,
        onAlertUnload: String,
      },
      window_events: {
        onWindowLoaded: Array,
      },
      user_events: {
        onLoginStatusChanged: String,
      },
    },
    bar: {
      state_events: {
        onAborted: Float,
      },
      user_events: {
        skipClicked: nil
      }
    }
  }
end

I want to dynamically generate factories using FactoryBot which will populate the correct event types for each attribute. For example, for alert_events, only events with type onAlertLoad or onAlertUnload should be created.

Naively, I could write the following:

class Event
  attr_accessor :type
end

FactoryBot.define do
  factory :event do
    type { nil }
  end

  factory :alert_event do
    type { ["onAlertLoad", "onAlertUnload"].sample }
  end

  factory :window_events do
    # and so on ...
  end
end

But obviously that'd be a lot of repetition. Instead I want a single source of truth, and source the definitions from the Hash VALID_EVENTS. Also, there is the problem that user_events can exist both for Foo and Bar, so I cannot use user_events alone, as it'd be ambiguous.

So I tried to dynamically generate factories foo_alert_event, foo_window_event etc.:

EventListConformanceValidator::VALID_EVENTS.each do |klass, attribute_hash|
    attribute_hash.keys.map(&:to_s).uniq.each do |attribute|
      factory_name = (klass.to_s + "_" + attribute.to_s.singularize).to_sym

      factory factory_name, class: Event, parent: :event do
        type { "how do I get the event types to sample?" }
      end

    end
end

But inside the dynamic evaluation of type, I don't know how I could refer to the actual factory class name (foo/bar) or attribute name (alert_events etc.) so I can look up the keys in VALID_EVENTS.

How could I achieve that?

Upvotes: 0

Views: 359

Answers (1)

sammygadd
sammygadd

Reputation: 299

It sounds like you want something like this (however I don't understand what you are trying to achieve)

VALID_EVENTS = { 
  foo: { 
    alert_events: { 
      onAlertLoad: String,
      onAlertUnload: String,
    },
    window_events: { 
      onWindowLoaded: Array,
    },
    user_events: { 
      onLoginStatusChanged: String,
    },
  },
  bar: { 
    state_events: { 
      onAborted: Float,
    },
    user_events: { 
      skipClicked: nil,
    },
  },
}.freeze

class Event
  attr_accessor :type
end

FactoryBot.define do
  VALID_EVENTS.each do |klass, attribute_hash|
    attribute_hash.each do |name, events|
      factory_name = "#{klass}_#{name.to_s.singularize}".to_sym
      types = events.keys.map(&:to_s)

      factory factory_name, class: Event do
        type { types } 
      end
    end
  end
end

describe "SO" do
  it "works" do
    klass = build(:foo_alert_event)
    expect(klass.type).to eq(["onAlertLoad", "onAlertUnload"])
  end
end
rspec
SO
  works

Finished in 0.94792 seconds (files took 3.25 seconds to load)
1 example, 0 failures

Upvotes: 1

Related Questions