This is a very interesting project and executes well something that many other projects have attempted in the past.
I have seen the sample contacts application, read the documentation, and I am currently reading through the gem source but there are still some things which are unclear to me. The most significant of which is where non-CRUD behaviour should be included.
For example, if I were building an application to sell T-shirts using Stripe, I might have an Order class with various LineItems that would consist of Products with corresponding quantities. Now, I could easily manage to create all of the relevant records via a create request to the OrdersController. However, where would I put the call to create my Stripe charge, schedule fulfilment, etc.?
Normally, I would leave my Order and corresponding models fairly simple, and organize charging and fulfilment via services. I would have, say, a PurchaseService which would be instantiated by a PurchaseController. This would arrange for the various models to be created, trigger the Stripe charge, and if successful, schedule fulfilment (possibly by delegating to another service).
The code in the PurchaseController would be very minimal and would not do anything more than pass parameters to the PurchaseService, make a single call on it, and then take care of response and error handling.
It seems like the intention with JR would be to leave the default implementation for the resource controllers, and add charging and purchasing behaviour to the resource code. For example, I would do:
class PurchaseController < JSONAPI::ResourceController
end
and
class PurchaseResource < JSONAPI::Resource
def create
# call to Stripe, etc. goes here
end
end
Does this make sense? Would you structure things differently?
One the one hand this seems like a good place to include the behaviour without needing to reproduce a lot of error and response handling code if I were to override the corresponding controller method. On the other, I've seen the callback support in JSONAPI::Resource but I'm not clear if/when the corresponding lifecycle methods are actually called within the resource code. Additionally, if the code were structured this way, the PurchaseResource would also have to coordinate validating and creating Orders, LineItems, etc. rather than relying on the default JSONAPI::Resource functionality to do so.
Anyhow, apologies for the long-winded and naive question but I feel like I am missing something obvious. Any help in clarifying would be appreciated.
@elucid
In the past, I've found anything that departs from CRUD should be looked at a little bit more carefully, especially in the case of purchasing. By creating a few extra resources, you can maintain a consistent pattern within your application.
In the context of your example, consider creating a FullfilmentRequest resource that the client must create to run a charge via Stripe. Alternatively, you could modify an attribute on your Order model. If the charge fails, the creation/modification of such a resource should be rejected with HTTP 402 Payment Required.
Keep in mind that nothing prevents you from subclassing ActionController::Base as you normally would for non-CRUD actions. Given my personal experience, I would recommend against that, since it requires clients to make similar considerations (i.e. knowing which endpoints are JSON API-compliant, and which are not).
Hope this helps!
@kellysutton thanks for the response. However, I'm not sure I quite get it yet.
To clarify, are you suggesting creating a resource responsible for triggering the charge which maps to multiple persistence model/database tables, and triggers the Stripe request as part of this coordination? In this case would I be managing this coordination via the callback API that JSONAPI::Resource exposes?
For example, the call to Stripe would occur in the before_save callback, and the success/failure of that would determine whether a FullfilmentRequest is created (or perhaps one is created regardless but possibly with a failed state). For successful charges, Orders and other related resource creation could also be triggered via callbacks on the FullfilmentRequest.
Is this what you are suggesting?
Perhaps the FulfillmentRequest was a bit of a bad suggestion.
Take a look at how Stripe exposes their different resources. In applications, I've found it can be helpful to map or wrap their resources onto your own data model. That is, allow clients to interact in a similar way with the Stripe API. In this case, a client would create a Charge resource. (Your application would need to run the checks to make sure a client is capable of such a request.) Then you would need to write some glue code to connect the Stripe::Charge object to a JSONAPI::Resource.
While it's fine for simple applications, I usually try to not run important operations in before_save callbacks. Extract that logic out into an explicit resource. Testing becomes much easier that way.
Modelling this as a FulfilmentRequest or a Charge doesn't much matter. What I'm missing is where the coordination or glue code goes. When you say "extract that logic out into an explicit resource", where in the resource does it belong if not in the resources callbacks?
I feel like I am not doing a very good job of articulating my question. The difficulty I'm having is that the default JR setup suggests a 1:1:1 mapping between client side resource requests : server side resources : database tables/AR models. In cases where you do not have a direct 1:1:1 mapping in this way (external API calls involved, server resource maps to multiple database tables, etc.), where should the logic that manages that additional coordination go so as to avoid losing the structural benefits that JR provides?
I haven't tried to use JR to solve this problem, so I'm not sure on the best way to proceed. This might be too simplistic, but off the top of my head the idea of creating a Charge resource seems right to me. I think putting the Stripe call in the around_create callback would let you make the attempted charge, and record the result in the new Charge entry. You could update the status on the order model if need be, or the order could simply refer to the existence of a complete charge for it's status.
In cases where you do not have a direct 1:1:1 mapping in this way (external API calls involved, server resource maps to multiple database tables, etc.), where should the logic that manages that additional coordination go so as to avoid losing the structural benefits that JR provides?
There's no generalized answer for this, unfortunately.
Given my past experience, it's usually best to shoehorn complex actions into CRUD actions on 1-to-N resources.
Sorry about the before_save comment previously, for some reason I thought you were talking about a before_filter. before_save should be fine on a Charge object, but not ideal. If possible, always avoid ActiveRecord callbacks. (Watch this video from RailsConf 2013 to learn why.) For something like charging customers, prefer explicit method calls perhaps wrapped in a transaction:
charge = Charge.new(...)
Charge.transaction do
charge.perform_transaction! # Creates the Stripe::Charge resource
charge.save!
end
Alternatively, extract that logic into a separate method perform_transaction_and_save!
Where should that method call go? Probably in a controller method, where the controller subclasses JSONAPI::ResourceController.
(Whew...long post ahead, sorry! But I'm really interested in this :) )
I have recently started transitioning my APIs over to JSON:API and jsonapi-resources and, while most of the process has been great and JR is very well constructed, I'm also hitting exactly the same questions that @elucid mentions here.
I follow the same patterns you do—I separate my business logic into service objects and call service methods to create records instead of putting everything in an ActiveRecord model or controller, and the service object takes care of the mess of calling third-party services, creating related records, etc. I do this because I like decoupling business logic from HTTP requests, making it easier to test, and keeping models/controllers lightweight. IMO this is a common pattern, it's advocated often as a way to "break up your rails monolith", and basically boils down to just "put logic in a testable, plain Ruby object" as a way to avoid coupling business logic to the concerns of models or controllers.
So, I think the natural way for people who want to do this is just override the controller create method per normal, which should definitely be possible and simple even when it's a JSONAPI::ResourceController subclass. However, as I've been transitioning my APIs over to JR, I keep hitting the same questions, including:
create controller method — how can I use my Resource object to parse/validate the input (since I've already defined createable_fields and other request formatting in the resource) but then use my service object for actually creating the object?JSONAPI::ResourceController's public vs. private methods? Do I have to worry about ResourceController changing its internals in a later version, or can I safely rely on calling superclass methods for parsing?I've started to play with some ugly hacks to get what I want, basically accessing the Operation object in my controller to get at the pre-parsed create params so I can rely on JR to do the parsing for me, but then I take over and pass these params to my service object:
(fake, rough example)
class V1::BuildController < JSONAPI::ResourceController
def create
# Does strong parameters checking for required attributes in "data", no way to do this in JR?
require_resource_params
# Use the operation's already-parsed input for this resource. :/ Yikes...
resource_params = @request.operations.first.values[:attributes]
# Use my service object instead of JR's 1:1 mapping to the ActiveRecord model to create it.
build = BuildService.create_build(
foo: resource_params[:version],
bar: resource_params[:other_param])
# Copy a bunch of behavior from the superclass because I can't re-use those methods.
serializer = JSONAPI::ResourceSerializer.new(resource_klass, ...)
render json: serializer.serialize_to_hash(resource_klass.new(build)), status: :created
end
end
Clearly this has some big issues, so I'm really interested to figure out the right pattern for this as well.
Fundamentally I agree with the above statement that JR seems to want you to have a 1:1:1 mapping all the way through, but if you follow a service-object approach for creating objects, this breaks down a bit.
A big thing I think would help: JSONAPI::ResourceController could be made to be a bit more friendly to overriding, give public ways of accessing a pre-parsed resource object, the right serializer, etc. so that you can simply take over the "create" part of the flow more easily. I think this would solve basically all of it and let people pull out their creation logic to wherever they want.
I've been loving JR and this shift to JSON:API, excited to see where it goes and happy to contribute if this makes sense.
want to override the create controller method — how can I use my Resource object to parse/validate the input (since I've already defined createable_fields and other request formatting in the resource) but then use my service object for actually creating the object?
Yes, subclasses should have access to createable_fields and so forth. I recommend giving a read through the source for more info.
What are JSONAPI::ResourceController's public vs. private methods? Do I have to worry about ResourceController changing its internals in a later version, or can I safely rely on calling superclass methods for parsing?
Check the source, make sure you have specs to cover parts of the code where you're overriding methods. If JR changes down the line, hopefully the overridden method will blow up in a big way.
How should I be mixing strong params / resource objects for parameter handling? Ideally, I can drop strong parameters entirely and just use the resource objects to validate input, but right now I have to mix with strong parameters because I can't see a way to use JR to validate required fields for me before I pass them on to the service object.
In how I've implemented JR in the past, I recommend leaning on it and only it for parameter handling. Drop strong parameters entirely. If you need to further validation outside of JSONAPI::Resource, consider adding logic to a method in your JSONAPI::ResourceController subclass.
Take a look at the examples around scope in JR, they can help with validation and so forth.
In your given example, I would try really hard to force the client to create the individual Foo and Bar instances. You might be pushing more work client-side, but I've found it leads to more flexible and maintainable systems. Either your Foo or Bar instance will have to be created first, and then the creation of a Bar instance should have to be linked to a Foo instance.
Issue #127 also brings up the need to decouple the Resource class from ActiveModel. This is something I hope to get to very soon.
@fotinakis Could you override the _save method on the resource? That would let you avoid ActiveModel? I think you could also use the around_create callback and skip calling the block. Have a look at https://github.com/cerebris/jsonapi-resources#callbacks
I would try to avoid overriding the controller methods. Once/if we get batch operations working you might not be able to easily transition to using them. I prefer to focus on the Operations rather than the controller methods. I think that will work out better in the long run.
There are a couple interesting approaches evolving in JSON API itself for handling multiple operations in a single request:
Operations.So these two methodologies are being explored in JSON API itself, and jsonapi-resources has considered both in its design. Although neither is quite there yet, I'm sure both will get there.
Excited to see that, multiple operations in a single request would be great and would take care of some of the atomicity concerns when creating multiple objects.
Unfortunately though, I don't think that addresses the conversation above—it's not just that other related objects might be created, it's that there are often complex needs as part of object creation, for example accepting a Base64-encoded field only on creation and streaming it to third-party storage with just a URL link in your DB, or like the OP's credit-card charging example above. Basically asking for a bit more of a documented, decoupled interface for how to keep resource creation (and the mess of logic that can come along with it) _out_ of jsonapi-resources's flow, for separation of concerns reasons. I've only been using this for about a week, but it has felt like JR makes a fair number of assumptions about how records should be created and it's unclear where the best place is to take over that logic.
@lgebhardt I'll definitely look into the _save method more, but it looks like the AR model object is already set up by that point? Would you be interested in contributions to make the logic inside ResourceController a bit more re-usable, or should I really be thinking about how this works at the Resource layer? (I've also read the source for all the Operation flows, and that seems interesting too but I took it to be more of an internal implementation detail than a public interface, maybe I'm wrong?) It seems like #127 could address most of this though if you more fully decouple AR creation from Resource objects.
Part of the tension here is that people like me are coming from an ActiveModelSerializers mentality, where it's just a serialization library and you have to do everything else yourself. But, with the structure that comes with the JSON:API spec, you can take this library the whole way and make it smart enough to basically manipulate the DB directly (cool). However, that can come with a lot of coupling through many layers and can make it hard to figure out where the line is if you want to break that apart a bit—ie. where to take over after deserialization is complete and you can take the parsed params and do whatever you want with them.
I think that's the real issue, and even though I'm very new to using this library it's been kind of hard to figure out where those clear boundaries are—just for creating objects, specifically. I think @elucid said it well above when he said that he's looking for more structure for when you have to do more complex things than just a 1:1:1 mapping between "client side resource requests : server side resources : database tables/AR models".
Whew, I usually try to write less, but it's been a long week. :) Thanks for the back-and-forth.
@fotinakis As you have observed JR was designed to handle taking an API request fully back and forth between the database (1:1:1 mapping between "client side resource requests : server side resources : database tables/AR models"). To date that has been entirely through ActiveRecord, though that hasn't been intended as the only path.
However, that can come with a lot of coupling through many layers and can make it hard to figure out where the line is if you want to break that apart a bit—ie. where to take over after deserialization is complete and you can take the parsed params and do whatever you want with them.
At some layers the line for where to break the stack apart and inject you own code is clearer than at others. This discussion (along with #127 and #133) shows that we should clean things up a bit (both code and documentation) so it's obvious where and how to have your code step in at any point in the process.
I've also read the source for all the Operation flows, and that seems interesting too but I took it to be more of an internal implementation detail than a public interface, maybe I'm wrong?
I think your observation is correct. Though I'm not sure that was the correct design decision. I'm strongly thinking custom operations have some potential, though it will require a refactor to get there. Hopefully it can be done with no, or minimal, impact on existing code.
@fotinakis. Your comment captures the same kind of things I'm trying to work through in trying to integrate this into an existing project using involving some fairly complex post logic. @lgebhardt you suggested in the #133 thread to overwrite setters in the resource class. This seems fine for a couple of overwrites but I'm trying to find the best way to integrate several large builder classes of custom logic that currently sit between the controller and a few models.
My strategy so far without forking the gem has been to overwrite #replace_fields in the resource and pass the data from there into my existing builder code. This stops all the write/update logic that the resource usually supports and gives you more manual control inside a plain ruby class. I'm thinking that if the builder acts as a facade to an AR object you can still get all the support of callbacks, error handling and transactional atomicity.
This gem seems very useful as we're building out a couple new projects and since they will all likely include complexed post/put logic I'd love to get some input on the best way to integrate this without too much hacking or overloading the resource class.
@mmartinson Can you replace the model class with your builder (as far as JR is concerned) and leave the resource structured as normal? Your builder could then act as a proxy to the actual model. That way JR could update the builder/proxy, and the builder/proxy could correctly update the model and do what ever else needs to be done.
@lgebhardt What would that look like like for serialization? Would you expect that the model proxy would be responsible for both parsing and fetching data? What fits my use case really well is using different classes for parsing and serialization, since they use employ method names that match the param fields but have very different behaviour for in vs out.
I wrote a base resource class that lets me define a builder_name in my resource alongside the model_name. If there is a builder present it uses that for create/update and otherwise uses the model. The full context is passed into the builder to be used inside the same was as in the resource.
The way I've set it up is working fine for me but I could imagine that there are lots of people that would benefit from clear ways to separate read/write behaviour. Would be happy to contribute if it is something that would be welcomed
I guess I'm seeing one resource with a field set where some are creatable and updateable and some are fetchable. There doesn't need to be overlap between the two sets. You might need methods (setters or getters) on the resource to override the default behavior of accessing a field from the model. I think that would let you set one set of fields and the results be serialized out with a different set. I hope I'm understanding what your trying to do.
That makes sense to me and I think is probably a clean answer for most use cases. I find that there can be value in separating getting and setting logic into different classes, if only because it's less noisy when you're trying to follow a particular request and because for a large model the one resource class gets very full and it's easy to split in half along that separation of concerns.
Thanks for you feedback btw. This gem has been working out well for be, despite the mutli issue threads
Just to add some more context to this thread, I think there are kind of two different use cases in discussion here and it seems like maybe not everyone is on the same page.
The idea of sending multiple payloads in one request is interesting, but it doesn't at all handle the use case where you need your system to take care of creating associated records at the time a new model is created. Think of things like internal bookkeeping, queueing objects for administrative review, triggering automatic background processing, etc... These are system level tasks that the client should not be responsible for, and in many cases the client will not even know that these associated actions are happening since they are "side effects" of using the public api.
To illustrate, here's a service object that I've been using that I'd like to mirror somehow in jsonapi-resources. The idea here is that when someone updates their Profile, we want to create an associated Message record that will let admins know that they need to review the changes to the Profile. We absolutely can not make the client responsible for creating these associated records.
class ProfileUpdater
attr_accessor :profile
attr_accessor :current_user
def initialize(profile,current_user)
@profile = profile
@current_user = current_user
end
def update(params)
profile.assign_attributes(params)
adjust_needs_review_score
if seller_profile.save
create_message_for_admins
return true
end
return false
end
def adjust_needs_review_score
if current_user && current_user.id == profile.user_id && !current_user.admin?
profile.needs_review_score += 1
end
end
def create_message_for_admins
message = Message.create!({
:messagable => @profile,
:subject => "Profile needs review",
:user => current_user
})
message.queue_for_processing
end
end
And then I call it like this in my controller:
def update
@profile = Profile.find(params[:id])
@profile_updater = ProfileUpdater.new(@profile,current_user)
if @profile_updater.update(profile_params)
head :no_content
else
render json: @profile.errors, status: :unprocessable_entity
end
end
We have a similar service for creating a new profile that lives in it's own class. In order to get this all working using jsonapi-resources I've been using the before_* and after_* hooks on the resource. This works but it seems like I'm loading a lot of functionality into the resource class which now has way more than one responsibility. Is there a better way?
class ProfileResource < JSONAPI::Resource
attributes :name, :website
before_create :manipulate_score_for_creation
after_create :create_message_for_creation
before_update :authorize_update
before_update :manipulate_score_for_update
after_update :create_message_for_update
def manipulate_score_for_creation(seller_profile = @model, context = @context)
#...
end
def manipulate_score_for_update(seller_profile = @model, context = @context)
#...
end
def create_message_for_creation(seller_profile = @model, context = @context)
#...
end
def create_message_for_update(seller_profile = @model, context = @context)
#...
end
def authorize_update(seller_profile = @model, context = @context)
puts "running authorize update"
current_user = context[:current_user]
unless current_user && (current_user.admin? || seller_profile.user_id == current_user.id)
raise "Not allowed"
end
end
end
For what it's worth, using a callback like you are seems totally reasonable to me.
Well, the whole reason for having the service class in the first place was to avoid the callback hell that happens when all of that kind of stuff is added as callback on the model itself. Granted, moving the callbacks onto the resource instead of the model removes _some_ of the problems and makes it _slightly_ less hellish, but it's still a bunch of callback soup. It also means that those bits of logic are tightly coupled to the API resource. What if I want to be able to create a record in some other way (not via jsonapi-resources)? With a service approach it's as simple as doing something like ProfileCreator.new(profile_params).save. With all of the callbacks living on the Resource then I have to duplicate that functionality somewhere. Does that make sense?
Yeah, my opinion is pretty context dependent. For many situations I don't mind after_commit callbacks on the model itself (if it's something that should _always_ happen). If it's more request-context specific then I think that putting it in the resource is fine. I agree that there are cases where you might want to have a service object that you'd delegate creation or saving or whatever to, but that seems like an exception that's only worth it if the added complexity is solving a clear problem.
:+1: for being able to instantiate contexts (as in DCI's Context, not the context hash passed by the controller down to the resource) or services (as in SOA, as others approached this) to be able to do a non 1:1:1 mapping between the client, interface and persistence layer and to be able to add system behavior or services between the request and database calls - without the callback soup. That would clearly solve the problem of being able to use this gem to anything more complex than just plain CRUD - which would be awesome! :)
We also lean heavily on service objects for encapsulating business logic. This lets us do things like also create rake tasks for certain actions to make development easier while using the same logic the actual API uses. The difficulty of this was a primary reason we were unable to use JR for our application.
@bdmac Same here, I ended up writing https://github.com/fotinakis/jsonapi-serializers/ for the same reasons.
@fotinakis sure but that only really handles what turns out to be a minor part of the jsonapi spec - serialization. It doesn't help you at all for supporting the appropriate relationship links or getting the data out of the params when it's in jsonapi format etc.
I was just going to say I ended up overriding 90% of my endpoints and decided to use https://github.com/fotinakis/jsonapi-serializers/ together with my custom JSON API compliant error serializer.
as for the params parsing - I just use a helper for the common stufff like params.require(:data).require(:attributes) and catch them in my API's BaseController with a rescue_from ActionController::ParameterMissing etc...
@bdmac True but I wouldn't call serialization the minor part of the spec, and the library does have full support for relationship links. I find just serializing to JSON-API and keeping everything else decoupled is very clean (JSON-API knowledge stops at the controllers, no structural overhead deeper in the app). I simply parse params in controllers before passing them on to service objects, lets me easily use normal things like strong parameters and authorization checks and deal with diverging behaviors for object creation vs. serialization. I've found this very easy to maintain and it looks like ActiveModelSerializers is developing support for JSON-API natively in their newest RCs as well.
Yah I realize it serializes the relationship links but there's a lot of boilerplate stuff to handle all of those /posts/1/relationships/comments routes in the various controllers. Of course I can do that myself but that's basically what this gem does already. If it were just _slightly_ more flexible it would be a pretty solid win.
@bdmac What additional flexibility do you need from this gem? Feel free to create new issues (or PRs) and we'll evaluate them and try to implement them if they seem like a good fit for the project.
Basically what others are asking for here. A simple/approved way to take control and use services where needed.
This is the one thing that stopped us from adopting this gem - lack of support for service objects.
What we'd need to utilize this project is a clean way of delegating the modification of a resource.
We wound up rolling our own implementation of json API using just AMS and found out a few things in doing so. 1.) Json API forces your API into a specific shape and to support things in a very defined manner. Doing this to spec makes using service objects pretty difficult from experience. We are doing it but it's super hacky due to the related relationship stuff in json API. 2.) We wound up implementing like half of jsonapi-resources to conform to the spec anyways.
Same for us, went with AMS + custom code, I find this gem is only useful if you have an exact 1:1:1 mapping between your database, models and frontend. Sounds like a good fit for building PHPMyAdmin with EmberJS and Ruby! :P
We ended up overriding more stuff than it gave us, making a big mess of the code, and limiting us way too much, so leaving this gem behind was a very wise choice. And our business logic is not even that complicated...
I believe an API is a UI too, which has to be simple, and easy to implement. And if your business logic is more than just wiring our your database relationships through your API, then you may want to look elsewhere.
I can also see this being useful for a "private" or internal API that is being used by a thick client (ideally implemented in EmberJS), where all the application logic is implemented in Ember and the JSON API is really there to just serve as a connection to your database. But for public APIs that may required more than a basic CRUD interface... there are better options!
12 months ago I would have agreed that CRUD sometimes isn't the right answer, but I've since changed my mind and realized that even the most complex workflows can be modeled in terms of state changes or virtual objects.
I think @kellysutton said it best at the start of this thread:
anything that departs from CRUD should be looked at a little bit more carefully
End of story.
How do you cleanly manage internal bookkeeping around CRUD? Putting
callbacks in the resource is not an option.
On Sat, Feb 20, 2016 at 6:27 PM Peter Lejeck [email protected]
wrote:
12 months ago I would have agreed that CRUD sometimes isn't the right
answer, but I've since changed my mind and realized that even the most
complex workflows can be modeled in terms of state changes or virtual
objects.I think @kellysutton https://github.com/kellysutton said it best at the
start of this thread:anything that departs from CRUD should be looked at a little bit more
carefullyEnd of story.
—
Reply to this email directly or view it on GitHub
https://github.com/cerebris/jsonapi-resources/issues/129#issuecomment-186716356
.
I understand the desire to avoid callbacks, since they tend to muddy the separation of concerns a bit, but they're the current "rails way" for bookkeeping.
The observer pattern may offer an interesting alternative, but it (a pretty bad observer implementation TBH) got ripped out of Rails a while back.
This issue hasn't been updated since Feb - has anything changed here? We're building a lot of apps using service objects to handle side-effect orchestration but having trouble shoe-horning them into JR (/cc @eoinkelly).
I've been thinking about this for a long time and have tried a number of different approaches to try to come up with a consistent interface between JR and my application code. Things I've tried:
Works fine for inserting custom logic, or better, redirecting to methods in your domain layer that implement custom logic. Becomes tricky when when mapping between resource and model is not 1:1, handling multiple saves in the resource layer, while not technically challenging, can be tricky to understand/follow when coming back to the codebase.
Useful for when your domain is modelled in discrete use cases, user actions do not map to updating model attributes in a straightforward way, or complex validation across models needs to be implemented. I actually prefer this because it gives the most control, but it does just sidestep a lot of the otherwise useful JR functionality, using it only for serialization, and requires a whole layer of custom code upstream from the resource. This has been my go-to for a while, because I care more about control than convenience, but is definitely not the best.
Resource#_replace_fields and sticking in a proxySee my comment (way) above about builder objects. I think this is the approach I actually like the most in terms of interface because it allows me to keep all domain logic out of and downstream from the resource class, and I can define by own interface for all write operations. It is definitely the most hacky and brittle though because it overrides an implementation detail that seems highly subject to change.
It would be nice to have an established best practice for using JR for apps that don't map easily to a simple set of REST/CRUD actions. I think we at least a reviewed and well-documented example using the existing API, or to do more we could consider what kind of configuration and interface to add to the resource class (or operation) to pass all attributes being set to a custom handler. If we go with the latter, I don't think it would need to affect the standard JR use case at all.
Without changing much code in #replace_fields, I think we could make it possible to configure a handler to be used on a per-operation basis.
Could change this:
def _replace_fields(field_data)
field_data[:attributes].each do |attribute, value|
begin
send "#{attribute}=", value
To this:
def _replace_fields(field_data)
handler = handler_for_operation || self
field_data[:attributes].each do |attribute, value|
begin
handler.send "#{attribute}=", value
and do the same for #_save. The handler could collect the model instance (queried by the resource), the context, implement a writer to for the state be set and implement only the public #save method to execute. It would then be up to the handler to decide how the DB state is modified and updated, and the resource would just reload its model and serialize as normal, indifferent to what changes actually happened.
Questions I have about this, are
#_replace_fields? Should these always be passed to the handler to deal with explicitly, always be done by the resource (how to control the save order?) or be configurable when specifying a handler?@mmartinson I'm going to take this on this weekend, if you haven't started. I keep running into the same issue and can't seem to use Processors to handle it.
I was thinking for the interface
class CommentResource < JSONAPI::Resource
create_with -> (field_data) { MyCreateService.new(field_data).whatever } #returns the model instance
update_with -> (field_data) { MyUpdateService.new(field_data).whatever } #returns the model instance
end
From _replace_fields if create_with or update_with is set, use them appropriately, otherwise default to current behavior
@lgebhardt, Thoughts? Precautions?
I have been busy otherwise and had not started anything significant for this. That sort of interface seems good to me. A couple thoughts/questions.
Do we need different handlers for create/update? Most of my use cases would involve a single handler that switches internally.
Can we provide a clear way for the handler to control whether http status is created or accepted (201/202). Replace fields method does this I believe. Would be nice to not have to override, and to allow handlers to return something that could be interpreted by the resource for this.
Will it default to the handlers taking full control of relationship updates as well as field updates? This is the part I'm least sure about.
Lets use .call to fire the handlers so the resource doesn't care if they're procs or complex objects. ( Unless I've misunderstood your suggested interface, in which case ignore this)
Can a custom processor be used to connect to a service? @coryodaniel you said you were having problems with it?
Hey guys, I don't know if you still have this problem but I came up with a solution recently.
Basically I created and adapter that decorates the model on the Resource class (I had to override the initializer for that) and delegates all of the getter methods to a presenter and the setters to a form object. I delegate the errors and valid? methods to the form object and implemented the save method on the adapter to call the form object and update the presenter with the result.
So far it works fine for me because it gets rid of all the assumptions that the jsonapi-resources gem makes about your architecture.
@thiagorp Interesting. Could you please provide the code example?
I could only come up with something like;
class V1::AccountCreateController < JSONAPI::ResourceController
def create
# Form object for the parameter validation
form = AccountCreateCustomerForm.new customer_params
form.validate!
# Use my service object instead of JR's 1:1 mapping to the ActiveRecord model to create it.
account = AccountCreateService.call(form.attributes)
if account.errors.blank?
# selialize the result and rednder
serializer = JSONAPI::ResourceSerializer.new(resource_klass, ...)
render json: serializer.serialize_to_hash(resource_account.new(account)), status: 201
else
render render json: 'error message', status: 422
end
end
end
I'd love to know some different approach.
Does jsonapi-utils not work for these issues?
Couple of thoughts:
Anyways, jsonapi-utils is everything I was hoping for and more to solve the problem of "I'm not just a blog service" resources. Highly recommend!
Most helpful comment
Does jsonapi-utils not work for these issues?