Rails Routing global Account id - ruby-on-rails-3

My rails application is based on accounts. So every user belongs to an account, every project and so on.
Currently I got routes like:
/mission-control
/tasks
/projects
And I'm getting the current account by the user. Since a user should be able to have permissions to many accounts I'd like to have the following routes:
/:account_id/mission-control
/:account_id/tasks
/:account_id/projects
I know I could write:
resource :accounts do
resource :tasks
end
but this would end up in e.g.
/accounts/1/tasks
Hope somebody could help me how to write routes for that!

Now I got the correct way:
At first I needed to define the scope like:
scope ":account_id" do
resources :tasks
resources :projects
end
Then, to make eversthing work, cause links within a loop like:
<%= link_to "Project", project %>
won't work, you need to set default url options in the application controller:
def default_url_options(options={})
if #current_account.present?
{ :account_id => #current_account.id }
else
{ :account_id => nil }
end
end
That fixes every No Route Matches Error for me. If there is no :account_id there will be no error, e.g. for that devise stuff.
For #Mohamad:
before_filter :set_current_account
# current account
def set_current_account
# get account by scoped :account_id
if params[:account_id]
#current_account = Account.find(params[:account_id])
return #current_account
end
# dont' raise the exception if we are in that devise stuff
if !devise_controller?
raise "Account not found."
end
end
That devise and error handling could be better tho. :S

You could do a scope like this:
scope ":account_id" do
resources :tasks
resources :projects
end

Related

Devise and Facebook Custom Routing

I am trying to create the user registration views and model on my website but I am having a small issue :
I am using devise and omniauth to get the facebook connect features working and it works,
But I want my facebook users when they sign in the first time to create their password,
That is also working, I redirect them to the filled sign up form and they only have to enter their password. But I want them to go to a second "sign_up form" named /views/registrations/new_facebook.html.erb where they can only enter their password and I will also add some other information,
I created the correct view and tested it but I have no idea how to create the correct routes to bypass Devise default
match '/facebook' => 'registrations#new', :as => 'new_facebook_user_registration'
I believe the issue is with match because that's what's not recognised,
If anyone can help me that would be great thanks,
I added my controller code for omniauth :
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def all
user = User.from_omniauth(request.env["omniauth.auth"])
if user.persisted?
flash[:success] = "Welcome back"
sign_in_and_redirect user
else
session["devise.user_attributes"] = user.attributes
redirect_to new_facebook_user_registration_url
end
end
alias_method :facebook, :all
end
How can I make the redirect_to new_facebook_user_registration_url actually work ?
devise_scope :user do
match "registrations/new_facebook" => "registrations#new_facebook"
end
That's the solution I copied in the registrations controller the new method and named it new_facebook and now everything is working as expected !
I think the issue is that you're not overriding the devise method that redirects to that path. Also according to the devise docs your routes should be set up with a "devise_for" call.
Here's the wiki page describing how to do what you are asking to do, although you may need a bit of custom logic to deal with cases that aren't facebook signups.
https://github.com/plataformatec/devise/wiki/How-To:-Redirect-to-a-specific-page-on-successful-sign-up-(registration)
Some example code from that page:
class RegistrationsController < Devise::RegistrationsController
protected
def after_sign_up_path_for(resource)
'/an/example/path'
end
end
and the one for routes:
devise_for :users, :controllers => { :registrations => "registrations" }

Rails 3 recursive polymorphic comments model, how to do redirect to top-most parent

I'm still earning my stripes in Rails and have ran into a problem I can use some help on. I'm building an app that has various models (stories, photos, artwork, etc.) that a user can comment on, as well as the comments themselves. I have 98-99% of the functionality working but am stuck on getting the redirect to redirect to the top-most parent (a story, photo, etc.) after the comment has been created.
My comment model look like this:
# /app/models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :commentable, :polymorphic => true
has_many :comments, :as => :commentable
end
...I have several models that a user can comment on, for example a story model:
# /app/models/story.rb
class Story < ActiveRecord::Base
has_many :comments, :as => :commentable
end
My comments controller looks like this at this point:
# /app/controllers/comments_controller.rb
class CommentsController < ApplicationController
def index
#commentable = find_commentable
#comments = #commentable.comments
end
def new
#commentable = find_commentable
end
def create
#commentable = find_commentable
#comment = #commentable.comments.build(params[:comment])
if #comment.save
redirect_to :back
else
render :action => 'new'
end
end
protected
def find_commentable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
end
...And of course my routes look like this:
# /config/routes.rb
resources :comments do
resources :comments
end
resources :stories do
resources :comments
end
The particular line I need to change is the redirect_to :back line (everything else, the polymorphism, the recursion of comments, etc. works fine). The current code works as intended when a user is commenting on a story but it's not ideal when a user is commenting on a comment because the form for that functionality is not on the story "show" page (perhaps it needs to be?).
What I have tried to do (and what I suspect the solution might be) is a method that finds the parent object and recurses when that object is a comment. My previous attempts at doing this has not been clean at all and I have yet to get a working prototype working.
I used this railscasts episode to base the majority of my code but the redirect_to :id => nil doesn't work for me as the create comment method is somehow called and it results in a NilClass error when it attempts to build comments (perhaps something is wrong with my routing as I don't see how the index action would call create?).
So Rails experts, what am I doing wrong? What do I need to do here to get this working? I feel like I'm 99% there but that last 1% is driving me crazy.
Thanks in advance...
OK i had to read this a couple times...
#comment.commentable
would return an instance of Story or whatever object that did the comment.
Solved this...It's probably not the cleanest but it works:
I first added a method in comments controller...
def get_master
#parent = #comment.commentable
if #parent.respond_to?('commentable_type')
#comment = #parent
get_master
else
return #parent
end
end
Then I changed my redirect_to to call this method in the create controller.
The key was understanding that #object.respond_to? was what I needed to do check if a method is defined.
Here's a full example of how it works: http://t.co/N6WIGzuW

Ruby on Rails nested resource routing error

I have a user, comment, and route model as shown:
class User < ActiveRecord::Base
has_many :routes, :dependent => :destroy
has_many :comments, :dependent => :destroy
end
class Route < ActiveRecord::Base
belongs_to :user
end
class Comment < ActiveRecord::Base
belongs_to :user
end
I have the routes.rb file nesting comments and routes within user as shown:
MyApp::Application.routes.draw do
resources :users do
resources :comments
resources :routes
end
When I run 'rake routes', the route to the Routes_controller index appears as so:
user_routes GET /users/:user_id/routes(.:format) {:action =>"index", :controller=>"routes"}
Yet for some reason when a user is signing in, I get a routing error saying that the routes controller cannot be found. This happens when the system is posting a new session in the session controller. I know that it attempts to sign in the user, but fails on the redirect. Any suggestions?
class SessionsController < ApplicationController
...
def create
user = User.authenticate(params[:session][:email],
params[:session][:password])
if user.nil?
flash.now[:error] = "Invalid email/password combination."
#title = "Sign in"
render 'new'
else
sign_in user
redirect_to user_routes_path
end
end
...
end
For some reason, the stack trace wasn't displayed when I redirect to user_routes_path, so I have it direct to root_path and the same thing happens. Here is the trace for that:
app/views/layouts/_header.html.erb:3:in
`_app_views_layouts__header_html_erb___917786942_46449696_315190'
app/views/layouts/application.html.erb:11:in
`_app_views_layouts_application_html_erb__423035099_46500948_0'
I will give it a try, after reading Fernandez: The rails 3 way about redirect_to.
When you look at the output from rake routes, you have the output:
user_routes GET /users/:user_id/routes(.:format) {:action =>"index", :controller=>"routes"}
The methods you may use to that route are:
user_routes_url: Full URL (with protocol and everything)
user_routes_path: Relative URL to the host
But your user_routes tells you another thing: the URL has to contain a user_id, and this user_id has to come from somewhere. So to call the different url and path methods, you have to look at the arguments:
users_path: no argument, shows all users
user_path(#user): one argument, because the information about the user is needed. Could be the user, or the user_id
`user_routes_path(#user): needs the user, so that all routes (index view) for one user could be shown.
So include in you source code in the controller:
...
else
sign_in user
redirect_to user_routes_path(user)
end
...
I don't understand the error message you have appended, but I think you should first correct the path call.

rails3 routing and yaml based API, using devise and cancan

I have an application that will have an API, with a /api/v1/ namespace:
namespace :api do
namespace :v1 do
resources :gateways do
resources :mappings do
# maybe more stuff
end
end
end
end
my application uses devise and cancan.
My mappings controller down in app/controllers/api/v1/mappings_controller.rb works correctly from rspec test cases if I leave out :format=>:yaml (asking for HTML, and getting a 406).
If I ask for :yaml, devise seems to think that my test user is not allowed.
My test case is stupid simple:
describe "Agent access to mappings" do
it "gets a list of mappings that includes test_user mapping" do
#test_agent = users(:firewallagent)
sign_in(#test_agent)
get :show, {:gateway_id => 1, :id => 2} #, :format => :yaml
assert_response 200
end
end
I can't see anything in devise/warden which would be format specific, but maybe I've missed it.
The fault was that :format=>:yaml needs to go into the first hash, rather than into the second hash for get. So:
get :show, {:gateway_id => 1, :id => 2, :format => :yaml}

How to redirect page after confirmation in Devise

Say a user clicks a link to a page that is protected. They are then redirected to a sign in screen where they can log in. If they do, then are successfully redirected to that page. But if they don't have an account they have to sign up. This is where things get tricky because I'm doing an email confirmation.
By clicking a link it creates a new session can I can't automatically redirect the user to that protected page. I'm trying to change this by putting in a reference to the redirect inside the confirmation link. I would like to do:
<%= link_to 'Confirm my account', confirmation_url(#resource, :confirmation_token => #resource.confirmation_token, :redirect_to => stored_location_for(#resource)) %>
But I can't figure out how to get access to stored_location_for (or if that is even the right location to get). It is defined in Devise::Controllers::Helpers, but it is an instance method so I can't do Devise::Controllers::Helpers.stored_location_for(…).
How do I get access to stored_location_for or what is the better way of doing this?
My goal is to do that and then in my custom ConfirmationsController define:
def show
if params[:redirect_to]
session["user_return_to"] = params[:redirect_to]
end
super
end
That should work right?
I figured it out. I'm not sure if this changes with the update Devise did yesterday in making Devise::Mailer put most of its functionality into a module. (See the code and ticket for more information).
Basically it boils down to not being able to access the session inside of a mailer view. Therefore you have to pass the redirect as a variable. Devise uses an after_create method on your resource (User in my case) which then sends the confirmation email. This meant I couldn't just pass the session variable directly to the mailer. Thus I feel like this is a pretty nasty work-around in order to get this functionality, but here is the code:
To get the redirect_to variable into the mailer you have to add a variable to the user, thus:
class User < ActiveRecord::Base
…
attr_accessor :return_to
…
end
Then you have to set that variable when you create the user for the first time.
I already had a custom controller setup for registration. (See Devise' Readme on how to set this up, or see #ramc's answer for direction). But it was relatively easy to do this part, I just added it to the parameters and let the rest take care of itself.
class RegistrationsController < Devise::RegistrationsController
def create
params[:user][:return_to] = session[:user_return_to] if session[:user_return_to]
…
super
end
end
Now the user has a variable return_to which is set. We just need to access that in the confirmation_instructions email. I've already rewritten part of confirmation_instructions.html.erb so inside there I just added:
<% if #resource.return_to %>
<%= link_to 'Confirm my account', confirmation_url(#resource, :confirmation_token => #resource.confirmation_token, :redirect_to => #resource.return_to) %>
<% else %>
<%= link_to 'Confirm my account', confirmation_url(#resource, :confirmation_token => #resource.confirmation_token) %>
<% end %>
(For those who are new to this, #resource is the variable Devise uses to define your user).
Now once the user clicks on that link we need to redirect them. #ramc's before filter works well for this:
class ConfirmationsController < Devise::ConfirmationsController
before_filter :set_redirect_location, :only => :show
def set_redirect_location
session[:user_return_to] = params[:redirect_to] if params[:redirect_to]
end
end
That will take care of the case where a new user goes to a protected page then signs up, clicks on the confirmation link and is properly redirected to the protected page.
Now we just need to take care of the case where a user does the above, but instead of clicking on the link, they try to go back to the protected page. In this case they are asked to sign-up/sign-in. They sign-in and then are asked to confirm their email and are given the option of resending the confirmation email. They put in their email and now we need to put the redirect_to variable in that new confirmation email.
To do this we need to modify the ConfirmationController, similarly to how we did the RegistrationController. This time we need to modify the create method. The way it works out of the box is to call a class method on the user called send_confirmation_instructions. We want to rewrite that method so we can pass the return_to variable into it.
class ConfirmationsController < Devise::ConfirmationsController
def create
self.resource = resource_class.send_confirmation_instructions(params[resource_name],session[:user_return_to])
if resource.errors.empty?
set_flash_message(:notice, :send_instructions) if is_navigational_format?
respond_with resource, :location => after_resending_confirmation_instructions_path_for(resource_name)
else
respond_with_navigational(resource){ render_with_scope :new }
end
end
end
The only thing different than what comes with Devise is that first line of create, we pass two variables in. Now we need to rewrite that method:
class User < ActiveRecord::Base
def self.send_confirmation_instructions(attributes={},redirect=nil)
confirmable = find_or_initialize_with_errors(confirmation_keys, attributes, :not_found)
confirmable.return_to = redirect if confirmable.persisted?
confirmable.resend_confirmation_token if confirmable.persisted?
confirmable
end
end
confirmable becomes an instance of User (the current user based on email). So we just need to set return_to.
That's it.
Looking at the way stored_location_for has been implemented in lib/devise/controllers/helpers.rb
def stored_location_for(resource_or_scope)
scope = Devise::Mapping.find_scope!(resource_or_scope)
session.delete("#{scope}_return_to")
end
It is possible to otherwise access it using session['user_return_to']. In your case, you would lose that session object because when the user clicks on the link from the confirmation mail, it might be a new session that is spawned.
You can implement whatever you have suggested as a before filter:
class Users::ConfirmationsController < Devise::ConfirmationsController
before_filter :set_redirect_location, :only => :show
def set_redirect_location
session["user_return_to"] = params[:redirect_to] if params[:redirect_to]
end
end
In addition to this, you will have to modify the route to make devise call your controller instead of its own confirmation controller.
devise_for :users,
:controllers => { :confirmations => 'users/confirmations'}
Hope this helps :)
Note: The code snippets are not complete and only contain relevant details.
From what I can see from the comments in the devise source code, all you need to do is implement the following in your registrations_controller.rb:
def after_inactive_sign_up_path_for(resource_or_scope)
session["user_return_to"]
end