Subdomain based resources in Rails - ruby-on-rails

I am looking for a simple way to replace all my routes matching mydomain.com/resources/xx by xx.mydomain.com with Rails 4.
Would anyone have an idea to do that easily and that would work with nested resources as well?
Thanks, Joris

Constraints
What you're looking for is constraints in your routes, specifically that you're looking to use one to determine whether you have a subdomain that you can access
There are a number of resources on how to achieve this:
Basecamp-style subdomains by DHH
Subdomains Railscast
The bottom line is that you'll probably have to create a custom subdomai constraint, which you can then use the standard routing structure for:
#lib/subdomain.rb
class Subdomain
def self.matches?(request)
if request.subdomain.present? && request.subdomain != 'www'
account = Account.find_by username: request.subdomain
return true if account # -> if account is not found, return false (IE no route)
end
end
end
#config/routes.rb
constraints(Subdomain) do
get "/", to: "controller#action"
resources :posts #-> subdomain.domain.com/posts
...
end
The above is untested - I also found the following with Rails' documentation:
#lib/subdomain.rb
class Subdomain
def initialize
#accounts = Account.all
end
def matches?(request)
if request.subdomain.present? && request.subdomain != 'www'
#accounts.include?(request.subdomain)
end
end
end
#config/routes.rb
constraints: Subdomain.new do
get "/", to: "controller#action"
resources :posts #-> subdomain.domain.com/posts
...
end

Here how I have done it before in a Rails 3 app:
constraints :subdomain => /ambassador/ do
namespace(:influencer, :path => '/') do
root :to => 'home#index'
match 'home' => 'sweepstakes#index', :as => :influencer_home
resources :sweepstakes
resources :associates
resources :widgets
resources :sessions
resources :reports do
resource :member
end
match 'faq' => 'info#faq'
end
end
Be sure to put this block towards the top of the routes.rb file so it takes precedence.
You can of course nest your resources in here like normal.

Related

Custom Routing in Rails 5

I'm having some issues with custom routing. What I'm looking to do is remove the model from the route and dynamically use the record name.
so instead of:
site.com/events/my-event
I would like it to be:
site.com/my-event
I hacked this to work with the below code, only issue is I can't access my admin namespace as it's being treated as an event record (and any other route):
get('/:id', to: redirect do |params, request|
id = request.path.gsub("/", "")
"/events/#{id}"
end)
I know this redirect is not right, I'm just not well versed in routing options. How should this be done properly?
routes.rb
Rails.application.routes.draw do
resources :events, param: :id, path: "" do
get 'login', to: 'sessions#new', as: :login
get 'logout', to: 'sessions#destroy', as: :logout
post 'sessions', to: 'sessions#create', as: :session_create
end
namespace 'admin' do
root "events#index"
resources :sessions, only: [:create]
get 'login', to: 'sessions#new', as: :login
get 'logout', to: 'sessions#destroy', as: :logout
resources :events
end
end
Rails lets you specify what a routing URL should look like:
https://guides.rubyonrails.org/routing.html#translated-paths
You don't need redirect.
You need to match all requests to one controller/action.
get '/(*all)', to: 'home#app'
Inside this action check what is coming in params and render appropriate view.
PS: it will capture all requests, even for not found images, js, etc.
(1) To fix /admin to go to the right path, move the namespace line above the events resources so that it is matched first:
Rails routes are matched in the order they are specified 2.2 CRUD, Verbs, and Actions
# routes.rb
namespace :admin do
root "events#index"
#...
resources :events
end
resources :events, param: :name, path: ""
Use :param and :path to match for site.com/myevent instead of site.com/events/:id
Your events controller:
# events_controller.rb
class EventsController < ApplicationController
# ...
private
def set_event
#event = Event.find_by(name: params[:name])
end
end
Your admin events controller:
# admin/events_controller.rb
class Admin::EventsController < ApplicationController
# ...
private
def set_event
#event = Event.find params[:id]
end
end
TIP: To get a complete list of the available routes use rails routes in your terminal 5.1 Listing Existing Routes

Do not match routes outside of subdomain constraint for requests in that subdomain

From routes.rb:
constraints subdomain: 'admin' do
scope module: 'admin', as: 'admin' do
resources :subscribers
root 'dashboard#index'
end
end
resources :users
root 'dashboard#index'
Under current snippet GET admin.domain.xzy/users still triggers users controller action. I understand that the rules will continue to be parsed until one specifies. Is there a way to modify this behavior? Such that for the subdomain constraint, the router will only search within that block.
Why not put the offending line inside another constraint?
constraints(NoSubdomain) do
resources :users
end
The constraint would look something like this:
class NoSubdomain
def self.matches?(request)
!request.subdomain.present?
end
end

Exclude all other resources from subdomain in Rails

I have a Rails 4.0 app with that allows users to access blogs through subdomains. My routes currently look like this:
match '', to: 'blogs#show', via: [:get, :post], constraints: lambda { |r| r.subdomain.present? && r.subdomain != 'www' }
resources :foobars
Now, when I navigate to somesubdomain.example.com I am indeed taken to the showaction of the blogs controller action, as expected.
When I navigate to example.com/foobars I can access the index action of the foobars controller, as expected.
However, I only get a behavior I do not desire:
When I navigate to somesubdomain.example.com/foobars, I can still access the the index action of foobars controller.
Is there a way to limit or exclude all resources that I do not specifically allow for a particular subdomain (i.e. somesubdomain.example.com/foobars will not work unless otherwise specified).
Thanks!
If you need to define a specific subdomain to exclude from a set of routes you can simply do this (uses negative lookahead regex):
# exclude all subdomains with 'www'
constrain :subdomain => /^(?!www)(\w+)/ do
root to: 'session#new'
resources :foobars
end
Or similarly, to define a specific subdomain to include a set of routes you could do this:
# only for subdomain matching 'somesubdomain'
constrain :subdomain => /^somesubdomain/ do
root to: 'blog#show'
resources :foobars
end
Another approach would be to define the constraint match in a class (or module) and then wrap all routes within constraints block:
class WorldWideWebSubdomainConstraint
def self.matches?(request)
request.subdomain.present? && request.subdomain != 'www'
end
end
App::Application.routes.draw do
# All "www" requests handled here
constraints(WorldWideWebSubdomainConstraint.new) do
root to: 'session#new'
resources :foobars
end
# All non "www" requests handled here
root to: 'blogs#show', via: [:get, :post]
end

Active admin won't accept my route

I've some problems with getting the routes up n' running for my application.
1.
I want to be able to point active admin to http://admin.lvh.me:3000/
I tried using this code, but it only displays the index page.
# config/routes.rb
scope :admin, constraints: { subdomain: "admin" } do
ActiveAdmin.routes(self)
devise_for :admin_users, ActiveAdmin::Devise.config.merge(path: "/")
end
# config/initializers/active_admin.rb
config.default_namespace = :admin
The only url that works is http://admin.lvh.me:3000/admin
Is it possible to avoid /admin?
2.
Each exam in my application has many parts.
I want to add a parts button to each exam using this code.
# app/admin/exams.rb
ActiveAdmin.register Exam do
# ...
index do
column :actions do |exam|
link_to "Part", admin_exam_parts_path(exam)
end
default_actions
end
# ...
end
The problem is that admin_exam_parts_path doesn't exist.
# config/routes.rb
scope :admin, constraints: { subdomain: "admin" } do
resources :exams do
resources :parts
end
ActiveAdmin.routes(self)
devise_for :admin_users, ActiveAdmin::Devise.config.merge(path: "/")
end
rake routes | grep /admin/exams/:exam_id/parts doesn't return anything.
What I'm I doing wrong?
I'm running
active admin 0.5.1
rails 3.2.12
ruby 1.9.3
For your first problem i have this on my routes.rb
root to: "admin/dashboard#index"
After resources and before devise_for, and works very fine!.
Good luck!
"admin" is the default namespace for all resources.
you can turn off a resources namespace explicitly in the registration block like this:
ActiveAdmin.register Exam, :namespace => false do
...
end
as far as you has_many: :parts association, can you post the code for your registration block for Parts? There are several ways I might set up this relationship depending on what I was trying to accomplish. So the more context you can give me, the better

Rails routes for an API documentation

I'm currently writing the documentation for the API of my website.
I'm not sure about the "best" way to write the routes. I think twitter does a good job and I'd like to copy their url structure:
https://dev.twitter.com/docs/api
https://dev.twitter.com/docs/api/1/get/statuses/show/:id
https://dev.twitter.com/docs/api/1/post/statuses/retweet/:id
It would be something like:
namespace :docs do
resources :api do
# and then... not sure
end
end
Not sure on how to write the routes for this part: /get/statuses/show/:id.
Should I just create a custom route?
match "/:verb/:resource/:action/:params" => "api#resource"
Or is there a better way?
What I ended up with, might help someone :)
Ibarcraft::Application.routes.draw do
def api_versions; [:v1] end
def api_verbs; [ :index, :show ] end
constraints subdomain: "api" do
scope module: "api", as: "api" do
versions = api_versions
versions.each do |version|
namespace version, defaults: { format: "json" } do
# all my routes
resources :barcrafts, only: api_verbs do
collection do
get :search
end
scope module: "barcraft" do
resources :users, only: [:index]
end
end
# and more...
end
end
match 'v:api/*path', to: redirect { |params, request| "/#{versions.last}/#{params[:path]}" + (params[:format] ? ".#{params[:format]}" : "") }
match '*path', to: redirect { |params, request| "/#{versions.last}/#{params[:path]}" + (params[:format] ? ".#{params[:format]}" : "") }
end
end
end
Yes you need custom route.
But note, that :action key is reserved for controller's action name, better use something like :action_name

Resources