I'm using the gems pundit and devise. I have a delete link that only shows up if you are an admin. I have an integration test that I would like to verify that the delete link only shows up for admins.
test 'comment delete link shows when it should' do
log_in_as #admin
get movie_path(#movie)
assert_select 'a[href=?]', movie_comment_path(comments(:one), #movie.id)
end
My test_helper.rb looks like this:
...
class ActiveSupport::TestCase
...
def log_in_as(user, options = {})
password = options[:password] || 'password'
if integration_test?
post user_session_path, 'user[email]' => user.email, 'user[password]' => user.password
else
Devise::TestHelpers.sign_in user
end
end
private
# Returns true inside an integration test.
def integration_test?
defined?(post_via_redirect)
end
end
The response.body looks all right, but indeed there is no delete link. There is one when I run the development server and visit the page myself. I've narrowed this down to the current_user that pundit uses in the policies is being passed in with a value of nil. This is my comment_policy.rb:
class CommentPolicy
attr_reader :current_user, :comment
def initialize(current_user, model)
#current_user = current_user
#comment = model
end
def create?
if #current_user
#current_user.member? or #current_user.content_creator? or #current_user.moderator? or #current_user.admin?
end
end
def destroy?
if #current_user
#current_user == #comment.user or #current_user.moderator? or #current_user.admin?
end
end
end
As a closing remark, I've heard that Rails 5 has opted for integration tests instead of controller tests as we know them from Rails 4 for the default type of tests to be generated for our controllers. If this is the case, devise would be a heck of a lot more useful out of the box when using Rails 5 if the sign_in/sign_out helpers that work in controller tests were made to work in integration tests as well. But would I still have this issue of pundit not knowing what current_user is? I'm assuming this all works fine in controller tests because the current_user is scoped to controllers? Any and all light shed on this topic is much appreciated, but I would really like to figure out how to get integration tests to work with this setup because I have about a billion I want to write right now.
Not that it totally matters, but does it need to be using current_user in the policy or can it just use user in the policy. By this I mean according to the elabs/pundit README on Github I would just use #user and user everywhere instead of current_user. Read the README if I confused you.
Additionally the nil for current_user typically occurs when you don't have a valid CSRF token for your request. When you do this on the website manually by going to localhost:3000 or w/e you are first performing a get on the login path before doing the post on the login path with your credentials. In your integration test I don't seem to see where you are performing that get in order to get the CSRF for your session.
Hope this helps!!!
Related
I'm new to Rails and I'm working through Michael Hartl's excellent Rails Tutorial for a second time, this time I'm trying to adapt the chapter 11 and chapter 12 microposts to a simple Devise/Pundit application I'm working on. I am able to create microposts through the seed file and display them, but I'm getting an authorization error with Pundit when I actually try to create a new post through the site. The error I'm getting is:
Pundit::AuthorizationNotPerformedError in MicropostsController#create
My Microposts Controller looks like this:
class MicropostsController < ApplicationController
before_action :authenticate_user!
after_action :verify_authorized
def create
#micropost = current_user.microposts.build(micropost_params)
if #micropost.save
flash[:success] = "Micropost created!"
redirect_to current_user
else
#feed_items = []
flash[:danger] = "Unable to create micropost!"
end
end
def destroy
end
private
def micropost_params
params.require(:micropost).permit(:content)
end
end
I'm thinking that do not have the authorization set up properly for the 'create' action, but I'm not sure exactly how it should be set. I do not have a policy for Pundit for Microposts. I tried adding a simple one but it didn't change anything. I'm learning to put all these pieces together, would someone point me in the right direction?
There is one after action filter verify_authorized because of which you are getting this error. If you have created a policy for the create action then use that to get rid of the error.
So I am trying to override Devise::RegistrationsController which they do have wiki for and tons of tutorial out there. The one thing that I can not find is the best implementation of how to override the controller whilst implementing the require admin approval feature as well.
I think I got the hang of it but before I go any further (from all the reading on the Devise's source code) I want to know, on the registrations controller there's a line that does:
resource.active_for_authentication?
However, on the Sessions controller it's just this:
def create
self.resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in) if is_flashing_format?
sign_in(resource_name, resource)
yield resource if block_given?
respond_with resource, location: after_sign_in_path_for(resource)
end
What I want to know is, if it's not confirmed or the active_for_authentication returns false, where or how does the session controller check this? I tried tracing back the source code but no luck.
So anyone who's very familiar with Devise perhaps you could answer my question? Thank you.
After authenticating a user and in each request, Devise checks if your model is active by calling model.active_for_authentication?. This method is overwritten by other devise modules. For instance, :confirmable overwrites .active_for_authentication? to only return true if your model was confirmed.
You can overwrite this method yourself, but if you do, don't forget to call super:
def active_for_authentication?
super && special_condition_is_valid?
end
Whenever active_for_authentication? returns false, Devise asks the reason why your model is inactive using the inactive_message method. You can overwrite it as well:
def inactive_message
special_condition_is_valid? ? super : :special_condition_is_not_valid
end
Thanks in advance!
Sidekiq is working just fine, but I cannot manage to test it with Devise Async, or should I say that I cannot test the latter?
According to Sidekiq's documentation, when test mode is set to fake!, any job given to a worker is pushed to an array named jobs of that same worker. So it is trivial to test the increase of this array.
But, with Devise Async, it is not so trivial, although its backend includes Sidekiq::Worker. Here's a small list of things that I tried to test:
Devise::Async::Backend::Sidekiq.jobs
Devise::Mailer.deliveries
ActionMailer::Base.deliveries
Devise::Async::Backend::Worker.jobs
None of these testing subjects points an increase in size. Since Devise sends its emails as models callbacks, I tried testing both in a model and in a controller spec. Using Factory Girl and Database Cleaner, I also tried both modes: transaction and truncation. Needless to say that I also tried both modes of Sidekiq: fake! and inline!.
What am I missing?
As mentioned in the documentation, you can check the queue size as
Sidekiq::Extensions::DelayedMailer.jobs.size
Was working on this issue, chanced upon a beautiful implementation done by gitlab which I thought might be helpful in testing devise-async or email that are push via the sidekiq queue.
spec_helper.rb
email_helpers.rb
By adding these lines in spec_helper.rb
# An inline mode that runs the job immediately instead of enqueuing it
require 'sidekiq/testing/inline'
# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
RSpec.configure do |config|
config.include EmailHelpers
# other configurations line
end
And adding /spec/support/email_helpers.rb
module EmailHelpers
def sent_to_user?(user)
ActionMailer::Base.deliveries.map(&:to).flatten.count(user.email) == 1
end
def should_email(user)
expect(sent_to_user?(user)).to be_truthy
end
def should_not_email(user)
expect(sent_to_user?(user)).to be_falsey
end
end
To run the test for example testing your forgot password, I am assuming you know rspec, factorygirl, capybara
/spec/features/password_reset_spec.rb
require 'rails_helper'
feature 'Password reset', js: true do
describe 'sending' do
it 'reset instructions' do
#FactoryGirl create
user = create(:user)
forgot_password(user)
expect(current_path).to eq(root_path)
expect(page).to have_content('You will receive an email in a few minutes')
should_email(user)
end
end
def forgot_password(user)
visit '/user/login'
click_on 'Forgot password?'
fill_in 'user[email]', with: user.email
click_on 'Reset my password'
user.reload
end
end
You would notice that in this test implementation
will cause sidekiq to run the job instead of enqueuing it,
The user model email attribute must be called email or you can just replace the code above.
ActionMailer::Base.deliveries.map(&:to).flatten.count(user.email) == 1 check to see ActionMailer::Base.deliveries is delivering to user.email
As preface, I've followed through some tutorials (i.e. Michael Hartl's) though I'm still fairly novice. Forgive any cloudy terminology.
I am trying to build a simple application in Rails 4 that does the following:
User logs into application (currently working with sign-in-with-twitter link and routing)
get "/auth/:provider/callback" => "sessions#create"
get "/signout" => "sessions#destroy", :as => :signout
Once <% if current_user %> is true, I have the view rendering a partial where there will be a list of simple buttons. When the user clicks a button I want the application to tweet on behalf of the current_user a preset string. Ideally, I'd do this all in ruby/rails.
These button functions are where I'm getting hung up. I've read a fistful of documents but there seem to be a lot of conflicting and old answers. Here's a quick list of the ones I think are closest, though not explicit about sending a tweet from a simple button/link in a view:
http://www.sitepoint.com/ruby-social-gems-twitter/
http://richonrails.com/articles/sending-a-tweet-to-twitter
Some call for controllers, a more robust oauth setup (which I have bundle installed and connected to the dev.twitter application, though not fleshed out beyond keys), and whatever else. It's got me turned around and I'm not yet good enough to synthesize all the information. Any help and direction would be great. Below are some other files in the app that might be helpful.
class SessionsController < ApplicationController
def create
auth = request.env["omniauth.auth"]
user = User.find_by_provider_and_uid(auth["provider"], auth["uid"]) || User.create_with_omniauth(auth)
session[:user_id] = user.id
redirect_to root_url, :notice => "Hi!"
end
def destroy
session[:user_id] = nil
redirect_to root_url, :notice => "Bye!"
end
end
And omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
provider :twitter, '_priv', '_priv'
end
Eep! I'm the author of the second link (RichOnRails). Did you take a look at the example app included with the tutorial? It does almost exactly what you want. If the tweets are hard coded you could approach it in a couple of different ways. If you take a look at the tweets controller, you'll see it takes a parameter called 'message'. Any message passed to that create method will tweet as the current user.
def create
current_user.tweet(twitter_params[:message])
end
The easiest (but not necessarily best) way to adapt this to fit your needs is to have a form for each tweet, and do a hidden field with the message you wish to tweet. The button becomes a submit for that particular form (you can add remote: true if you want to keep the page from refreshing, then use a bit of javascript to update the UI elements). Hope this helps.
I have a 'Feedback' model whereby a user should be able to request feedback on his/her job performance. I have written basic actions for creating a new feedback request, and the mailer for sending the request to the provider (person who will respond with feedback).
I would like advice from the community on implementing the following:
Once a new feedback request is created, the email that is sent should contain a link to a form where the provider can input his feedback on the users performance.
The feedback provider should not be required to log-in or sign-up in any way (i.e. completely external to the application).
Once submitted, feedback from the provider should be captured in the
system.
Now, I have the following ideas to implement it, but am not sure if this is the best way to proceed:
Generate a unique token upon the creation of a new feedback request. Something like this: Best way to create unique token in Rails?.
The token should then be entered into 'feedbacks' table.
Mailer should then generate variable (e.g. #url) which generates link to another controller (let's say 'external_feedback' and action which does not require log-in (e.g. no before_filter :authenticate_user! from Devise).
That URL should contain a parameter with the token for the specific feedback request.
The action should be to update the 'feedback' request and a form generated with simple_form.
The whole thing is similar to responding to a questionnaire or survey (like Survey Monkey).
After some research I believe the Friendly ID gem may be useful here. I was also reading Section 8 of http://guides.rubyonrails.org/form_helpers.html and perhaps I need to implement an authenticity_token in the formal sense. What I am really looking for is:
Is the above approach the generally correct way to go about doing this?
If so, any specifics on how you would implement it (with or without Friendly ID)?
Do you know of any gems that exist for generating such URLs/tokens?
Thank you in advance. I am now including the current state of model and controller details:
feedback.rb
# == Schema Information
#
# Table name: feedbacks
#
# id :integer not null, primary key
# user_id :integer
# p_first_name :string(255)
# p_last_name :string(255)
# p_email :string(255)
# goal_id :integer
# u_comment :text
# p_comment :text
# created_at :datetime
# updated_at :datetime
#
class Feedback < ActiveRecord::Base
belongs_to :user
belongs_to :goal
has_many :feedback_attributes
validates_presence_of :p_first_name, :p_last_name, :p_email, :goal_id
end
And this is my mailer:
class FeedbackMailer < ActionMailer::Base
def feedback_request(user, feedback)
#user = user
#feedback = feedback
#url = 'http://thisistheexampleurlforfeedback'
mail(to: #feedback.p_email, subject: "#{#user.first_name} #{#user.last_name} has requested your feedback", from: #user.email)
end
end
Add a token field to the feedback model with an index and add a callback to populate it on create e.g.
feedback.rb
before_create :add_token
private
def add_token
begin
self.token = SecureRandom.hex[0,10].upcase
end while self.class.exists?(token: token)
end
now add a new route for the providers feedback
resources :feedbacks do
get 'provider'
put 'provider_update' # you might not need this one, if you are happy to use update
end
In your controller make sure they don't get rejected by devise
before_filter :authenticate_user!, except: [:provider, :provider_update]
...
def provider
#feedback = Feedback.find_by token: params[:token]
end
then in the app/views/feedback/provider.html.haml you can use url in simple_form to send it to the correct update location and only provide the input that they should see.
f.inputs :p_comment
Now update your mailer.
#url = provider_feedback_url(#feedback, token: #feedback.token)
You could do something similar to this using friendly id but you would still need to create some sort of unique slug and then use Feedback.friendly.find instead. I think you would want to combine it with a token to ensure it's still the provider giving the feedback - so the only benefit would really be hiding the true id/count. I think you should update p_* fields to provider_* so that the next dev knows what's in it - it's not the 90s!