I need to pass a variable to a devise message, devise.registrations.signed_up like :
devise.en.yml:
signed_up: "Welcome to %{my_var}"
app/controllers/users/registrations_controller.rb:
def create
set_flash_message :notice, :signed_up, :app_name => "my app name"
super
end
It gives the error: missing interpolation argument because in the super class the set_flash_message sets the message without the variable.
Is there a way to this with devise?
You might want to hack into the def create of Devise::RegistrationsController directly instead of subclassing it and set the flash message there itself.
So, In a way, instead of calling super you can simply paste in the code from the devise create method and set the flash message to whatever you want there itself.
Related
I am playing around with Devise in a project, and am just trying to better understand how it all works. The Sessions controller in particular is doing a few things that I don't understand:
class Devise::SessionsController < ApplicationController
def new
# What benefit is this providing over just "resource_class.new"?
self.resource = resource_class.new(sign_in_params)
clean_up_passwords(resource)
# What is "serialize_options" doing in the responder?
respond_with(resource, serialize_options(resource))
end
def create
self.resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(resource_name, resource)
respond_with resource, :location => after_sign_in_path_for(resource)
end
...
protected
...
def serialize_options(resource)
methods = resource_class.authentication_keys.dup
methods = methods.keys if methods.is_a?(Hash)
methods << :password if resource.respond_to?(:password)
{ :methods => methods, :only => [:password] }
end
def sign_in_params
devise_parameter_sanitizer.sanitize(:sign_in)
end
end
I assume that these methods are adding some sort of security. I'd just like to know what exactly they are protecting against.
The implementation of devise_parameter_sanitizer is creating a ParameterSanitizer instance. I often find looking at tests to be helpful in understanding the purpose of code; and this test I think illustrates it best-- since you're creating a new user, you don't want to allow users to assign any value they want to any parameter they want, so sanitize means "strip out any attributes other than the ones we need for this action". If this wasn't here, and you had a role attribute, a user could send a specially-crafted POST request to make themselves an admin when signing up for your site. So this protects against what's called, in the general case, a mass assignment vulnerability. Rails 3 and Rails 4 protect against this in different ways but the protections can still be turned off, and I'm guessing Devise is trying to set some good-practice defaults.
The serialize_options method is creating a hash of options to support rendering to XML or JSON. I found this out by looking at the implementation of responds_with, which calls extract_options! which uses the last argument as options if the last argument is a Hash. The documentation for responds_with says "All options given to #respond_with are sent to the underlying responder", so I looked at ActionController::Responder, whose documentation explains the process it takes to look for a template that matches the format, then if that isn't found, calling to_#{format}, then calling to_format. There's a view for HTML, and ActiveRecord objects respond to to_xml and to_json. Those methods use those options:
The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the
attributes included, and work similar to the +attributes+ method.
To include the result of some method calls on the model use <tt>:methods</tt>.
So this keeps you from exposing more information than you might want to if someone uses the XML or JSON format.
I know that you can override the default devise controllers and I did so for the Registrations and Sessions Controller. I know that you can also change the text for the flash messages in devise under locale. However, I am not sure how to change the type of flash message showing for the sessions controller when there is an invalid combination of username and password.
The create method looks like
def create
self.resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(resource_name, resource)
respond_with resource, :location => after_sign_in_path_for(resource)
end
I suspect that the validation is done during the follow call
warden.authenticate!(auth_options)
But this is where I am not sure how to overwrite that in my app.
Also, I think it is a complex override for such a simple use case of changing the color of a flash notice.
Any insights would be much appreciated.
Thanks!
Nick
You can do it with custom failure app. As you can see this flash message is setting right here So you can change it in your custom failure app.
So at first you inherit your failure app from Devise's one:
class CustomFailure < Devise::FailureApp
def recall
env["PATH_INFO"] = attempted_path
flash.now[:error] = i18n_message(:invalid)
self.response = recall_app(warden_options[:recall]).call(env)
end
end
place this file somewhere in your app and say Devise to use it like this (config/initializers/devise.rb):
config.warden do |manager|
manager.failure_app = CustomFailure
end
I'm overriding the devise_invitable controller and in my create method I'd like to pass extra values to the invitations_instructions email template. For example group name, has anyone been successful at this, if so please give me some clues here.
what I've tried...
my #group in my Users::InvitesController < Devise::InvitationsController create method is undefined in the email template.
tried to add :skip_invitation => true in my create and then send the email manually like...
self.resource = resource_class.invite!(params[resource_name], current_inviter, :skip_invitation => true)
::Devise.mailer.invitation_instructions(self.resource).deliver
but this gives the wrong number of arguments so there is something I'm not reading correctly from the documentation.
UPDATE - possible solution
The only way appears to be this, but I'm curious if there is a better way that uses the templates provided and devise mailer
in my /app/controller/users/InvitesController#create
(inherits from InvitationsController)
self.resource = resource_class.invite!(params[resource_name], current_inviter) do |u|
u.skip_invitation = true
end
UserMailer.invitation_instructions(self.resource, current_inviter, #object).deliver
where UserMailer is my general (standard) action mailer and goes something like...
def invitation_instructions(resource, inviter, object)
#resource = resource
#object = object
#inviter = inviter
mail(:to => resource.email, :subject => 'New invitation from ' + inviter.first_name)
end
There is a cleaner way to achieve the solution that you're looking for, and that is to use Devise's own procedures for overriding mailer templates.
First create a custom mailer that extends from Devise::Mailer:
class MyMailer < Devise::Mailer
helper :application # gives access to all helpers defined within `application_helper`.
include Devise::Controllers::UrlHelpers # Optional. eg. `confirmation_url`
default template_path: 'devise/mailer' # to make sure that your mailer uses the devise views
end
Then, in config/initializers/devise.rb, set config.mailer to "MyMailer". This is going to allow you to override ANY email that devise sends out and customize to your liking.
Then for you, you can override invitable_instructions like this:
def invitation_instructions(record, token, opts={})
# Determine a way to set object -- likely through a query of some type
#object = get_object_for(record)
opts[:subject] = 'New invitation from ' + inviter.first_name
super
end
The main sticking point from your example was passing in extra data to set #group/#object. To do that, I would personally go with some type of query within the mailer (not clean, but it is encapsulated and therefore less "magical") to retrieve those objects.
Additionally, if you wanted to use custom email templates instead of devise's, you can simply add them to the app/views/my_mailer/ directory and devise will prefer emails in that directory over emails from the gem.
I'm using devise & devise_invitable in a rails 3 project, and I'm trying to manipulate some of the 'User' object fields in the devise controller.
The action is question is this:
def update
self.resource = resource_class.accept_invitation!(params[resource_name])
resource.first_name = 'Lemmechangethis'
if resource.errors.empty?
set_flash_message :notice, :updated
sign_in(resource_name, resource)
respond_with resource, :location => after_accept_path_for(resource)
else
respond_with_navigational(resource){ render_with_scope :edit }
end
end
I'd have thought that the (commented out) resource.first_name call would influence resource in much the same way as a model - but it doesn't seem to. I'm still getting a 'blank' validation error on this form.
So, the question is, how do I specify values to the User model in devise (and/or devise_invitable) that will actually be subject to verification?
Any suggestions appreciated,
John
resource does return a User models instance. So the resource.first_name = 'Lemmechangethis' statement does change your User models instance but it doesnot trigger your User models validations, which is probably why resource.errors always returns an empty array. One way to trigger the User models validation is to call resource.valid? for example. You can then check the resource.errors array for specific error messages.
I have an object that inherits from ActiveRecord, yet it has an attribute that is not persisted in the DB, like:
class Foo < ActiveRecord::Base
attr_accessor :bar
end
I would like to be able to track changes to 'bar', with methods like 'bar_changed?', as provided by ActiveModel Dirty. The problem is that when I try to implement Dirty on this object, as described in the docs, I'm getting an error as both ActiveRecord and ActiveModel have defined define_attribute_methods, but with different number of parameters, so I'm getting an error when trying to invoke define_attribute_methods [:bar].
I have tried aliasing define_attribute_methods before including ActiveModel::Dirty, but with no luck: I get a not defined method error.
Any ideas on how to deal with this? Of course I could write the required methods manually, but I was wondering if it was possible to do using Rails modules, by extending ActiveModel functionality to attributes not handled by ActiveRecord.
I'm using the attribute_will_change! method and things seem to be working fine.
It's a private method defined in active_model/dirty.rb, but ActiveRecord mixes it in all models.
This is what I ended up implementing in my model class:
def bar
#bar ||= init_bar
end
def bar=(value)
attribute_will_change!('bar') if bar != value
#bar = value
end
def bar_changed?
changed.include?('bar')
end
The init_bar method is just used to initialise the attribute. You may or may not need it.
I didn't need to specify any other method (such as define_attribute_methods) or include any modules.
You do have to reimplement some of the methods yourself, but at least the behaviour will be mostly consistent with ActiveModel.
I admit I haven't tested it thoroughly yet, but so far I've encountered no issues.
ActiveRecord has the #attribute method (source) which once invoked from your class will let ActiveModel::Dirty to create methods such as bar_was, bar_changed?, and many others.
Thus you would have to call attribute :bar within any class that extends from ActiveRecord (or ApplicationRecord for most recent versions of Rails) in order to create those helper methods upon bar.
Edit: Note that this approach should not be mixed with attr_accessor :bar
Edit 2: Another note is that unpersisted attributes defined with attribute (eg attribute :bar, :string) will be blown away on save. If you need attrs to hang around after save (as I did), you actually can (carefully) mix with attr_reader, like so:
attr_reader :bar
attribute :bar, :string
def bar=(val)
super
#bar = val
end
I figured out a solution that worked for me...
Save this file as lib/active_record/nonpersisted_attribute_methods.rb: https://gist.github.com/4600209
Then you can do something like this:
require 'active_record/nonpersisted_attribute_methods'
class Foo < ActiveRecord::Base
include ActiveRecord::NonPersistedAttributeMethods
define_nonpersisted_attribute_methods [:bar]
end
foo = Foo.new
foo.bar = 3
foo.bar_changed? # => true
foo.bar_was # => nil
foo.bar_change # => [nil, 3]
foo.changes[:bar] # => [nil, 3]
However, it looks like we get a warning when we do it this way:
DEPRECATION WARNING: You're trying to create an attribute `bar'. Writing arbitrary attributes on a model is deprecated. Please just use `attr_writer` etc.
So I don't know if this approach will break or be harder in Rails 4...
Write the bar= method yourself and use an instance variable to track changes.
def bar=(value)
#bar_changed = true
#bar = value
end
def bar_changed?
if #bar_changed
#bar_changed = false
return true
else
return false
end
end