Operation

Operation Builder

Last updated 29 November 2016 operation v1.1

Different contexts like “admin user” vs. “signed in” can be handled in the same code asset along with countless ifs and elses. Or, you can use polymorphism as it is encouraged by Trailblazer.

The easiest way is to use normal inheritance for context-specific operations.

class Thing::Create < Trailblazer::Operation
  # generic code, contracts, etc.

  class SignedIn < self
    # specific code
  end
end

A builds block allows to let the operation class take care of the instantiation process.

class Thing::Create < Trailblazer::Operation
  builds -> (params) do
    return SignedIn if params[:current_user]
  end

When running the top-level operation, the builder will instantiate the correct subclass according to the params environment.

op = Thing::Create.(current_user: admin)
op.class #=> Thing::Create::SignedIn

If the builds block doesn’t return a constant, the original constant will be used for instantiation. In our example, this would resolve to Thing::Create.

Shared Builders

builds blocks are not inherited. You can copy them to other classes, though.

class Thing::Update < Trailblazer::Operation
  self.builder_class = Create.builder_class

Be careful about constant resolving here: the block you copied has to have runtime evaluation of constants.

class Thing::Create < Trailblazer::Operation
  builds -> (params) do
    return self::SignedIn if # ...
  end

Now, the block can safely be copied to other classes where SignedIn will be resolved in the new context.

Resolver

A resolver allows you to use both the operation model and the policy in the builder.

class Thing::Create < Trailblazer::Operation
  include Resolver

  policy Thing::Policy, :create?
  model Thing, :create

  builds -> (model, policy, params) do
    return Admin if policy.admin?
    return SignedIn if params[:current_user]
  end

Please note that the builds block is run in class context, no operation instance is available, yet. It is important to understand that Resolver also changes the way the operation’s model is created/found. This, too, happens on the class layer, now.

You have to configure the CRUD module using ::model so the operation can instantiate the correct model for the builder.

If you want to change the way the model is created, you have to do so on the class level.

class Thing::Create < Trailblazer::Operation
  include Resolver
  # ..

  def self.model!(params)
    Thing.find_by(slug: params[:slug])
  end

Do not include CRUD when using Resolver.