I have the following piece of code in the file app/mailers/user_mailer.rb .
Now how does format.html know what to render .
It is not in the action of any controller.
I understand the basic fact that it renders show view by default but here is no controller no action.
How does it still work ?
class UserMailer < ActionMailer::Base
def SIGNUP_NOTIFICATION(user)
#recipients = "#{user.email}"
#from = "#{sender.email}"
mailer_name = "SIGNUP_NOTIFICATION"
mail(:to => #recipients, :from => #from) do |format|
format.html
end
end
You're right that ActionMailer is not a controller (that's ActionController). The respond_to method of ActionController and the mail method of ActionMailer are entirely different things, however they both have the concept of "what kind of response should we be sending" and so the rails devs chose to offer a similar interface for format picking in both.
In ActionController the respond_to method takes a block and uses the HTTP request environment to pick which format to render.
In ActionMailer, mail takes a block and uses different logic to decide which format to render. When you give it format.html it simply looks in your views folder for templates with <method_name>.<format>.<acceptable_template_type>, e.g., SIGNUP_NOTIFICATION.html.erb. As an aside, you should name all your methods in snake_case (signup_notification). Uppercase method names are non-idiomatic and might cause problems.
Technical explanation
While you don't need to know this to use ActionMailer, I think it's interesting to walk through the flow of how template generation works here. It includes some interesting meta-programming but the logic is simpler than in regular controllers (no HTTP environment).
You can see in the source for mail that it calls a function to render the messages. If you gave mail a block, mail creates an instance of ActionMailer::Collector (which includes AbstractController::Collector) and yields to this collector. So, in your example the collector gets the function html called on it (since your do |format| ... end has an argument, format will be the collector instance when yield(collector) is called here.
So an instance of ActionMailer::Collector is passed into your do |format| ... block, where it has the function html called on it, The collector uses method missing tricks
from AbstractController::Collector so that it can respond to any mime type, passing the requested type over to ActionMailler::Collector#custom which actually builds the email by looking in the views folder for templates matching that type.
Mailer is like a controller but without inheriting ActionController, for your mailer you would put your view in app/views/user_mailer/SIGNUP_NOTIFICATION.html.erb
And you can send this email calling the method in your mailer:
UserMailer.SIGNUP_NOTIFICATION(some_user_instance).deliver
I am not sure if this is what you are asking for.
Related
I have a custom Class in my Rails application, which validates a bunch of settings.
The code:
class UserSettingObject < RailsSettings::SettingObject
validate do
if !/^([0-9]|0[0-9]|1[0]):[0-5][0-9]$/.match(self.time)
redirect_to settings_path, notice: 'Invalid time format'
end
end
end
I check SO posts and found a similar problem (here), the recommendation was include ActionController::Redirecting but it doesn't works, resulting in undefined method `config_accessor' error.
How to use Rails redirects methods in a custom Class?
In addition to what #SergioTulentsev already said, validations are for validating, not for taking actions. What you could do instead is leaving the regexp as a method, and in controller check the time using it and redirect based on the result of the validation.
Short answer is: you can't do that.
redirect_to only makes sense in request context (read: when being called from within controller action). You can't redirect from random objects, because they don't know what request to manipulate.
And yes, what #AndreyDeineko says.
In every example for create or update action that I see, they have something like this.
def create
#user = User.new(params)
if #user.save
redirect_to #user
else
render 'new'
end
end
Here how the redirect_to #user goes to show action of the controller. Can anybody explain me this?
Let's start from the redirect_to documentation.
redirect_to post_url(#post)
is used to redirect to a specific URL generated using one of the Rails route helpers. In your case, it means you can write
redirect_to user_url(#user)
However, redirect_to also accepts a single model instance. Behind the scenes, redirect_to relies on url_for to generate an URL from the input when the input is not an object.
url_for, in turns, when you pass an instance of a model by default will compute the corresponding GET action to view the model.
In conclusion, the following code:
redirect_to #post
is equivalent to
redirect_to post_url(#post)
However, personally I prefer the explicit version. Even if it's a little bit longer, I've noticed it tends to produce more maintainable code in the long run. Writing the full route will allow you to easily search your code base when you need to debug or rename routes.
It's all in the documentation.
Record - The URL will be generated by calling url_for with the options, which will reference a named URL for that record.
So, url_for will be called on your #user which will produce the url for redirection. (/users/1234 or something)
This is just one of many ways to do redirection, by the way.
In Ruby (the language which supports Rails), you set #instance_variables to store data for that request. Whilst you can store many types of data in a variable, Rails often assigns #model objects to them...
#user = User.find 1
#-> #user = <User id: "1", name: "john" .... >
This means that whenever you use a helper (such as redirect_to, or even a path_helper), you're actually able to pass the object to it and Rails will extract the data it requires.
For example...
user_path(#user)
edit_user_path(#user)
In the instance of a path, the helper extracts the id of the object; redirect_to extrapolates the functionality to route the request to the show path for that user.
Passing redirect_to accepts an object, invoking the show action for that object.
The reason why this is important is to understand that Ruby (& by virtue Rails) is object orientated.
Object orientated programming means that you should be dealing with objects (not variables).
In the case of Rails, each model should be an object. Every time you load the model, or create a new instance of it, you should be dealing with the object rather than the data.
Therefore, allowing you to pass #objects to methods such as redirect_to is just another way to make Rails more object-orientated.
I'm working on a rails app that serves some json and I'm having hard time understanding what is going on in the code below (simplified for the purpose of understanding the issue).
module Api
class ProjectController < ApplicationController
respond_to :json
def show
x = {"id"=>17, "name"=>"abc", "version"=>1}
respond_with x.to_json, status: 200
end
def create
x = {"id"=>17, "name"=>"abc", "version"=>1}
respond_with x.to_json, status: 200
end
end
end
The show action works fine but when I call the create action I get
NoMethodError (undefined method '{"id":17,"name":"abc","version":1}_url' for
Api::ProjectsController:0x007fbb2294cd18)
Why do I get this error while show works just fine? is it because create makes a post instead of a get?
How can I solve it?
Thanks for your help and have a nice day.
The issue is that your controller is in a module (Api). That affects the namespace, and thus the routing; you must include that namespace as part of what you pass to respond_with.
Normally, I'd direct you to this stackoverflow answer (credit goes there). But since you are using a hash instead of a model, this answer might be more applicable.
Note how Rails is trying to call a ..._url method. The ... is your hash, in JSON format. You need to help Rails here on how to render.
I would love to call a controller action from within a model. Yes, MVC. Thanks.
So, why would i like to call a controller action and fetch the response?
Because my controller knows how to render the file I would like to cache. Why should I duplicate code to collect all data needed by my view?
Setup:
InvoicesController responds_to :html, :pdf
Invoice uses state_machine (:new -> :open -> paid)
What I "need" within the state transition from :new to :open
generate /invoices/:id.pdf
cache the PDF as Invoice#file for later use in delayed_job or simliar
What interface i would love to use elsewhere?
#invoice.build_pdf
Any suggestions?
Update:
I would like to cache the PDF as model attachement for later use (delayed_job mailing, etc)
It may be better to take the code you're using in your controller to render the PDF data, and create a module or class in your lib directory let's say lib/pdf.rb then use that code in both your controller and your model.
Update: You can cache the PDF itself on the filesystem as a file with a timestamp as it's name or something like that that is uniquely identifiable. Then add a file or invoice attribute to the model to store the location in the database for later use.
Update: You can use the block form of respond_to like so:
respond_to do |format|
format.html
format.pdf { InvoiceBuilder.create(#invoice) }
end
Update: It seems that ActionDispatch::Integration::Session is a class for creating and interface for integration testing. But I think you could exploit it for your purposes.
You should be able to use it like this:
# app/models/invoice.rb
class Invoice < ActiveRecord::Base
def get_body
app = ActionDispatch::Integration::Session.new(Rails.application)
app.get("invoices/#{self.id}.pdf")
app.response.body # this returns the rendered content that the browser will see.
end
end
Another way would be to grab the content by using curl i.e. curl http://localhost:3000/invoices/1.pdf and store the output.
My rails app produces XML when I load /reports/generate_report.
On a separate page, I want to read this XML into a variable and save it to the database.
How can I do this? Can I somehow stream the response from the /reports/generate_report.xml URI into a variable? Or is there a better way to do it since the XML is produced by the same web app?
Here is my generate_report action:
class ReportsController < ApplicationController
def generate_report
respond_to do |format|
#products = Product.all
format.xml { render :layout => false }
end
end
end
Here is the action I am trying to write:
class AnotherController < ApplicationController
def archive_current
#output = # get XML output produced by /reports/generate_report
# save #output to the database
respond_to do |format|
format.html # inform the user of success or failure
end
end
end
Solved: My solution (thanks to Mladen Jablanović):
#output = render_to_string(:file => 'reports/generate_report.xml.builder')
I used the following code in a model class to accomplish the same task since render_to_string is (idiotically) a protected method of ActionController::Base:
av = ActionView::Base.new(Rails::Configuration.new.view_path)
#output = av.render(:file => "reports/generate_report.xml.builder")
Perhaps you could extract your XML rendering logic to a separate method within the same controller (probably a private one), which would render the XML to a string using render_to_string, and call it both from generate_report and archive_current actions.
What I typically do in this type of situation is to create a separate module/class/model to generate the report (it could even potentially be right in the Product model). This separate component could be in app/models or it could be in lib. In any case, once you have it extracted you can use it anywhere you need it. The controller can call it directly. You can generate it from the console. You can have a cron job generate it. This is not only more flexible, but it also can help smooth out your request response times if the report becomes slow to generate.
Since you are using a template it's understandable that the controller route is convenient, but even if you have to include some kind of ruby templating system in your auxiliary lib, it's still probably going to be less hassle and more flexible then trying to go through the controller.
#output = Product.all.to_xml
I'm sorry, is you question about Xml or about sessions? I mean is the fact that your action generates Xml material to the question? Or do you just want to save the output of the action for latter use?
You said on a "separate" page - you mean on another request? (like after user approved it?)
Why do you want to save the output? Because it should be saved exactly as rendered? (for example user can get frustrated if he clicked to save one report and you saved another)
Or is this thing expensive to generate?
Or may be, I got it wrong and it's about refactoring?