where to put utility code used by a controller and a mailer? - ruby-on-rails

Our app has "notifications" which you view through your inbox on the site, and can also get email to tell you about them. When you receive a notification, it contains a link to reply to the message in question. That might mean sending a PM back to the sender of the original message, or might mean leaving a comment on a post.
This is the code to figure out what reply link to use:
if #notification.post
# comment on the post in question
#reply_link = new_comment_path(:post_id => #notification.post.id)
else
# by default, reply link sends a PM in return
#reply_link = new_notification_path(
:recipient_id => #notification.sender.id,
:subject => #notification.subject =~ /^Re: / ?
#notification.subject :
"Re: " + #notification.subject
)
end
I took that from our controller code, btw: app/controllers/notifications_controller.rb
Now we want to include the same reply link in our email notifications, which means we need to do the same sort of reply link generation in app/mailers/notifier.rb
I don't want to repeat myself, so I would rather create a reply_link method and put it somewhere where both the controller and the mailer can access it.
My first thought was to put it in the model, so that we could have Notification.reply_link. That'd be nice, but it doesn't work because we need new_comment_path and new_notification_path which aren't available in the model.
My second thought was to use a helper, but a) everyone seems to think that helpers suck, and b) we couldn't get it to work anyway.
So, where should I be putting this handy reply_link method, so that it will be accessible to both the controller and the mailer, and in keeping with good coding practices?

Extract it to a module and 'mix it in' to access it in both places you need it.
You can put the module anywhere you like: the lib folder has been a historically popular place, or create a 'modules' folder in your app directory.

Put it in /app/helpers or /lib
I generally tend to put methods accessed from controllers in helpers

Related

Rails: Model.find() or Model.find_by_id() to avoid RecordNotFound

I just realized I had a very hard to find bug on my website. I frequently use Model.find to retrieve data from my database.
A year ago I merged three websites causing a lot of redirections that needed to be handled. To do I created a "catch all"-functionality in my application controller as this:
around_filter :catch_not_found
def catch_not_found
yield
rescue ActiveRecord::RecordNotFound
require 'functions/redirections'
handle_redirection(request.path)
end
in addition I have this at the bottom of my routes.rb:
match '*not_found_path', :to => 'redirections#not_found_catcher', via: :get, as: :redirect_catcher, :constraints => lambda{|req| req.path !~ /\.(png|gif|jpg|txt|js|css)$/ }
Redirection-controller has:
def not_found_catcher
handle_redirection(request.path)
end
I am not sure these things are relevant in this question but I guess it is better to tell.
My actual problem
I frequently use Model.find to retrieve data from my database. Let's say I have a Product-model with a controller like this:
def show
#product = Product.find(params[:id])
#product.country = Country.find(...some id that does not exist...)
end
# View
<%= #product.country.name %>
This is something I use in some 700+ places in my application. What I realized today was that even though the Product model will be found. Calling the Country.find() and NOT find something causes a RecordNotFound, which in turn causes a 404 error.
I have made my app around the expectation that #product.country = nil if it couldn't find that Country in the .find-search. I know now that is not the case - it will create a RecordNotFound. Basically, if I load the Product#show I will get a 404-page where I would expect to get a 500-error (since #product.country = nil and nil.name should not work).
My question
My big question now. Am I doing things wrong in my app, should I always use Model.find_by_id for queries like my Country.find(...some id...)? What is the best practise here?
Or, does the problem lie within my catch all in the Application Controller?
To answer your questions:
should I always use Model.find_by_id
If you want to find by an id, use Country.find(...some id...). If you want to find be something else, use eg. Country.find_by(name: 'Australia'). The find_by_name syntax is no longer favoured in Rails 4.
But that's an aside, and is not your problem.
Or, does the problem lie within my catch all in the Application Controller?
Yeah, that sounds like a recipe for pain to me. I'm not sure what specifically you're doing or what the nature of your redirections is, but based on the vague sense I get of what you're trying to do, here's how I'd approach it:
Your Rails app shouldn't be responsible for redirecting routes from your previous websites / applications. That should be the responsibility of your webserver (eg nginx or apache or whatever).
Essentially you want to make a big fat list of all the URLs you want to redirect FROM, and where you want to redirect them TO, and then format them in the way your webserver expects, and configure your webserver to do the redirects for you. Search for eg "301 redirect nginx" or "301 redirect apache" to find out info on how to set that up.
If you've got a lot of URLs to redirect, you'll likely want to generate the list with code (most of the logic should already be there in your handle_redirection(request.path) method).
Once you've run that code and generated the list, you can throw that code away, your webserver will be handling the redirects form the old sites, and your rails app can happily go on with no knowledge of the previous sites / URLs, and no dangerous catch-all logic in your application controller.
That is a very interesting way to handle exceptions...
In Rails you use rescue_from to handle exceptions on the controller layer:
class ApplicationController < ActionController::Base
rescue_from SomeError, with: :oh_noes
private def oh_noes
render text: 'Oh no.'
end
end
However Rails already handles some exceptions by serving static html pages (among them ActiveRecord::RecordNotFound). Which you can override with dynamic handlers.
However as #joshua.paling already pointed out you should be handling the redirects on the server level instead of in your application.

rails the best way to saving page duration and page loading speed

Hi I'm a beginner of rails and I'm not good at English. so if there is some total nonsense please understand..
I'm trying to record loading speed and page duration in every pages.
I made a database "pages" and method "savepage" in my "Page" model.
To save in every page I put "savepage" method in application controller.
Page.rb
def self.savepage
.
.
.
end
application_controller.rb
before_filter :dosave
def dosave
Page.savepage
end
these kind of format..
My question is
1. am I doing correct? using before_filter to do save in very first of loading process?
2. to save after loading all the contents in a page what should I use?
3. to save after user leave this page what should I use?
I saw before_destroy and after_filter, but I can't find what it is... what filter means.... what action means destroy....
thank you in advance!
before_filter is the first thing which loads before giving request to controller.But your need is completely different . Fundamentally filter are used boolean checking.If certain method is true,it will run otherwise it may not. This filter are further extended and we put code into that filters.(And Even sometimes it is consider as best practice) .
Now, before_filter :dosave might be right but is it not true way of knowing page(UI) loading process. I suggest you to use javascript call or use some manually created helper methods and place it into view .erb files.
May be this will interest you
https://github.com/grosser/record_activities
Log user activities in ROR
what action means ?
Action Controller is the C in MVC. After routing has determined which controller to use for a request, your controller is responsible for making sense of the request and producing the appropriate output. Luckily, Action Controller does most of the groundwork for you and uses smart conventions to make this as straightforward as possible.
Source : http://guides.rubyonrails.org/action_controller_overview.html
I highly suggest you to read above documentation. It is very necessary for you and it covers topic which you asked here.`
And one more thing,
what is action destroy ?
This is simply an action method just like new. Since, rails follow Convention over configuration ( and its developer too) so they put code which do some delete destroy or some destruction. This make thing simple,otherwise more configuration will require which is against rails policy.

What is the best way to show my users a preview of email templates in Ruby on Rails?

My software sends emails for users. I want to show them what the emails will look like before they get sent. However, with ActionMailer conventions, the entire template is in one file. This means the html,head,body tags, etc. Can anyone think of a good way to give my users a preview of what the emails I send out will look like?
Thanks!
I had the same issue. I built out the display with the associated model I was sending rather than in the mailer. I was able to feed sample data or live data to display it to the user.
when it came time to actually send it, I rendered the exact same thing within the mailer view
EDIT:
I apologize for the crap variable names in advance. I am not sure I am allowed to explicitly talk about them :)
Lets say I have a BarMailer function called foo(status,bar)
where status is a test email or a live email and bar is my associated model.
I called deliver_foo("test",bar)
deliver_foo sends out a multipart message so for each part I render_message and pass along variables I need. for example:
p.body = render_message('bar_html', :bar => bar, :other_data => bar.other_data)
so, that render_message is is saying to specifically use the bar_html view (I also have a bar_text for plain text).
this is the contents of my bar_html view:
<%=render :inline => #bar.some_parent.some_other_model.html, :locals => {:other_data => #other_data, :time => Time.now, :bar => #bar }%>
Its a little complicated, but it is based on a template system. By rendering inline everywhere, I am able to use the same code for a number of different functions including previewing and sending. I like this because it becomes a WYSIWIG. No extra code or functionality that could be buggy and muck with the potential output in an email. If it works in one area, it will work in the other. Plus keeping it DRY means I am not going to forget to modify a copy (which I would do frequently, hehe).

In RESTful design, what's the best way to support different kinds of GETs?

In a current project I need to support finding a User by login credentials and also by email address. I know that in RESTful design you use a GET to find resources. In Rails...
GET /users # => UsersController.index -- find all the users
GET /users/1 # => UsersController.show -- find a particular user
But I also need something akin to...
GET /users?username=joe&password=mysterio
GET /users?email=foo#bar.com
Is it conventional to add additional routes and actions beyond index and show?
Or is it more common to put conditional logic in the show action to look at the params and detect whether we're finding by one thing or another?
There's a similar issue with PUT requests. In one case I need to set a User to be "active" (user.active = true), and in another case I just need to do a general form-based editing operation.
Thanks guys. Eventually I'm going to figure out this REST stuff.
I'm new to SO, so I can't comment, but the checked green answer is not RESTful.
In a RESTful world, your controller grabs all the parameters and passes it to the model layer for processing. Typically, you shouldn't create another action.
Instead, you should do do something like this:
def show
#user = User.find_by_login_or_email(params[:user])
... #rest of your action
end
Your model can have a method like this:
class User
self.find_by_login_or_email(params)
return find_by_login(params[:login]) unless params[:login].blank?
return find_by_email(params[:email]) unless params[:email].blank?
nil #both were blank
end
end
Your view could look like this:
<%= f.text_field :user, :email %>
or
<%= f.text_field :user, :login %>
Note: untested code, so may be buggy...but the general line of thinking is usually not to create new actions for every one-off rule. Instead, look to see if you can push the logic into the models. If your controllers start to have too many non-standard actions, then it may be time to re-evaluate your domain modeling, and perhaps it's refactor the actions to some new models.
ps: you should never pass in passwords via a GET like that
I don't know how much of this is convention, but this is what I would do. I
would add another action, as long as it's specifically related to that
resource. In your example, show is a find by userid, so it makes sense as
another action on UsersController. You can turn it into a sentence that makes
sense, "get me the user with this email address"
For the other one, GET /users?username=joe&password=mysterio, I would do
that as another resource. I assume you're thinking that action would log in
the user if the password were correct. The verb GET doesn't make sense in that
context.
You probably want a 'session' resource (BTW, this is how restful_auth works).
So you would say "create me a session for this user", or something like POST
/sessions where the body of the post is the username & password for the user.
This also has the good side effect of not saving the password in the history
or letting someone capture it on the HTTP proxy.
So your controller code would look something like this:
class UsersController < ActionController::Base
def show
#user = User.find_by_id(params[:id])
# etc ...
end
def show_by_email
#user = User.find_by_email(params[:email)
end
end
class SessionsController < ActionController::Base
def create
# ... validate user credentials, set a cookie or somehow track that the
# user is logged in to be able to authenticate in other controllers
end
end
You would set up your routes like this:
map.connect "/users/byemail", :controller => "users", :action => "show_by_email", :conditions => { :method => :get }
map.resources :users
map.resources :sessions
That will get you URLs like /users/byemail?email=foo#example.com. There are
issues with encoding the email directly in the URL path, rails sees the '.com'
at the end and by default translates that into the :format. There's probably a
way around it, but this is what I had working.
Also like cletus says, there are ways to make your route match based on the format of the parts of the URL, like all numbers or alphanumeric, but I don't know off hand how to make that work with the dots in the url.
The first thing you can do is make your GETs as smart as possible. In your example, this can be handled programmatically. The argument can be processed this way:
Is a number? It's a userid;
Has a # in it? It's an email;
Otherwise? It's a username.
But I assume that you're not just talking about this example and want something to handle the general case rather than just this specific one.
There are basically two ways of dealing with this:
Add extra path information eg /users/email/me#here.com, /users/name/cletus; or
Be more specific in your "top-level" URL eg /user-by-email/me#here.com, /user-by-name/cletus.
I would handle it programmatically if you can.
Regarding the "ByEmail" request, have you considered creating a new email resource.
GET /email/foo_at_bar_dot_com
The response could contain a link to the related user.
I see so many people trying to apply RESTful design principles to their URL structure and then mapping those urls to procedural handler code. e.g. GET = Show, or is it GET = Index or ShowByEmail. By doing this you are really just pretending to do a RESTful design and then trying to create a mapping between a resource oriented URL space and procedurally oriented implementation. That is really hard to do and the procedural nature keeps leaking out into the URLs.
Resource oriented design often requires a very different way of thinking about problems that we are used to and unfortunately many of the frameworks out there keep sucking us back into the RPC model.
You might be able to set up different routes for different tasks. So for this case you could have one route to a method in UserControll dedecated to getting a user by email, and another for getting the information by credentials.

ActionMailer- access Mail::Message content, or manually initialize view in controller?

I've got a form where an internal user can request that informational materials be sent to a client. When the form is submitted, it sends an email to the person in charge of physically mailing the materials to the client.
Now, I want to capture the content of the email so I can add a note to the client's show page, and I'm unsure how to go about it.
One option I've looked at is to use an after_filter in the MaterialsRequestMailer, but calling message.body returns a large string with way more text than I need and I want to avoid adding a bunch of parsing logic to get the message content.
Basically, I want what is generated by the views/materials_request_mailer/send_request_notification.text.erb template. I've looked through http://www.rubydoc.info/github/mikel/mail/Mail/Message and can't find a method to return just the rendered template content. Is there a way to do that?
If not, is there a way to manually initialize a View in the controller, where I already have the instance variables I'm passing to the mailer? That doesn't seem to be an ideal solution, because I'm using DelayedJob, and the code for adding the note would be run before the email is actually sent. Also, due to DelayedJob, it appears that I can't directly access the mail object from within the controller (if I do mail = MaterialsRequestMailer.delay.send_request_notification(...) it assigns an instance of Delayed::Backend::ActiveRecord::Job to mail).
Found a solution that works- message.text_part.body.raw_source is what I was looking for. (credit to this answer: https://stackoverflow.com/a/15257098/2599738)
class MaterialsRequestMailer < ActionMailer::Base
include AbstractController::Callbacks
after_filter :add_note_to_client
def send_request_notification(client, ...)
#client = client
...
end
def add_note_to_client
mail_text = message.text_part.body.raw_source
#client.add_account_note(mail_text)
end
end

Resources