How to implement user specific sub domains in rails - ruby-on-rails

I am writing a rails application where users can have their own username based subdomain like GitHub pages => USERNAME.github.io
What I have done is created a controller which parses subdomain from the request and finds the corresponding user.
def show
#user = User.where(name: request.subdomain)
end
now how should I write my route so that I can accept any user specific subdomain and direct it to the above controller

Yes, you can get the subdomain in your routes and direct to your controller.
You will get your subdomain either with a helper or in your routes with regexp. a regexp example is below
get '/', to: 'controller#show', constraints: { subdomain: '/^[a-zA-Z]*/' }
the above example should get a typical subdomain.
you can find some good examples here in the links below. The rails cast has a very good example of routing by subdomain
http://guides.rubyonrails.org/routing.html
http://railscasts.com/episodes/221-subdomains-in-rails-3

Related

What else to namespace when name spacing controllers?

I plan to make an app with different subdomains for different user types. I will namespace controllers for each of my user types. Now I am thinking what else I need to namespace to make it work right?
If I namespace controllers like this:
app/controllers/student/users_controller.erb
app/controllers/student/...
app/controllers/student/...
Then I guess I also need to namespace Views like this:
app/views/student/homeworks/index.html.erb
app/views/student/homeworks/...
app/views/student/homeworks/...
Should I also namespace helpers and my SessionController where I handle user logins? Also, I don't think I should namespace ApplicationController so how should I handle that problem?
Thanks!
Without seeing the rest of your code it is difficult to know exactly what you are trying to achieve but there is a great Railscasts episode, on sub-domains in Rails, that may help you.
Namespacing your code is not essential to get your code working, although it will help you keep your code organized. Below are some additional things to think about, when working with sub-domains:
Routes
...
match '/' => 'users#show', :contraints => { :subdomain => /.+/ }
...
This example uses a constraint to route all subdomains to the users#show action. You can use this technique to differentiate between subdomains, routing them to appropriate controller actions.
Controller
Once you have set up your routes file to route subdomains correctly, you can retrieve the subdomain in any controller via the request:
def show
#subdomain = request.subdomain
end
This will allow you to add sub-domain specific logic to your application.
View
Linking to views with other subdomains is straight forward, simply pass in the subdomain option to your url method:
root_url(subdomain: 'student')
The easiest way is to use genarators
rails g controller 'student/users' new create etc.
or
rails g controller student::users
When you want to add another controller:
rails g controller student::classes
This creates automatically the necessary structure for the controller and the views.
Then add in your routes:
namespace :student do
resources :users
resources :classes
end
You can use route helpers like new_student_user_path
With a non-namespaced controller you'd typically type form_for #user to create a user, with a namespaced:
<%= form_for [:student, #user] do |f| %>
...
Btw if the difference between the users is just their abilities it's better to use a gem like cancan or declarative_authorization to manage authorization.
If you store different information for each type you could make a single user model with a polymorphic relationship to different profiles.
More info:
How to model different users in Rails
Rails App with 3 different types of Users
......................................
Edit
You can set the layout from application_controller. I guess upon signing in you could store the layout in the cookies or the session hash: session[:layout] = 'student'
layout :set_layout
def set_layout
session[:layout] || 'application'
end
Or if you have a current_user method which gets the user you could check its class and choose the layout.
I strongly advise you to reconsider splitting the user class. Different user models will lead to repeated logic and complicated authentication.

Understanding routing with rails

I am trying to make a stupid simple CMS for one of my clients using rails. I generated scaffolding for Page and have been successful at creating and rendering those pages, however, the routing is ugly.
Right now, if I want to view the home page, I get a url like this: example.com/pages/1
I'd like to accomplish 2 things:
How do I set the routing so that example.com automagically grabs the page named "home"
and
How do I set the routing so that example.com/page/page_name performs a
#page = Page.find_by name: 'page_name'
Q1:
How do I set the routing so that example.com automagically grabs the page named "home"
In `routes.rb:
root :to => '[controller]#[action]'
#'pages#home' for example, if your home page is in `pages_controller`
# and uses the `home` action
Q2:
How do I set the routing so that example.com/page/page_name performs a
#page = Page.find_by name: 'page_name'
match '/page/:name', :to => 'pages#some_name', :as => :some_name
this would generate the following in $rake routes:
some_name /page/:name(.:format) pages#some_name
when you link to (or redirect, or otherwise access) this page, you'd write
link_to "click this link!", some_name_path(#SomeModel.some_name)
To accomplish the first thing you need to open the file routes.rb which is in the config folder and add the following:
root to: "home#index"
I'm assuming you have a controller named home which contains a method called index and this is the one that displays the home page.
If you want to make it the same as example.com/pages then you would have to use
root to: "pages#index"
to make a general rule you need to use
root to: "controller#method"
Don't forget to remove the index.html from the public folder too.
I recommend you make the blog application presented here:
http://guides.rubyonrails.org/getting_started.html
It can help you understand more.
Here's a solution that assumes your controller is named pages_controller instead of page_controller.
Add these routes in config/routes.rb:
root to: 'pages#show', page_name: 'home'
get 'pages/:page_name', to: 'pages#show'
For the controller app/controllers/pages_controller.rb:
class PagesController < ApplicationController
def show
#page = Page.find_by(name: params[:page_name])
end
end
Note: In rails4 the find_by dynamic finders have been deprecated... so if you're app is on 4 you should look into updating them. These docs have further details.
Also, if you're just trying to get static looking urls then I would definitely go with Marian Theisen's suggestion and use friendly_id.

Rails controller and routes configuration for website

I have an application in RAILS, it is composed of a set of API, a basic website, and an admin dashboard.
For the API routing I have no problems, as they belong to a model and a controller and are compliant with the RAILS RESTful pattern (a controller for each model, and a method for each HTTP method).
What I'm not comfortable with is writing routes and controllers for website.
The main website is at / so the default route is root :to => "home#index"and I have
Routes for the website pages which look like
get "home/index"
get "map/index"
get "api/index"
get "cgu/index"
get "legal/index"
Which I think it is not good, as I have a controller per view and I need to define a get for each views.
Now for the dashboard I tried a different approach.
It is at /dashboard, the default route is match "dashboard" => "dashboard#index" and here is few pages as an examples
get "dashboard/index"
get "dashboard/users"
get "dashboard/users_stats"
get "dashboard/routes"
get "dashboard/routes_stats"
get "dashboard/charts"
get "dashboard/financial"
So for the dashboard I have a massive dashboard_controller, which contains a def method for each dashboard pages. IE:
#dashboard/users
def users
#users = User.all
respond_to do |format|
format.html {render :layout => 'dashboard'}
end
end
the controller of the dashboard is at /controller but for views and assets I have put it in /folder/dashboard/
Here is 2 questions:
What is the best way to build the home website and dashboard ? Should I have a controller per page or a global controller where I have a method per pages ? (Which I find very convenient, less code).
How should I organize my routes to avoid to set a get "something/something" for each page ?
Or it is normal with RAILS that I have a route defined for each of my page ? I'm fairly new.
EDIT:
To clarify, the dashboard is built around an existing application with API that follow RESTFul Rails pattern:
resources :users
resources :routes
But the dashboard is not tied to any existing resources, it only do stats about those resources.
If you have custom controller action names, then yes, you'll need to define every route. If you use Restful routes, then you can define them easily as
resources :users
which will automatically create routes for actions: index, show, edit, update, create and destroy.
This might be helpful: http://guides.rubyonrails.org/routing.html
For your dashboard, which probably is bringing together a lot of resources, so they'll probably be custom methods. I'd suggest focusing on building your app by individual resource. Then, once you've defined them all, build your dashboard.
I agree with everything that other people have said here. You should definitely try to be more RESTful and create more routes like this:
resources :users
However, there is usually a controller that is not RESTful (usually called pages or static) that serves pages such as Privacy, About Us, etc, etc. For those routes, I usually do this:
['api', 'privacy', 'us'].each do |p|
get p, :controller => 'pages', :action => p
end
check this guide out if you haven't;
I think you haven't embraced the MVC concept of Rails.
For example, say you have "users". You should have users_controller.rb, users.rb (model), /views/users (view) under the app directory.
users_controller contains the index, show, create, etc default actions and your custom actions like stats
user.rb contains instance/static/helper methods
/views/users/ contains templates that corresponds to actions in the controller.

Is there a way to specify a default subdomain in Rail's named routes?

I'm working with subdomains in my Rails 3.2 app where request.subdomain is meaningful and is used to populate an Account resource.
A default behavior I expected of named routes was that regardless of what subdomain I was at, a named route would always use 'www' before hostname unless I used the subdomain option.
Example, if I were at http://www.example.com/path/to/something, the following is true:
root_url # http://www.example.com/
root_url(subdomain: 'subdomain') # http://subdomain.example.com/
Even though it makes sense what I didn't exact was that if I were at http://subdomain.example.com/path/to/something, the following would be the case:
root_url # http://subdomain.example.com/
This is fine, but a lot of my controller redirects and view helper links are for urls outside of my subdomain constraint which omits 'www' and ''. So I really don't want to have new_session_url(subdomain: 'www'), thing_url(subdomain: 'www') and etc. all over my application.
Is there a way to have named routes default to 'www' if no subdomain option is passed in, regardless of what the current subdomain is?
Wrap the routes with the following:
defaults :subdomain => '' do
routes
end

No route matches - after login attempt - even though the route exists?

I am working on a rails application and added a simple login system according to a book.
I created the controller admin:
rails generate controller admin login logout index
It added the following routes to routes.db
get "admin/login"
get "admin/logout"
get "admin/index"
I can got to http://localhost:3000/admin/login there is no problem at all.
But when I try to login I get: No route matches "/admin/login"!
Now, the first confusing part is that the "login" method of my AdminController is not executed at all.
The second confusing part is that this code works like a charm - redirects everything to /admin/login:
def authorize
unless User.find_by_id(session[:user_id])
flash[:notice] = "you need to login"
redirect_to :controller => 'admin', :action => 'login'
end
end
Sidenotes:
I restarted the server several times.
I tried a different browser - to be sure there is no caching problem.
Try
match "/admin/login" => "admin#login"
match "/admin/logout" => "admin#logout"
match "/admin/index" => "admin#index"
(notice the leading /)
As an aside, unless you're creating a login system to learn about Rails and/or authentication, you're probably better off using something like Devise.
Following on from David Sulc's answer:
You're defining the routes as get requests, meaning to go to them you must perform a GET /admin/login request which is basically what happens when you type the URL into your address bar or follow a link that uses it.
However when you try to use these URLs in a form, the form does a POST request and because you've defined all of these as get-only requests, Rails will not be able to find a compatible route.
I definitely agree with David that you should look at an alternative system such as Devise.

Resources