rom-rb / rom

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

Command plugin to accept input with aliased keys #517

Closed waiting-for-dev closed 4 years ago

waiting-for-dev commented 5 years ago

This PR adds an :alias command plugin that allows to use the alias for aliased attributes as input keys:

class User < ROM::Relations[:sql]
  schema(infer: true) do
    attribute :first_name, Types::String.meta(alias: name)
  end
end

class CreateUser < ROM::Commands::Create[:sql]
  result :one
  use :alias
end

result = rom.command(:user).create.call(name: 'Jane')
result[:first_name]  #=> 'Jane'

In my view, this can fix the current asymmetry between reading/writing data to/from the datastore. Now, when you configure an alias for an attribute, reading methods (like #first or #to_a), automatically perform the configured translation and return keys with the aliased names. However, when the user comes back to the engine with a command, that automation is lost and the "unwrapping" translation has to be handled manually. I see it as an abstraction leak, where the application level suddenly must be aware of canonical names (besides schema configuration, of course).

The good news are that it should be 100% backwards compatible, because commands using :alias plugin would still accept the canonical names as keys. So what do you think about activating this plugin by default? We could just check whether the schema has any alias at all and, if not, do not enter the tuple iteration for better performance.

In my limited knowledge of rom behaviour, I'm not sure if it is needed to test the plugin for different command scenarios. If so, please tell me.

Tests and documentation must be updated if #512 is merged.

Thanks.

Resources

waiting-for-dev commented 5 years ago

I have given it a second thought. Maybe instead of a command plugin, it would make more sense for it to be a relation command overloading insert, update relation methods if they are present in the adapter. I guess that commands are calling them internally, and it seems the actual application/database layer boundary.

solnic commented 5 years ago

This should be a command plugin. In the future, commands will be decoupled from relations, and will work with datasets directly. Relation insert and update are meant to stay bare-bones, as they are simple building blocks for other APIs.

solnic commented 5 years ago

@waiting-for-dev I'm sorry but I won't be able to work on finalizing this for 5.0.0 but we can pick it up quickly right after 5.0.0 is released.

waiting-for-dev commented 5 years ago

No worries @solnic . In the meantime I'm using a custom plugin in my project. However, let me know anything I can do.

solnic commented 5 years ago

@waiting-for-dev 👋 would you like to wrap this up for 5.1.0 release?

waiting-for-dev commented 5 years ago

@solnic for now I rebased to master and adapted it to work with alias now being an option and not part of the meta.

But I think we have to give it some thoughts before merging.

Related to #524 , maybe we can 100% hide original database field names in the schema layer. If we make #[] not recognize original names which has been aliased, I think the command plugin defined here should neither recognize them. For the same reason, I think this plugin should be activated by default or even not being a plugin but part of the core behavior.

What do you think?

solnic commented 4 years ago

FYI we'll be releasing 5.2.0 with the primary goal to support ruby 2.7 and there's no time to finish this one for that release. I scheduled it to be included in 6.0.0 release instead.

solnic commented 4 years ago

I'm gonna close this because it needs more thinking/discussions. ie at the moment I think it should be a built-in behavior, we can apply mapping if there are aliased attributes automatically. I'll start an alias-related thread on the forum to discuss how to approach aliasing in 6.0.0.