diff --git a/Gemfile b/Gemfile index 387348b70..9a447602e 100644 --- a/Gemfile +++ b/Gemfile @@ -12,13 +12,14 @@ if ENV["USE_DRY_INITIALIZER_MASTER"].eql?("true") gem "dry-initializer", github: "dry-rb/dry-initializer", branch: "master" end -if ENV["USE_ROM_SQL_MASTER"].eql?("true") - gem "rom-sql", github: "rom-rb/rom-sql", branch: "master" -else - gem "rom-sql", "~> 3.3", ">= 3.3.1" -end +# if ENV["USE_ROM_SQL_MASTER"].eql?("true") +# gem "rom-sql", github: "rom-rb/rom-sql", branch: "master" +# else +# gem "rom-sql", "~> 3.3", ">= 3.3.1" +# end group :sql do + gem "rom-sql", github: "rom-rb/rom-sql", branch: "decouple-commands-from-relations" gem "dry-monitor" gem "jdbc-postgres", platforms: :jruby gem "jdbc-sqlite3", platforms: :jruby diff --git a/lib/rom/command.rb b/lib/rom/command.rb index 4e299b060..688e255ce 100644 --- a/lib/rom/command.rb +++ b/lib/rom/command.rb @@ -194,18 +194,22 @@ class Command # @!attribute [r] relation # @return [Relation] Command's relation - param :relation + param :dataset CommandType = Types::Strict::Symbol.enum(:create, :update, :delete) Result = Types::Strict::Symbol.enum(:one, :many) + # @!attribute [r] schema + # @return [Schema] Relation's schema + option :schema, optional: true + # @!attribute [r] type # @return [Symbol] The command type, one of :create, :update or :delete option :type, type: CommandType, optional: true # @!attribute [r] source - # @return [Relation] The source relation - option :source, default: -> { relation } + # @return [Dataset] The source dataset + option :source, default: -> { dataset } # @!attribute [r] result # @return [Symbol] Result type, either :one or :many @@ -227,26 +231,18 @@ class Command # @return [Array] An array with after hooks configuration option :after, Types::Coercible::Array, reader: false, default: -> { self.class.after } - input Hash - result :many - - # Return name of this command's relation - # - # @return [ROM::Relation::Name] - # + # !@attribute :name + # @return [ROM::Relation::Name] Return name of this command's relation # @api public - def name - relation.name - end + option :name, optional: true - # Return gateway of this command's relation - # - # @return [Symbol] - # + # !@attribute :gateway + # @return [Symbol] Return gateway of this command's relation # @api public - def gateway - relation.gateway - end + option :gateway, optional: true + + input Hash + result :many # Execute the command # @@ -314,7 +310,7 @@ def curry(*args) if curry_args.empty? && args.first.is_a?(Graph::InputEvaluator) Lazy[self].new(self, *args) else - self.class.build(relation, **options, curry_args: args) + self.class.build(dataset, **options, curry_args: args) end end @@ -346,7 +342,7 @@ def curried? # # @api public def before(*hooks) - self.class.new(relation, **options, before: before_hooks + hooks) + self.class.new(dataset, **options, before: before_hooks + hooks) end # Return a new command with appended after hooks @@ -357,7 +353,7 @@ def before(*hooks) # # @api public def after(*hooks) - self.class.new(relation, **options, after: after_hooks + hooks) + self.class.new(dataset, **options, after: after_hooks + hooks) end # List of before hooks @@ -378,15 +374,15 @@ def after_hooks options[:after] end - # Return a new command with other source relation + # Return a new command with other source dataset # - # This can be used to restrict command with a specific relation + # This can be used to restrict command with a specific dataset # # @return [Command] # # @api public - def new(new_relation) - self.class.build(new_relation, **options, source: relation) + def new(new_dataset) + self.class.build(new_dataset, **options, source: dataset) end # Check if this command has any hooks @@ -432,22 +428,13 @@ def many? result.equal?(:many) end - # Check if this command is restrictible through relation - # - # @return [TrueClass,FalseClass] - # - # @api private - def restrictible? - self.class.restrictable.equal?(true) - end - # Yields tuples for insertion or return an enumerator # # @api private def map_input_tuples(tuples, &mapper) return enum_for(:with_input_tuples, tuples) unless mapper - if tuples.respond_to? :merge + if tuples.respond_to?(:merge) mapper[tuples] else tuples.map(&mapper) diff --git a/lib/rom/command_compiler.rb b/lib/rom/command_compiler.rb index f5c4aba1b..629285826 100644 --- a/lib/rom/command_compiler.rb +++ b/lib/rom/command_compiler.rb @@ -217,9 +217,9 @@ def register_command(rel_name, type, rel_meta, parent_relation = nil) command: klass, gateway: gateway, dataset: relation.dataset, adapter: adapter ) - klass.extend_for_relation(relation) if klass.restrictable + command_input = meta[:input] || relation.input_schema - registry[rel_name][type] = klass.build(relation) + registry[rel_name][type] = klass.build(relation.dataset, input: command_input) end end diff --git a/lib/rom/commands/class_interface.rb b/lib/rom/commands/class_interface.rb index f55a50a26..e39212491 100644 --- a/lib/rom/commands/class_interface.rb +++ b/lib/rom/commands/class_interface.rb @@ -71,8 +71,8 @@ def adapter_namespace(adapter) # @return [Command] # # @api public - def build(relation, **options) - new(relation, **self.options, **options) + def build(dataset, **options) + new(dataset, **self.options, **options) end # Create a command class with a specific type diff --git a/lib/rom/core.rb b/lib/rom/core.rb index cedd3e377..9985634c3 100644 --- a/lib/rom/core.rb +++ b/lib/rom/core.rb @@ -25,9 +25,9 @@ # container factory require "rom/create_container" -# register known plugin types require "rom/schema_plugin" +# register known plugin types ROM::Plugins.register(:command) ROM::Plugins.register(:mapper) ROM::Plugins.register(:relation) @@ -37,7 +37,6 @@ # register core plugins require "rom/plugins/relation/registry_reader" require "rom/plugins/relation/instrumentation" -require "rom/plugins/command/schema" require "rom/plugins/command/timestamps" require "rom/plugins/schema/timestamps" @@ -48,7 +47,6 @@ module ROM register :timestamps, ROM::Plugins::Schema::Timestamps, type: :schema register :registry_reader, ROM::Plugins::Relation::RegistryReader, type: :relation register :instrumentation, ROM::Plugins::Relation::Instrumentation, type: :relation - register :schema, ROM::Plugins::Command::Schema, type: :command register :timestamps, ROM::Plugins::Command::Timestamps, type: :command end end diff --git a/lib/rom/memory/commands.rb b/lib/rom/memory/commands.rb index caeb7bc22..3838f10b9 100644 --- a/lib/rom/memory/commands.rb +++ b/lib/rom/memory/commands.rb @@ -13,13 +13,12 @@ module Commands # @api public class Create < ROM::Commands::Create adapter :memory - use :schema # @see ROM::Commands::Create#execute def execute(tuples) Array([tuples]).flatten.map { |tuple| attributes = input[tuple] - relation.insert(attributes.to_h) + dataset.insert(attributes.to_h) attributes }.to_a end @@ -30,12 +29,11 @@ def execute(tuples) # @api public class Update < ROM::Commands::Update adapter :memory - use :schema # @see ROM::Commands::Update#execute def execute(params) attributes = input[params] - relation.map { |tuple| tuple.update(attributes.to_h) } + dataset.map { |tuple| tuple.update(attributes.to_h) } end end @@ -47,7 +45,7 @@ class Delete < ROM::Commands::Delete # @see ROM::Commands::Delete#execute def execute - relation.to_a.map do |tuple| + dataset.to_a.map do |tuple| source.delete(tuple) tuple end diff --git a/lib/rom/plugins/command/schema.rb b/lib/rom/plugins/command/schema.rb deleted file mode 100644 index 6100e1244..000000000 --- a/lib/rom/plugins/command/schema.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -module ROM - module Plugins - module Command - # Command plugin which sets input processing function via relation schema - # - # @api private - module Schema - def self.included(klass) - super - klass.extend(ClassInterface) - end - - # @api private - module ClassInterface - # Build a command and set it input to relation's input_schema - # - # @see Command.build - # - # @return [Command] - # - # @api public - def build(relation, **options) - if relation.schema? && !options.key?(:input) - relation_input = relation.input_schema - command_input = input - - composed_input = - if command_input.equal?(ROM::Command.input) - relation_input - else - -> tuple { relation_input[command_input[tuple]] } - end - - super(relation, **options, input: composed_input) - else - super - end - end - end - end - end - end -end diff --git a/lib/rom/relation/commands.rb b/lib/rom/relation/commands.rb index 58cec5ed7..f5eff97fe 100644 --- a/lib/rom/relation/commands.rb +++ b/lib/rom/relation/commands.rb @@ -54,11 +54,7 @@ def command(type, mapper: nil, use: EMPTY_ARRAY, plugins_options: EMPTY_HASH, ** base_command end - if command.restrictible? - command.new(self) - else - command - end + command.new(dataset) end end end diff --git a/lib/rom/setup/finalize/finalize_commands.rb b/lib/rom/setup/finalize/finalize_commands.rb index 99ed83581..61b0abaab 100644 --- a/lib/rom/setup/finalize/finalize_commands.rb +++ b/lib/rom/setup/finalize/finalize_commands.rb @@ -39,9 +39,12 @@ def run! command: klass, gateway: gateway, dataset: relation.dataset, adapter: relation.adapter ) - klass.extend_for_relation(relation) if klass.restrictable - - klass.build(relation) + klass.build( + relation.dataset, + input: relation.input_schema, + name: relation.name, + gateway: relation.gateway + ) end registry = Registry.new @@ -54,7 +57,7 @@ def run! ) @relations.each do |(name, relation)| - rel_commands = commands.select { |c| c.relation.name == relation.name } + rel_commands = commands.select { |c| c.dataset.eql?(relation.dataset) } rel_commands.each do |command| identifier = command.class.register_as || command.class.default_name diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d9f2b829a..30a7b4376 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -20,6 +20,7 @@ require "rom/core" require "rom-changeset" +require "byebug" Dir[root.join("support/**/*.rb").to_s].each do |f| require f unless f.include?("coverage") diff --git a/spec/unit/rom/commands/lazy_spec.rb b/spec/unit/rom/commands/lazy_spec.rb index 09e53443d..349e90a9d 100644 --- a/spec/unit/rom/commands/lazy_spec.rb +++ b/spec/unit/rom/commands/lazy_spec.rb @@ -142,7 +142,7 @@ def associate(tuple, user) ROM::Commands::Lazy[update_task].new( update_task, evaluator, - -> cmd, user, task { cmd.by_user(user[:name]).by_title(task[:title]) } + -> cmd, user, task { relation.by_user(user[:name]).by_title(task[:title]).command(:update) } ) end diff --git a/spec/unit/rom/commands/pre_and_post_processors_spec.rb b/spec/unit/rom/commands/pre_and_post_processors_spec.rb index 517222685..67a9be9c5 100644 --- a/spec/unit/rom/commands/pre_and_post_processors_spec.rb +++ b/spec/unit/rom/commands/pre_and_post_processors_spec.rb @@ -4,8 +4,8 @@ require "rom/memory" RSpec.describe ROM::Commands::Create[:memory], "before/after hooks" do - let(:relation) do - spy(:relation) + let(:dataset) do + spy(:dataset) end describe "#before" do @@ -19,7 +19,7 @@ def init(*); end def normalize(*); end def prepare(*); end - end.build(relation) + end.build(dataset) end it "returns a new command with configured before hooks" do @@ -38,7 +38,7 @@ def finalize(*); end def filter(*); end def prepare(*); end - end.build(relation) + end.build(dataset) end it "returns a new command with configured after hooks" do @@ -62,7 +62,7 @@ def prepare(*); end def execute(tuples) input = tuples.map.with_index { |tuple, idx| tuple.merge(id: idx + 1) } - relation.insert(input) + dataset.insert(input) input end @@ -73,7 +73,7 @@ def prepare(tuples) def finalize(tuples) tuples.map { |tuple| tuple.merge(finalized: true) } end - end.build(relation) + end.build(dataset) end let(:tuples) do @@ -93,7 +93,7 @@ def finalize(tuples) expect(command.call(tuples)).to eql(result) - expect(relation).to have_received(:insert).with(insert_tuples) + expect(dataset).to have_received(:insert).with(insert_tuples) end end @@ -106,7 +106,7 @@ def finalize(tuples) def execute(tuples) input = tuples.map.with_index { |tuple, idx| tuple.merge(id: idx + 1) } - relation.insert(input) + dataset.insert(input) input end @@ -117,15 +117,15 @@ def prepare(tuples, name) def finalize(tuples, *) tuples.map { |tuple| tuple.merge(finalized: true) } end - end.build(relation) + end.build(dataset) end let(:tuples) do [{email: "user-1@test.com"}, {email: "user-2@test.com"}] end - let(:relation) do - spy(:relation) + let(:dataset) do + spy(:dataset) end it "applies before/after hooks" do @@ -141,7 +141,7 @@ def finalize(tuples, *) expect(command.curry(tuples).call("User")).to eql(result) - expect(relation).to have_received(:insert).with(insert_tuples) + expect(dataset).to have_received(:insert).with(insert_tuples) end end @@ -154,7 +154,7 @@ def finalize(tuples, *) def execute(tuples) input = tuples.map.with_index { |tuple, idx| tuple.merge(id: idx + 1) } - relation.insert(input) + dataset.insert(input) input end @@ -165,15 +165,15 @@ def prepare(tuples, name) def finalize(tuples, *) tuples.map { |tuple| tuple.merge(finalized: true) } end - end.build(relation) + end.build(dataset) end let(:tuples) do [{email: "user-1@test.com"}, {email: "user-2@test.com"}] end - let(:relation) do - spy(:relation) + let(:dataset) do + spy(:dataset) end it "applies before/after hooks" do @@ -189,7 +189,7 @@ def finalize(tuples, *) expect(command.curry(tuples, "User").call).to eql(result) - expect(relation).to have_received(:insert).with(insert_tuples) + expect(dataset).to have_received(:insert).with(insert_tuples) end end @@ -202,7 +202,7 @@ def finalize(tuples, *) def execute(tuples) input = tuples.map.with_index { |tuple, idx| tuple.merge(id: idx + 1) } - relation.insert(input) + dataset.insert(input) input end @@ -213,15 +213,15 @@ def prepare(tuples, opts) def finalize(tuples, opts) tuples.map { |tuple| tuple.merge(opts) } end - end.build(relation) + end.build(dataset) end let(:tuples) do [{name: "Jane"}, {name: "Joe"}] end - let(:relation) do - spy(:relation) + let(:dataset) do + spy(:dataset) end it "applies before/after hooks" do @@ -237,7 +237,7 @@ def finalize(tuples, opts) expect(command.call(tuples)).to eql(result) - expect(relation).to have_received(:insert).with(insert_tuples) + expect(dataset).to have_received(:insert).with(insert_tuples) end end @@ -250,7 +250,7 @@ def finalize(tuples, opts) def execute(tuples) input = tuples.map.with_index { |tuple, idx| tuple.merge(id: idx + 1) } - relation.insert(input) + dataset.insert(input) input end @@ -261,15 +261,15 @@ def prepare(tuples, parent, opts) def finalize(tuples, parent, opts) tuples.map { |tuple| tuple.merge(opts).merge(user_id: parent[:id]) } end - end.build(relation) + end.build(dataset) end let(:tuples) do [{name: "Jane"}, {name: "Joe"}] end - let(:relation) do - spy(:relation) + let(:dataset) do + spy(:dataset) end it "applies before/after hooks" do @@ -285,7 +285,7 @@ def finalize(tuples, parent, opts) expect(command.curry(tuples).call(id: 1)).to eql(result) - expect(relation).to have_received(:insert).with(insert_tuples) + expect(dataset).to have_received(:insert).with(insert_tuples) end end @@ -298,7 +298,7 @@ def finalize(tuples, parent, opts) def execute(tuples) input = tuples.map.with_index { |tuple, idx| tuple.merge(id: idx + 1) } - relation.insert(input) + dataset.insert(input) input end @@ -309,15 +309,15 @@ def prepare(tuples, parent, opts) def finalize(tuples, parent, opts) tuples.map { |tuple| tuple.merge(opts).merge(user_id: parent[:id]) } end - end.build(relation) + end.build(dataset) end let(:tuples) do [{name: "Jane"}, {name: "Joe"}] end - let(:relation) do - spy(:relation) + let(:dataset) do + spy(:dataset) end it "applies before/after hooks" do @@ -333,7 +333,7 @@ def finalize(tuples, parent, opts) expect(command.call(tuples, id: 1)).to eql(result) - expect(relation).to have_received(:insert).with(insert_tuples) + expect(dataset).to have_received(:insert).with(insert_tuples) end end end diff --git a/spec/unit/rom/commands_spec.rb b/spec/unit/rom/commands_spec.rb index a53626a09..d21678757 100644 --- a/spec/unit/rom/commands_spec.rb +++ b/spec/unit/rom/commands_spec.rb @@ -6,14 +6,6 @@ include_context "gateway only" include_context "users and tasks" - let(:users_relation) do - Class.new(ROM::Memory::Relation) do - def by_id(id) - restrict(id: id) - end - end.new(users_dataset) - end - describe ".build_class" do it "creates a command class constant" do klass = ROM::ConfigurationDSL::Command.build_class(:create, :users, adapter: :memory) { @@ -25,7 +17,7 @@ def super? expect(klass.name).to eql("ROM::Memory::Commands::Create[Users]") expect(klass.register_as).to eql(:create) - command = klass.build(users_relation) + command = klass.build(users_dataset) expect(command).to be_a(ROM::Memory::Commands::Create) expect(command).to be_super @@ -56,7 +48,7 @@ def super? relation :users end - command = klass.build(users_relation) + command = klass.build(users_dataset) expect(command).to be_kind_of(ROM::Memory::Commands::Create) end @@ -66,7 +58,7 @@ def super? relation :users end - command = klass.build(users_relation) + command = klass.build(users_dataset) expect(command).to be_kind_of(ROM::Memory::Commands::Update) end @@ -76,16 +68,16 @@ def super? relation :users end - command = klass.build(users_relation) + command = klass.build(users_dataset) expect(command).to be_kind_of(ROM::Memory::Commands::Delete) end end describe "#>>" do - let(:users) { double("users", schema: nil) } - let(:tasks) { double("tasks", schema: nil) } - let(:logs) { double("logs", schema: nil) } + let(:users) { double("users", schema: nil, dataset: []) } + let(:tasks) { double("tasks", schema: nil, dataset: []) } + let(:logs) { double("logs", schema: nil, dataset: []) } it "composes two commands" do user_input = {name: "Jane"} @@ -96,7 +88,7 @@ def super? create_user = Class.new(ROM::Commands::Create) { def execute(user_input) - relation.insert(user_input) + dataset.insert(user_input) end }.build(users) @@ -108,7 +100,7 @@ def associate(task_input, user_tuple) end def execute(task_input) - relation.insert(task_input) + dataset.insert(task_input) end }.build(tasks) @@ -116,7 +108,7 @@ def execute(task_input) result :one def execute(task_tuple) - relation << task_tuple + dataset << task_tuple end }.build(logs) @@ -137,7 +129,7 @@ def execute(task_tuple) create_user = Class.new(ROM::Commands::Create) { def execute(user_input) - relation.insert(user_input) + dataset.insert(user_input) end }.build(users) diff --git a/spec/unit/rom/memory/commands_spec.rb b/spec/unit/rom/memory/commands_spec.rb index 11313cd5e..b4a4877f3 100644 --- a/spec/unit/rom/memory/commands_spec.rb +++ b/spec/unit/rom/memory/commands_spec.rb @@ -13,8 +13,12 @@ end.new(ROM::Memory::Dataset.new([])) end + let(:options) do + { input: relation.input_schema } + end + describe "Create" do - subject(:command) { ROM::Commands::Create[:memory].build(relation) } + subject(:command) { ROM::Commands::Create[:memory].build(relation, options) } describe "#call" do it "uses default input handler" do @@ -26,7 +30,7 @@ end describe "Update" do - subject(:command) { ROM::Commands::Update[:memory].build(relation) } + subject(:command) { ROM::Commands::Update[:memory].build(relation, options) } before do relation.insert(id: 1, name: "Jane") diff --git a/spec/unit/rom/plugins/command/schema_spec.rb b/spec/unit/rom/plugins/command/schema_spec.rb deleted file mode 100644 index cbdcb3b0a..000000000 --- a/spec/unit/rom/plugins/command/schema_spec.rb +++ /dev/null @@ -1,68 +0,0 @@ -# frozen_string_literal: true - -require "rom/relation" -require "rom/command" -require "rom/plugins/command/schema" - -RSpec.describe ROM::Plugins::Command::Schema do - describe ".build" do - let(:command_class) do - Class.new(ROM::Command) { use :schema } - end - - context "when relation has no schema defined" do - let(:relation) do - instance_double(ROM::Relation, schema?: false) - end - - it "sets default input handler when command does not have a custom one" do - command = Class.new(command_class).build(relation) - - expect(command.input).to be(ROM::Command.input) - end - - it "sets custom input handler when command defines it" do - my_handler = double(:my_handler) - - command = Class.new(command_class) { input my_handler }.build(relation) - - expect(command.input).to be(my_handler) - end - - it "sets custom input handler when it is passed as an option" do - my_handler = double(:my_handler) - - command = Class.new(command_class).build(relation, input: my_handler) - - expect(command.input).to be(my_handler) - end - end - - context "when relation has a schema" do - let(:relation) do - instance_double(ROM::Relation, schema?: true, input_schema: input_schema) - end - - let(:input_schema) do - double(:input_schema) - end - - it "sets schema hash as input handler" do - command = Class.new(command_class).build(relation) - - expect(command.input).to be(input_schema) - end - - it "sets a composed input handler with schema hash and a custom one" do - my_handler = double(:my_handler) - - command = Class.new(command_class) { input my_handler }.build(relation) - - expect(my_handler).to receive(:[]).with("some value").and_return("my handler") - expect(input_schema).to receive(:[]).with("my handler").and_return("a tuple") - - expect(command.input["some value"]).to eql("a tuple") - end - end - end -end diff --git a/spec/unit/rom/plugins/command/timestamps_spec.rb b/spec/unit/rom/plugins/command/timestamps_spec.rb index 25e9e6f3d..30bf72891 100644 --- a/spec/unit/rom/plugins/command/timestamps_spec.rb +++ b/spec/unit/rom/plugins/command/timestamps_spec.rb @@ -11,17 +11,8 @@ let(:time) { DateTime.now } before do - configuration.relation :users do - def by_name(name) - restrict(name: name) - end - end - - configuration.relation :tasks do - def by_priority(priority) - restrict(priority: priority) - end - end + configuration.relation :users + configuration.relation :tasks configuration.commands(:users) do define :create_with_timestamps_options, type: :create do @@ -63,6 +54,7 @@ def by_priority(priority) timestamp :updated_at, :created_at before :assign_task + def assign_task(tuple, task) tuple.merge(task_id: task[:id]) end diff --git a/spec/unit/rom/repository/inspect_spec.rb b/spec/unit/rom/repository/inspect_spec.rb index aa5379bf0..82d8f6a7f 100644 --- a/spec/unit/rom/repository/inspect_spec.rb +++ b/spec/unit/rom/repository/inspect_spec.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "rom" + RSpec.describe ROM::Repository, "#inspect" do subject(:repo) do Class.new(ROM::Repository) do diff --git a/spec/unit/rom/repository/transaction_spec.rb b/spec/unit/rom/repository/transaction_spec.rb index e0f4dac16..0ce4910ae 100644 --- a/spec/unit/rom/repository/transaction_spec.rb +++ b/spec/unit/rom/repository/transaction_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "rom-changeset" +require "rom" RSpec.describe ROM::Repository, "#transaction" do let(:user_repo) do