I'm interested in how people handle post form save...
# cfwheels
m
I'm interested in how people handle post form save emails in CFWheels. For example, someone submits a form with a user comment field. That comment gets emailed to someone for review. Do you handle the email in the controller such as this: if (comment.save()){ emailNewComment(); } Or do you handle it inside the model with something like afterSave("emailNewComment"); I've done both. I'm assuming since this isn't directly dealing with a database the answer is to do it in the controller? However it's sometimes convenient and nice to handle it in the model. Keeps the controller a little cleaner and I get to see what happens after a save directly in the model. However, inside the model there isn't access to the includePartial() function if I want to put the body of the email in a separate file. Or any other functions inside the controller easily. Just wondering any other pros/cons and if there's a recommended way. Thanks.
a
This isn't really a CFWheels question, it's an MVC question. Business logic doesn't belong in a controller method. End of. The decision that an email needs to be sent after processing is business logic. It belongs in the Business logic tier, or: model.
I'm assuming since this isn't directly dealing with a database the answer is to do it in the controller
This is incorrect thinking. The only think a controller is supposed to do is to accept a request from the routing tier, extract values from the request (eg: form, url, cookies etc that the model might need), pass them to the model tier. Then based on the response from the model, decide which view (and return status code, header, etc) to use to transform the model data for the response to the user. For your specific requirement, the latter is the way to go. Whether or not it's done in an event handler called by the
afterSave
event would be situation-specific, I think. But I think what you suggest is a nice approach. As for how to create the body of the email in an MVC fashion, in other frameworks I'd generally call a subcontroller to handle that, so the email render itself follows an MVC process, but I suspect CFWheels can't handle this, so you might be stuck.
m
Right, more MVC related. Thanks for the background. I guess I knew that, but good to be reminded. I've usually done it in the model, but since the model doesn't have a good way to call an include (that I can see) it had me wondering more how people handle it. I mean, I can just put the whole body of the email in the model, but it could get a little long. Was trying to organize things a little better.
a
Yeah the way CFWheels is architected does not lend itself to encouraging well-designed code, unfortunately. It's OK for quick prototyping, but beyond that... well...
:-/
p
Adam brings up a good point and in a pure sense of MVC, that may make sense. Personally, I always tend to put this in the controller action though. I guess I like looking at the controller and understanding everything that is going on, without having to jump to the model to check out if any user interaction code is in there as well. I tend to think of the model as the data layer and any data related code to maintain validation and data integrity I put in there but user interaction code I keep in the controller. I'll have to check to see if there is a good reason why the sendEmail() function isn't available in the model or was this an arbitrary decision that could be changed so the developer could have the option to move this type of interaction into the model if they so choose.
m
I wasn't even thinking about the sendemail() cfwheels function. Tend to forget about that and just use the standard Coldfusion version. Something I'll have to consider. But to your bigger point, it would be great to have some of the controller functions available in the model, such as sendemail, or includepartial, etc. Not sure it all makes sense, but whatever would be useful. Just from reading the CFWheels docs, I tend to think about the model as a data layer as well and not much else. But I do find myself doing aftersave(), aftercreate() type functions, such as sending emails, or uploading files, etc.
a
in a pure sense of MVC, that may make sense
Those are weasel words, Peter. You're positioning this like it's not well-defined what the concepts of model / view / controller mean in MVC. It's also not purist or dogmatic to suggest that there's very good reasons to actually follow industry-accepted design patterns. The problem for CFWheels developers is that the design of CFWheels itself is not great, and it works against writing well-crafted, well-designed code, due to its design limitations. These limitations can be seen in the intro documentation, where CFWheels demonstrates it fundamentally doesn't understand MVC: From https://guides.cfwheels.org/cfwheels-guides/introduction/frameworks-and-cfwheels#model-view-controller-mvc
Model: Just another name for the representation of data, usually a database table.
That's just wrong. That is not what the model is. Storage considerations like "database tables" and the handling thereof are just a boundary part of the model: stuff needs to be stored, and a DB is a good place to store it, but that just is the very "edge" of the model, and is usually abstracted well away from the actual domain logic part of the model. CFWheels is not helped here by its decision to promote its interpretation of the ActiveRecord pattern (or close to an anti-pattern IMO, but hey) way too far up into the front of the model. This makes it quite hard to write a good business tier in a CFWheels app. That Mike has to hang business logic of an evert that fires around storage considerations demonstrates this.
Controller: The behind-the-scenes guy that's coordinating everything.
Also... if not objectively "wrong" per se, but so poorly described that it kinda suggests how you got to "I put in there but user interaction code I keep in the controller." It's not up to CFWheels of CFWheels devs to redefine the fundamentals of MVC. You can put your code wherever you want. Knock yerself out, but don't kid yerself that yer not really doing MVC; you're just "writing some code". The code will likely be badly-coupled and a testing, maintenance, and scalability... challenge but... well. [shrug] I feel for you though, because CFWheels really does make it hard to write good MVC code.
it would be great to have some of the controller functions available in the model, such as sendemail, or includepartial,
OMG. Neither of those are methods that belong in a controller anyhow!!! The former should be in a mail service class (part of the model), and include_partial_ is a view method! But. Yes. Back to what I said about CFWheels poor design. I realise there is very little overlap in CFWheels of "where things are implemented" and "where things should be implemented". You are experiencing this ATM, Mike. 😞
p
In practice I've often looked at the controller in MVC as in fact the perfect place for the business logic. Consider a warehouse management system, where you have pickers and packers, and a shipping department. Parts come into the warehouse and are placed on shelves. Then orders are dropped to the warehouse, a picker picks up the order and pulls parts of the shelf. Then packs them into boxes, and ultimately the boxes get shipped out of the warehouse. How we run our operation I think of as the business logic. For instance when parts come in put a portion on the lower and shelf the rest on the over stock shelf. Or pickers need to scan parts off the shelf and place them in the cart and packers need to scan parts out of the cart and put them into the box. Or take a weight measurement of the of the box and if the weight variance is more that 10% of what we expected hold the order and inform management. These are all business logic stuff and I tend to think of them belonging int he controller. The model's job is to put data into the database and pull it out and present it to the controller in my opinion. I should be able to change the database from MySQL to SQL Server to Oracle to UniVerse without loosing or effecting business logic. I should also be able to change the interface for the pickers and packers from printed forms, to tablets with cameras, to hand held scanners without changing the business logic and only changing or creating new views. Anyway, I guess the point I'm trying to make is that the definition of MVC as defined at https://en.wikipedia.org/wiki/Model–view–controller or https://techterms.com/definition/mvc is inline with this opinion. Also keeping in mind that CFWheels was ultimately modeled after Ruby on Rails and even in Rails 6.0 the triggering of the sending of the email is placed in the controller action, I'd say we are in line with our architectural brethren.
s
That Wikipedia link to MVC (not the link to Model alone, which Slack picked up) says: Model The central component of the pattern. It is the application's dynamic data structure, independent of the user interface.[14] It directly manages the data, logic and rules of the application. Controller Accepts input and converts it to commands for the model or view.[15] I concede that it is common in Rails to put business logic in controllers but it is still wrong.
Viewing the Model as just data and persistence puts you square in the Anemic Data Model antipattern.
The decision to send email is part of the "rules of the application", and the construction and sending of that email is a responsibility of the Model -- it has nothing to do with marshaling or validating user input or rendering views, so it does not belong in the Controller.
Ideally, you should be able to take your entire Model code and drop it into a different context, e.g., a different framework, with very few changes. Active Record can make that hard because it tends to tie persistence (and a bunch of other stuff) into the "MVC" framework -- so I view it as somewhat of an antipattern itself and Rails makes that even worse. Java's Spring is much closer to what MVC is supposed to be since the persistence mechanism, much as I dislike ORMs, is at least separate from the Controller portion of things -- and that's generally true of other MVC frameworks in Java and other languages.
a
I was about to say that! [edit: I specifically meant your first comment Sean. But obvs the rest too, now that see them] Peter your description of a warehouse operation is a poorly realised analogy. A better one would be the storemen receiving the goods from the truck are the controllers. The shelves are just the storage tier. The decision as to which products go on what shelves are the business rules and are the model. And there's no view at all in this situation. It's a suck MVC analogy ;-) Also you say your "interpretation" of MVC matches that wikipedia article, but it clearly doesn't. The Wiki article describes them the same way I did. All the controller does is marshal things. It does not codify any of the actual behaviour. And it also says the model MANAGES the data, logic etc. It doesn't say it is the data tier. You are fundamentally misunderstanding MVC. Interestingly... in the same way CFWheels does. That second article you link to has a very un-OOP understanding of how the parts of MVC interact: misunderstanding the fundamental OOP concept of "tell don't ask" (https://martinfowler.com/bliki/TellDontAsk.html). It is - badly - separating data from the behaviour of the data. But yeah, this is indeed how you seem to be approaching things. It's an anti-pattern. Hey dude I am not having a go at you. You are just giving bad advice, and this is based on a clear misunderstanding of good MVC and OOP design principles. You can write your code however you feel comfortable doing so, but it might help to understand the concepts you're trying to use a bit better. That's all.
👍🏻 1
m
Thanks for the discussion everyone. Great to see the back and forth to understand things better. I've enjoyed CFWheels over the last 10 years or so. We tend to work with forms a lot and not overly large applications. It works well for this. Based on this I think I'll keep the after form save email trigger in the model and live with whatever limitations there might be in the framework.
a
I do my best to articulate what goes in a controller in this article I wrote a while ago: https://blog.adamcameron.me/2021/07/what-logic-should-be-in-controller-and.html. Interestingly this stemmed from a situation I found myself in looking at a CFWheels controller with business logic all over the place. Just the first half of it is on-topic for this thread. The second half gets into implementation detail of the model... but it's showing why it's in the model, not in the controller. I also note I never answered one of your questions, Sean.
@mike42780 can you like do something like
controller("MyEmailController").run(emailArgsHere)
(conceptual pseudo code) in your email method, and then still have a nice MVC separation for the creation of the email content?
Having a templating service would be nice for this. But CFWheels doesn't do with the concept of services, I think (again: due to misunderstanding "model").
cos I think my pseudo code there... actively calling a subcontroller from a model... might not be great 😉
s
At work, we create and send a lot of HTML emails -- hundreds of thousands a day -- so we wrote a component that handles that, wrapping up a templating approach (it started out simply
include
-ing template files but eventually we moved those into the database and built a little CMS for the business team to manage the content on their own). It is used somewhat as a "library" all over the Model, wherever the business logic determines an email needs to be sent.
1
By having it as a "library" component, available to the Model (through DI/IoC, in our case), we were able to modify its implementation completely independent of any framework -- and in fact use it from both ColdBox and FW/1 for a while -- and later on we rewrote the core email templating system in another language and were still able to call it from those apps with zero changes to that code (except the component implementation changed from doing it in CFML to calling the new version).
a
Yeah this is something ppl miss when they don't stop to think about separating their code out properly, and minimising controller code: potential reuse. Code stuck in a controller method simply can't be reused. That's not to say that one should code for an uncertain future ("this might never get reused"), but design patterns exist for a reason, any it really is very very easy to just start some code's life in their own well-defined class. It's no more difficult or complex, but it leaves doors open for the future, instead of building walls.