-
Notifications
You must be signed in to change notification settings - Fork 215
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
A proposal for a non-magical, but stricter context #67
Comments
+1 context.user feels excessively verbose, and I didn't like it at all. |
I love the idea and I think it may kill two birds with one stone. I like the explicit There's also a persistent (and justified) gripe that it's hard to glean what an interactor receives in its context. This helps to address that concern as well. The syntax that comes to mind for me is: class AuthenticateUser
include Interactor
receives :email, :password
provides :user, :message
def call
if user = User.authenticate(email, password)
context.user = user
context.token = user.secret_token
else
context.fail!(message: "authenticate_user.failure")
end
end
end The In the future, this could be extended with options such as I wouldn't take any measures to ensure that a given context doesn't contain extra keys because that's perfectly legitimate. In the short term, I think these methods would simply provide convenience access to the context. Thoughts? |
Most (all?) of the interactors I've written receive and provide different things, so I'm in favor of that syntax. In the future it could be used to only add the Every time I've used OpenStruct I've either not liked it, or regretted it, so I'm in favor of being more strict about what's in the context. But until we've got a good example that argues either way, we'll just have to disagree philosophically. 😄 I do agree that shortcut access to the context with |
Could you elaborate on this? I don't understand. |
We could make it so arguments aren't added to the context unless they're part of returned_context = AuthenticateUser.call(email: "[email protected]", password: "password")
returned_context.attribute_names # => [:user, :token, :message] Arguments not added to the context can still be accessed with methods inside the interactor, they're just stored on some sort of local_arguments object instead of on the context. |
I don't think there's any need for something like |
I actually like this idea more than I initially thought I would. It prevents a |
would this need to be a major version bump? |
@ersatzryan Could you explain the |
My thought is that this would start as a backwards-compatible change. The receives :email, :password, required: true I'm still not clear on what the default behavior should be for missing context keys/values. Should it fail? Should it raise an error? |
since we return a context from As opposed to having an object that throws errors when you call methods that do not exist. Tracking down an "Undefined Method for Nil::Class" 5 levels past where that nil was introduced is much harder than a custom error from the context or even a NoMethod just on the context. |
Since we return the context from If I currently have something like class FooController < ApplicationController
def create
result = FooCreator.call(foo_params: params)
if result.foo
redirect_to result.foo
else
render :new
end
end
class FooCreator
include Interactor
def call
foo = Foo.new(context.foo_params)
if foo.save
context.foo = foo
else
fail!
end
end would it not bomb in the controller if it were upgraded to version that has new context not based on |
I've been thinking more about the internal interface (inside the interactor itself) but it's a good point that these changes would also apply to how the interactor is accessed from the controller as well. I'm not very concerned currently with unexpected We'd probably want to make sure that the before do
context.user ||= User.find(context.user_id)
end |
I'd be a fan of documenting the inputs and outputs of an interactor. As far as required parameters go, we're using this syntax now: required_parameters or: [:order_id, :order]
required_parameters :something_else I use a before hook to ensure that both Edit: I haven't yet gone so far as to write methods on to the interactor for those parameters but we've been talking about it as we upgrade from the old 'v3' branch to the released version 3. |
LightService has a nice syntax for a similar feature documented here. The functionality differs from what we're talking about here but the macro methods are similar and might get the creative juices flowing for naming. |
I like the macro methods of |
or perhaps |
I've used the "provide" terminology in the specs before. |
Any suggestions for the issue of ensuring required attributes are passed in while this feature is still being developed? |
@wyattjoh What about being able to set default values to context attributes? |
Maybe in the future, the context object can evolve into something more complex and complete, like trailblazer's contracts (https://github.com/apotonick/trailblazer), thus allowing the validations to be removed from the model too... |
I just started using this gem (thanks, it's great), and I really like where this is headed. As a quick and dirty "solution" to some of these issues, I've been using E.g. |
Love the concept behind this gem, though, it would be nice to have context be any type of object. Simply delegate method missing on the context to the passed-in object itself. I feel like forcing the developer to break up parameters into key/value pairs reduces flexibility. |
👍 to this proposal. I've been using this library (thank you! btw) for about a month and I almost immediately added an equivalent to |
What about something that looks like this: class CreateContact
include Interactor
expects :email, :first_name # these are required, otherwise error
provides :contact_id # this may or may not be populated by the interactor
allows :last_name, :phone_number # these can be supplied, but if not default is nil
allows :category do # this can be supplied, or the block provides a default
ContactCategories.default
end
allows :country, default: 'USA' # this can be supplied, default declared inline
end These could just start out as convenience methods, per Steve's suggestion, so that they don't break backwards compatibility. |
FYI, all, if you're following this issue, I'm working on a PR that attempts to address it. You can find it here: #82 |
+1 for this proposal! |
👍 Is this still in the cards? |
👍 |
Huh... Great minds think alike, I guess! I worked on something like this. Would welcome feedback on the features: |
@berfarah I made a gem for this, as well. Check it out, here: https://github.com/jonstokes/troupe |
Closing for a few reasons:
|
In #46 we decided to use an OpenStruct for the context. It removed the magic of creating methods when things were set on the context. It seemed wrong to magically add methods to the interactor based on the calling parameters.
However, now we have problem that every time you want the given email, you have to say
context.email
. This is a break in the syntax-shortening advantage of encapsulation.What if:
Instead became:
The context itself is no longer an OpenStruct. Accessing or setting an undeclared attribute on the context will raise
NoMethodError
.There are several advantages to this approach:
user
instead ofcontext.user
.AuthenticateUser.perform(eamil: "[email protected]")
can explode with anArgumentError
.An organizer would gather all the
context_attribute
of its constituents and pre-populate the context will all the appropriate methods so that the application can call the organizer with arguments needed by any of the interactors.The text was updated successfully, but these errors were encountered: