contentful / contentful.rb

Ruby client for the Contentful Content Delivery API
https://www.contentful.com/developers/documentation/content-delivery-api/
MIT License
135 stars 64 forks source link

Creating custom-class objects with factory_bot #226

Open toadle opened 4 years ago

toadle commented 4 years ago

Hey guys,

I'm trying to upgrade our contentful gem to the most recent version. We are currently still on 0.8. Our test-suite relies on being able to create instances of custom Contentful-class via factory_bot.

Here is an example configuration of a factory:

FactoryBot.define do
  factory :category, class: Contentful::Category do
    title       { FFaker::DizzleIpsum.word }
    slug        { FFaker::Internet.slug.delete(".") }
  end
end

whereas Contentful::Category looks like:

class Contentful::Category < Contentful::Entry
...
end

and is mentioned in the entry_mapping.

Since upgrading to 2.15.3 I have to give factory_bot an initialize_with-methode since Content::FieldsResource requires itemand configuration as parameters (https://github.com/contentful/contentful.rb/blob/66961c154632c67bac87fbc395d1580b57a872f2/lib/contentful/fields_resource.rb#L12)

But initialize_with({},{}) won't do and then I'm in the process of reconstructing real-world contentful-API-responses.

Is there a good way to quickly create instances of Contentful-Custom-Classes without the whole enchilada?

@dlitvakb We solved this once waaaay back when https://github.com/contentful/contentful.rb/issues/79 ;-)

toadle commented 4 years ago

OK, I have a temporary (very ugly) solution like this. Create a new super-class for all you Contentful::Entrys.

Mine is called Contentful::BaseEntry.

Then do this:

# frozen_string_literal: true

class Contentful::BaseEntry < Contentful::Entry
  def initialize(*_args)
    if Rails.env.test?
      enabled_factory_bot_object_creation!
    else
      super
    end
  end

  def enabled_factory_bot_object_creation!
    @raw = {}
    @default_locale = "de-DE"
    @depth = 0
    @configuration = {}
    @sys = { "id" => SecureRandom.hex(8) }
    @localized = false
    @fields = {}

    def content_type_field?(_name)
      false
    end

    def repr_name
      "#{self.class}[#{sys['id']}]"
    end

    def method_missing(name, *args)
      if name.to_s.ends_with?("=")
        field_name = name.to_s[0..-2]
        @fields[field_name] = args[0]
      else

        @fields[name.to_s]
      end
    end

    def respond_to_missing?(name, _include_private = false)
      (name.to_s.ends_with?("=") || @fields.keys.include?(name.to_s))
    end
  end
end

Now, if you have a Contentful::Article like so:

class Contentful::Article < Contentful::BaseEntry
end

You'll be able to create object with factory_bot like so:

FactoryBot.define do
  factory :article, class: Contentful::Article do
    title             { FFaker::DizzleIpsum.word }
  end
end

Still thinking about, if this is doable in a more clean way...