Skip to content

Instantly share code, notes, and snippets.

@delbetu
Created February 15, 2019 18:49
Show Gist options
  • Save delbetu/26dc4b7d952669ee6ea7cdc87e9facfd to your computer and use it in GitHub Desktop.
Save delbetu/26dc4b7d952669ee6ea7cdc87e9facfd to your computer and use it in GitHub Desktop.
These gists show a refactor which objective is isolate code that is part of a controller.
class IsolatedCode
def initialize(release_being_sent = { state: 'published' },
release)
@release_being_sent = release_being_sent
@release = release
end
def foo
ActiveRecord::Base.transaction do
if publishing?
namespace.lock!('FOR UPDATE NOWAIT')
if publishable?
release.update!(release_update_params)
Release::Publication::ProcessAfterActions.call(release: release)
else
return 'not_publishable'
end
else
release.update!(release_update_params)
end
end
end
private
def publishing?
if release_being_sent.present? && release_being_sent[:state] == 'published' &&
!release.published?
return true
end
false
end
def publishable?(release)
validation = Release::Publication::Validate.call(release: release)
# Raise an exception if the context failed while evaluating release errors
# Don't confuse this (unlikely) processing error handling with the errors/warnings related to the release
fail "Publication validation exception: #{validation.message}" if validation.failure?
validation.errors.blank?
end
end
def update
o = IsolatedCode.new(params[:release], release: release, namespace: namespace)
if o.foo == 'not_publishable'
render(status: :bad_request, body: I18n.t('release_validation.errors_present')) && return
end
render status: :no_content
end
def namespace
@namespace ||= Th::Namespace.includes(:releases).find_by!(api_name: params[:namespace_api_name])
end
def releases
@releases ||= namespace.releases
end
def release
@release ||= releases.find_by!(api_name: params[:api_name])
end
class IsolatedCode
def initialize(release_being_sent = { state: 'published' },
release)
@release_being_sent = release_being_sent
@release = release
end
def foo
ActiveRecord::Base.transaction do
if publishing?
namespace.lock!('FOR UPDATE NOWAIT')
if publishable?
release.update!(release_update_params)
Release::Publication::ProcessAfterActions.call(release: release)
else
return 'not_publishable'
end
else
release.update!(release_update_params)
end
end
end
private
def publishing?
if release_being_sent.present? && release_being_sent[:state] == 'published' &&
!release.published?
return true
end
false
end
def publishable?(release)
validation = Release::Publication::Validate.call(release: release)
# Raise an exception if the context failed while evaluating release errors
# Don't confuse this (unlikely) processing error handling with the errors/warnings related to the release
fail "Publication validation exception: #{validation.message}" if validation.failure?
validation.errors.blank?
end
end
class IsolatedCode
def initialize(release_being_sent = { state: 'published' },
release, transaction_manager: ActiveRecord::Base,
release_publication: Release::Publication::ProcessAfterActions,
release_validator: Release::Publication::Validate
)
@release_being_sent = release_being_sent
@release = release
end
def foo
transaction_manager.transaction do
if publishing?
namespace.lock!('FOR UPDATE NOWAIT')
if publishable?
release.update!(release_update_params)
release_publication.call(release: release)
else
return 'not_publishable'
end
else
release.update!(release_update_params)
end
end
end
private
def publishing?
if release_being_sent.present? && release_being_sent[:state] == 'published' &&
!release.published?
return true
end
false
end
def publishable?(release)
validation = release_validator.call(release: release)
# Raise an exception if the context failed while evaluating release errors
# Don't confuse this (unlikely) processing error handling with the errors/warnings related to the release
fail "Publication validation exception: #{validation.message}" if validation.failure?
validation.errors.blank?
end
end
@delbetu
Copy link
Author

delbetu commented Feb 15, 2019

initial_solution shows a controller action which depends on active record.
second_solution introduces a collaborator. It helps to split responsibilities. Controller only does controller stuff (manage request and render the page). Since Controller doesn't do much you can avoid testing it and test the collaborator.
third_solution Identifies the active_record (and others) dependencies on IsolatedCode and parameterize them.
After this refactor you can test IsolatedCode#foo instead of testing the controller#update.
All dependencies are parameterized so the test can pass fake objects for these dependencies.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment