10 Most Common Rails Mistakes
toptal.com
toptal.com
if current_user.guest?
Although I don't like how the example given returns an OpenStruct instead of a NullUser, for instance.I find this pattern to be the most manageable especially in large projects.
But I agree with your point. "if current_user" is miles better than "if current_user.name != 'Guest'".
Edited to add: As far as this articles use of current_user.name being guest, it makes sense to use a presenter to display the data in the view. Conditionally displaying a user name or 'Guest' in a view is kinda gross. Again I'll suggest a gem for this pattern https://github.com/drapergem/draper
All objects coming into the view should be wrapped, especially and ActiveRecord objects.
Your example is pretty simple, so my solution will sound like overkill, but I would use some other type of object such as a Presenter to handle the rendering logic.
I've worked on very large and well used applications (billions of loads, multiple data centers, hundreds of thousands of lines of code), not that that should really matter to the argument (logical fallacy and all that), but the thing that I've found messes up Rails applications is large model objects ("god objects"), poorly thought out data relationships, and inconsistent assumptions across systems that lead to hacks. Not nils.
Sorry, you're completely wrong. Polymorphism and duck typing exist specifically so that you don't have to do `is_a?` checks.
> but the thing that I've found messes up Rails applications is large model objects ("god objects"),
How do you think god object models get created in the first place? By not extracting out logic. When you have models that have very different state, you actually have very different objects. Those differences should be extracted into different classes.
embrace OO
You also could make use of ActiveModel::Conversion#to_partial_path, like so:
<%= render partial: current_user %>
This implicitly calls to_partial_path on the current user object and looks up a template to render based on that result. So the user model would return "users/user" and your guest object could return "users/guest".The problem with using nil here is that you have to remember to check it everywhere, all of your tests have to exercise both the nil and not-nil cases, and most of the "else" clauses are just going to substitute default values anyways.
In my opinion if your views know so much about your user model that substituting a "guest" object is hard, maybe it's a sign you need to pull some logic back out of your views.
Generally you will have sections of your app that are only for logged in users, so your ACL logic in your controller handily takes care of that, while still giving you the convenience of being able to call current_user and know that you actually have a user, while still being able to say things like `if current_user` where you need it.
I do like your render partial example, but in most cases you will want to be doing something like showing a like button if the user is signed in, in which case you can do:
<%= render partial: current_user %>
And if the current_user is nil the partial will just be skipped. current_user.username>There are lots of places in an application that you'll want to conditionally show logic, like whether or not to show a "reply" button, and it is more natural to say "if current_user" than "if current_user.name != 'Guest'".
"if current_user.name != 'Guest'" is way more natural (not saying that is good or bad.) I read that as "if the current user is 'Guest'" whereas the former reads, "if there is a current user". There is obviously a user. When isn't there one?
Don't read too much into this, I am just speaking about what looks natural in your specific comment.
If instead you'd have a car object in the context of an app that sells junkyard items, it would make sense to return '0' instead of 'nil' when it concerns wheels (wow, this might be a terrible example...).
It seems to me that using too much of this implicit 'semantic thinking' is to be avoided, especially when the semantic meaning is not entirely clear or always agreed upon, like with user objects.
And if you look at this that way, it might be best to avoid returning nil in general (except perhaps for 'primitives?), and instead always go for a boolean return value.
(I have little experience in these matters, so please correct me if I'm wrong!)
It is even more natural to say `if signed_in?` :)
Devise provides such a method that checks for effective auth in warden, afaik (so it should know to make difference between a NullObject and an actual User). It would be easy to implement it yourself :
def signed_in?
current_user.is_a? User
endOne thing I've been having trouble with as my project gets more complex is when to refactor. I guess I'm not yet at the stage where I can accurately judge the benefits of refactoring a code vs. the cost. To use an analogy, am I just re-arranging the furniture to change the look-and-feel of the room (superficial), or am I actually making it easier for the guests to make themselves comfortable (real benefit)? I've read books like Pragmatic Programming [1] that advise refactoring whenever possible to avoid technical debt build-up. But it seems to be that there are times when working on a new feature would result in more value-add for the project as a whole.
Another thing is motivating myself to write test cases. The Hartl tutorial taught TDD, but when I started my project from scratch I quickly realized that TDD would slow down my progress significantly, and I didn't yet have enough experience to assess how a feature should work to be able to write tests for it. This did bite me in the ass just last night though, as I spent three hours troubleshooting a bug that a test case would have caught. Oh well, live and learn, right? :)
Don't know if you have seen http://railscasts.com yet. But it really helped me.
I usually wait until (A) something is painful and (B) I have enough instances of that pain to see the pattern that my refactor/abstraction will address/consolidate.
Else I'm just rearranging furniture or making it harder to rearrange in the future because my assumptions were wrong -- I've built a fridge and stove in a room that never became the kitchen when I could've just fetched a bucket of ice and a bunsen burner as I needed them. But now all the doors are too small to migrate my appliances because I thought this was going to be a hobbit house. Turns out the guests are only half hobbit.
It's easier to refactor naive, repetitive, even dirty code than it is to refactor failed and premature refactors/abstractions.
Seeking correctness is cool, but often I can't see far enough ahead to know what "correct" actually entails. Writing what feels like a simple/obvious hack now can be better down the road than trying to prematurely engineer something that seems correct.
Using OpenStruct as a replacement for a user doesn't remove the conditionnal, it just removes it from that use case. You still need to know if the user is logged in or not.