Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce an object registry #371

Open
wants to merge 39 commits into
base: master
Choose a base branch
from
Open

Introduce an object registry #371

wants to merge 39 commits into from

Conversation

georgebrock
Copy link
Collaborator

What?

This PR is a large refactor which replaces dependency injection with the registry pattern for shared instances, or direct references to class names that were previously passed as constructor arguments.

Previously we had a lot of classes that looked like this:

class Foo
  def initialize(env, bar_factory = Gitsh::Bar)
    @bar = bar_factory.new(env)
    @env = env
  end

  private

  attr_reader :bar, :env
end

Now, those same classes look like this:

class Foo
  extend Registry::Client
  use_registry_for :env

  def initialize
    @bar = Gitsh::Bar.new
  end

  private

  attr_reader :bar
end

The objects moved to the registry are the shared instances of:

  • Gitsh::Environment
  • Gitsh::GitRepository
  • Gitsh::LineEditorHistoryFilter (decorating Gitsh::LineEditor)

Why?

The nature of gitsh means that some amount of long-lasting, global state is inevitable. Unlike a Web application, which needs to handle multiple concurrent requests from different users, a gitsh process only runs a single command at any given time, and does so for a single user in a single session.

We get a lot of benefits from this approach, which include:

  • Fewer redundant arguments

    There were several classes which held a reference to the shared Gitsh::Environment instance only because they needed to pass it along to some other object that needed to use it.

  • Easier to break up Gitsh::Environment

    The Gitsh::Environment has become a bit of a junk drawer. The main reason is that it was the one object that was available everywhere, so access to any shared state was easiest if it went through this object.

    Introducing the registry means we are able to drop all of the Gitsh::Environment#repo_* methods (which delegated directly to a Gitsh::GitRepository instance) and instead share both the Gitsh::Environment and Gitsh::GitRepository via the registry.

    The remaining responsibilities of Gitsh::Environment (I/O, variable access, and access to some global config) can be broken out in future PRs.

  • Easier to share other instances

    The final straw in doing this refactor was starting work on globbing patterns (Wildcards don't seem to work correctly #145), and realising we will need to access the tab completion automaton while evaluating variable values. Adding yet another thing to the environment was not appealing, nor was passing another instance around through the whole system, or building multiple instances of a pretty large tab completion graph.

    After this refactor, we'll be able to use the registry to share the tab completion automaton instance, and easily access it directly from where it's needed.

Code review

This diff is very large (~1,000 changed lines), but many of the changes are very similar and reviewers should probably focus on:

  • The Gitsh::Registry implementation in lib/gitsh/registry.rb
  • The new test helpers in spec/support/registry.rb

Instead of passing around `env` absolutely everywhere, let's make it
available globally in a single, well-defined place.

We can't use a singleton for this, because we do sometimes need to create
different, modified environments, e.g. subshells.
initialized?: true,
has_modified_files?: true,
has_untracked_files?: false,
))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style/TrailingCommaInArguments: Put a comma after the last parameter of a multiline method call.

format: "%c",
)
prompter = Gitsh::Prompter.new(env: env)
set_registered_env_value('gitsh.prompt', "%c")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.

initialized?: true,
has_modified_files?: true,
has_untracked_files?: false,
))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style/TrailingCommaInArguments: Put a comma after the last parameter of a multiline method call.

prompter = Gitsh::Prompter.new(env: env)
set_registered_env_value('gitsh.prompt', "%#")
register_repo(status: double(
"Status",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.

format: "%#",
)
prompter = Gitsh::Prompter.new(env: env)
set_registered_env_value('gitsh.prompt', "%#")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.

build_env(variables: ['user.name', 'user.email', 'greeting']),
register_line_editor
register_env(
available_variables: [:'user.name', :'user.email', :'greeting'],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style/SymbolArray: Use %i or %I for an array of symbols.
Style/SymbolLiteral: Do not use strings for word-like symbol literals.


input_strategy.handle_parse_error('my message')

expect(env).to have_received(:puts_error).with('gitsh: my message')
expect(registered_env).to have_received(:puts_error).with('gitsh: my message')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics/LineLength: Line is too long. [84/80]

input_strategy = build_input_strategy
allow(line_editor).to receive(:readline).and_return('user input')
input_strategy = described_class.new
allow(registered_line_editor).to receive(:readline).and_return('user input')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics/LineLength: Line is too long. [82/80]

line_editor_results = StubbedMethodResult.new.
raises(Interrupt).
returns('user input after interrupt')
allow(line_editor).to receive(:readline) { line_editor_results.next_result }
allow(registered_line_editor).to receive(:readline) { line_editor_results.next_result }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics/LineLength: Line is too long. [93/80]

input_strategy = build_input_strategy
allow(line_editor).to receive(:readline).and_return('user input')
input_strategy = described_class.new
allow(registered_line_editor).to receive(:readline).and_return('user input')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics/LineLength: Line is too long. [82/80]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

1 participant