setting erb template in Rails with mailgun-ruby - ruby-on-rails

I would like to use the mailgun-ruby MessageBuilder API to build and send application emails in my production environment. The documentation suggests that I do something like this:
def confirmation_instructions(record, token, opts={})
mg_client = Mailgun::Client.new ENV['MAILGUN_API_KEY']
mb_obj = Mailgun::MessageBuilder.new
mb_obj.from("from#site.com", {"first" => "First", "last" => "Last"})
mb_obj.add_recipient(:to, record.email)
mb_obj.subject("Please confirm your account")
mb_obj.body_html("<p>how would i pass the default confirmation_instructions erb template as an argument to this method?</p>")
mg_client.send_message("mail.site.com", mb_obj)
end
The problem is, once I started using Mailgun, I also stopped using ActionMailer's default mail() syntax. This seems to prevent Rails from manually selecting the template that matches the method name.
My first thought is I need to select it manually. An answer here says that I can change the template like this:
mail(to: user.email, subject: "New Projects") do |format|
format.html { render layout: layout_name }
format.text { render layout: layout_name }
end
But this syntax conflicts with what is expected by mailgun-ruby. I can't leave the body/text setting methods out, and if I do, it throws an error: Mailgun::CommunicationError (400 Bad Request: Need at least one of 'text' or 'html' parameters specified).
In the first example above, how would I pass the proper layout to body_html? That is, without directly writing the html in as an argument?
Update: my rake routes for confirmations
new_user_confirmation GET /users/confirmation/new(.:format) users/confirmations#new
user_confirmation GET /users/confirmation(.:format) users/confirmations#show
POST /users/confirmation(.:format) users/confirmations#create
update_user_confirmation PATCH /users/confirmation(.:format) users/confirmations#update

Looking at the gem's issues, it looks like it is not supported.
The way to go is to use your preferred template engine and give the output to the body_html.
You're not telling it, but looking at the method name, I guess you're using the Devise gem, and you'd like to send its content.
Here is how to make it working (didn't try it):
def confirmation_instructions(record, token, opts={})
email_html_body = ERB.new(File.read('devise/mailer/confirmation_instructions.html.erb'))
.result_with_hash(
email: current_user.email,
resource: resource,
token: #token
)
# ...
mb_obj.body_html(email_html_body)
# ...
end

Wow, so...Rails default mail() syntax does still work with Mailgun. I don't know why it was not at first attempt, but I think it had something to do with the fact that I had not set the production.rb config to config.action_mailer.delivery_method = :mailgun.

Related

No route matches [GET] on a post request

I started Ruby on Rails few days ago, and I'm struggling with routing.
Indeed, I would like to make a post request through my routes.rb, but I keep having a
No route matches [GET] "/orders/refresh"
error.
Here is my routes.rb :
# frozen_string_literal: true
Rails.application.routes.draw do
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
get '/orders', to: 'orders#index'
get '/orders/active(/:q)', to: 'orders#active'
post '/orders/refresh', to: 'orders#refresh'
end
and here is my controller (orders_controller.rb) :
# frozen_string_literal: true
class OrdersController < ApplicationController
def index
#orders = Order.order(:departure_date).all
render json: #orders.to_json
end
def active
if !params[:q]
#orders = Order.order(:departure_date).where(active: true)
else
#orders = Order.order(:departure_date).where("reference = ? OR client_name = ? OR departure_city = ? OR arrival_city = ?",
params[:q], params[:q], params[:q], params[:q])
.where(active: true)
end
render json: #orders.to_json
end
def refresh
response = RestClient.get 'https://wakeo-technical-test.s3.eu-west-3.amazonaws.com/api.json'
json = JSON.parse response
if !json.nil?
json.each do |order|
old_order = Order.find_by(reference: order["client_number"])
if !old_order.nil?
old_order.update(departure_date: order["dep_time"])
old_order.update(arrival_date: order["arr_time"])
old_order.update(client_name: order["company"])
old_order.update(departure_city: order["dep_city"])
old_order.update(arrival_city: order["arr_city"])
end
end
else
puts "error seeding external API"
end
end
end
From what I have understood, it seems like RoR will try to find a GET request for that specific URL, and since it won't find any, it will throw that error. How could I make that request be a POST for Rails ?
Also, I would appreciate any suggestion about how I should use ActiveRecord Querying, I'm pretty sure I could do it better here.
Thanks, have a great day !
EDIT : Here is the list of different routes my app seems to be capable of, including my POST.
Routes and error
The most common reason you unexpectly get GET requests instead of PUT, PATCH, POST or DELETE is that you are using link_to 'Something', '/some_path', method: :post and you broke the Rails Unobtrusive Javascript Driver (Rails UJS):
Because submitting forms with HTTP methods other than GET and POST
isn't widely supported across browsers, all other HTTP methods are
actually sent over POST with the intended method indicated in the
_method parameter. Rails automatically detects and compensates for this.
Rails does that with a JavaScript event handler attached to any link with the data-method attribute. But if you broke that functionality the browser will just perform its default action which is sending a GET request when the user clicks a link.
This problem usually boils down to one or more of:
Your javascript is throwing an error which halts script execution (use the browser console to find the error, make it suck less).
Rails UJS is not included in your assets pipeline or webpacker packs and thus not in the page.
The quick and easy solution to sidestep the problem is by using button_to which actually creates a form and does not require any JavaScript trickery. After all forms can send POST requests. And by just passing a _METHOD hidden field Rack will treat the request as any other HTTP verb.
button_to 'Something', '/some_path', method: :post
But in the long run you should probably fix the problem if you want to use any of the features of Rails UJS.
Your routes.rb is expecting a POST request to /orders/refresh routes, but apparently you are testing with a GET request.
Try changing your routes.rb:
Rails.application.routes.draw do
# ...
get '/orders/refresh', to: 'orders#refresh'
end
... or change your request to a POST request. If you are using Rails forms, you must do something like this:
form_with(url: "/orders/refresh", method: "post")
Ok, I think I figured it out.
It might be because when I hit /orders/refresh directly in my web browser, it will try to find a GET corresponding to the request.
I managed to make POST using a client like Postman, and everything works fine.
Thank you for your help !

Setting Up Rails to Receive HTTP POST Request

Is setting up Rails to receive a HTTP POST request that sends information encoded in JSON form as easy as adding the following code to the sessions_controller? Or are there other steps involved?
def create
if user = User.authenticate(params["email"], params["password"])
session[:user_id] = user.id
render:json => "{\"r\": \"t\"}" + req
else
render :json => "{\"r\": \"f\"}"
end
end
Should be that easy, though you will need to add a route to your routes.rb file as well specifying POST as the HTTP verb and pointing it to sessions#create. You also might want to use strong parameters just to validate what parameters are required and which you'll accept. As a heads up, I'm not entirely sure what "{\"r\": \"t\"}" + req is supposed to represent. It looks like req would be undefined in this case, but perhaps you're just omitting some code. Lastly, render :json => ... is sort of the old way of including a hash. I believe as of Ruby 2 the standard is something more like render json: .... Hopefully that helps.

How to create a route without an associated View?

I want to create a route such as
get '/referrals/send_invite/:email_address'
I will be calling this route via remote: true and GET. However if I issue the GET request in my browser, the route will still try to find a View, thus leading me to:
Template is missing
Is there a way that I could tell rails that the send_invite method in Referrals Controller doesn't have a view associated?
I would hope that this could be accomplished by just using rails routes.
Thanks.
Not completely sure, but I am guessing you want to start the action send_invite so you are not interested in an actual result, correct?
You could do something like
def send_invite
SomeMailer.mail(:email => params[:email_address])
# or queue it or whatever
head :ok
end
Note that that is not the only option, you could also do something like
render :text => "The mail has been sent to #{params[:email_address]}"
or
render :json => {:result => 'ok', :email_adress => params[:email_address]}
Also note this should be a POST, since this action is not idempotent (a GET should not have side-effects).
Hope this helps.
More likely than not your template missing is coming from not having a sendinvite.js.erb. Try putting a blank one in your view folder to see that sorts it out.

Custom path generator

I thought I knew how to override a path helper, but I'm not getting my expected behavior.
I tried adding something like this to my ApplicationHelper:
def post_path(post)
"/posts/#{post.id}/#{post.url}"
end
But for some reason, in one of my controllers when I try to use post_path(#post) it just returns the full url, something like /posts/4faddb375d9a1e045e000068/asdf (which is the current url in the browser) rather than /posts/4faddb375d9a1e045e000068/post-title-here.
In my routes file:
get '/posts/:id/:slug' => 'posts#show', :as => 'post'
The strange thing is if I use post_path(#post, #post.url), it works correctly. And if in a view I use post_path(#post) it works correctly. (#post.url returns the url friendly title)
In case you can't tell, I'm trying to eventually get the behavior similar to stackoverflow's where the url contains the id and a slug at the end and if the slug doesn't match the model with the given id, it'll redirect to the correct url.
What I'd try would be to put the whole def post_path in the application_controller.rb and make it a helper with helper_method :post_path. You'll get the best of both worlds.

Rails redirect_to with params

I want to pass parameters (a hash) to redirect_to, how to do this? For example:
hash = { :parm1 => "hi", :parm2 => "hi" }
and I want to redirect to page /hello
URL like this: /hello?parm1=hi&parm2=hi
If you don't have a named route for /hello then you'll have to hardcode the params into the string that you pass to redirect_to.
But if you had something like hello_path then you could use redirect_to hello_path(:param1 => 1, :param2 => 2)
Instead of:
redirect_to some_params
You can do:
redirect_to url_for(some_params)
You're turning the params into a url with url_for before passing it to redirect_to, so what you pass redirect_to ends up being a URL as a string, which redirect_to is happy to redirect to.
Note well: I don't understand why redirect_to refuses to use params. It used to be willing to use params. At some points someone added something to Rails to forbid it. It makes me suspect that there are security reasons for doing so, and if so, these security reasons could mean that manually doing redirect_to url_for(p) has security implications too. But I haven't yet been able to find any documentation explaining what's up here.
update: I've found the security warning, but haven't digested it yet: https://github.com/rails/rails/pull/16170
The easiest way (if it's not a named route) will be:
redirect_to "/hello?#{hash.to_param}"
See: http://apidock.com/rails/Hash/to_param
Simply, pass the hash into an argument in the URL, and in your code parse it to get out all needed values.
param_arr = []
hash.each do |key , val|
param_arr << "#{key}=#{val}"
end
params_str = param_arr.join("&")
redirect_to "http://somesite.com/somepage?#{params_str}"
I know this might be very basic way to do it, but hey, it'll get you somewhere :)

Resources