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

Implements skip stage #2

Merged
merged 1 commit into from
Jul 17, 2018
Merged

Conversation

rafaels88
Copy link
Contributor

It introduces a new stage skip and solve the issue #1 .

Usage example:

 defmodule CreateUserPipeline do
   use Opus.Pipeline

   skip if: :user_exists?
   step :persist_user

   def user_exists?(_), do: true
 end

 CreateUserPipeline.call(%{}) # {:ok, :skipped}

@@ -0,0 +1,68 @@
digraph G {
Copy link
Owner

Choose a reason for hiding this comment

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

Was this meant to be committed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually I didn't know if it was meant to be commited since it was auto generated. I'll remove it!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed!

defmodule Opus.Pipeline.Stage.SkipTest do
use ExUnit.Case

describe "when the stage returns false and there's no next stage" do
Copy link
Owner

Choose a reason for hiding this comment

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

We should add an example about what happens if there are multiple skips.

defmodule SomePipeline do
  skip if: :skip?
  skip if: :please_skip?
  skip if: :should_it_skip_maybe?

  step :some_step
end

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure! I'll add it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added three more cases about multi skips, take a look

@coveralls
Copy link

coveralls commented Jun 14, 2018

Pull Request Test Coverage Report for Build 60

  • 5 of 5 (100.0%) changed or added relevant lines in 3 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage remained the same at 100.0%

Totals Coverage Status
Change from base Build 53: 0.0%
Covered Lines: 151
Relevant Lines: 151

💛 - Coveralls

@rafaels88 rafaels88 force-pushed the implement-skip-stage branch 2 times, most recently from 6fe99f3 to 7e3b93a Compare June 14, 2018 20:40

def run({module, type, [if: func], opts} = stage, input) do
case Stage.maybe_run({module, type, nil, opts |> put_in([:if], func)}, input) do
ret when ret == :stage_skipped ->
Copy link
Owner

Choose a reason for hiding this comment

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

def run({module, type, [if: func], opts}, input) do
    case Stage.maybe_run({module, type, nil, opts |> put_in([:if], func)}, input) do
      :stage_skipped -> {:halt, :skipped}
      _ -> {:cont, input}
    end
  end
  1. The guard is not needed here
  2. When the conditional returns false (and we don't skip) you can return {:cont, input} so that the pipeline continues.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Totally, thanks for noticing! Just changed!

def maybe_run({module, :skip, name, %{if: {_m, _f, _a} = condition}}, input) do
case Safe.apply(condition) do
true ->
skip_stage({module, name}, input)
Copy link
Owner

Choose a reason for hiding this comment

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

This is tricky because in this case the whole pipeline is skipped, not just a single stage. I think we should use a different instrumentation event, something like pipeline_skipped.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ouch, really well noticed! Thanks again! Changed it, hope it's better now

defmodule SingleSkipFalsePipeline do
use Opus.Pipeline

skip(if: :should_skip?)
Copy link
Owner

Choose a reason for hiding this comment

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

Please mind to update the .formatter.exs file.
https://github.com/Zorbash/opus/blob/master/.formatter.exs#L11

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ups, changing!

defmodule SkipFalsePipeline do
use Opus.Pipeline

skip(if: :should_skip?)
Copy link
Owner

Choose a reason for hiding this comment

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

What happens if the user tries the following?

defmodule SomeMod do
  skip # without an :if option
end

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(CompileError) test/opus/pipeline/stage/skip_test.exs:127: undefined function skip/0

Make sense, wdyt?

Copy link
Contributor Author

@rafaels88 rafaels88 Jul 14, 2018

Choose a reason for hiding this comment

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

And, when a param other then "if" is given: eg: skip another: :value

(FunctionClauseError) no function clause matching in Opus.Pipeline.Stage.Skip.run/2

README.md Outdated

This stage receives a `:if` option only, on which is expected to return a
boolean value. If `true`, then the pipeline halts and Opus returns
`{:ok, :skipped}`. If `false`, then the next stage is called with no side effect.
Copy link
Owner

Choose a reason for hiding this comment

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

From the code I can see that if true we skip, otherwise (with any other value) we don't.

Copy link
Contributor Author

@rafaels88 rafaels88 Jul 14, 2018

Choose a reason for hiding this comment

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

Should I just change the docs to something like If anything else, then the next stage is called with no side effect. or do you think that if no boolean is returned, then Opus should raise an error?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just changed the description to: "If false or any other value is returned (including non-boolean), then the next stage is called with no side effect."

Also implemented a new spec for this case.

Let me know if that makes sense!

@@ -24,6 +24,22 @@ defmodule Opus.Pipeline.Stage do
when is_atom(fun),
do: maybe_run({module, type, name, %{opts | if: {module, fun, [input]}}}, input)

def maybe_run({module, :skip, name, %{if: {_m, _f, _a} = condition}}, input) do
Copy link
Owner

Choose a reason for hiding this comment

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

There's some implied behaviour here I think. When _f is nil which means that no :if option is provided then we don't skip the pipeline, right? In that case it might be worth it raising a compile-time error since the skip definition will essentially be invalid.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just mentioned about it above, but just to be clear, those are some tests I've tried:

  1. No param
defmodule SkipNoParamPipeline do
      use Opus.Pipeline
      skip
    end

(CompileError) test/opus/pipeline/stage/skip_test.exs:127: undefined function skip/0

  1. Non-keyword param
defmodule SkipAnotherParamPipeline do
      use Opus.Pipeline
      skip :another
end
** (FunctionClauseError) no function clause matching in Opus.Pipeline.Stage.Skip.run/2
     The following arguments were given to Opus.Pipeline.Stage.Skip.run/2:
         # 1
         {Opus.Pipeline.Stage.SkipTest.SkipAnotherParamPipeline, :skip, :another, %{stage_id: 24357}}
  1. Keyword param without if
defmodule SkipAnotherValuePipeline do
      use Opus.Pipeline
      skip another: :value
end
** (FunctionClauseError) no function clause matching in Opus.Pipeline.Stage.Skip.run/2
     The following arguments were given to Opus.Pipeline.Stage.Skip.run/2:
         # 1
         {Opus.Pipeline.Stage.SkipTest.SkipAnotherValuePipeline, :skip, [another: :value], %{stage_id: 24772}}
  1. Another keyword param and if
defmodule SkipAnotherValueAndIfPipeline do
      use Opus.Pipeline
      skip if: :cond, another: :value
end
** (FunctionClauseError) no function clause matching in Opus.Pipeline.Stage.Skip.run/2
     The following arguments were given to Opus.Pipeline.Stage.Skip.run/2:
         # 1
         {Opus.Pipeline.Stage.SkipTest.SkipAnotherValueAndIfPipeline, :skip, [if: :cond, another: :value], %{stage_id: 28804}}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But, feel free to recommend any refactoring or implementation about this situation!

Copy link
Owner

Choose a reason for hiding this comment

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

To conclude this discussion, I think it's fine to merge it as is. Then I'll make an attempt to make the error message a bit more user-friendly.

Copy link
Owner

Choose a reason for hiding this comment

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

To conclude this discussion, I think it's fine to merge it as is. Then I'll make an attempt to make the error message a bit more user-friendly.

@rafaels88 rafaels88 force-pushed the implement-skip-stage branch 2 times, most recently from ab407e1 to f2d4d97 Compare July 14, 2018 08:27
@rafaels88
Copy link
Contributor Author

Btw, sorry for the delay =/

README.md Outdated
@@ -42,6 +42,7 @@ end
defmodule ArithmeticPipeline do
use Opus.Pipeline

skip if: greater_than_fifty?
Copy link
Owner

Choose a reason for hiding this comment

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

greater_than_fifty? should be an Atom.

README.md Outdated
@@ -112,6 +114,13 @@ This stage is to link with another Opus.Pipeline module. It calls
`call/1` for the provided module. If the module is not an
`Opus.Pipeline` it is ignored.

### Skip

This stage receives a `:if` option only, on which is expected to return a
Copy link
Owner

Choose a reason for hiding this comment

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

I'd rephrase to This stage expects or This stage accepts.

@zorbash
Copy link
Owner

zorbash commented Jul 17, 2018

@rafaels88 Please make changes for

#2 (comment)
#2 (comment)

and it's good to merge :-)

The `:skip` stage makes the Pipeline halts when the given conditional
returns `true`.
@rafaels88
Copy link
Contributor Author

@zorbash Changed! Thanks for the review!

@zorbash zorbash merged commit a895928 into zorbash:master Jul 17, 2018
@zorbash zorbash added the enhancement New feature or request label Jul 17, 2018
@zorbash
Copy link
Owner

zorbash commented Jul 17, 2018

@rafaels88 Thanks for contributing, I'll update the changelog and releases it as 0.6.0.

@rafaels88 rafaels88 deleted the implement-skip-stage branch July 18, 2018 16:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants