-
Notifications
You must be signed in to change notification settings - Fork 14
Implementing an Op
Ops have some fluff, but not much. The Subroutine::Op
class' entire purpose in life is to validate user input and execute a series of operations. To enable this we filter input params, type cast inputs if desired, perform validations, and perform authorization. Only after these things are complete will the Op
perform its operation.
Inputs can be declared via the field
method but are more commonly declared by type (integer, decimal, date).
class MyOp < ::Subroutine::Op
field :first_name
field :age, type: :integer
field :subscribed, type: :boolean, default: false
# ...
end
class MyOpWithTypes < ::Subroutine::Op
string :first_name
integer :age
boolean :subscribed, default: false
# ...
end
-
type - declares the data type which the input should be cast. Available types are declared in
Subroutine::TypeCaster::TYPES
. New types can be registered via::Subroutine::TypeCaster.register
-
default - the default value of the input if not otherwise provided. If the provided default is callable, the result of that
call
will be used at runtime. -
mass_assignable - whether an input can be passed via the constructor. Defaults to
true
. - field_reader - whether a reader method is created in the op
- field_writer - whether a writer method is created in the op
-
groups - the param groups this field should be part of. Omission of this makes the field available in
params
. - aka - [deprecated] an alias (or aliases) that is checked when errors are inherited from other objects.
Since ops can use other ops, sometimes it's nice to explicitly state the inputs are valid. To "inherit" all the inputs from another op, simply use inputs_from
. Do note this inherits the defaults, groups, etc, but does not bring over any validations you may declare against the fields.
class MyOp < ::Subroutine::Op
string :token
inputs_from MyOtherOp
protected
def perform
verify_token!
MyOtherOp.submit! params # will include any params relevant to MyOtherOp as well as `token`
end
end
Since Ops include ActiveModel::Model, validations can be used just like any other ActiveModel object.
class MyOp < ::Subroutine::Op
string :first_name
validates :first_name, presence: true
end
Inputs are accessible within the op via field accessors and param accessors. You can see if an input was provided via the field_provided?
method.
class MyOp < ::Subroutine::Op
string :first_name
validate :validate_first_name_is_not_bob
protected
def perform
# whatever this op does
true
end
def validate_first_name_is_not_bob
if field_provided?(:first_name) && first_name.downcase == 'bob'
errors.add(:first_name, 'should not be bob')
end
end
end
All provided params are accessible via the params
accessor. All default values are accessible via the defaults
accessor. The combination of the two is available via params_with_defaults
.
class MyOp < ::Subroutine::Op
string :name
string :status, default: "browsing"
def perform
puts params.inspect
puts defaults.inspect
puts params_with_defaults.inspect
puts respond_to?(:name)
puts field_provided?(:status)
end
end
MyOp.submit(name: "foobar", status: nil)
# => { name: "foobar" }
# => { status: "browsing" }
# => { name: "foobar", status: nil }
# => true
# => true
MyOp.submit(name: "foobar")
# => { name: "foobar" }
# => { status: "browsing" }
# => { name: "foobar", status: "browsing" }
# => true
# => false