Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/operational/error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ class Error < StandardError; end
class InvalidContractModel < Error; end
class MethodNotImplemented < Error; end
class UnknownStepType < Error; end
class MethodCollision < Error; end
end
51 changes: 51 additions & 0 deletions lib/operational/form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,57 @@ class Form
include ActiveModel::Attributes
include ActiveModel::Dirty

def self.inherited(subclass)
super
subclass.instance_variable_set(:@_operational_sync_check_pending, true)
end

def self.method_added(method_name)
super
if method_name == :sync && @_operational_sync_check_pending
raise MethodCollision,
"#{self} defines #sync, which collides with Operational::Form#sync. " \
"Rename your method to #on_sync — it will be called automatically during sync."
end
end

def self.build(model: nil, model_persisted: nil, state: {}, prepopulate_method: :prepopulate)
form = new

if model
raise InvalidContractModel unless model.respond_to?(:attributes)
valid_form_attrs = form.attribute_names
valid_params = model.attributes.slice(*valid_form_attrs).compact
form.assign_attributes(valid_params)
end

form.instance_variable_set(:@_operational_model_persisted, (model_persisted.nil? ? model&.persisted? || false : !!model_persisted))
form.instance_variable_set(form.send(:_operational_state_variable), state.dup.freeze)
form.send(prepopulate_method, state) if form.respond_to?(prepopulate_method)
form.changes_applied if form.respond_to?(:changes_applied)
form
end

def validate(params = {})
params = params.to_unsafe_h if params.respond_to?(:to_unsafe_h)
params = params.with_indifferent_access if params.respond_to?(:with_indifferent_access)
valid_params = params.slice(*attribute_names)
assign_attributes(valid_params)
valid?
end

def sync(model: nil, state: {}, sync_method: :on_sync)
if model
raise InvalidContractModel unless model.respond_to?(:attributes)
valid_model_attrs = model.attribute_names
valid_params = attributes.slice(*valid_model_attrs)
model.assign_attributes(valid_params)
end

send(sync_method, state) if respond_to?(sync_method)
true
end

def persisted?
@_operational_model_persisted
end
Expand Down
47 changes: 15 additions & 32 deletions lib/operational/operation/contract.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,16 @@ class Operation
module Contract
def self.Build(contract:, name: :contract, model_key: nil, model_persisted: nil, prepopulate_method: :prepopulate)
lambda do |state|
state[name] = contract.new

if model_key.present?
raise InvalidContractModel if !state[model_key]&.respond_to?(:attributes)
valid_form_attrs = state[name].attribute_names
valid_params = state[model_key].attributes.slice(*valid_form_attrs).compact
state[name].assign_attributes(valid_params)
end

state[name].instance_variable_set(:@_operational_model_persisted, (model_persisted.nil? ? state[model_key]&.persisted? || false : !!model_persisted))
state[name].instance_variable_set(state[name].send(:_operational_state_variable), state.dup.freeze)
state[name].send(prepopulate_method, state) if state[name].respond_to?(prepopulate_method)
state[name].changes_applied if state[name].respond_to?(:changes_applied)
return true
model = model_key.present? ? state[model_key] : nil
raise InvalidContractModel if model_key.present? && model.nil?

state[name] = contract.build(
model: model,
model_persisted: model_persisted,
state: state,
prepopulate_method: prepopulate_method
)
true
end
end

Expand All @@ -28,30 +24,17 @@ def self.Validate(name: :contract, params_path: nil)
when params_path.is_a?(Array) then params_path
end

valid_attrs = state[name].attribute_names

raw_params = state.dig(*valid_path) || {}
raw_params = raw_params.to_unsafe_h if raw_params.respond_to?(:to_unsafe_h)
raw_params = raw_params.with_indifferent_access

valid_params = raw_params.slice(*valid_attrs)

state[name].assign_attributes(valid_params)
state[name].valid?
state[name].validate(raw_params)
end
end

def self.Sync(name: :contract, model_key: nil, sync_method: :sync)
def self.Sync(name: :contract, model_key: nil, sync_method: :on_sync)
lambda do |state|
if model_key.present?
raise InvalidContractModel if !state[model_key]&.respond_to?(:attributes)
valid_model_attrs = state[model_key].attribute_names
valid_params = state[name].attributes.slice(*valid_model_attrs)
state[model_key].assign_attributes(valid_params)
end
model = model_key.present? ? state[model_key] : nil
raise InvalidContractModel if model_key.present? && model.nil?

state[name].send(sync_method, state) if state[name].respond_to?(sync_method)
return true
state[name].sync(model: model, state: state, sync_method: sync_method)
end
end
end
Expand Down
Loading