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

refactor: use generator methods, and class instance variables to separate duties between classes #143

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

jim80net
Copy link
Contributor

@jim80net jim80net commented Oct 26, 2022

Uses the factory method to manage the differences and commonalities in the DataDog API.

The major features of this refactor are:

  • instance methods are responsible for manipulating singular resources
  • class methods are used for manipulating multiples of resources.

TODO:

  • client_spec.rb
  • cli_spec.rb
  • consider collapsing local_filesystem into resources.rb
  • consider collapsing class_methods.rb into parent files.

@jim80net jim80net marked this pull request as draft October 27, 2022 01:49
@jim80net jim80net marked this pull request as ready for review November 7, 2022 22:47
@jim80net jim80net marked this pull request as draft November 7, 2022 22:52
Style/ClassVars:
Enabled: false

Style/GlobalVars:

Choose a reason for hiding this comment

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

The proscription against class and global variables is in general pretty sensible. I haven't yet digested enough of this PR to know whether they can be done without, but if not, I think the Rubocop checks should be disabled on a linewise basis rather than disabling them globally. For example, one a line with a global variable like $options, you can append a comment:

# rubocop:disable Style/GlobalVars

...to turn off the warning for that line only. It's a bit noisy, but I think it's better to draw attention to places where the usual style rules are being overridden by necessity.

...Also, when I check out your branch and search the codebase for the string @@, I don't find any instances. I would expect to find some if Ruby class variables are being employed somewhere. Is it actually necessary to disable the Style/ClassVars rule at all?

def any_resource_instance
resource_instances.first
def initialize(options)
$options = options
Copy link

@grimnebulin grimnebulin Nov 10, 2022

Choose a reason for hiding this comment

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

The original code stored the options in an instance variable rather than a global variable, which would be the more normal practice. This PR is a little large for me to take in all at once, so I'll just ask here: What other code needs to access the options passed in here via the global variable $options?

...Coming back to this comment a bit later, I have some ideas for how to minimize the drawbacks of the use of a global variable. (That is, if its use can't be easily eliminated.)

First, it would be cleaner if the code that instantiates the DatadogBackup::Cli object assigns the options to the global variable rather than leaving it up to the class to do it. I see the code that does the instantiation in bin/datadog_backup:

DatadogBackup::Cli.new(prereqs(defaults)).run!

I'd suggest instead something like:

$options = prereqs(defaults).freeze
DatadogBackup::Cli.new($options).run!

I see also that the Cli class only cares about the resources, force_restore, and action option values. The class could be constructed in a more standard style be storing these as instance variables in the class.

def initialize(resources:, force_restore:, action:, **)
  @resources = resources
  @force_restore = force_restore
  @action = action
end

(The ** will cause any unknown keys in the hash to be ignored.)

Then in the rest of the class you can write just @resources instead of $options[:resources], and likewise for the other keys.

The above only applies if no other code is going to update the options on the fly, which appears to be the case.

def diffs
$options[:resources].each do |resource|
resource.all.each do |resource_instance|
next if resource_instance.diff.nil? || resource_instance.diff.empty?

Choose a reason for hiding this comment

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

It doesn't seem like ActiveSupport is available in this repo...? If it is, you can combine these two tests into just:

next if resource_instance.diff.blank?

@banlist.each do |key|
outhash.delete(key) # delete returns the value at the deleted key, hence the tap wrapper
end
outhash
end

Choose a reason for hiding this comment

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

This method could be more concisely implemented as:

@banlist.each_with_object(hash.dup) do |key, outhash|
  outhash.delete(key)
end

file.write(data)
ensure
file.close
end

Choose a reason for hiding this comment

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

The File.open method can be given a block, and will take care of closing the file afterwards:

::File.open(filename, 'w') do |file|
  file.write(data)
end


def initialize
@client = Faraday.new(
url: ENV.fetch('DD_SITE_URL', 'https://api.datadoghq.com/'),

Choose a reason for hiding this comment

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

The default url might be better stored as a class constant, eg:

DEFAULT_DD_SITE_URL = 'https://api.datadoghq.com/'

And later:

url: ENV.fetch('DD_SITE_URL', DEFAULT_DD_SITE_URL)

@all ||= get_all.map do |resource|
new_resource(id: resource.fetch(@id_keyname), body: resource)
end
LOGGER.info "Found #{@all.length} #{@api_resource_name}s in Datadog"

Choose a reason for hiding this comment

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

This logging will happen even if the get_all response has already been cached in the @all instance variable. Is that intended?

@get_all = @dig_in_list_body ? body.fetch(*@dig_in_list_body) : body
end

# Fetch the specified resource from Datadog and remove the @banlist elements

Choose a reason for hiding this comment

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

Is this comment accurate? The method is very short and doesn't seem to affect @banlist at all.

end

def backup_all
all.map(&:backup)

Choose a reason for hiding this comment

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

I see only one place backup_all is called, and the result is discarded. If the result of calling backup is not needed, you can signal that more explicitly by saying all.each(&:backup) here.

class Resources
##
# Meant to be mixed into DatadogBackup::Resources
# Relies on $options[:backup_dir] and $options[:output_format]

Choose a reason for hiding this comment

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

I don't see $options[:backup_dir] mentioned in this file; is this comment accurate?

@@ -3,142 +3,4 @@
require 'spec_helper'

describe DatadogBackup::Cli do

Choose a reason for hiding this comment

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

This file is now nearly empty and does no testing; can it be removed?

@@ -0,0 +1,4 @@
# frozen_string_literal: true

describe DatadogBackup::Client do

Choose a reason for hiding this comment

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

Is this file necessary? It doesn't seem to do anything.

@strategy = FactoryBot.strategy_by_name(:create).new
end

#delegate :association, to: :@strategy

Choose a reason for hiding this comment

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

Better to just delete this line probably.

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

Successfully merging this pull request may close these issues.

2 participants