Organizing the Controller Directory in Rails (3) - ruby-on-rails

I'm developing an application that is primarily an API gateway. In the expectation that we'll be developing multiple versions of the API over time and with the interest of having backward compatibility, I am looking to have something along the lines of:
http://host.domain.com/apiv1/:token/:module(/:id(:method))
Given this, what I am looking to do is to have a sub-routing system of my own within each API. What I'd like to have in terms of a file structure in the Controller directory is something akin to the following:
/app/controllers/apiv1_controller.rb
/app/controllers/apiv1/module_controller.rb
/app/controllers/apiv1/response_controller.rb
and eventually also have:
/app/controllers/apiv2_controller.rb
/app/controllers/apiv2/module_controller.rb
/app/controllers/apiv2/response_controller.rb
What this breaks down to is that I am unsure how to call methods within the controllers in the subdirectories, using something like:
return Apiv1::ResponseController.index
gives me:
undefined method `index' for Apiv1::ResponseController:Class
Any leads? Does this setup require that I explicitly "require" the requisite file manually?
Pasted Here in response to the question:
routes.rb
AppName::Application.routes.draw do
resources :users
match 'api-v1/:token/:module(/:id(/:method))' => 'apiv1#route'
root :to => "welcome#index"
end
apiv1_controller.rb
class Apiv1Controller < ApplicationController
protect_from_forgery
respond_to :json
def route
Rails.logger.level = 0
logger.info("ROUTE ACTION")
logger.info("params: #{params}")
Apiv1::ResponseController.index(params)
end
end
apiv1/response_controller.rb
class Apiv1::ResponseController < ApplicationController
protect_from_forgery
respond_to :json
def index(params)
Rails.logger.level = 0
logger.info("INDEX ACTION")
result = {
'success' => true,
'controller' => 'response',
'api' => 'v1'
}
render :json => result
end
end

If you are looking for versioning your REST APIs, you can use the restful_route_version gem. It also takes care of inheriting one version from another so that you don't rewrite the same REST APIs for every version.

Related

Rails Take all actions in the controllers in area

In my rails application I add an "api" area with controllers
In the route.rb file
I add the area
namespace :api do
#get "dashboard/sales_rate"
end
The controllers Class:
class Api::DashboardController < Api::ApplicationController
before_filter :authenticate_user!
def user_service
render :json => {"user_id" => current_user.id}
end
The Path is:
app/controllers/api/dashboard_controller
My question is if I can that the route take all action
for example /api/dashboard/user_service
and I will not add for each route row on the route.rb page
/api/{controller_under_api_namespace}/{action}
You can do with some meta programming sprinkle!
Api.constants.each |c|
c.action_methods.each do |action|
get [c.controller_name, action].join('/')
end
end
This method limits only to GET request. You can overcome it with RESTful routing.
Api.constants.each |c|
resources c.controller_name.to_sym
end
Hope, that helps. :)
I try add the code on the route.rb file
and I got this error
This is my file
But before trying to fix this part of code, I want to know if it's can change the performance or the calls to those pages?
If it not good for the performance I leave this option.

Circular dependency detected while autoloading constant Api::V1::VersionsController

I am building a rails API, and I want to see the data in my browser when I use the appropriate url followed by .json or .xml. I am getting the following error:
Circular dependency detected while autoloading constant Api::V1::VersionsController
I set up a versions_controller.rb in the path music-app/app/controllers/api/v1/versions_controller.rb:
class API::V1::VersionsController < ApplicationController
respond_to :json, :xml
def index
#versions = Version.all
respond_with(#versions)
end
def show
#versions = Song.find_by(:id => params[:song_id]).versions
respond_with(#versions)
end
end
and my routes:
namespace :api do
namespace :v1 do
get '/versions' => 'versions#index'
get '/versions/:id' => 'versions#show'
end
end
Am I missing something, and how can this be resolved?
It seems to be the uppercase of API in the controller that is causing your issues.
Try capitalized
class Api::V1::VersionsController < ApplicationController

Rails routes: different domains to different places

I've got a Rails app up running on a server. It's a big project so there are lots of routes involved, and two domains point to the root at the moment. I'd like to somehow design my routes.rb to interpret one domain to take it to a certain part of the app as if it was the root, and use the other for everywhere else.
Something like this (very pseudocode, hope you get the idea):
whole_app.com
whole_app.com/documents
whole_app.com/share
whole_app.com/users
partial_app.com, :points_to => 'whole_app.com/share'
Can Rails handle this? Thank-you!
You can achieve this by overriding default url_options method in application controller. This will override host url for every request.
class ApplicationController < ActionController::Base
....
def default_url_options
if some_condition
{:host => "partial_app.com"}
else
{:host => "whole_app.com"}
end
end
....
end
And for pointing a route to some specific url, you may use:
match "/my_url" => redirect("http://google.com/"), :as => :my_url_path
The better way is to do settings on server to redirect some url to a specific location.
is it going to /share based on some kind of criteria? if so you can do this:
routes.rb
root :to => 'pages#home'
pages_controller.rb
def home
if (some condition is met)
redirect_to this_path
else
render :layout => 'that'
end
end

Ruby on Rails - possible error in naming convention throughout a route

I am making a login route and added this to the routes.rb resources :sign_in
I made a controller like this:
class Mobile::Sign_inController < ApplicationController
layout "mobile/application"
def get
respond_to do |format|
format.html
end
end
def index
respond_to do |format|
format.html
end
end
end
and it seems to get routed correctly, but my view file which is located here:
/app/views/mobile/sign_in.html.haml
which just has 1 line for test purposes:
%strong{:class => "code", :id => "message"} Hello Signin!
But when I go to the url: http://m.cmply.local:8800/signin in the browser, the screen is totally white with nothing rendered in the browser.
Any idea why this happens and how to fix it?
Thanks!
A few problems here:
Your controller name should be SignInsController, not Sign_inController. Consider changing your name to UserSessionsController or similar, since that better reflects the resource it represents. You can still specify an alternate name for the URL (such as sign_in).
Why is your controller namespaced under Mobile? Your routes given don't reflect that, but you don't seem to have provided them all. The route should probably be under a scope:
scope :module => "mobile" do
resource :sign_in
end
Since there is only "one" sign in, it should have its route declared resource :sign_in, and probably even resource :sign_in, :only => [:new, :create, :destroy], depending on what you want. This means that the index action no longer exists, and you probably want to replace it with the new action`.
There is no get action by default for RESTful resources, I'm not sure what you meant it to be, but it should be something else.

Multiple robots.txt for subdomains in rails

I have a site with multiple subdomains and I want the named subdomains robots.txt to be different from the www one.
I tried to use .htaccess, but the FastCGI doesn't look at it.
So, I was trying to set up routes, but it doesn't seem that you can't do a direct rewrite since every routes needs a controller:
map.connect '/robots.txt', :controller => ?, :path => '/robots.www.txt', :conditions => { :subdomain => 'www' }
map.connect '/robots.txt', :controller => ?, :path => '/robots.club.txt'
What would be the best way to approach this problem?
(I am using the request_routing plugin for subdomains)
Actually, you probably want to set a mime type in mime_types.rb and do it in a respond_to block so it doesn't return it as 'text/html':
Mime::Type.register "text/plain", :txt
Then, your routes would look like this:
map.robots '/robots.txt', :controller => 'robots', :action => 'robots'
For rails3:
match '/robots.txt' => 'robots#robots'
and the controller something like this (put the file(s) where ever you like):
class RobotsController < ApplicationController
def robots
subdomain = # get subdomain, escape
robots = File.read(RAILS_ROOT + "/config/robots.#{subdomain}.txt")
respond_to do |format|
format.txt { render :text => robots, :layout => false }
end
end
end
at the risk of overengineering it, I might even be tempted to cache the file read operation...
Oh, yeah, you'll almost certainly have to remove/move the existing 'public/robots.txt' file.
Astute readers will notice that you can easily substitute RAILS_ENV for subdomain...
Why not to use rails built in views?
In your controller add this method:
class StaticPagesController < ApplicationController
def robots
render :layout => false, :content_type => "text/plain", :formats => :txt
end
end
In the view create a file: app/views/static_pages/robots.txt.erb with robots.txt content
In routes.rb place:
get '/robots.txt' => 'static_pages#robots'
Delete the file /public/robots.txt
You can add a specific business logic as needed, but this way we don't read any custom files.
As of Rails 6.0 this has been greatly simplified.
By default, if you use the :plain option, the text is rendered without
using the current layout. If you want Rails to put the text into the
current layout, you need to add the layout: true option and use the
.text.erb extension for the layout file. Source
class RobotsController < ApplicationController
def robots
subdomain = request.subdomain # Whatever logic you need
robots = File.read( "#{Rails.root}/config/robots.#{subdomain}.txt")
render plain: robots
end
end
In routes.rb
get '/robots.txt', to: 'robots#robots'
For Rails 3:
Create a controller RobotsController:
class RobotsController < ApplicationController
#This controller will render the correct 'robots' view depending on your subdomain.
def robots
subdomain = request.subdomain # you should also check for emptyness
render "robots.#{request.subdomain}"
end
end
Create robots views (1 per subdomain):
views/robots/robots.subdomain1.txt
views/robots/robots.subdomain2.txt
etc...
Add a new route in config/routes.rb: (note the :txt format option)
match '/robots.txt' => 'robots#robots', :format => :txt
And of course, you should declare the :txt format in config/initializers/Mime_types.rb:
Mime::Type.register "text/plain", :txt
Hope it helps.
If you can't configure your http server to do this before the request is sent to rails, I would just setup a 'robots' controller that renders a template like:
def show_robot
subdomain = # get subdomain, escape
render :text => open('robots.#{subdomain}.txt').read, :layout => false
end
Depending on what you're trying to accomplish you could also use a single template instead of a bunch of different files.
I liked TA Tyree's solution but it is very Rails 2.x centric so here is what I came up with for Rail 3.1.x
mime_types.rb
Mime::Type.register "text/plain", :txt
By adding the format in the routes you don't have to worry about using a respond_to block in the controller.
routes.rb
match '/robots.txt' => 'robots#robots', :format => "text"
I added a little something extra on this one. The SEO people were complaining about duplicated content both in subdomains and in SSL pages so I created a two robot files one for production and one for not production which is also going to be served with any SSL/HTTPS requests in production.
robots_controller.rb
class RobotsController < ApplicationController
def robots
site = request.host
protocol = request.protocol
(site.eql?("mysite.com") || site.eql?("www.mysite.com")) && protocol.eql?("http://") ? domain = "production" : domain = "nonproduction"
robots = File.read( "#{Rails.root}/config/robots-#{domain}.txt")
render :text => robots, :layout => false
end
end

Resources