rom-rb / rom

Data mapping and persistence toolkit for Ruby
https://rom-rb.org
MIT License
2.08k stars 161 forks source link

Combine renaming, symbolizing and unwrap in rom-mapper #500

Closed v-kolesnikov closed 6 years ago

v-kolesnikov commented 6 years ago

Hi! 👋

Seems like there is some strange behaviour in rom-mapper.

What I want to do is simple map the following data:

{
  name: 'Jane',
  login: 'janecat',
  details: { 'age' => 18, 'city' => 'Moscow' } # Comes from YAML
}

to:

{
  name: 'Jane',
  user_login: 'janecat',
  age: 18,
  user_city: 'Moscow' 
}

So, I have written mapper as following:

attribute :name
attribute :user_login, from: :login

unwrap :details do
  attribute :age
  attribute :user_city, from: 'city'
end

but it doesn't works as expected.

#!/usr/bin/env ruby

require 'bundler/inline'

gemfile(true) do
  source 'https://rubygems.org'

  gem 'pry-byebug'

  gem 'dry-types'
  gem 'rom'
  gem 'rom-sql', '~> 2.5'
  gem 'sqlite3'
  gem 'rspec'
end

require 'yaml'
require 'rspec/autorun'

RSpec.configure do |config|
  config.color = true
  config.formatter = :documentation
end

module Types
  include Dry::Types.module

  YAMLRead =
    (Types::Array | Types::Hash)
    .constructor(&YAML.method(:safe_load))

  SafeYAML =
    (Types::Array | Types::Hash)
    .constructor(&:to_yaml)
    .meta(read: YAMLRead)
end

RSpec.describe do
  let(:uri) { 'sqlite::memory' }
  let(:conn) { Sequel.connect(uri) }
  let(:conf) { ROM::Configuration.new(:sql, conn) }
  let(:container) { ROM.container(conf) }

  before do
    conn.create_table :users do
      primary_key :id
      String :name
      String :login
      String :details
    end

    conf.relation(:users) do
      schema(:users) do
        attribute :id,  ROM::SQL::Types::Serial
        attribute :name, ROM::SQL::Types::String.optional
        attribute :login, ROM::SQL::Types::String.optional
        attribute :details, ::Types::SafeYAML.optional
      end
    end

    conf.mappers do
      define(:users) do
        register_as :symbolized_keys

        symbolize_keys true

        attribute :name
        attribute :user_login, from: :login

        unwrap :details do
          attribute :age
          attribute :user_city, from: 'city'
        end
      end

      define(:users) do
        register_as :renamed

        attribute :name
        attribute :user_login, from: :login

        unwrap :details do
          attribute :age
          attribute :user_city, from: 'city'
        end
      end
    end

    container.relations.users.command(:create).(
      name: 'Jane',
      login: 'janecat',
      details: { 'age' => 18, 'city' => 'Moscow' }
    )
  end

  subject(:data) do
    container.relations.users.map_with(mapper).first
  end

  let(:expected) do
    {
      id: 1,
      name: 'Jane',
      user_login: 'janecat',
      age: 18,
      user_city: 'Moscow'
    }
  end

  # expected: {:id=>1, :name=>"Jane", :user_login=>"janecat", :age=>18, :user_city=>"Moscow"}
  #      got: {:id=>1, :name=>"Jane", :login=>"janecat", :age=>18, :user_city=>"Moscow"}
  context do
    let(:mapper) { :symbolized_keys }

    it { is_expected.to eql expected }
  end

  # expected: {:id=>1, :name=>"Jane", :user_login=>"janecat", :age=>18, :user_city=>"Moscow"}
  #      got: {:id=>1, :name=>"Jane", :user_login=>"janecat", :details=>{"age"=>18}, :user_city=>"Moscow"}
  context do
    let(:mapper) { :renamed }

    it { is_expected.to eql expected }
  end
end
solnic commented 6 years ago

Use ROM::Transformer instead, this will be the default in ROM 5.0 and current DSL is going away exactly due to the type of issues you're having.

v-kolesnikov commented 6 years ago

@solnic OK. Thank you for your answer! I actually have resolved my problem with a couple of workarounds. I will look more at ROM::Transformer as well.

solnic commented 6 years ago

@v-kolesnikov the DSL is identical as in transproc's Transformer, this is much more straight-forward and predictable than ROM::Mapper's DSL, that's why we decided to use it instead. Changesets' map feature already uses transformer DSL.