Skip to content

Ingredient command line options#6

Open
thomcorley wants to merge 6 commits into
masterfrom
ingredient-command-line-options
Open

Ingredient command line options#6
thomcorley wants to merge 6 commits into
masterfrom
ingredient-command-line-options

Conversation

@thomcorley
Copy link
Copy Markdown
Owner

Introduces TTY prompt to allow user to input up to 4 ingredients as command line options, then select from a list of recipes containing those ingredients.

Comment thread bin/keller
Comment on lines +7 to +8
case
when ingredients.none?
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Since this is an empty case statement I think it's equivalent toif/elsif?

Comment thread lib/html_presenter.rb
private

def recipe_info
def recipe_info(recipe)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Almost certainly overkill but all these methods take recipe as an argument, meaning there could be an object here e.g.:

class HTMLRecipe
  def initialize(recipe); @recipe = recipe; end
  def info; ... end
  def ingredients; ... end
  def instructions; ... end
end

so then

def present(recipe:)
would simplify to:

def present(recipe)
  <<~STR
    #{recipe.info}
    #{recipe.ingredients}
    ...
    STR
end

and we wouldn't need e.g.

def recipe_info

That feels definitely overkill - it's an extra object that returns HTML given a Recipe, but it feels like it crosses too many layers of abstraction at once. Better to, as you've done here, use methods on the domain object to build up the HTML, within the same class.

It might be interesting to consider templating, similar to Rails views. That may clean up the output << "myhtml" stuff, since it could use the templating loop functionality.

Another comment more generally is about inheriting from Presenter. Implementing present in the parent class is quite restrictive. A way to think about it is to ask what do HtmlPresenter and PlainTextPresenter share in behaviour? The fact that they can stringify Recipes i.e.

present :: Recipe -> String

is possibly all. That'd mean that having an implementation of present in the parent class is too restrictive - if we want to wrap the HtmlPresenters output in e.g. a <body> tag we'd have to override present, suggesting it wasn't shared behaviour between Presenters.

Comment thread lib/recipe_repository.rb
Comment on lines +21 to +23
all_recipes.select do |recipe|
ingredients.all? { |ingredient| recipe.contains?(ingredient) }
end
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nice 👍 I like the split between the repository and using methods on the domain object.

Comment thread bin/keller
ingredients = ARGV
cli = Cli.new

case
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Possibly an opportunity for pattern matching (case in) on ingredients.

Copy link
Copy Markdown

@ShaneDrury ShaneDrury Jun 8, 2021

Choose a reason for hiding this comment

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

Maybe:

case ingredients
in []
  puts cli.sample
in [a, a, a, a]
  cli.select_recipes(ingredients: ingredients)
else
  ...

Comment thread lib/cli.rb
Comment on lines +22 to +23
selected_recipe = RecipeSelector.new(recipes: matching_recipes).select
puts presenter.present(recipe: selected_recipe)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Possibly rename to TerminalRecipeSelector. Could also use as an instance variable to hide which one we use e.g.:

selected_recipe = recipe_selector.select_one_from(matching_recipes)

Comment thread lib/recipe.rb
Comment on lines +31 to +33
recipe_data["ingredient_entries"].map do |ingredient_entry|
ingredient_entry["ingredient"]
end
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Could be interesting to return an Ingredient object here. We could do things like group ingredients together:

class RecipeBook # or whatever name
  def ingredients
    [
      Ingredient.new("lime"),
      SynonymGroup.new([Ingredient.new("bacon"), Ingredient.new("pancetta")]),
      Ingredient.new("chicken stock"),
    ]
  end
end

I imagine then given an input ingredient we'd iterate through this list until we find the first one that matches, then use that as our canonical ingredient object. We could do the same when creating a Recipe object.
e.g.

class Ingredient
  def initialize(name)
    @name = name
  end
  def match?(name)
    @name =~ name # can do case insensitive matches here too
  end
end

class SynonymGroup
  def initialize(ingredients)
    @ingredients = ingredients
  end
  def match?(name)
    @ingredients.any? { |ingredient| ingredient.match?(name) }
  end
end

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.

2 participants