# Relation create command returns Array, expect Hash! ROM crash

**URL:** https://discourse.rom-rb.org/t/relation-create-command-returns-array-expect-hash-rom-crash/516
**Category:** Issues
**Created:** [March 25, 2022, 4:43pm UTC](https://discourse.rom-rb.org/t/relation-create-command-returns-array-expect-hash-rom-crash/516 "2022-03-25T16:43:27Z")
**Posts on this page:** 9
**Page:** 1

<div class="post-metadata">

### Author: ![codeslinger](https://avatars.discourse-cdn.com/v4/letter/c/e9a140/32.png) [@codeslinger](https://discourse.rom-rb.org/u/codeslinger)
#### Post date: [March 25, 2022, 4:43pm UTC](https://discourse.rom-rb.org/t/relation-create-command-returns-array-expect-hash-rom-crash/516/1 "2022-03-25T16:43:27Z")

</div>

Thank you for creating such an amazing tool. I appreciate the separation of concerns! I can change the underlying db from mysql to pg to sqlite memory via only simple config changes - amazing!!!

I’m running into an issue where a relation command sometimes passes a hash to the mapper and sometimes passes the hash wrapped in an array causing a rom crash.

Here are the two relations and mapper involved (note one of the names is not pluralized)

```auto
module Relations
  class RangeEachYear < ROM::Relation[:sql]
    gateway :default

    schema "#{ENV["DB_TABLE_PREFIX"]}_range_each_year".to_sym, infer: true, as: :range_each_year do

      associations do
        belongs_to :temporal_set_expressions
      end

      use :timestamps, attributes: %i[created_at created_at]
    end

  end
end

module Relations
  class TemporalSetExpressions < ROM::Relation[:sql]
    gateway :default

    schema "#{ENV["DB_TABLE_PREFIX"]}_temporal_set_expressions".to_sym, infer: true, as: :temporal_set_expressions do
      attribute :type, Types::Strict::String.enum(
        'Union',
        'Intersection',
        'Difference')

      associations do
        has_many :range_each_year
      end

      use :timestamps, attributes: %i[created_at created_at]
    end

  end
end

module Mappers
  class TemporalSetExpressionsMapper < ROM::Transformer
    relation :temporal_set_expressions
    register_as :temporal_set_expressions_mapper

    map do
      reject_keys [:foobar]
    end

  end
end

```

I get the relation and create 3 commands, one with combine, one with mapper, and one with both…

```auto
tse_rel = MyApp.container.relations[:temporal_set_expressions]
# cmd with combine
cmd_c = tse_rel.combine(:range_each_year)
  .command(:create, use: :timestamps, plugins_options: {timestamps: {timestamps: %i[created_at updated_at]}})
# cmd with mapper
cmd_m = tse_rel
  .command(:create, mapper: :temporal_set_expressions_mapper, use: :timestamps, plugins_options: {timestamps: {timestamps: %i[created_at updated_at]}})
# cmd with combine and mapper
cmd_cm = tse_rel.combine(:range_each_year)
  .command(:create, mapper: :temporal_set_expressions_mapper, use: :timestamps, plugins_options: {timestamps: {timestamps: %i[created_at updated_at]}})

```

Below is the results of calling each command with data to create:

```auto
> cmd_c.call(parent_id: nil, type: 'Union', range_each_year: [])
=> {:type=>"Union", :created_at=>2022-03-25 16:22:15 -0400, :id=>1, :parent_id=>nil, :updated_at=>2022-03-25 16:22:15 -0400, :range_each_year=>[]}

> cmd_m.call(parent_id: nil, type: 'Union', range_each_year: [])
=> {:type=>"Union", :created_at=>2022-03-25 16:22:17 -0400, :id=>2, :parent_id=>nil, :updated_at=>2022-03-25 16:22:17 -0400}

> cmd_cm.call(parent_id: nil, type: 'Union', range_each_year: [])
.../gems/transproc-1.1.1/lib/transproc/hash.rb:212: warning: wrong element type Hash at 0 (expected array)
.../gems/transproc-1.1.1/lib/transproc/hash.rb:212: warning: ignoring wrong elements is deprecated, remove them explicitly
.../gems/transproc-1.1.1/lib/transproc/hash.rb:212: warning: this causes ArgumentError in the next release
ArgumentError: invalid number of elements (0 for 1..2)
.../gems/transproc-1.1.1/lib/transproc/hash.rb:212:in `[]'

```

I investigated to find the following:

```auto
    211: def self.reject_keys(hash, keys)
 => 212: require 'pry'; binding.pry;
    213: Hash[hash].reject { |k, _| keys.include?(k) }
    214: end

[1] pry(Transproc::HashTransformations)> hash
=> [{:type=>"Union", :created_at=>2022-03-25 16:39:04 -0400, :id=>1, :parent_id=>nil, :updated_at=>2022-03-25 16:39:04 -0400}]

```

Why is the combination of mapper + combine on the create command returning an array?  
What is the mistake I am making or what workaround can I use?

Many Thanks!!!

---

<div class="post-metadata">

### Author: ![solnic](https://yyz1.discourse-cdn.com/flex029/user_avatar/discourse.rom-rb.org/solnic/32/279_2.png) [@solnic](https://discourse.rom-rb.org/u/solnic)
#### Post date: [March 28, 2022, 7:18am UTC](https://discourse.rom-rb.org/t/relation-create-command-returns-array-expect-hash-rom-crash/516/2 "2022-03-28T07:18:06Z")

</div>

It’s because a combined relation is no longer the same relation. rom doesn’t automatically adjust your mapper to work with the combined one.

I’m not sure if it’s gonna work, but try this:

```ruby
cmd_m = tse_rel
  .map_with(:temporal_set_expressions_mapper)
  .combine(:range_each_year)
  .command(:create, use: :timestamps, plugins_options: {timestamps: {timestamps: %i[created_at updated_at]}})

```

---

<div class="post-metadata">

### Author: ![codeslinger](https://avatars.discourse-cdn.com/v4/letter/c/e9a140/32.png) [@codeslinger](https://discourse.rom-rb.org/u/codeslinger)
#### Post date: [March 28, 2022, 12:20pm UTC](https://discourse.rom-rb.org/t/relation-create-command-returns-array-expect-hash-rom-crash/516/3 "2022-03-28T12:20:00Z")

</div>

Thank you for taking the time to respond! I tried your suggestion and it looks like the mapper is not being triggered at all. If it’s not automatic is there a way to manually configure a “combined relation mapper”?

[EDIT]  
Am I correct in assuming I need something like this? …

> <https://github.com/rom-rb/rom/blob/aba4817411eef71b36ff38ae5af08efe65438808/spec/suite/rom/relation/combined/map_with_spec.rb>

---

<div class="post-metadata">

### Author: ![solnic](https://yyz1.discourse-cdn.com/flex029/user_avatar/discourse.rom-rb.org/solnic/32/279_2.png) [@solnic](https://discourse.rom-rb.org/u/solnic)
#### Post date: [March 29, 2022, 6:52am UTC](https://discourse.rom-rb.org/t/relation-create-command-returns-array-expect-hash-rom-crash/516/4 "2022-03-29T06:52:40Z")

</div>

> [@codeslinger](#):
>
> If it’s not automatic is there a way to manually configure a “combined relation mapper”?

You can use `#map_with` but it seems like the behavior when used with commands is not what you need. Can you tell me why you need a custom mapper when using commands? Maybe you should use changesets instead?

> [@codeslinger](#):
>
> Am I correct in assuming I need something like this? …

No, that’s a low level spec.

---

<div class="post-metadata">

### Author: ![codeslinger](https://avatars.discourse-cdn.com/v4/letter/c/e9a140/32.png) [@codeslinger](https://discourse.rom-rb.org/u/codeslinger)
#### Post date: [March 29, 2022, 12:33pm UTC](https://discourse.rom-rb.org/t/relation-create-command-returns-array-expect-hash-rom-crash/516/5 "2022-03-29T12:33:51Z")

</div>

> [@solnic](#):
>
> No, that’s a low level spec.

I mean could I use the ROM::Relation::Combined#map\_with method that the spec demonstrates? But I guess it would help to go deeper into my intent.

temporal\_set\_expressions relation is a hierarchical tree structure (parent\_id to self) as well as 8 other associations.

```auto
        associations do
          has_many :temporal_set_expressions, foreign_key: :parent_id
          has_many :range_each_year
          has_many :range_each_month
          has_many :range_each_week
          has_many :range_each_day
          has_many :range_after
          has_many :range_before
          has_many :day_each_month
          has_many :day_each_week
        end

```

So I would like to use the create command with nested hash. The result should combine all the associations recursively, nest all associations under one key/attribute called “expressions” and constructor\_inject the structs into PORO. So I believe I need combine + mapper on a create command.

```auto
      set = subject.create_with_expr({
        type: "Union",
        parent_id: nil,
        range_each_year: [{
          start_month: 1,
          end_month: 4,
          start_day: 10,
          end_day: 15
          }
        ]
      })

      assert_kind_of SetExpression::Union, set
      assert_kind_of Expression::RangeEachYear, set.expressions[0]
      assert_equal 4, set.expressions[0].end_month

```

Here is my mapper without the constructor\_inject…

```auto
    class TemporalSetExpressionsMapper < ROM::Transformer
      relation :temporal_set_expressions
      register_as :temporal_set_expressions_mapper

      map do
        recursion do
          guard(->(expr) { expr.is_a?(Hash) && ['Union', 'Intersection', 'Difference'].include?(expr[:type]) }) do

            nest :expressions, [:temporal_set_expressions, :range_each_year, :range_each_month,
              :range_each_week, :range_each_day, :range_after, :range_before, :day_each_month,
              :day_each_week]

            map_value :expressions, ->(value) {
              (value[:temporal_set_expressions].nil? ? [] : value[:temporal_set_expressions]) |
              (value[:range_each_year].nil? ? [] : value[:range_each_year]) |
              (value[:range_each_month].nil? ? [] : value[:range_each_month]) |
              (value[:range_each_week].nil? ? [] : value[:range_each_week]) |
              (value[:range_each_day].nil? ? [] : value[:range_each_day]) |
              (value[:range_after].nil? ? [] : value[:range_after]) |
              (value[:range_before].nil? ? [] : value[:range_before]) |
              (value[:day_each_month].nil? ? [] : value[:day_each_month]) |
              (value[:day_each_week].nil? ? [] : value[:day_each_week])
            }
          end
        end
      end

```

And here is result of the create I would like (if possible):

```auto
=> {:type=>"Union",
 :created_at=>2022-03-29 12:11:51 -0400,
 :id=>1,
 :parent_id=>nil,
 :updated_at=>2022-03-29 12:11:51 -0400,
 :expressions=>
  [{:type=>"Intersection",
    :created_at=>2022-03-29 12:11:51 -0400,
    :id=>2,
    :parent_id=>1,
    :updated_at=>2022-03-29 12:11:51 -0400,
    :expressions=>
     [{:type=>"Union", :created_at=>2022-03-29 12:11:51 -0400, :id=>3, :parent_id=>2, :updated_at=>2022-03-29 12:11:51 -0400, :expressions=>[]},
      {:created_at=>2022-03-29 12:11:51 -0400, :id=>2, :start_month=>2, :end_month=>4, :start_day=>10, :end_day=>15, :temporal_set_expression_id=>2, :updated_at=>2022-03-29 12:11:51 -0400}]},
   {:type=>"Difference", :created_at=>2022-03-29 12:11:51 -0400, :id=>4, :parent_id=>1, :updated_at=>2022-03-29 12:11:51 -0400, :expressions=>[]},
   {:created_at=>2022-03-29 12:11:51 -0400, :id=>1, :start_month=>1, :end_month=>4, :start_day=>10, :end_day=>15, :temporal_set_expression_id=>1, :updated_at=>2022-03-29 12:11:51 -0400}]}

```

I would then have to modify the mapper to use constructor\_inject to return PORO rather than hashes/structs.

---

<div class="post-metadata">

### Author: ![codeslinger](https://avatars.discourse-cdn.com/v4/letter/c/e9a140/32.png) [@codeslinger](https://discourse.rom-rb.org/u/codeslinger)
#### Post date: [March 29, 2022, 2:35pm UTC](https://discourse.rom-rb.org/t/relation-create-command-returns-array-expect-hash-rom-crash/516/6 "2022-03-29T14:35:28Z")

</div>

> [@solnic](#):
>
> No, that’s a low level spec.

I mean, could I use ROM::Relation::Combined#map\_with that the spec demonstrates in order to get combine + mapper working?

I would like (if possible) to use the create command to write aggregates and have the result combined with newly created associations and mapped.

```auto
      set = subject.create_with_expr({
        type: "Union",
        parent_id: nil,
        range_each_year: [{
          start_month: 1,
          end_month: 4,
          start_day: 10,
          end_day: 15
          }
        ]
      })

      assert_kind_of SetExpression::Union, set
      assert_kind_of Expression::RangeEachYear, set.expressions[0]
      assert_equal 4, set.expressions[0].end_month

```

The relation is a hierarchical structure (parent\_id points to self) with 8 other associations:

```auto
        associations do
          has_many :temporal_set_expressions, foreign_key: :parent_id
          has_many :range_each_year
          has_many :range_each_month
          has_many :range_each_week
          has_many :range_each_day
          has_many :range_after
          has_many :range_before
          has_many :day_each_month
          has_many :day_each_week
        end

```

Here is the current mapper (without constructor\_inject which will need to be added) Note: I have added import ::Transproc::Recursion to get this working:

```auto
    class TemporalSetExpressionsMapper < ROM::Transformer
      relation :temporal_set_expressions
      register_as :temporal_set_expressions_mapper

      map do
        recursion do
          guard(->(expr) { expr.is_a?(Hash) && ['Union', 'Intersection', 'Difference'].include?(expr[:type]) }) do

            nest :expressions, [:temporal_set_expressions, :range_each_year, :range_each_month,
              :range_each_week, :range_each_day, :range_after, :range_before, :day_each_month,
              :day_each_week]

            map_value :expressions, ->(value) {
              (value[:temporal_set_expressions].nil? ? [] : value[:temporal_set_expressions]) |
              (value[:range_each_year].nil? ? [] : value[:range_each_year]) |
              (value[:range_each_month].nil? ? [] : value[:range_each_month]) |
              (value[:range_each_week].nil? ? [] : value[:range_each_week]) |
              (value[:range_each_day].nil? ? [] : value[:range_each_day]) |
              (value[:range_after].nil? ? [] : value[:range_after]) |
              (value[:range_before].nil? ? [] : value[:range_before]) |
              (value[:day_each_month].nil? ? [] : value[:day_each_month]) |
              (value[:day_each_week].nil? ? [] : value[:day_each_week])
            }
          end
        end
      end

```

Here is an example of the desired result from the create command. I would like the mapper init PORO rather than hashes/structs (using constructor\_inject?).

```auto
=> {:type=>"Union",
 :created_at=>2022-03-29 12:11:51 -0400,
 :id=>1,
 :parent_id=>nil,
 :updated_at=>2022-03-29 12:11:51 -0400,
 :expressions=>
  [{:type=>"Intersection",
    :created_at=>2022-03-29 12:11:51 -0400,
    :id=>2,
    :parent_id=>1,
    :updated_at=>2022-03-29 12:11:51 -0400,
    :expressions=>
     [{:type=>"Union", :created_at=>2022-03-29 12:11:51 -0400, :id=>3, :parent_id=>2, :updated_at=>2022-03-29 12:11:51 -0400, :expressions=>[]},
      {:created_at=>2022-03-29 12:11:51 -0400, :id=>2, :start_month=>2, :end_month=>4, :start_day=>10, :end_day=>15, :temporal_set_expression_id=>2, :updated_at=>2022-03-29 12:11:51 -0400}]},
   {:type=>"Difference", :created_at=>2022-03-29 12:11:51 -0400, :id=>4, :parent_id=>1, :updated_at=>2022-03-29 12:11:51 -0400, :expressions=>[]},
   {:created_at=>2022-03-29 12:11:51 -0400, :id=>1, :start_month=>1, :end_month=>4, :start_day=>10, :end_day=>15, :temporal_set_expression_id=>1, :updated_at=>2022-03-29 12:11:51 -0400}]}

```

Please let me know if I can clarify anything for you!

---

<div class="post-metadata">

### Author: ![solnic](https://yyz1.discourse-cdn.com/flex029/user_avatar/discourse.rom-rb.org/solnic/32/279_2.png) [@solnic](https://discourse.rom-rb.org/u/solnic)
#### Post date: [April 6, 2022, 5:30am UTC](https://discourse.rom-rb.org/t/relation-create-command-returns-array-expect-hash-rom-crash/516/7 "2022-04-06T05:30:30Z")

</div>

This sounds a bit complicated. I’d recommend handling it explicitly via changesets and then getting back the desired result via another query with your custom mappers applied. Trying to collapse this entire process into a single command with custom mappers is _maybe_ possible now, but definitely not easy.

---

<div class="post-metadata">

### Author: ![codeslinger](https://avatars.discourse-cdn.com/v4/letter/c/e9a140/32.png) [@codeslinger](https://discourse.rom-rb.org/u/codeslinger)
#### Post date: [April 7, 2022, 11:49am UTC](https://discourse.rom-rb.org/t/relation-create-command-returns-array-expect-hash-rom-crash/516/8 "2022-04-07T11:49:43Z")

</div>

Thank-you, I came to a similar conclusion that trying to do an aggregate create and have the result combined and mapped did not seem possible. So I broke it up into a write command and a query command.

Is there any way to tell the write command to only return id and not all attributes of relation? Is there any performance gain in doing so?

Thanks again for your time!

---

<div class="post-metadata">

### Author: ![solnic](https://yyz1.discourse-cdn.com/flex029/user_avatar/discourse.rom-rb.org/solnic/32/279_2.png) [@solnic](https://discourse.rom-rb.org/u/solnic)
#### Post date: [April 9, 2022, 8:31am UTC](https://discourse.rom-rb.org/t/relation-create-command-returns-array-expect-hash-rom-crash/516/9 "2022-04-09T08:31:13Z")

</div>

> [@codeslinger](#):
>
> Is there any way to tell the write command to only return id and not all attributes of relation? Is there any performance gain in doing so?

Not yet, but it will be possible in rom 6.0 🙂 For now you can always implement your own command type and override default behavior.
