Currently users can access their "profile" through many paths.
localhost:3000/users/current_user
localhost:3000/users/current
localhost:3000/users/id#
How can I make it that they can only get to their "profile" through localhost:3000/users/current_user
One suggestion on the 'what' of your question: instead of the ideal url being localhost:3000/users/current_user I suggest localhost:3000/user or something even more descriptive such as localhost:3000/profile or localhost:3000/account.
Could you include the entries in your routes.rb? Even if Authlogic, etc. add routes to your app, they should do it in routes.rb. If you have the entry:
map.resource :users
then that's where the /users/123 route is coming from. I agree with Matchu that even if you don't use /users/123 you should keep it and route other requests to it.
An Additional Idea
If you don't want to get into the (kinda complicated, and not pretty) business of preserving model validation errors across redirects, here's another way. I'm assuming from here that you already have map.resource :users, so that you have the 7 default actions on your UsersController (index, new, create, show, edit, update, destroy).
In your routes.rb:
map.profile 'profile', :controller => 'users', :action => 'show'
map.edit_profile 'profile/edit', :controller => 'users', :action => 'edit', :conditions => { :method => :get }
map.update_profile 'profile/edit', :controller => 'users', :action => 'update', :conditions => { :method => :put }
You will need to update your form_for tag slightly:
<% form_for #user, :url => update_profile_path do |f| %> ...
Now, assuming you start on /profile, and click an edit link that takes you to /profile/edit (should show the form), if you fill out the form such that it fails validation then you should end up back on /profile/edit with the correct errors in the f.error_messages output.
Your controller code for edit should stay the same, and your code for update should be:
def update
#user = current_user || User.find(params[:id])
if #user.update_attributes(params[:user])
flash[:notice] = "Successfully updated user."
redirect_to #user
else
render :action => 'edit'
end
end
The render (rather than a redirect) preserves the state of the #user model (including errors) and just renders the edit template again. Since you directed it at the update_profile_path the url viewed by the user is still /profile/edit.
Umm, first, remove the /users/current route that you must have in your routes.rb somewhere. (Although I prefer /users/current to /users/current_users, since the latter is rather redundant.)
As for /users/123, in your controller, you can check if the current user's ID matches 123 or whatever, and, if so, redirect.
But I really prefer the opposite effect. Pushing /users/current to /users/123 makes more sense in my brain, since it keeps the routes consistent for all users while still allowing you to cache links to /users/current.
Related
I'm very new to rails, so please forgive my limited knowledge.
I have a controller called users. It has two methods: new and create.
When users#new is called, a form is shown to sign up for an account on my site. I have set up a route for this which makes the URL /signup, like so:
match "signup" => "users#new", :as => "signup"
When the user navigates to /signup, I create a new user instance variable and show them the form, like so:
UsersController
def new
#user = User.new
end
New View
<%= form_for #user do |f| %>
<!-- Form code here... -->
<!-- Then at the end: -->
<%= f.submit :value => 'Sign Up' %>
<% end %>
When the user submits this form, it sends the data to users#create.
My code for users#create in UsersController looks like so:
def create
#user = User.new(params[:user])
if #user.save
redirect_to root_url, :notice => 'Signed Up!'
else
render "new"
end
end
The if/else statement is to check if rails was able to create my new user or not. If it was, it redirects to the index no problem.
If it wasn't able to create the user, it renders my new view, and it displays the errors fine.
But, the URL it then gives to us is /users, because when it submits the form it submits to /users. How can I get it so if the signup fails, it will redirect to /signup, and still show the errors that occured?
UPDATE: routes.rb
Flightdb::Application.routes.draw do
get "users/new"
get "home/about"
get "home/index"
root :to => 'home#index'
match 'about' => 'home#about'
match "signup" => "users#new", :as => "signup"
resources :users
end
Well the answer you don't want to hear is that this usually isn't done. The semantics of the URLs aren't ideal either way. /new implies a fresh new form... but a form with errors is sort of a "partially created" user. The user will never need to use the URL in either case, so no functionality is lost.
Also, consider putting registration and authentication actions on an 'account' (singular) resource. the 'users' controller/resource should probably only be for a backend admin interface. if there are public proiles per user, put them on a 'profiles' resource. put the user's dashboard on a 'dashboard' controller (not a resource).
I've come across the very same issue today and based on Alex's and Marian's comments, I ended up with these changes:
1) in form view:
<%= form_for #user, url: signup_path do |f| %>
2) in routes.rb:
get "signup" => "users#new", :as => "signup"
post "signup" => "users#create"
resources :users
root :to => "home#index"
I'm a newbie in RoR so I'd welcome comments if there are any side-effect or issues. Or if there is some better way.
the route is indeed corrected this way, but the context of the corresponding controller action "users#new", such as variables that have been initialized by the action, is lost.
So we end up in a kind of unstable situation where we're neither in the "new" context nor outside of it...
How to control that context is the question ? Maybe through ActiveModel::Validator
I'm not sure where and how to alter this behaviour...
When I am on the index page and click on the delete link to destroy the post i get that error:
Unknown action
No action responded to delete. Actions: add, edit, and index
The edit link next to delete works with out a problem I do not understand why delete won't work. This is what is in my controller car_controller.rb
def delete
#car = Car.find(params[:id])
flash[:notice] = "Question #{#car.name} deleted!"
#car.destroy
redirect_to :controller => :car, :action => :index
end
map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'
map.root :controller => "main"
map.root :controller => "car"
end
Isn't the action for delete actually destroy?
If you look at a controller that was generated as part of scaffold, you should see what the delete action maps to...
Rails provides the 7 classical RESTful actions out of the box when you generate the controller and each one has the actual URL + method commented above the action method...
# DELETE /subject_families/1
# DELETE /subject_families/1.xml
def destroy
...
end
I hope this helps...
Make sure your request is using the correct HTTP verb. Rails' default for REST resources is to use HTTP DELETE on the destroy action. Are you using GET (a simple link) or POST instead of DELETE?
I recommend checking your routes - run rake routes - to confirm what Rails is expecting. If you are using Rails' resources route generator, then I would expect your view template to contain something like this:
<%= form_for #car, :html => { :method => :delete } do |f| %>
<%= submit_tag 'Delete Car' %>
<% end %>
I am new to rails. My rails version is 2.3.5. I found usage like:
In controller, a destroy method is defined and in view, you can use :action => "delete" to fire that method. Isn't the action name has to be the same as the method name? Why delete is mapped to destroy?
Again, in my controller, I define a method called destroy to delete a record. In a view, I have <%= link_to "remove", :action => 'destroy', :id => myrecord %>. But it never works in practice. Every time I press the remove link, it redirects me to the show view, showing the record's content. I am pretty sure that my destroy method is:
def destroy
#myobject = MyObject.find(params[:id])
#myobject.destroy
#redirect_to :action = 'index'
end
If I change the method name from destroy to something like remove_me and change the action name to remove_me in the view, everything works as expected.
In the above two weird problems, I am sure there is no tricky routing set in my configuration.
All in all, seems the destroy and delete are mysterious keywords in rails. Can anyone explain this to me?
You probably set MyObject as a resource in routes.rb. Resources get a couple of routes that don't directly match the name of the action. When you use an action name that does not match the routes defined by the resource, you'll get the default route which directly maps to the name of the action.
I found that this link explains rails' routing very well. Take a look at the "RESTful routing" section.
If you are using REST routing, destory only support delete method. you can change your code like this
link_to "remove", :action => 'destroy', :id => myrecord", :method => :delete
Adding :method => :delete
rails will add a hidden input with name "_method", value "delete"
Replace all :post => true with :method => :post
Let's say I have a Ruby on Rails blogging application with a Post model. By default you would be able to read posts by http://.../post/id. I've added a route
map.connect ':title', :controller => 'posts', :action => 'show'
that will accept http://.../title (titles are unique) and the controller will do a query for the title and display the page. However when I am now calling <%= link_to h(post.title), post %> in a view, Rails still gives me links of the type post/id.
Is it possible to get Rails to automatically create the pretty links for me in this case?
If you are willing to accept: http:/.../1234-title-text you can just do:
def to_param
[id, title.parameterize].join("-")
end
AR::Base.find ignores the bit after the id, so it "just works".
To make the /title go away, try naming your route:
map.post ':id', :controller => 'posts', :action => 'show', :conditions => {:id => /[0-9]+-.*/ }
Ensure this route appears after any map.resources :posts call.
You can override ActiveRecord's to_param method and make it return the title. By doing so, you don't need to make its own route for it. Just remember to URL encode it.
What might be a better solution is to take a look at what The Ruby Toolbox has to offer when it comes to permalinks. I think using one of these will be better than to fixing it yourself via to_param.
I would use a permalink database column, a route, and I normally skip using link_to in favor of faster html anchor tags.
Setting your route like:
map.connect '/post/:permalink', :controller => 'post', :action => 'show'
then in posts_controller's show:
link = params[:permalink]
#post = Post.find_by_permalink(link)
You link would then be
Link
then in your create method, before save, for generating the permalink
#post = Post.new(params[:post])
#post.permalink = #post.subject.parameterize
if #post.save
#ect
There is a Gem for you to get this done perfectly
https://github.com/rsl/stringex
Okay, these two related questions are in reference to Railscast #21:
I'm having some trouble with routes. Two issues:
1) The routes in the tutorial seem to be relative to the root of the application; I want them to be relative to the root of the model. So
"http://example.com/login" I need to be "http://example.com/model/login" (and vice versa for logout).
I'm using permalinks to refer to my records, and I don't know how to specify an override, because every time I try to use "http://example.com/model/login" I get an error that says it can't find the record "login". How can I override this for login/logout?
2) Going to a custom route for me doesn't seem to keep the custom route in my address bar. So going to "http://example.com/login" gets me to the right page, but the browser now says "http://example.com/session/new" in the address bar. In the tutorial this doesn't happen: the app serves the correct page and keeps the custom route in the address bar. How can I get this to happen for me?
## Sessions Controller
class SessionController < ApplicationController
def create
session[:password] = params[:password]
flash[:notice] = "Successfully Logged In"
redirect_to :controller => 'brokers', :action => 'index'
end
def destroy
reset_session
flash[:notice] = "Successfully Logged Out"
redirect_to login_path
end
end
## Routes
ActionController::Routing::Routes.draw do |map|
map.resources :brokers, :session
map.login 'login', :controller => 'session', :action => 'create'
map.logout 'logout', :controller => 'session', :action => 'destroy'
map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'
end
What do you mean with "root of the model"? You should make routes to controllers and their actions. The controller should communicate with the model.
The error messages can be a bit confusing, but as far as I can see, this url:
http://example.com/model/login
would call the action called login in the controller called model with an empty id (wich probably doesn't exist), using this route:
map.connect ':controller/:action/:id'
If you want to have a "subfolder" in your route you can use namespaces, I don't remember every detail about it but you can find a lot of info about it here. A word of warning: namespaces makes the route debugging a lot harder, I've had a lot of fun figuring out wich route is really used. I ended up creating many very specific routes to be sure the correct one was used.