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
Best practice for monkeying with params? #366
Comments
|
IMO it is a clear anti-pattern to send the full params hash to a decorator. Much like sending the full current context to a method or object and say, "hell, you figure out what to do with this mess!!". A better much pattern is to send it only those arguments it needs for its functionality. This is in line with the Command pattern and many others. Unfortunately Rails has a history of a very "dirty" approach to such matters, which eventually always leads to a lot of grief and misery whenever a project scales up or new team members come aboard and start "hacking". I believe the time has come to start cleaning up and try to avoid such anti-patterns in the future. Strong params is just one example of this. Thick bloated models was always a crazy idea! |
|
I also agree that sending the full params is bad. The point of a controller is to coordinate between components, so I wouldn't think it's a good idea. That said, if we don't do something, people will keep running into things. Maybe we should just |
|
I have to say, I agree with Kristian, but I'm bouncing ideas around inside my skull trying to figure out a good solution. While it makes it more explicit to have the View passing parameters to the Decorator instead of the Decorator sneaking around and snagging them out of the view context, it still feels wrong for the View to be snagging them out of the view context. It seems like the preferred approach is that the Controller goes and gathers everything necessary, sets up an instance variable, and then hands that single instance variable to the View whereupon the View has everything it needs to render things. Also, I've been using the And then in the Decorator, the various "helpers" have access to the limited context information set up in the Controller. This way the View doesn't have to snoop around in the Controller trying to snag things and pass them to the Decorator - in effect, the Controller is "decorating the decorator". It hands over a Decorator to the View that is guaranteed to have everything it needs for the View to do its job properly. The View's job is overall layout, and the Decorator's job is all the fancy rendering needed for that layout. It's not the View's job to give the Decorator all the information - that information is supposed to be provided by the Controller. All I have to do then is define the right set of attributes in Of course, this approach might have issues with collections of Decorators. Perhaps there should be a way to set some sort of context information on a Thoughts? |
This is a direction I'd like Draper to move in eventually. https://gist.github.com/3886837 and https://gist.github.com/3856921 I don't like mutator (setter) methods personally. Constructors should create a valid object. |
|
I added some comments to the gist. Perhaps a project like https://github.com/solnic/virtus could be useful for managing/validating attributes ;) Seen it used it other contexts and projects. I think it is a bit dangerous to take the approach of having a whole, complex Decorator tree the way @tovodeverett sketched it out, but I understand the need to do something like that. However for this scenario, I think something like Cells is better, where each "widget" gets its own mini controller and view. The widget controller then extracts the information needed for its view, then renders the view. You can even setup communication (data exchange) between the widgets... To me, a Draper decorator is mainly to avoid partials and vastly simply the views for each widget and make them more reusable and testable and less "dirty". |
Agreed. You can pass your sliced params in to the decorator constructor (or, equivalently, @article = Article.find(params[:id]).decorate(params.slice(:foo, :bar))
@article.options # => {foo: ..., bar: ...}I don't think the way you use those setters will work because
Indeed, there is. |
|
I can't sleep (2:40 AM here), so I might as well write instead of thinking about writing. One thing I've been thinking about ever since I decided to throw in my hat with Rails is elevating fields (i.e. attributes) to being full objects. A decade ago, I developed a web framework for ASP using Perl. It was incredibly inefficient, both due to run-time performance issues and due to the need to compile the whole stack for every request (although I spent a lot of time developing lazy-loading and lazy-instantiation techniques to deal with this). There was very little MVC separation, but there was a lot of MVC-like thinking, and one of the core ideas was that each field was a class/object. For performance reasons, attribute storage was done in a hash, but all access to that hash was mediated through a field object. The field objects were implemented using Each field had a full set of methods that all interacted to handle things like rendering (in a whole bunch of contexts), normalizing user input, validation, retrieving from params, security (who can view this attribute, who can edit it), etc. Of course, there was a whole class hierarchy of different field object classes along with some custom ones like:
I had a world where I would define a view layout once for both Rails has all these "helpers", both the ones we define and the ones Rails provides. But the helpers are just a bunch of procedural code to help us out and they don't really seem to address the issue. Draper helps clean up the ones we define, but we're still relying on the procedural ones Rails provides. Where is the MVC for each field? Anyway, I'm wondering if implementing something like that either as part of or on top of Draper might make sense. I've looked at Cells, but I'm thinking it may be a bit too much if I try to define a Cell for every field in my system! The vast majority of the customized logic when using my framework was related to the View and Controller (although I didn't know them as such at the time), and from that perspective it seems like Draper might be a viable place to implement that concept. |
|
@haines: Once I get some more sleep, I'll get to work on cutting over my code to using the beta version of Draper - I'm realizing that 0.18.0 is really just the first sketch and that it makes a lot more sense to be on the github version. I was wondering last night about the memoization of But I'm still debating. That lets my Controller use the local variable when talking to the Model and the instance variable when talking to the Decorator. Or maybe I should use: The first is perhaps a bit confusing, but once you know the pattern it's succinct. The latter is a bit more explicit, but it's more verbose. I need to think about Controllers a bit - I'm debating whether they should primarily interact with the Decorator or the Model. Does the options hash get passed to association decorators? For what it's worth, my current use of Rails is for an internal photo archiving/sharing site. I have 27 GB of family photos and videos from the past decade along with an indexing/display system running off my home server, and I'm rewriting the front-end in Rails (it's currently a bunch of ugly Perl code reading from YAML files that slowly grew over the past decade). So I'm perfectly happy using beta code in it. Once I've got that all finished and used it as a learning experience, I have a startup to go implement! |
|
"may be a bit too much if I try to define a Cell for every field in my system!" Hmm, this has nothing to do with Cells. Not what it is used for at all. I would suggest you look at this: http://apotomo.de/ and especially this example here: http://apotomo.de/peters-guide.html Then imagine taking the display view code from Peters guide and using Draper for the heavy lifting there! I would also look at Focused Controller to better encapsulate your controller actions ;) |
|
Regardless, I think this issue can be settled: Draper isn't responsible for what kind of craziness you do with messing around in your Rails environment. Since we allow you to set options for your decorator when you create it, that's how this should be done. |
|
I'm going to propose an idea for "encouraging" good behavior. Part of the problem is that helpers have access to Perhaps there should be a user configurable option to set The Permissive mode would model the current behavior - one gets access to everything from |
|
Hmmm. Okay, I'll re-open this for discussion... |
|
Why would you want helper modules in the first place? Using helpers has always been a bad design idea. I think a lot or developers have noted this... much better to use real "helper" objects. You can find tons of critiques of Rails misuse of helpers and how it leads to really ugly design, bloated namespaces and violating Single Responsibility and such. Just my thoughts. |
|
The whole point of Draper is to replace helpers, @kristianmandrup ;) That said, Rails' built-in helpers are very good. Like |
|
Unfortunately, there's a huge single, polluted, evil namespace that mixes:
Things in the first category are very useful, and preventing users from using them is likely to result in hand-coded HTML that is not safe. Things in the second category are dangerous. Things in the other two categories are probably OK - users will move methods into Decorators as they convert their codebases, and I see no reason to prevent users from trying to mix Draper and things like Formtastic. There could even be a three-way switch on the helpers in the second category - Strict, Warn, and Permissive. |
|
:) so true. Perhaps contextualizing the "helpers" via https://github.com/kristianmandrup/contextualize There really is a need to only include certain modules/functionality in certain contexts, not to pollute the namespace just in case it is needed in some context. This is the main problem as I see it. But categorising all this mess into some general categories could be a first fix I guess. |
|
I'm thinking that this is going to add a lot of complication for not a huge amount of benefit. I'd be willing to check out a PR for this, but I'm gonna give it a close otherwise. |
|
If and when I decide I feel strongly enough about this, I'll go work on it and submit a pull request. But more than likely I'll come to the same conclusion you just did! |
After a lot of trial and error, web searching, and generally stumbling around in the dark, I came up with the following approach for specifying the contents of
h.paramswhen testing Draper Decorators with RSpec. This assumes that one is using something likelet(:params) { {some_param: 'some_value'} }to set up the desiredparamscontents.I figured out the first part pretty quickly, but it took me a long time to realize that my tests were poisoning each other. So many things get automagically torn down when done from a
before(:each)block that I made the mistake of assuming that would happen here. Once I realized that no cleanup was happening, I figured out the cleanest way to clean stuff up would be to wipe out bothcurrent_controllerandcurrentso that they could be repopulated automatically by Draper.Perhaps this is just Draper's way of telling me I really shouldn't be using
paramsin my decorators, but #184 seems to imply that I'm not the only one doing this. If that's the case, shouldn't there be a less obscure way to test that modifyingparamshas the desired effect on the decorator?The text was updated successfully, but these errors were encountered: