Geo: use Dependency Injection to abstract out database fields
While I was having a look at !18886 (closed), I was seeing an increased use of things like .public_send("#{type}s_pending_verification") and repository_state_table["#{repo_type}_retry_at"].
I'm not a huge fan of that, so I want to suggest dependency injection as an alternative.
Proposal
(don't pin me down on the naming, it's just a proposal)
We introduce something like RegistryAccessor and SyncableRepoAccessor. These classes are "protocols" (also sometimes called "interfaces") which all derived classes should respond to.
When for example a AnyRepositorySyncService wants to sync a repository (it can be project repo, wiki repo, design repo, or whatever), it will given upon constructions an instance of RegistryAccessor and SyncableRepoAccessor. The object stores these in instance variables, and uses these to access properties of the repo it wants to sync.
Example
Let me try to explain with a crude example.
module Geo
class AnyRepositorySyncService
def initialize(repo_accessor, registry_accessor)
@repo_accessor = repo_accessor
@registry_accessor = registry_accessor
end
def sync!
# Do the magic
rescue
failed!
end
def failed!
@registry_accessor.failed!
end
def last_sync
@repo_accessor.last_sync
end
end
class WikiRepositoryAccessor
def initialize(project)
@project = project
end
def last_sync
@project.last_wiki_synced_at
end
end
class ProjectRepositryAccessor
def initialize(project)
@project = project
end
def last_sync
@project.last_repository_synced_at
end
end
class WikiRegistryAccessor
def initialize(registry)
@registy = registry
end
def failed!
@registry.wiki_sync_failed = true
end
end
class ProjectRepositryAccessor
def initialize(registry)
@registy = registry
end
def failed!
@registry.repository_sync_failed = true
end
end
end
The Geo::AnyRepositorySyncService is not capable of syncing any type of repo, as long as you provide it with 2 objects that know how to read/write things (on second though, they don't have to be 2 separate objects).
So to sync a wiki:
project = Project.first
wiki_repo_accessor = WikiRepositoryAccessor.new(project)
wiki_registry_accessor = WikiRegistryAccessor.new(project)
Geo::AnyRepositorySyncService(wiki_repo_accessor, wiki_registry_accessor)
Or to sync the project repo:
proj_repo_accessor = ProjectRepositoryAccessor.new(project)
proj_registry_accessor = ProjectRegistryAccessor.new(project)
Geo::AnyRepositorySyncService(proj_repo_accessor, proj_registry_accessor)
Only at initialization you need to specify the type, once things are instantiated, no more dynamic method or column names are needed. The objects know where to find things.