Dry-transformer with dynamic options

Hi, I’m trying to dynamically pass an option hash to the rename_keys transformer but it looks like I cant access the class scope inside de define! block

$global_keys = {user_name: :name}
class MyMapper < Dry::Transformer::Pipe
  import Dry::Transformer::ArrayTransformations
  import Dry::Transformer::HashTransformations

  def initialize(rename_keys)
    @rename_keys = rename_keys
  end

  define! do
    map_array do
      symbolize_keys
      # rename_keys @rename_keys
      rename_keys $global_keys
      # rename_keys user_name: :name
      nest :address, [:city, :street, :zipcode]
    end
  end
end

I believe its a scope problem because I can access the contents of the global variable but not the class variable.

@cpgo so, it’s not yet documented but it’s possible to create instance-based transformers. See this spec to get an idea.

1 Like

That almost works. I’m not really sure what am I doing wrong, I modified that test to look like my use case.

# frozen_string_literal: true

RSpec.describe Dry::Transformer, "instance variables" do
  subject(:transformer) do
    Class.new(Dry::Transformer[registry]) do
      def initialize(rename_keys)
        @rename_keys = rename_keys
      end

      def my_keys
        @rename_keys
      end

      define! do
        map_array do
          rename_keys "user_name" => :user # this works as expected
          # rename_keys @rename_keys  # this dont work
        end
      end
    end.new({"user_name" => :user})
  end

  let(:registry) do
    Module.new do
      extend Dry::Transformer::Registry

      import Dry::Transformer::HashTransformations
      import Dry::Transformer::ArrayTransformations
    end
  end

  it "registers a new transformation function" do
    expect(transformer.call([{"user_name" => "jane"}])).to eql([{user: "jane"}])
  end
end

When using the instance variable when the transformer reach the self.rename_keys(source_hash, mapping) method the mapping parameter is nil

Looks like I’m not supposed to pass a hash to rename_keys, I tried splatting the args like rename_keys *@rename_keys but no success, it turns the mapping value into [:fn, [:my_keys, []]]

I also modified the test to look like this

# frozen_string_literal: true

RSpec.describe Dry::Transformer, "instance methods" do
  subject(:transformer) do
    Class.new(Dry::Transformer[registry]) do
      def initialize(append_value)
        @append_value = append_value
      end
      define! do
        map_array(&:append)
      end

      def append(input)
        "#{input} #{@append_value}"
      end
    end.new("baz")
  end

  let(:registry) do
    Module.new do
      extend Dry::Transformer::Registry

      import Dry::Transformer::ArrayTransformations
    end
  end

  it "registers a new transformation function" do
    expect(transformer.call(%w[foo bar])).to eql(["foo baz", "bar baz"])
  end
end

And this one works as expected. So the problem might be that I dont know how to properly use rename_keys :smiley:

I’m sure this is not the right way to do it, but with some hacking I managed to make it “work”.
My branch is here

# My class
      def my_keys
        @rename_keys
      end

      define! do
        map_array do
          rename_keys(&:my_keys)
        end
      end
# lib/dry/transformer/hash.rb
      def self.rename_keys(source_hash, mapping)
        Hash[source_hash].tap do |hash|
          # mapping.each { |k, v| hash[v] = hash.delete(k) if hash.key?(k) }
          mapping.fn.call.each { |k, v| hash[v] = hash.delete(k) if hash.key?(k) }
        end
      end

Yep, its a very ugly hack that will probably break. Will keep investigating the proper way of doing this later today.

1 Like

After thinking a little bit about this I had an idea.

What if we had to explicitly pass a method(:my_method) to the transformer instead of assuming that we want the method instead of the methods return


      def k
        @my_keys
      end

      define! do
        map_array do
          rename_keys method(:k) # will pass the method to be called by the visit_fn on the compiler
          rename_keys k # will pass the return value of `k` to rename_keys
        end
      end

@solnic is that a bad idea?