Rails Gateway endpoints loaded from yml/env file - ruby-on-rails

I have been trying to implement the Rails API Gateway concept and I have been struggling with a way to define routes without too much of a hustle.
My current implementation looks something like this:
module ServicesEndpoints
class StackOverflow
def self.check_comment(id)
begin
RestClient.get endpoint + comment_path(id)
rescue => e
#if the server is not up or errors occur from our point of view there is no data about the driver
raise my_error
end
end
private
def self.endpoint
ENV['STACKOVERFLOW_SERVICE_ADDRESS']
end
def self.comment_path(id)
"/comments/#{id}"
end
end
end
However this means that everytime i have to add an endpoint i need 1 class and for every route 1 action. Thinking about this I was wondering if you couldn't do this in an yml/.env file and just load them. However even if I know how to put the endpoint in the .env file routes with params are a little tricky(I have no good idea on how to do it).
Has someone tackled this problem and if so how did you go about to solve it?

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.

How would one add headers to requests in Her?

I'm using Her to speak to an API I made so that I could retrieve information from it. I need an Authorization token passed in through the headers. How would I do this? The documentation doesn't show a solution anywhere, but it seems like a very much needed utility.
#sethetter's comment basically covered it, but since I recently did something similar, I thought I would include some code, which someone might find useful in future. So, to do this, you would create a request middleware, which adds the desired headers in the call method. I had to do something similar, and the result looked like this:
class Authentication < Faraday::Middleware
def call(env)
env[:request_headers]['auth-token'] = generate_token(env)
#app.call(env)
end
def generate_token(env)
# generate the token based on the request, if needed
end
end
And then, when you construct the API stack:
require 'authentication' #if you've defined it in a different file
#api = Her::API.new
#api.setup(:url => my_api_base_url) do |c|
c.use Authentication
# other middlewares as usual
end

Read only mode for Ruby on Rails application

I have an interactive Ruby on Rails application which I would like to put into a "read only mode" during certain times. This will allow users to read the data they need but block them from actions that write to the database.
One way to do this would be to have a true/false variable located in the database that was checked before any write was made.
My question. Is there a more elegant solution for this problem out there?
If you really want to prevent any database write, the easiest way I can imagine would be to override the readonly? method of a model to always return true, either in selected models or maybe even for all ActiveRecord models. If a model is set to readonly (normally done by calling #readonly! on it), any try to save the record will raise an ActiveRecord::ReadOnlyRecord error.
module ActiveRecord
class Base
def readonly?
true
end
end
end
(actually untested code, but you get the idea…)
Zargony's solution seems to be the best one, but I would like to add to it a bit.
So, about his code:
This works nicely. A good solution is to add this in an initializer and run this code only if an env var is set, so that you can choose whether to run the app in read-only mode on launching the app.
if ENV['READ_ONLY'] == 'true'
module ActiveRecord
class Base
def readonly?
true
end
end
end
end
And then run the server from command prompt like READ_ONLY=true bin/rails s. Also, adding
rescue_from ActiveRecord::ReadOnlyRecord, with: ->() {
flash[:alert] = "The site is running in read-only mode. We are going to return to full operation soon. Thank you for your patience!"
redirect_to root_path
}
to the ApplicationController (that all of your controllers should inherit from) is a nice way to show the users what is going on.
Another good one which I liked a little better is Declarative Authorization
which is covered by Railscasts as well: Railscasts - Declarative Authorization
Permissions plugin? Something simple like cancan where you define what a user can do, when. It will allow you to display links, or not, and restrict access to controller actions. The railscast will explain better than I can.
http://github.com/ryanb/cancan
http://railscasts.com/episodes/192-authorization-with-cancan
The answer by Zargony will work well but raise an exception if your application is trying to write anything. If you want your application to fail silently on writes so that it doesn't show error pages on each operation (e.g. if you update a timestamp on login in your code, you will get an exception), you can use the approach below:
unless Rails.env.test?
class ActiveRecord::Base
before_save do
raise ActiveRecord::Rollback, "Read-only"
end
before_destroy do
raise ActiveRecord::Rollback, "Read-only"
end
end
end

Include params/request information in Rails logger?

I'm trying to get some more information into my Rails logs, specifically the requested URI or current params, if available (and I appreciate that they won't always be). However I just don't seem able to. Here's what I've done so far:
#config/environments/production.rb
config.logger = Logger.new(config.log_path)
config.log_level = :error
config.logger.level = Logger::ERROR
#config/environment.rb
class Logger
def format_message(level, time, progname, msg)
"**********************************************************************\n#{level} #{time.to_s(:db)} -- #{msg}\n"
end
end
So I can customize the message fine, yet I don't seem to be able to access the params/request variables here. Does anyone know if this is possible, and if so how? Or if there's a better way to get this information? (Perhaps even something Redis based?)
Thanks loads,
Dan
(Responding a long time after this was asked, but maybe it will help the next person.)
I just did something similar.
1) you need to override your logger separately from logging request-leve details. Looks like you've figured customizing your logger out. Answer is here:
Rails logger format string configuration
2) I log the request and response of all requests into my service. Note, that Rails puts a tonne of stuff into the headers, so just straight dumping the request or the headers is probably a bad idea. Also of note, my application is primarily accessed via an API. If yours is primarily a web-app, as I'm guessing most people's are, you probably don't want to inspect the response.body as it will contain your html.
class ApplicationController < ActionController::Base
around_filter :global_request_logging
...
def global_request_logging
http_request_header_keys = request.headers.keys.select{|header_name| header_name.match("^HTTP.*")}
http_request_headers = request.headers.select{|header_name, header_value| http_request_header_keys.index(header_name)}
logger.info "Received #{request.method.inspect} to #{request.url.inspect} from #{request.remote_ip.inspect}. Processing with headers #{http_request_headers.inspect} and params #{params.inspect}"
begin
yield
ensure
logger.info "Responding with #{response.status.inspect} => #{response.body.inspect}"
end
end
end
This should work! :) cheers.
logger.info({:user_agent =>
request.user_agent, :remote_ip =>
request.remote_ip}.inspect)
logger.info(params.inspect)
By the by.. This should be placed in your controllers action. Ex: If you place it in your create action it should also log the user_agent i.e the browser, remote_ip i.e the remote ip of the user and all the params.
you should look in the request class
like puts request.uri.
check here for more detail http://api.rubyonrails.org/classes/ActionController/AbstractRequest.html

Encrypting Parameters across controllers

I need to pass a parameter from one method in a controller to another.
From my understanding I have to pass the parameters as a GET exposing it in the url string.
What is the best way to encrypt the data so no one can see what is actually getting passed in the string? Also, is there a way to pass it via POST or is my original understanding correct?
I haven't used RoR, but in the web world, this problem is solved with sessions. Using sessions you can store the parameters on the server and avoid sending sensitive data with GET or POST (both are insecure).
The Ruby on Rails Security Guide looks like a great read related to this.
I suggest you abstract your code into lib/ so that you don't have to call additional methods. Instead of making a new HTTP request, just put the code in a central place and call it from there.
class MyController < ApplicationController
def index
MyLibrary::Thing.do_stuff
end
def show
MyLibrary::Thing.do_stuff
end
end
# lib/my_library/thing.rb
module MyLibrary
module Thing
def self.do_stuff
# do stuff!
end
end
end
That way you can access the same code in multiple actions, without doing extra HTTP requests.

Resources