In my rails app a lib class is used to recieve emails. The email reciever class parses the email and post it to a application controller using a HTTP response. The code for this is as shown :
uri = 'http://localhost:3000/incoming_mail'
body = {'from'=> from, 'to' => to, 'subject' => subject, 'message' => message}
response = Net::HTTP::post_form(URI.parse(uri), body)
The problem is I don't want to specify the complete URL. Is there any way to use 'incoming_mail_path' instead of 'localhost:3000/incoming_mail' ?
I tried to add :
include Rails.application.routes.url_helpers
But this is not working and gives the following error :
<class:EmailReceiver>': uninitialized constant EmailReceiver::Rails (NameError)
Can anyone please suggest a solution for this.
I am posting the entire class here(Updated the class with include statement):
require 'mail'
require 'net/https'
require 'net/http'
require 'uri'
class EmailReceiver
include Rails.application.routes.url_helpers
attr_accessor :url
def initialize
end
def submit(content)
mail = Mail.read_from_string(content)
body = mail.body.decoded
from = mail.from.first
to = mail.to.first
subject = mail.subject
if mail.multipart?
part = mail.parts.select { |p| p.content_type =~ /text\/plain/ }.first rescue nil
unless part.nil?
message = part.body.decoded
end
else
message = mail.decoded
end
unless message.nil?
uri = incoming_mail_url
#uri = 'http://localhost:3000/incoming_mail'
body = {'from'=> from, 'to' => to, 'subject' => subject, 'message' => message}
response = Net::HTTP::post_form(URI.parse(uri), body)
end
end
end
handler = EmailReceiver.new
handler.submit(STDIN.read)
Debugging:
After reading your comments, i figured out that you are running it as a ruby script which does't even recognize Rails.
Before figuring out how to include all the requirements to the file. I tried to run the file through rails environment (while the server was already running) by:
cat sample.email | bundle exec rails runner "eval(File.read 'lib/email_receiver.rb')"
I got the error for the incoming_mail_url:
Missing host to link to! Please provide the :host parameter,
set default_url_options[:host], or set :only_path to true (ArgumentError)
While the incoming_mail_path successfully executed /incoming_mail (which is not what you need).
Conclusion:
This means that what ever you do, as long as you don't run the file from the server (ex: initializers) then the host will never exist.
When you run this module from your server, its going to recogize the route through the url_helpers that you have included.
Alternative suggestion:
Griddler gem is a Rails engine that provides an endpoint for services that convert incoming emails to HTTP POST requests. It parses these POSTs and hands off a built email object to a class implemented by you.
To solve your immediate problem you may want to try to unscope your include statement. Right now it seems that interpreter thinks that "Rails" is a class within the EmailReceiver namespace. if you Add :: to the include statement it should reference "Rails" at the top level which is what you want.
It should work, but there maybe something else wrong in your setup that is causing you to have use this otherwise unnecessary workaround
Edit
What I meant by add "::" just to clarify
include ::Rails.application.routes.url_helpers
Related
I am trying to implement post action using httparty gem and this is what I have. I am running everything in docker and I have code below that will run as active job. I is in one service and I am trying to make post to api in other service. I am able to do get but not having any luck with post. I looked and searched a lot online but I am not sure what is it I am doing wrong. I always get error 403 at self.class.post line. I also tried to do a postman call to api and I am able to hit the api but with the code below its not even reaching to the other service.
Any help is appreciated. Thanks.
require 'uri'
class CustomerProductAPI
include HTTParty
format :json
def initialize(customer_product_id)
#customer_product = CustomerProduct.find(customer_product_id)
#customer = Customer.find(#customer_product.student_id)
#product = Product.find(#customer_product.product_id)
self.class.base_uri environment_based_uri + '/customer_product_api'
end
def create_customer_product
uri = URI(self.class.base_uri + "/customer/#{customer.id}")
self.class.post(uri, body: body_hash).response.value
end
private
attr_reader :customer_product, :customer, :product
def body_hash
{
token: ENV['CUSTOMER_PRODUCT_API_TOKEN'],
customer: customer.name,
product: product.name,
}
end
def environment_based_uri
ENV['CUSTOMER_PRODUCT_URL']
end
end
While we can't actually be sure of exactly what the server thats accepting the response expects you're definately doing quite a few non-idiomatic things here which will aggrevate trouble shooting.
base_uri should just be set in the class body. Not in initialize for each instance. You also do not need to construct a URI with HTTParty. Just pass a path and it will construct the request uri relative to the base_uri.
When getting configuration from ENV use ENV.fetch instead of the bracket accessors as it will raise a KeyError instead of just letting a nil sneak through.
Your HTTP client class should not be concerned with querying the database and handling the potential errors that can occur if the records cannot be found. That should be the responsibility of the controller/job/service object that calls the client. Since you're only actually using three simple attributes it doesn't actually need records at all as input and its actually better that it doesn't have to know about your models and their assocations (or lack thereof in this case).
class CustomerProductAPI
# lets you stub/inspect the constant
CUSTOMER_PRODUCT_URL = ENV.fetch('CUSTOMER_PRODUCT_URL') + '/customer_product_api'
include HTTParty
format :json
base_uri CUSTOMER_PRODUCT_URL
def initialize(id:, product_name:, customer_name:)
#id = id
#product_name = product_name
#customer_name = customer_name
end
def create_customer_product
self.class.post("/customer/#{#id}", body: {
token: ENV.fetch('CUSTOMER_PRODUCT_API_TOKEN'),
customer: #customer_name,
product: #product_name
})
# don't return .response.value as it will make error handling impossible.
# either handle unsuccessful responses here or return the whole response
# for the consumer to handle it.
end
end
Rails version: '~> 4.2.7.1'
Spree version: '3.1.1'
TlDr:
How do I get route as /api/products/:id or controller and action of that route in a middleware of Rails 4 application.
Details:
I am adding a middleware in my rails app which is similar to gem scout_statsd_rack. This adds following middleware to rails app to send metrics via statsd:
def call(env)
(status, headers, body), response_time = call_with_timing(env)
statsd.timing("#{env['REQUEST_PATH']}.response", response_time)
statsd.increment("#{env['REQUEST_PATH']}.response_codes.#{status.to_s.gsub(/\d{2}$/,'xx')}")
# Rack response
[status, headers, body]
rescue Exception => exception
statsd.increment("#{env['REQUEST_PATH']}.response_codes.5xx")
raise
end
def call_with_timing(env)
start = Time.now
result = #app.call(env)
[result, ((Time.now - start) * 1000).round]
end
What I want is to find current route in the middleware so that I can send metrics specific to each route.
I tried approach described here, which tells env['PATH_INFO'] can provide path, which it does, but it gives with URL params like this: /api/products/4 but what I want is /api/products/:id as my puropose is to track performance of /api/products/:id API.
env['REQUEST_PATH'] and env['REQUEST_URI'] also gives same response.
I tried answer provided here and here:
Rails.application.routes.router.recognize({"path_info" => env['PATH_INFO']})
or like this
Rails.application.routes.router.recognize(env['PATH_INFO'])
But it gave following error:
NoMethodError (undefined method path_info' for {"path_info"=>"/api/v1/products/4"}:Hash):
vendor/bundle/gems/actionpack-4.2.7.1/lib/action_dispatch/journey/router.rb:100:infind_routes'
vendor/bundle/gems/actionpack-4.2.7.1/lib/action_dispatch/journey/router.rb:59:in recognize'
vendor/bundle/gems/scout_statsd_rack-0.1.7/lib/scout_statsd_rack.rb:27:in
call'
This answer discusses request.original_url, but How do I access variable request, I think it should be same as env but not able to get route as want from this.
Edit #1
You can see the sample repo here, with code of rails middleware here, Setup of this can be done as stated in README and than this API can be hit: http://localhost:3000/api/v1/products/1.
Edit #2
I tried approach given by #MichałMłoźniak like following:
def call(env)
(status, headers, body), response_time = call_with_timing(env)
request = ActionDispatch::Request.new(env)
request = Rack::Request.new("PATH_INFO" => env['REQUEST_PATH'], "REQUEST_METHOD" => env["REQUEST_METHOD"])
Rails.application.routes.router.recognize(request) { |route, params|
puts "I am here"
puts params.inspect
puts route.inspect
}
But I got following response:
I am here
{}
#<ActionDispatch::Journey::Route:0x007fa1833ac628 #name="spree", #app=#<ActionDispatch::Routing::Mapper::Constraints:0x007fa1833ace70 #dispatcher=false, #app=Spree::Core::Engine, #constraints=[]>, #path=#<ActionDispatch::Journey::Path::Pattern:0x007fa1833acc90 #spec=#<ActionDispatch::Journey::Nodes::Slash:0x007fa1833ad230 #left="/", #memo=nil>, #requirements={}, #separators="/.?", #anchored=false, #names=[], #optional_names=[], #required_names=[], #re=/\A\//, #offsets=[0]>, #constraints={:required_defaults=>[]}, #defaults={}, #required_defaults=nil, #required_parts=[], #parts=[], #decorated_ast=nil, #precedence=1, #path_formatter=#<ActionDispatch::Journey::Format:0x007fa1833ac588 #parts=["/"], #children=[], #parameters=[]>>
I have pushed the changes as well here.
You need to pass ActionDispatch::Request or Rack::Request to recognize method. Here is an example from another app:
main:0> req = Rack::Request.new("PATH_INFO" => "/customers/10", "REQUEST_METHOD" => "GET")
main:0> Rails.application.routes.router.recognize(req) { |route, params| puts params.inspect }; nil
{:controller=>"customers", :action=>"show", :id=>"10"}
=> nil
The same will work with ActionDispatch::Request. Inside middleware, you can easily create this object:
request = ActionDispatch::Request.new(env)
And if you need more information about recognized route, you can look into that route object that is yielded to block, by recognize method.
Update
The above solution will work for normal Rails routes, but since you only have spree engine mounted you need to use different class
request = ActionDispatch::Request.new(env)
Spree::Core::Engine.routes.router.recognize(request) { |route, params|
puts params.inspect
}
I guess the best would be find a generic solution that works with any combination of normal routes and engines, but this will work in your case.
Update #2
For more general solution you need to look at the source of Rails router, which you can find in ActionDispatch module. Look at Routing and Journey modules. What I found out is that the returned route from recognize method can be tested if this is a dispatcher or not.
request = ActionDispatch::Request.new(env)
Rails.application.routes.router.recognize(req) do |route, params|
if route.dispatcher?
# if this is a dispatcher, params should have everything you need
puts params
else
# you need to go deeper
# route.app.app will be Spree::Core::Engine
route.app.app.routes.router.recognize(request) do |route, params|
puts params.inspect
}
end
end
This approach will work in case of your app, but will not be general. For example, if you have sidekiq installed, route.app.app will be Sidekiq::Web so it needs to be handled in different way. Basically to have general solution you need to handle all possible mountable engines that Rails router supports.
I guess it is better to build something that will cover all your cases in current application. So the thing to remember is that when initial request is recognized, the value of route yielded to black can be a dispatcher or not. If it is, you have normal Rails route, if not you need to recursive check.
I have a Rails app which creates/builds some Jekyll sites on the same server. Right now, I'm calling Jekyll commands with backticks like this:
def build_jekyll
result = `jekyll build -s /some/source/path -d /some/dest/path`
end
This works fine but feels a little un-ruby like. If the jekyll gem is in my Rails Gemfile, is there a way I can build a jekyll site using ruby?
(from the docs, it looks like I would call Jekyll::Commands::Build.build but I'm not sure how to initialize the site parameter).
TL;DR
require 'jekyll'
conf = Jekyll.configuration({
'source' => 'path/to/source',
'destination' => 'path/to/destination'
})
Jekyll::Site.new(conf).process
But how did you find out?
I figured this out by looking at the source code. When you run jekyll build, you enter into the source file bin/jekyll. The interesting part here is
command :build do |c|
# ommitted
c.action do |args, options|
options = normalize_options(options.__hash__)
options = Jekyll.configuration(options)
Jekyll::Commands::Build.process(options)
end
end
Hm, looks like the actual work is done in Jekyll::Commands::Build.process, so let's take a look into that method in lib/jekyll/commands/build.rb:
def self.process(options)
site = Jekyll::Site.new(options)
self.build(site, options)
# other stuff
end
Again, the actual magic happens somewhere else, namely in Jekyll::Commands::Build.build, also in lib/jekyll/commands/build.rb
def self.build(site, options)
# some logging going on here
self.process_site(site)
end
This in turn calls a class method called process_site, which comes from the superclass Jekyll::Command defined in lib/jekyll/command.rb
def self.process_site(site)
site.process
rescue Jekyll::FatalException => e
# some error handling
end
So we actually want to call process on a Jekyll::Site. One thing we have yet to find out is how to specify options for the Jekyll::Site instance. Let's take a closer look at lib/jekyll/site.rb
def initialize(config)
# more options ...
self.source = File.expand_path(config['source'])
self.dest = File.expand_path(config['destination'])
# more options ...
end
So apparently we need to supply a hash with the 'source' and 'destination' keys pointing to the desired directories. The rest of the configuration will be generated by Jekyll with the Jekyll.configuration method which we saw earlier in bin/jekyll. That's about it. Now, the only thing left to do is putting the pieces together ;-)
Updating this because it looks like the syntax changed, this works now
require "jekyll"
options = {
"source" => './',
"destination" => './_site',
"watch" => true,
"verbose" => true
}
Jekyll::Commands::Build.process(options)
Rails ActiveResource is awesome ... except for one thing: as far as I can tell, there is no way to see what URL it is using behind the scenes. For instance, let's say I have an ActiveResource called Issue, for a webservice at myIssues.com/issues.xml. If I do:
Issue.find(:all, :params => {:page => 2})
I would expect that ActiveResource would make a call to:
myIssues.com/issues.xml?page=2
... but I don't actually know that. For all I know, ActiveResource could have decided it doesn't like the word "page", so it's actually using:
myIssues.com/issues.xml?mod_page=2
This makes debugging difficult. Right now I've got a situation where, if I go to the URL I think ActiveResource is using, it works just fine. However, when I actually use ActiveResource, it doesn't work. Seeing the URL it's GETing would be immensely helpful in this, so ...
Does anyone know a way to log (or otherwise output; if there's some resource.url method that would work great too) the URL(s) that ActiveResource uses to do its thing?
If you add the following line to your environment.rb file, it will at least log the requests so you know that URLs ActiveResource is hitting:
ActiveResource::Base.logger = ActiveRecord::Base.logger
I'm still searching for a better solution that shows me the response and the data posted to update calls, but at least this is a step in the right direction. I'm really not sure why ActiveResource has a separate logger to start with, but that's another matter.
I just ran into this same exact issue, and came across this post as I was looking for answers. What I did find, that proved useful, is the collection_path method on ActiveResource::Base. So for example, let's say you have the following resource:
class UserPost < ActiveResource::Base
self.site = "http://someApp.com/user/:user_id"
self.element_name = "post"
If you go to the rails console, here are some examples of the output:
>> UserPost.collection_path
"/user//post"
>> UserPost.collection_path(:user_id => 5)
"/user/5/post
This should provide you with exactly what you need to determine how ActiveResource is translating your request into a URL.
To get detail login for ActiveResource have to patch the request method inside the gem(method.
place bellow files inside config/initializers you will get http method, path, request body, request hedaers
response body and header is already there if you need. doc
config/initializers/activeresource_patch.rb
module ActiveResource
class Connection
private
def request(method, path, *arguments)
result = ActiveSupport::Notifications.instrument("request.active_resource") do |payload|
payload[:method] = method
payload[:request_uri] = "#{site.scheme}://#{site.host}:#{site.port}#{path}"
payload[:request_path] = path
payload[:request_body] = arguments[0]
payload[:request_headers] = arguments[1]
payload[:result] = http.send(method, path, *arguments)
end
handle_response(result)
rescue Timeout::Error => e
raise TimeoutError.new(e.message)
rescue OpenSSL::SSL::SSLError => e
raise SSLError.new(e.message)
end
end
end
config/initializers/activeresource_logger.rb
Rails.application.configure do
def activeresource_logger
#activeresource_logger ||= Logger.new("#{Rails.root}/log/activeresource_logger.log")
end
ActiveSupport::Notifications.subscribe('request.active_resource') do |name, start, finish, id, payload|
if Rails.env.development?
activeresource_logger.info("====================== #{start} : #{payload[:method].upcase} ======================")
activeresource_logger.info("PATH: #{payload[:request_path]}")
activeresource_logger.info("BODY: #{payload[:request_body]}")
activeresource_logger.info("HEADERS: #{payload[:request_headers]}")
# activeresource_logger.info("STATUS_CODE: #{payload[:result].code}")
# activeresource_logger.info("RESPONSE_BODY: #{payload[:result].body}")
end
end
end
I specified the default_url_options in my environments/test.rb with
config.action_mailer.default_url_options = { :host => "www.xyu.at" }
This is quite ok and in my cucumber story, where i test registration of users,
the user-activation link gets generated right
invitation_activation_url(1)
=> "www.xyu.at/signup/1231hj23jh23"
But when I try to follow the link provided in the email with following code in features/steps/user_steps.rb (using email-rspec from http://github.com/bmabey/email-spec/tree/master):
When /^I follow the invitation link$/ do
When 'I follow "'+invitation_activation_url(1) + '" in the email'
end
Here the url gets created with the default-host:
invitation_activation_url(1)
=> "www.example.com/signup/1231hj23jh23"
Can anybody help me? I don't get what I'm doing wrong....
Thanks!
EDIT:
It seems to do with the method
current_url
but I dont know where it comes from..?
EDIT:
And I have the right environment specified in my features/support/env.rb
ENV["RAILS_ENV"] ||= "test"
EDIT:
My temporary solution is, what edbond said,
invitation_activation_url(1, :host => "www.xyz.at")
=> "www.xyz.at/signup/1231hj23jh23"
but I dont want to name the domain explicit this way
(i specified it already in my environments/test.rb file - that way it wouldnt be dry)
Use :host option in your url.
invitation_activation_url(1, :host => "www.xyz.at")
=> "www.xyz.at/signup/1231hj23jh23"
EDIT:
You can parse email body and get link
mail = YourMailer.deliveries.last
email_html = Nokogiri::HTML mail.body.to_s
approve_link = email_html.at_css("a")["href"]
I know its years since this was posted.. but I had this issue and took me hours to decipher until I got it figured out.
You should use instead
When /^I follow the invitation link$/ do
When 'I follow "'+invitation_activation_path(1) + '" in the email'
end
the _url generates the URL path; whilst the _path generates the URI path.
web_steps.rb uses the URI to determine the current_url which it uses to work out the host.
from http://api.rubyonrails.org/classes/ActionDispatch/Routing.html
Routes can be named by passing an :as option, allowing for easy
reference within your source as name_of_route_url for the full URL and
name_of_route_path for the URI path.
from web_steps.rb
Then /^(?:|I )should be on (.+)$/ do |page_name| | # end
current_path = URI.parse(current_url).path | #
if current_path.respond_to? :should | # collection do
current_path.should == path_to(page_name) | # get 'sold'
else | # end
assert_equal path_to(page_name), current_path | # end
end |
end
You say that you edited config/environments/test.rb. Are you sure that your Cucumber features are actually executing in the 'test' environment?
I recently added Cucumber to a project I'm working on, and it seems to set itself up to use a 'cucumber' environment by default.
In features/support/env.rb in my project there is this:
ENV["RAILS_ENV"] ||= "cucumber"
So if your project is similar, then you will need to customize config/environments/cucumber.rb as well.
I'm not terribly familiar with Cucumber, so I can't say with certainty where exactly you'll have to apply this fix. But the problem is that the default_url_options is not set in another place where you're trying to generate your url...
So my advice is to first find out in what context the faulty url is being generated. Before or after it, just output self.class. That's the class you'll have to monkey-patch. For the example, let's say 'ClassName' was printed out.
When you have that, in your config/environments/test.rb, just add the attribute accessor and then set it to what you want:
class ClassName
cattr_accessor :default_url_options
# or mattr_ if it's a module
end
and then set it the same way as your actionmailer
ClassName.default_url_options = { :host => "www.xyu.at" }
This whole process can be useful as well when you want to generate urls in models or in other esoteric places (then you'll also need to include ActionController::UrlWriter).
One solution (based on info here) is to have a step definition like
Given /^the domain "(.+?)"$/ do |domain|
host! domain
end
and use it like
Given the domain "www.foo.com"
in features.
With that, though, I ran into issues where redirects were not followed. I tried applying this patch but had no luck.
I ended up using a very simple workaround in Cucumber's env.rb file:
# There is a bug in internal_redirect? when used with explicit (non-example.com) domains.
# This is a simple workaround but may break if we start specing external redirects.
# https://webrat.lighthouseapp.com/projects/10503/tickets/140-current_url-should-be-fully-qualified
class Webrat::Session
alias internal_redirect? redirect?
end
As mentioned in the comment, this may of course break with external redirects, but we have none.