break away from a controller action - ruby-on-rails

My question is if there is a rails equivalent to 'breaking' away from a controller action such as
def new
if some_confirmation
do_stuff
break #I know this only breaks out of a loop but I want it to break out of the action. is this possible?
end
do_some_other_stuff_which_it_should_not_reach_after_breaking
end
Also this raises the question if I'm doing something wrong and shouldn't be rather doing it with a before_filter
Also last question, is it better to enclose everything in a 'if-else'-statement in such definitions (not necessarily controller actions but also normal definitions) or do it like the way I intend to do up there ^?

The answer is to use return. Controller actions are just methods and return is how you return early from a method. There's nothing wrong with doing this and you should feel free to do so as needed.
You can use before_filters (which has been renamed to before_action in Rails 4), as you mention, but I recommend only doing this if every method in the controller requires the condition. Otherwise, you end up with a huge list of before_actions at the top that you have to keep in mind (or be surprised by) while reading the actions later; and it gets especially confusing if you have to keep in mind which ones apply and which ones don't for which actions!
Whether or not if-else statements are preferred is a bit of an opinion... but, inspired by Avdi Grimm's excellent book Confident Ruby, I recommend to do as you've done here... what you've set up is essentially a guard clause whereby you take care of exiting the method early up front, and then you get into the actual meat of the method. In Confident Ruby, Avdi Grimm talks about breaking methods up into logical parts to tell a coherent story that doesn't force readers to keep track of various states throughout the life of the method. Using an if-else statement tends to force readers to keep track of state, whereas using guard clauses allows you to quickly identify conditions to leave the method via and then forget about them so you can focus on the actual purpose of the method.

Related

Rails proper way to distinguish methods from variable names?

Now that my controllers are expanding beyond the basic REST actions it can get somewhat confusing as to whether a given expression is either a built in method, a controller method, from the model, or a variable.
Would adding empty parentheses to my controller methods when I call them from other methods cause any problems down the road? Like so:
if customer.save?
subscription_checker()
end
I'm at least trying to always use an underscore in the names of the methods I create in order to make them look different from most of the built in methods.
Would adding empty parentheses to my controller methods when I call them from other methods cause any problems down the road?
This is a valid way to distinguish between variables vs methods in ruby, but "You're Doing It Wrong" ™
Part of the ruby paradigm is that you shouldn't typically care whether the thing you're referencing is a variable or a method. This makes the code easier to read and refactor; keeping the developer's attention focused on code's intent rather than its implementation.
This design pattern is often referred to as "bare words", and I highly recommend the following RubyTapas episode on the subject: http://www.virtuouscode.com/2012/10/01/barewords/
I would also recommend that you have a quick read through a ruby style guide, to see what common conventions are considered "good" or "bad" by the community. (Ruby code styles are extremely well conformed to by the community!) For example, from this guide:
Only omit parentheses for
Method calls with no arguments:
# bad
Kernel.exit!()
2.even?()
fork()
'test'.upcase()
# good
Kernel.exit!
2.even?
fork
'test'.upcase
In your particular code above, although it's hard to say for sure without knowing more context, I suspect that subscription_checker should actually be abstracted into a separate class rather than yet-another-method in the controller. But as I say, in order to give any concrete advice here I'd need to see more of the source file this is taken from.

Ruby on Rails: Creating a link based on new database entry

I'm in the process of updating a website I made almost 2 years ago. It was my first real website and I made some mistakes (some more serious that others).
What apparently is one of my biggest is making database calls from the view.
I'm pretty damn sure there is a better way to do this:
Use Case:
Someone fills out a form for a new subject, populating the Subject table, and they have been marked "enrolled", Subject.enrolled = 1
Based on that, I now need to create a record in 5 other tables (such as Baseline)
Downhill from here, here is my method
Determine if the record exist based on subject_id from Subject (sub)
<$ if Baseline.where(subject_id: sub.subject_id).first != nil $>
If it does not exist, create the record, (otherwise display the link)
<%= Baseline.create(subject_id: sub.subject_id) %>
This all happens in the view, and creates a front-end table with links to each record in the process. So I'm creating records based on for-loop logic...
Question:
So I'm looking for direction. I don't want to guess how to do this - I'm pretty sure the model/controller should do this - I want to learn how to do it correctly. How do I create records automatically, based on a value in a table?
Thank you for your time.
Not quite sure how your domain and code looks like, but to answer this question: 'How do I create records automatically, based on a value in a table?', it seems that you could use ActiveRecord callbacks, like this:
class Subject < ActiveRecord::Base
after_commit :create_baseline_if_enrolled, on: [:create, :update]
private
def create_baseline_if_enrolled
return unless enrolled?
# enrolled? == true, you may create these models here
end
end
To answer your question:
It depends :) This is just one possible solution. Another one would be to put such a custom logic in your SubjectsController and call it directly from the #create, #update methods. Both approaches have pros and cons. For example, abusing callbacks (anywhere) makes code less readable and harder to debug. On the other hand, putting such logic in controllers puts a burden on you that you have to remember about calling it if you happen to be editing subjects in other places (but is more explicit). Whichever way you choose, remember not to make your classes too fat, for example try to use service object pattern to separate such custom logic as soon as you feel like it is getting out of hand. :) And don't forget about tests - when things go wrong, tests make refactoring easier.

Clean controllers: preparing data for views

Consider I have a controller with an action which renders a view. The view needs data to render. I know the following ways to prepare and send it to the view:
Using instance variables
class CitiesController < ApplicationController
def index
#cities = Cities.order(:name).limit(10)
end
end
This is the default approach which can be found in Rails documentation, but it has some disadvantages:
It makes the action code fat which becomes responsible not only for controller logic, but also for the data preparation.
Views need to access this data through instance variables – those #-variables break the principle of least astonishment.
Using helper methods
class CitiesController < ApplicationController
helper_method :cities
def index
end
def cities
#cities ||= Cities.order(:name).limit(10)
end
end
That's the way I prefer the most. It keeps action methods clean, so I may implement controller logic there not mixing it with data preparations in one method. Also, there's no need to use mysterious instance variables in views, making them isolated. However:
The data preparations are still in the controller. It becomes unreadable when there are a lot of these helper methods, especially when they are relative to different actions/views.
There's a need of having a unique name for each helper method. Say, I can't have a method called products which will return different data for different actions (of course, I can do it in one method, but it would look ugly).
Using the facade pattern
Partially the problem is solved in this article: https://medium.com/p/d65b86cdb5b1
But I didn't like this approach because it introduces a #magic_facade_object in views.
Using inherited resources
It may look beautiful in examples, but in my opinion when it comes to the real code, controller code becomes a spaghetti-monster very fast. The other thing is that a page view usually needs not only the resource but also other data to render (sidebar blocks, etc.) and I still have to use another way to prepare it. Combining different approaches makes the code even more unreadable. Finally, I don't like to use resource variable, because it makes not very clear what is the view about.
So, here is the question. How do you keep your controllers clean?
How do you keep your controllers clean?
By writing DRY code and sprinkling some gem magic around.
Having a look at your bullet points, I think I have a different opinion on most of the stuff.
#cities = Cities.order(:name).limit(10) is exactly what i think belongs into a rails controller and it does not violate the principle of least surprise, it's kind of the opposite. instance variables are the default way of passing around variables from controllers to views, even though that is a pretty ugly thing to do. it's "the rails way" (TM)!
decent_exposure takes away most of these concerns
please stop applying old-school pattern to rails or ruby code. it's really just useful in large applications where you are struggling to keep sane with the amount of code that's within a single controller method. write clean code, test it thoroughly and you will be fine 80% of the time.
don't use "one size fits all" tools. most often, you need to write more configuration than you would need code to make it work. it's also getting a lot more complex through this kind of things.

RoR - Don't destroy object, just flag as hidden

I have a simple model in RoR and I would like to keep eveything people enter on the site. But I also want to be able to hide some content if the user click on "Remove".
So I added a bolean attribute in my model called "displayed".
I would like to know, what would be the best-practices-styled method.
I guess I have to change the controller with something like :
def destroy
#point = Point.find(params[:id])
#point.displayed = false
#point.save
respond_to do |format|
format.html { redirect_to points_url }
format.json { head :no_content }
end
But I am not sure it is clean. What would be the best way to do it.
As you guess I am noobish with RoR. Chunks of code would be appreciated.
Thank you
Implement it yourself (rather than using a gem). It's much, much easier than it seems at first, and it's less complex than any of the gems out there that change the meaning of the destroy method, which is a bad idea, in my opinion.
I'm not saying that using the gems themselves are complex - I'm saying that by changing the meaning of the destroy method you're changing the meaning of something that people in the Rails world take for granted - that when you call destroy that record is going to go away and that destroy maybe also be called on dependent objects if they are chained together via dependent: destroy callbacks.
Changing the meaning of destroy is also bad because in the "convention over configuration" world, when you screw with conventions you're essentially breaking the "automagic-ness" of your Rails code. All that stuff you take for granted because you read a piece of Rails code and you know that certain assumptions generally apply - those go out the window. When you change those assumptions in ways that aren't obvious you're almost certain to introduce a bug down the line because of it.
Don't get me wrong, there's nothing better than actually reading the code for checking your assumptions, but it's also nice, as a community, to be able to talk about certain things and generally have their behavior act in a certain way.
Consider the following:
There's nothing in Rails that says you have to implement the destroy action in the controller, so don't. It's one of the standard actions, but it's not required.
Use the update action to set and clear an archived boolean attribute (or something similarly named)
I've used the acts_as_paranoid gem, and if you need to add any scopes to your models (other than the ones the gem provides) you're going to find yourself having to hack your way around it, turning off the default "hide archived records" scope, and when you run into that it almost immediately loses its value. Besides, that gem does almost nothing on its own, and its functionality could easily be written yourself (and I mean barely more work than installing the gem itself), so there's really no benefit to using it from that perspective.
As previously stated, overriding the destroy method or action is a bad idea because it breaks the Rails (and ActiveRecord) convention as to what it means to call destroy on an object. Any gem that does this (acts_as_paranoid for example) is also breaking that convention, and you're going to wind up confusing yourself or someone else because destroy simply won't mean what it's supposed to mean. This adds confusion, not clarity to your code. Don't do this - you'll pay for it later.
If you want to use a soft-delete gem because you are protecting against some theoretical, future developer who might hork up your data...well, the best solution to that is not to hire or work with those people. People that inexperienced need mentorship, not a gem to prevent them from making mistakes.
If you really, absolutely, must prevent destroying a record of a given model (in addition to being able to simply archive it), then use the before_destroy callback and simply return false, which will prevent it from being destroyed at all unless an explicit call to delete is used (which isn't the same as destroy anyway). Also, having the callback in place makes it (a) really obvious why destroy doesn't work without changing its meaning, and (b) it's easy to write a test to make sure it's not destroyable. This means in the future, if you should accidentally remove that callback or do something else that makes that model destroyable, then a test will fail, alerting you to the situation.
Something like this:
class Point < ActiveRecord::Base
def archive
update_attribute!(:displayed, false)
end
end
And then call #point.archive in the destroy action of your controller where you would normally call #point.destroy. You can also create a default_scope to hide archived points until you explicitly query for them, seethe RoR guide on appling a default scope.
Edit: Updated my answer as per normalocity & logan's comments below.
Look at the acts_as_archive gem. It will do soft deletes seamlessly.
Your solution is good, but you can use the acts_as_paranoid gem to manage that.
In this scenario, instead of adding a new boolean flag its better to added a deleted_at:datetime column
#point = Point.find(params[:id])
#point.touch(:deleted_at)
...
Then later
Point.where(deleted_at: nil) # these are NOT hidden
Point.where.not(deleted_at: nil) # these are hidden

Best practices for coding in controller and models?

I have this short and simple code for sending an email notification to the user when someone comments on his post. What I'm concerned about is the location of this snippet.
if user.settings.enabled_notifications && some_other_conditions
NotificationMailer.notify_topic_owner(comment,owner)
end
notify_topic_owner() just shoots a mail according to the parameters passed to it.
Basically, some_other_conditions contain some 3-4 conditions to be evaluated to true so as to send a mail. So clearly a controller isn't the right place for this code (I read somewhere that a controller code should be light and clean).
I dont think i can move this snippet to a helper as helpers contain code for views. Again, models dont look right either as the code is not really about the model (or is it?).
Do I make a new module for this short snippet? Going forward, I would really appreciate if you could also tell about the best practices or some reference for such dull confusions. I find myself struggling with this quite often!
You are asking the right questions. Why not go one step further and attempt to do some OOP :
(the code below is not ideal, but it should give you a good idea of how to approach it). I have not taken "some_other_conditions" into consideration because those are likely something you know best where it will fit into your domain logic.
# A class for notification. I usually avoid depending directly on xxxMailer and similar
class Notifier
# Inject the recipient
def initialize(recipient)
#recipient = recipient
end
def topic_commented(comment)
# Only let Notifier know that NotificationMailer exists. (not perfect OOP. could inject this too)
NotificationMailer.notify_topic_owner(comment,#recipient) if #recipient.notifications_enabled? # Ideally should be telling, not asking. Oh well.
end
end
class User
# Sprinkling of Law of Demeter
def notifications_enabled?
settings.enabled_notifications
end
end
You call Notifier.new(current_user).topic_commented("Hello World"). In future, the topic_commented can send SMS, smoke signals, print, write to database etc. all without you having to change the calling code lke NotificationMailer.xxxx in many places.
I don't see what would be wrong with putting this in a controller. If it's related to a method in your controller, it can definitely go there. If it's called after a save or something, you can probably move it to the model.
Generally I think the best practice is try to put as much stuff into models and classes as possible. Save the controller for controller specific code, and helpers should only contain code related to rendering content in views. A lot of times, I'll take code in my controller and move it to the model while refactoring. My opinion anyway :)
The convention I use to think about it is: "Should the mail be sent every time a comment is added, no matter by what action?". Think about whether if, in the future, you implemented an automated system that added comments, the mail should be sent in that case. If so, it's probably model code; otherwise, it's related to the way in which the comment was added, and it's controller code.

Resources