I am building a Ruby on Rails app where researchers can run studies.
I would like a new sample study to be created for a researcher (user) when they first sign up. This is a bit different to a db seed since it will need to dynamically create the study for this particular user, rather than once for the entire database.
I'm using Devise for user accounts.
What's a good approach for this? Thank you!
You can do it by overriding the Devise::RegistrationsController Devise::InvitationsController.
# feel free to call this class whatever you want.
class Users::InvitationsController < Devise::InvitationsController
def create
# the block is yielded after the resource has been saved
# but before anything has been rendered.
super do |user|
if user.valid?
user.studies.create(title: 'New study')
end
end
end
end
And then we need to tell Devise to route to our custom controller:
# config/routes.rb
devise_for :users, controllers: {
registrations: 'users/registrations',
invitations: 'users/invitations'
}
If you want to extract the creation process from the controller you can use a factory method or a service object:
class Study < ActiveRecord::Base
# ...
def self.create_default(**kwargs)
# the default options are merged with the keyword arguments
attrs = {
title: 'Foo',
bar: 'Baz'
}.merge(kwargs)
study = scoped.build(attrs)
# lets us pass a block just like .new and .create
yield study if block_given?
study
end
end
# app/controllers/user/registrations_controller.rb
# ...
def create
# the block is yielded after the resource has been saved
# but before anything has been rendered.
super do |user|
if user.valid?
study = user.studies.create_default(baz: 'Something else')
study.save
end
end
end
config/routes.rb
devise_for :users, :controllers => { :sessions => "sessions" }
app/controllers/sessions_controller.rb
class SessionsController < Devise::SessionsController
before_action :before_login, only: :create
after_action :after_login, only: :create
def before_login
end
def after_login
if current_user.sign_in_count == 1
User.studies.create(title: 'New study')
end
end
end
Related
My goal is simple. I want to allow a user to sign up only if he possesses a unique secret code. I would like to check if this code is valid (being in my secret codes database) and then delete it once the user signed up.
I have a secret_code model with:
:secret_code column
I have a input for the secret code in my sign up form
<%= f.input :secret_code, required: true, autofocus: true %>
Should I personalize the class RegistrationsController < Devise::RegistrationsController?
How ? Thanks !
You can do that by overriding registrationcontroller. Uncomment create method like this.
def create
super
end
and add a filter like:
before_action :check_secret_code, only: [:create]
and you must modify your routes to tell devise use your controller instead of its default:
devise_for :users, controllers: { registrations: "users/registrations" }
you can specify what you want in that check_secret_code method and make render 'new' if code is wrong. Hope it helps.
You can achieve this with a simple before filter. This way you don't have to mess with devise code.
class RegistrationsController < Devise::RegistrationsController
before_filter :check_conditions, only: :create
def create
super
end
private
def check_conditions
#your conditions
end
end
Solved! Along to #Mehmet answer I had to cutomize application_helper.rb
module ApplicationHelper
def resource_name
:user
end
def resource
#user ||= User.new
end
def devise_mapping
#devise_mapping ||= Devise.mappings[:user]
end
end
And put include ApplicationHelper after the class name in registrations_controller.rb
I have a few singular resources in my app, e.g.:
# routes.rb
MySite::Application.routes.draw do
resource :thing
end
# things_controller.rb
class ThingsController < ApplicationController
def edit
load_thing
end
def update
load_thing
if #thing.update_attributes(thing_params)
...
else
...
end
end
private
def load_thing
#thing ||= current_user.thing
end
def thing_params
params.require(:thing).permit(...)
end
end
I'm wondering how to enforce policy scoping using Pundit (before_action :verify_policy_scoped has been set in ApplicationController).
I'm not sure how to form my policy scope for singular resources, i.e.:
# thing_policy.rb
class ThingPolicy < ApplicationPolicy
Scope < Scope
def resolve
# What to do here...
# scope => ?
end
end
end
# things_controller.rb
def load_thing
# ...and what to do here
#thing ||= policy_scope(...)
end
According to Pundit's docs:
...the method resolve...should return some kind of result which can be
iterated over.
However, with singular resources, this iterability clause isn't really valid and there is no AR-style scope as such... just a single record.
Anyone have any suggestions for how to go about this?
Scoping is generally performed on the index action to restrict the base set of records the user can access before filtering is applied. For individual records, you should authorize them instead.
In any case, you are already scoping #thing to current_user
Would suggest to limit before_action :verify_policy_scoped to only: [:index]
I want to create a method that, when called from a controller, will add a nested resource route with a given name that routes to a specific controller. For instance, this...
class Api::V1::FooController < ApplicationController
has_users_route
end
...should be equivalent to...
namespace :api do
namespace :v1 do
resources :foo do
resources :users, controller: 'api_security'
end
end
end
...which would allow them to browse to /api/v1/foo/:foo_id/users and would send requests to the ApiSecurityController. Or would it go to Api::V1::ApiSecurityController? It frankly doesn't matter since they're all in the same namespace. I want to do it this way because I want to avoid having dozens of lines of this:
resources :foo do
resources :users, controller: 'api_security'
end
resources :bar do
resources :users, controller: 'api_security'
end
Using a method is easier to setup and maintain.
I'm fine as far as knowing what to do once the request gets to the controller, but it's the automatic creation of routes that I'm a little unsure of. What's the best way of handling this? The closest I've been able to find is a lot of discussion about engines but that doesn't feel appropriate because this isn't separate functionality that I want to add to my app, it's just dynamic routes that add on to existing resources.
Advice is appreciated!
I ended up building on the blog post suggested by #juanpastas, http://codeconnoisseur.org/ramblings/creating-dynamic-routes-at-runtime-in-rails-4, and tailoring it to my needs. Calling a method from the controllers ended up being a bad way to handle it. I wrote about the whole thing in my blog at http://blog.subvertallmedia.com/2014/10/08/dynamically-adding-nested-resource-routes-in-rails/ but the TL;DR:
# First draft, "just-make-it-work" code
# app/controllers/concerns/user_authorization.rb
module UserAuthorization
extend ActiveSupport::Concern
module ClassMethods
def register_new_resource(controller_name)
AppName::Application.routes.draw do
puts "Adding #{controller_name}"
namespace :api do
namespace :v1 do
resources controller_name.to_sym do
resources :users, controller: 'user_security', param: :given_id
end
end
end
end
end
end
end
# application_controller.rb
include UserAuthorization
# in routes.rb
['resource1', 'resource2', 'resource3'].each { |resource| ApplicationController.register_new_resource(resource) }
# app/controllers/api/v1/user_security_controller.rb
class Api::V1::UserSecurityController < ApplicationController
before_action :authenticate_user!
before_action :target_id
def index
end
def show
end
private
attr_reader :root_resource
def target_id
# to get around `params[:mystery_resource_id_name]`
#target_id ||= get_target_id
end
def get_target_id
#root_resource = request.fullpath.split('/')[3].singularize
params["#{root_resource}_id".to_sym]
end
def target_model
#target_model ||= root_resource.capitalize.constantize
end
def given_id
params[:given_id]
end
end
Using Devise to manage users sessions / registrations I would need to perform specific tasks (updating some fields in the users table for this specific user for example) each time a user signs in, and before he gets redirected by devise to the home page for connected users.
Do I have to override devise SessionsController, and if yes, how?
Alternatively, you can create your own sessions controller
class SessionsController < Devise::SessionsController
def new
super
end
def create
self.resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(resource_name, resource)
if !session[:return_to].blank?
redirect_to session[:return_to]
session[:return_to] = nil
else
respond_with resource, :location => after_sign_in_path_for(resource)
end
end
end
And in routes.rb add:
devise_for :users, controllers: {sessions: "sessions"}
If you look at Devise's implementation of sessions_controller#create, you'll notice that they yield if you pass a block.
So, just subclass their sessions controllers and pass a block when you call super. To do that, first tell Devise in routes.rb that you'd like to use your own sessions controller:
devise_for :users, controllers: { sessions: 'users/sessions' }
And then create a SessionsController class and pass a block when you call super in your create method. It would look something like this:
# app/controllers/users/sessions_controller.rb
class Users::SessionsController < Devise::SessionsController
layout "application"
# POST /login
def create
super do |user|
if user.persisted?
user.update(foo: :bar)
end
end
end
end
Most of the Devise controller methods accept a block, so you could do this for registration, forgot password, etc as well.
Devise provides after_database_authentication callback method.You have full access for the current authenticated user object over there.
If you want to update current user name after every successful login you can do that like below.
class User < ActiveRecord::Base
devise :database_authenticatable
def after_database_authentication
self.update_attributes(:name => "your name goes here")
end
end
configure devise for using your controller changing config/routes.rb devise_for :users, controllers: { ... , sessions: "sessions", ... }
create a app/controllers/sessions_controller.rb or generate it using rails g devise:controllers users -c=sessions
prepend_before_action
class SessionsController < Devise::SessionsController
prepend_before_action :your_task, only: [:create] # Change this to be any actions you want to protect.
private
def your_task
return if your_task_is_OK # if your task is good, return to the super method otherwise render the new
self.resource = resource_class.new sign_in_params
respond_with_navigational(resource) do
flash.now[:alert] = "your notice message"
render :new
end
end
end
I'm going to create a multi user app, so, I will have a admin user that will have permission to create new ones.
I've created the UsersControllerbut when trying to create a new user, being already signed in, I'm getting redirect to root_path with an error message that says "You are already signed in".
So, what should I do to make this possible?
Found.
I have just to removed the registerable module from devise and it works.
In a controller method can't you just go:
def create_user
#user = User.new(:email => params[:email], :password => params[:password])
#user.save
...
end
This is how I am doing it in 2015
# in your terminal
rails g controller Registrations
Registrations controller should look like this,
# registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
skip_before_filter :require_no_authentication, only: [:new]
def new
super
end
end
The important line is the skip_before_filter...
That will disable the requirement that there be no user logged in.
The routes for the controller looks like this,
# routes.rb
devise_for :users,
controllers: {:registrations => "registrations"}
That will tell devise to use your custom registrations controller
Finally, setting up a custom route to that action:
# routes.rb
as :user do
get "/register", to: "registrations#new", as: "register"
end
There is another solution.
You must override registration controller and remove action (or actions) from prepend_before_filter.
DeviseRegistrationController source is here.
You can see:
prepend_before_filter :require_no_authentication, only: [:new, :create, :cancel]
It jumps into require_no_authentication before create method. If you want create new user while you are logged in, just remove :create from array.
I added in the Registrations Controller:
class RegistrationsController < Devise::RegistrationsController
...
skip_before_action :require_no_authentication, only: [:new, :create]
...
end
And it worked for me. I can now go and create a new user.
You could either override the default devise controller and add in your custom logic, or, It would probably be easier however to make a new (Admin) controller and simply create a user using one of it's actions.
#user = User.create!(:name => params[:foo], :email => params[:bar])
redirect_to #user
Devise has loads of guides on how to customise it's behaviour here: https://github.com/plataformatec/devise/wiki/_pages
This one in particular may be of interest to you: https://github.com/plataformatec/devise/wiki/How-To:-Manage-Users-with-an-Admin-Role-(CanCan-method) But make sure to have a look over the rest of the articles, there's a lot of them.
in case someone still looking for help, because it tooks a while for this to work , no clear answers
in your controller
class UsersController < ApplicationController
def new
#user = User.new
end
def add_user
#user = User.new(user_params)
if #user.save!
redirect_to root_path
end
end
private
def user_params
params.require(:user).permit(:email, :password, :password_confirmation)
end
end
in your routes :
get 'employees', to: 'users#new'
post 'employees', to: 'users#add_user'
and finally a form like this:
<%= form_for User.new , :url => {:action => "add_user"} do |user| %>
<%=user.email_field :email%>
<%=user.password_field :password%>
<%=user.password_field :password_confirmation%>
<%=user.submit 'add'%>
<%end%>
#Mourkeer +1
For simple_form 4.2.0 take #Mourkeer code and replace route.rb by:
# route.rb
devise_for :users, path_names: { registration: "registrations" }