First, an example of what I'm trying to do:
If you go to http://www.meetup.com and you are not signed in, you see a page that shows 'Do something • Learn something...' etc. which is a public page
But when you are logged in, that same page (URL) shows 'Welcome, xxx...Whats happening...' etc. which is specific to you.That is what I'm trying to do in my app as well.
How to go about this in Rails 2.3.8?
So far, I have:
An AboutsController intended to serve up semi-static pages (wish the About wasn't plural!)
Root route is map.root => :controller => "about".
Now, when a non-logged-in-user goes to the http://www.abc.com, he would get the contents of the view about/index. So far so good.
But, when a user is logged in, I want that the products/index view should be displayed for the same URL i.e. http://www.example.com URL (and not http://www.example.com/products)
Is this possible in Rails 2.3.8?
The cleanest way to do this is with something called a before_filter. It's a command at the top of your controller that is called before any action in that controller (that you want). Normally, you'll want to do the same check for more than one action in the controller, so it doesn't make sense to put that check into each action directly.
Let's say you have a Comments controller, and you want the edit, update, and destroy actions to be something only a logged in user can do. This is very common. Let's look at an example. For the sake of brevity I won't spell out all the controller actions, just the unique stuff:
class CommentsController < ApplicationController
before_filter :user_logged_in?, :only => [:edit, :update, :destroy]
# all your actions would go here - index, show, etc #
protected
def user_logged_in?
redirect_to dashboard_path unless current_user
end
end
In the example above, the user_logged_in? method is going to run before the edit, update, or destroy actions are run. If a render or redirect is called inside that method, rails will stop short and never run the action That's why it's called a filter. Instead will honor the render or redirect request given.
The current_user method is a common helper that most user authentication plugins give you, which is usually nil if there is no current user. So our filter method is telling rails to redirect to a certain path, unless the user is logged in.
This is the de facto rails way to handle something like this. Very good question.
It's certainly possible. You can render whichever views you need conditionally like this:
def index
if current_user
render :action => 'products', :controller => 'index'
else
render :action => 'index', :controller => 'about'
end
end
Assuming that you're authenticating with Authlogic, Devise. Whatever logic you use to determine if a user is logged in would go into the conditional.
I've seen a lot of websites handle this with a redirect to, say, /dashboard, to keep their application as clean internally as possible. No need to get too worked up about it still being the root URL, though it's distinctly possible, as the other solutions indicate :)
Related
In my Rails routes.rb file I'm wanting to do something like the following.
get '/:id' => 'pages#show'
get '/:id' => 'articles#show'
So that if a visitor types in
http://www.example.com/about-this-site
The pages controller in the above example would get first shot at handling it. Then if not, the next controller in line would get a shot.
REASONs for wanting to do this:
1) I'm trying to port my Wordpress site over without establishing new urls for all my pages and blog posts. As it stands, all of my blog post files and pages are accessed directly off the root uri '/' folder.
2) Because I'm not able to, it's a learning thing for me. But, I want to do it without a hack.
How about redirecting to the second controller from your first controller?
in PagesController
def show
unless Page.find_by(id: params[:id])
redirect_to controller: :articles, action: :show, id: params[:id]
end
end
in ArticlesController
def show
# Handle whatever logic here...
end
Edit
If you really don't want to redirect then you can consolidate the logic into a single action:
def show
if Page.find_by(id: params[:id])
render :show
elsif Article.find_by(id: params[:id])
render controller: :articles, action: :show
else
# Handle missing case, perhaps a 404?
end
end
However, I'd recommend using a redirect if possible. It's a cleaner solution and keeps your controller code isolated.
Is is possible to force a 301 redirect when someone attempts to browse to a page using the old /:id URL, rather than than the preferred /:friendly_id link?
Apparently such redirections help to tell Google that you have updated the link.. so it stops displaying the old non-friendly link.
With the latest version of friendly_id (5.0.3 at the time of writing this answer) and Rails 4, I do this in the controller:
class ItemsController < ApplicationController
before_action :set_item, only: [:show, :edit, :update, :destroy]
...
private
def set_item
#item = Item.friendly.find(params[:id])
redirect_to action: action_name, id: #item.friendly_id, status: 301 unless #item.friendly_id == params[:id]
end
end
Here's a description of the redirect_to line, broken down piece by piece:
action: action_name retains the action that you're connecting to (which can be show, edit, update, or destroy based on the before_action that's in place) so that if you're accessing /items/1/edit you will be redirected to /items/pretty-url/edit
id: #item.friendly_id ensures that the URL you're being redirected to is the pretty URL
status: 301 sets the redirect to the status of 301, for SEO
unless #item.friendly_id == params[:id] makes sure that we're not redirecting people who access #item through its pretty URL
just defined the redirection inside the routes file
get '/:old_id', to: redirect {|params, req| "/#{X.find(params[:old_id]).friendly_id}" }
While James Chevalier's answer is correct, you can extract this method to the ApplicationController in order to use with any model that uses FriendlyId:
def redirect_resource_if_not_latest_friendly_id(resource)
# This informs search engines with a 301 Moved Permanently status code that
# the show should now be accessed at the new slug. Otherwise FriendlyId
# would make the show accessible at all previous slugs.
if resource.friendly_id != params[:id]
redirect_to resource, status: 301
end
end
As you can see it's also unnecessary to pass a specific action key to redirect_to. Passing a Rails model to redirect_to will automatically attempt to access the show action on the associated collection resource route (assuming it's set up that way). That also means it's unnecessary to pass an id key since FriendlyId always returns the latest slug in the model's #to_param.
Not being a huge fan of unless (confusing semantics) I tend to shy away from it but that's more my personal preference.
Routes
I don't think your routes are the problem here
The problem is the backend handling of the route (I.E whether it uses friendly_id or not). All Google will see is this:
domain.com/users/45
domain.com/users/your_user
If both of those routes work, Google will be happy. I think you're alluding to the idea that if you change the routes to only handle your_user, you'll need to be able to get Google to appreciate the redirects
Redirects
Considering you can handle both id and slug in the backend (we have code for this if you want), I'd handle redirects using the ActionDispatch::Routing::Redirection class:
#config/routes.rb
begin
User.all.each do |u|
begin
get "#{u.id}" => redirect("#{u.slug}")
rescue
end
end
rescue
end
Yes it is possible, you need to define both routes on your config/routes.rb
get 'path/:id' => 'controller#action'
get 'path/:friendly_id' => 'controller#action_2'
then in your legacy action method you need to provide a
return redirect_to controller_action_2_path(friendly_id: friendly_id),
status: :moved_permanently
this will generate a 301 response code. Which will eventually make bots start hitting your new pattern, without losing any of your traffic or indexing (SEO).
I have a really hard time understanding routes and I hope someone can help me.
Here's my custom controller
class SettingsController < ApplicationController
before_filter :authenticate_user!
def edit
#user = current_user
end
def update
#user = User.find(current_user.id)
if #user.update_attributes(params[:user])
# Sign in the user bypassing validation in case his password changed
sign_in #user, :bypass => true
redirect_to root_path
else
render "edit"
end
end
end
and I have the file settings/edit.html.erb and my link
<li><%= link_to('Settings', edit_settings_path) %></li>
The route
get "settings/edit"
doesn't work for this, because then I get
undefined local variable or method `edit_settings_path' for #<#<Class:0x00000001814ad8>:0x00000002b40a80>
What route do I have to give this? I can't figure it out. If I put "/settings/edit" instead of a path it messes up as soon as I'm on a other resource page because the resource name is put BEFORE settings/edit
Thx
Following should do:
get 'settings/edit' => 'settings#edit', :as => :edit_settings
# you can change put to post as you see fit
put 'settings/edit' => 'settings#update'
If you use /settings/edit directly in link, you shouldn't have problem with other resource name being prepended in path. However, without the leading slash, i.e. settings/edit it might have that issue.
Reason why edit_settings_path is not working might be because you didn't declare a named route. You have to use :as option to define by which method you will be generating this path/url.
If you want to explicitly define the route, you would use something like
get 'settings/edit' => 'settings#edit', :as => edit_settings
This statement defines that when a GET request is received for setting/edit, call the SettingsController#edit method, and that views can reference this link using 'edit_settings_path'.
Take some time to read the Rails guide on routing. It explains routing better than any other reference out there.
Also keep in mind the rake routes task, that lists the details of all the routes defined in your application.
How would I set in my rails app so that if a first time user comes to my site www.example.com they see a page that they can sign in but if an already logged in goes to www.example.com it now displays their own posts but still at the same www.example.com url.
Would I do something like render template based if they are logged in or is there some other way to do this?
You can set the users#home to be the root URL:
UsersController:
def home
if logged_in?
#blogs = current_user.blogs
render :action => 'logged_in'
else
render :action => 'non_logged_in'
end
end
Have 2 files in the app/views/users folder:
logged_in.html.erb & non_logged_in.html.erb
A great article was writen by Steve Richert. He is using advanced constraint when defining the route, see here
It depends on how you are making your log in logic
Usually you should have two actions, one for home/login form and another for user logged in home. You can make a before_filter on your application controller, so you can test if the user is logged in or not and then redirect him to home (logged out) if not.
If you are not using your own code or another solution I would like to recommend you this gem called devise, it implements a lot of login logic itself and is easy to change too.
EDIT: I think this solutions is better than the others that were presented and I didn't put the code (although it is quite the same code of the before_filter link), so here it is:
class ApplicationController < ActionController::Base
before_filter :require_login
private
def require_login
unless logged_in?
flash[:error] = "You must be logged in to access this section"
render :controller => 'home', :action => 'not_logged_in'
else
# whatever code you need to load from user
render :controller => 'home', :action => 'logged_in'
end
end
end
This solutions works perfectly because it tests if the user is logged in in every controller/action he tries to access.
redirect_to :controller=>'groups',:action=>'invite'
but I got error because redirect_to send GET method I want to change this method to 'POST' there is no :method option in redirect_to what will I do ? Can I do this without redirect_to.
Edit:
I have this in groups/invite.html.erb
<%= link_to "Send invite", group_members_path(:group_member=>{:user_id=>friendship.friend.id, :group_id=>#group.id,:sender_id=>current_user.id,:status=>"requested"}), :method => :post %>
This link call create action in group_members controller,and after create action performed I want to show groups/invite.html.erb with group_id(I mean after click 'send invite' group_members will be created and then the current page will be shown) like this:
redirect_to :controller=>'groups',:action=>'invite',:group_id=>#group_member.group_id
After redirect_to request this with GET method, it calls show action in group and take invite as id and give this error
Couldn't find Group with ID=invite
My invite action in group
def invite
#friendships = current_user.friendships.find(:all,:conditions=>"status='accepted'")
#requested_friendships=current_user.requested_friendships.find(:all,:conditions=>"status='accepted'")
#group=Group.find(params[:group_id])
end
The solution is I have to redirect this with POST method but I couldn't find a way.
Ugly solution: I solved this problem which I don't prefer. I still wait if you have solution in fair way.
My solution is add route for invite to get rid of 'Couldn't find Group with ID=invite' error.
in routes.rb
map.connect "/invite",:controller=>'groups',:action=>'invite'
in create action
redirect_to "/invite?group_id=#{#group_member.group_id}"
I call this solution in may language 'amele yontemi' in english 'manual worker method' (I think).
The answer is that you cannot do a POST using a redirect_to.
This is because what redirect_to does is just send an HTTP 30x redirect header to the browser which in turn GETs the destination URL, and browsers do only GETs on redirects
It sounds like you are getting tripped up by how Rails routing works. This code:
redirect_to :controller=>'groups',:action=>'invite',:group_id=>#group_member.group_id
creates a URL that looks something like /groups/invite?group_id=1.
Without the mapping in your routes.rb, the Rails router maps this to the show action, not invite. The invite part of the URL is mapped to params[:id] and when it tries to find that record in the database, it fails and you get the message you found.
If you are using RESTful routes, you already have a map.resources line that looks like this:
map.resources :groups
You need to add a custom action for invite:
map.resources :groups, :member => { :invite => :get }
Then change your reference to params[:group_id] in the #invite method to use just params[:id].
I found a semi-workaround that I needed to make this happen in Rails 3. I made a route that would call the method in that controller that requires a post call. A line in "route.rb", such as:
match '/create', :to => "content#create"
It's probably ugly but desperate times call for desperate measures. Just thought I'd share.
The idea is to make a 'redirect' while under the hood you generate a form with method :post.
I was facing the same problem and extracted the solution into the gem repost, so it is doing all that work for you, so no need to create a separate view with the form, just use the provided by gem function redirect_post() on your controller.
class MyController < ActionController::Base
...
def some_action
redirect_post('url', params: {}, options: {})
end
...
end
Should be available on rubygems.