I am building a Rails-API using Omniauth-facebook and Devise-token-auth with Angular and ng-token-auth for the frontend.
However when logging in with facebook I am presented with the error:
undefined local variable or method `flash' for #<Devise::OmniauthCallbacksController:0x007fd027a51e10>
It seems omniauth automatically uses flash middleware however the rails-api doesn't include this and I have been unsuccessfully disabling the use of flash with omniauth.
My configuration is as below:
application.rb:
require File.expand_path('../boot', __FILE__)
require "rails"
# Pick the frameworks you want:
require "active_model/railtie"
require "active_job/railtie"
require "active_record/railtie"
require "action_controller/railtie"
require "action_mailer/railtie"
require "action_view/railtie"
require "sprockets/railtie"
# require "rails/test_unit/railtie"
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
module PathfinderApi
class Application < Rails::Application
config.active_record.raise_in_transactional_callbacks = true
config.middleware.insert_before 0, "Rack::Cors" do
allow do
origins '*'
resource '*', :headers => :any, :methods => [:get, :post, :options]
end
end
config.api_only = true
config.middleware.use ActionDispatch::Flash
config.middleware.use ActionDispatch::Cookies
config.middleware.use ActionDispatch::Session::CookieStore
end
end
devise_token_auth.rb:
DeviseTokenAuth.setup do |config|
Rails.application.secrets.facebook_app_secret
config.change_headers_on_each_request = true
end
devise.rb:
Devise.setup do |config|
config.navigational_formats = [:json]
end
omniauth.rb:
Rails.application.config.middleware.use OmniAuth::Builder do
provider :facebook, ENV['APP_KEY'], ENV['APP_SECRET']
end
I have not managed to disable the flash error with:
config.navigational_formats = [:json]
and devise/omniauth is still using flash middleware and throws the error, any help appreciated!
Had the same problem. Searched the devise source code for 'flash'. Found about 17 matches, all using set_flash_message! (with exclamation mark), except for the failure method in the OmniauthCallbacksController, which uses set_flash_message (without exclamation mark). Looking at the definition we see:
\app\controllers\devise\omniauth_callbacks_controller.rb
# Sets flash message if is_flashing_format? equals true
def set_flash_message!(key, kind, options = {})
if is_flashing_format?
set_flash_message(key, kind, options)
end
end
\lib\devise\controllers\helpers.rb
def is_flashing_format?
is_navigational_format?
end
def is_navigational_format?
Devise.navigational_formats.include?(request_format)
end
The actual flash message is generated in the method without exclamation mark (I would have progged it the other way around...). The missing exclamation mark is the reason why setting the navigational_formats as mentioned in other solutions doesn't work here.
Conclusion: they forgot the exlamation mark.
The fix: monkey-patch the failure method from the OmniauthCallbacksController. Do this in an initializer, for example in
\config\initializers\devise.rb
Rails.application.config.to_prepare do # to_prepare ensures that the monkey patching happens before the first request
Devise::OmniauthCallbacksController.class_eval do # reopen the class
def failure # redefine the failure method
set_flash_message! :alert, :failure, kind: OmniAuth::Utils.camelize(failed_strategy.name), reason: failure_message
redirect_to after_omniauth_failure_path_for(resource_name)
end
end
end
Had the same problem in Rails (5.0.0.1) + devise_token_auth (0.1.39).
Besides the override in #Koen's answer, the following addition is also necessary in my case:
# in config/application.rb
config.middleware.use ActionDispatch::Cookies
I managed to solve this by adding this to the devise.rb config:
config.navigational_formats = []
This way devise will never attempt to use flash and never throw a 500 error.
Taken from https://github.com/heartcombo/devise/issues/4275
Related
I'm using sidekiq to do a simple sample worker. When a make a request to my endpoint, it finished successfully but in the worker I got the following error:
*** NameError Exception: uninitialized constant SaveMessageWorker::Message
I'm using:
ruby 2.5.7
rails 5.2.3
sidekiq 6.0.0
This is part of my api rails project structure:
app
---controllers
------api
---------v1
------------messages_controller.rb
---workers
------save_message_worker.rb
config
---initializers
------sidekiq.rb
---application.rb
These are my files and paths:
app/workers/save_message_worker.rb
class SaveMessageWorker
include Sidekiq::Worker
def perform(message_list)
# byebug
# When I tried the below line, I got: *** NameError Exception: uninitialized constant SaveMessageWorker::Message
message = Message.new(message: message_list)
end
end
app/controllers/api/v1/messages_controller.rb
def multiple_concurrent_workers
begin
SaveMessageWorker.perform_async("Hello World!")
return (render json: {status: "success", data:"processed successfully"})
rescue => exception
logger.error CONTROLLER_NAME + ': An error happened in method multiple_concurrent_workers: ' + exception.to_json
return (render json: {status: "error", message: exception})
end
end
config/routes.rb
Rails.application.routes.draw do
require 'sidekiq/web'
mount Sidekiq::Web => '/sidekiq'
devise_for :users
namespace :api, defaults: { format: "json" } do
namespace :v1 do
resources :messages do
collection do
post :multiple_concurrent_workers
end
end
end
end
end
config/initializers/sidekiq.rb
Sidekiq.configure_server do |config|
config.redis = { url: 'redis://' + Rails.application.credentials[Rails.env.to_sym][:redis][:host].to_s + ':' +Rails.application.credentials[Rails.env.to_sym][:redis][:port].to_s + '/' + Rails.application.credentials[Rails.env.to_sym][:redis][:database].to_s }
end
Sidekiq.configure_client do |config|
config.redis = { url: 'redis://' + Rails.application.credentials[Rails.env.to_sym][:redis][:host].to_s + ':' +Rails.application.credentials[Rails.env.to_sym][:redis][:port].to_s + '/' + Rails.application.credentials[Rails.env.to_sym][:redis][:database].to_s }
end
config/application.rb
require_relative 'boot'
require "rails"
require "active_model/railtie"
require "active_job/railtie"
require "active_record/railtie"
require "active_storage/engine"
require "action_controller/railtie"
require "action_mailer/railtie"
require "action_view/railtie"
require "action_cable/engine"
require "rails/test_unit/railtie"
Bundler.require(*Rails.groups)
module IbeenDev
class Application < Rails::Application
config.load_defaults 5.2
config.middleware.use Rack::MethodOverride
config.middleware.use ActionDispatch::Cookies
config.middleware.use ActionDispatch::Session::CookieStore
config.middleware.use ActionDispatch::Flash
config.log_level = :error
config.autoload_paths << Rails.root.join("lib")
config.eager_load_paths << Rails.root.join("lib")
config.api_only = true
Koala.config.api_version = 'v2.0'
config.active_job.queue_adapter = :sidekiq
end
end
There was an opened issued in the github project, but author said that this is not an exactly sidekiq problem.
Can anyone help me to find the solution? Any help will be appreciated
I'm developing a mountable engine (called SimpleUser) which uses Devise, OmniAuth and OmniAuth-Facebook. First I made a test app with the gems about and every worked fine. Next, I started building the engine from scratch, using the code of the test app as an example.
Everything is almost done, except for the connection with Facebook (it uses the Javascript popup). When I click in "log in" the FB popup is displayed, I grant the app, it redirects to the route specified (see routes), but throws this error:
NoMethodError in SimpleUser::AuthController#create
undefined method `[]' for nil:NilClass
The error occurs in that action, in the line authentication = Authentication.find_by_provider_and_uid(auth['provider'], auth['uid']) where auth is nil (auth comes from auth = request.env["omniauth.auth"]).
One thing I check is that the Callback phase it's no initialised. This is the log of the test app:
Started GET "/auth/facebook/callback" for 127.0.0.1 at 2013-03-14
08:52:56 -0600 (facebook) Callback phase initiated. Processing by
AuthController#create as HTML Parameters: {"provider"=>"facebook"}
This is the log of the engine app:
Started GET "/simple_user/auth/facebook/callback" for 127.0.0.1 at 2013-03-14 08:51:19 -0600
Processing by SimpleUser::AuthController#create as HTML
Parameters: {"provider"=>"facebook"}
For manage OmniAuth, I added the gem to the .gemspec and to the Gemfile; also, I require the gems in the engine, and within a generator of the engine I move a template of omniauth.rb to config/initializers of the parent app during installation. This is what I have:
AuthController (located in app/controllers/simple_user/auth_controller.rb)
module SimpleUser
class AuthController < ApplicationController
def create
auth = request.env["omniauth.auth"]
authentication = Authentication.find_by_provider_and_uid(auth['provider'], auth['uid'])
if authentication
flash[:notice] = "Signed in successfully."
sign_in(:user, authentication.user)
redirect_to root_url
else
user = User.build_new_auth(auth)
#user.apply_omniauth(auth)
if user.save(:validate => false)
flash[:notice] = "Account created and signed in successfully."
sign_in(:user, user)
redirect_to root_url
else
flash[:error] = "Error while creating the user account. Please try again."
redirect_to root_url
end
end
end
end
end
Engine
module SimpleUser
require 'rubygems'
require 'devise'
require 'cancan'
require 'rolify'
require 'omniauth'
require 'omniauth-facebook'
require 'simple_form'
class Engine < ::Rails::Engine
isolate_namespace SimpleUser
config.before_configuration do
env_file = File.join(Rails.root, 'config', 'fb_config.yml')
YAML.load(File.open(env_file)).each do |key, value|
ENV[key.to_s] = value.to_s
end if File.exists?(env_file)
env_file = File.join(Rails.root, 'config', 'devise_config.yml')
YAML.load(File.open(env_file)).each do |key, value|
ENV[key.to_s] = value.to_s
end if File.exists?(env_file)
end
end
end
Generator
module SimpleUser
module Generators
class InstallGenerator < ::Rails::Generators::Base
source_root File.expand_path("../templates", __FILE__)
desc "Install SimpleUser"
def copy_config_file
copy_file "fb_config.yml", "config/fb_config.yml"
copy_file "devise_config.yml", "config/devise_config.yml"
copy_file "omniauth.rb", "config/initializers/omniauth.rb"
end
def copy_migrations
rake "simple_user:install:migrations"
SimpleUser::Engine.load_seed
end
end
end
end
Template of the omniauth.rb
require 'omniauth'
require 'omniauth-facebook'
OmniAuth.config.logger = Rails.logger
Rails.application.config.middleware.use OmniAuth::Builder do
provider :facebook, ENV['FACEBOOK_KEY'], ENV['FACEBOOK_SECRET'], :scope => ENV['FACEBOOK_SCOPE']
end
Routes (on engine)
match 'auth/:provider/callback', to: 'auth#create'
match 'auth/failure', to: redirect('/')
Routes (on dummy app)
mount SimpleUser::Engine => "/simple_user", :as => "simple_user"
.gemspec dependencies
s.add_dependency "rails", "~> 3.2.12"
s.add_dependency "devise"
s.add_dependency "cancan"
s.add_dependency "rolify"
s.add_dependency "omniauth"
s.add_dependency "omniauth-facebook", "1.4.1"
s.add_dependency "simple_form"
#s.add_development_dependency "mysql2"
s.add_development_dependency "sqlite3"
s.add_development_dependency "jquery-rails"
s.add_development_dependency "debugger"
Gemfile
source "http://rubygems.org"
gemspec
gem 'devise'
gem 'cancan'
gem 'rolify'
gem 'omniauth'
gem 'omniauth-facebook', '1.4.1'
gem 'simple_form'
# Development
gem 'jquery-rails'
gem 'debugger'
I think the problem is the callback that is not initialised, and the reason may be that OmniAuth doesn't get loaded, but I don't know if it is and how to solve it.
You can check the project in https://github.com/pablomarti/simple_user, and if you want to clone it and test you can use the generator rails g simple_user:install, and you can see the code of test/dummy also to get the idea.
Thank you very much in advance.
The solution was to remove the omniauth.rb and include the middleware of OmniAuth in the engine, so the engine looks like this:
module SimpleUser
require 'rubygems'
require 'devise'
require 'cancan'
require 'rolify'
require 'omniauth'
require 'omniauth-facebook'
require 'simple_form'
class Engine < ::Rails::Engine
isolate_namespace SimpleUser
middleware.use OmniAuth::Builder do
provider :facebook, ENV['FACEBOOK_KEY'], ENV['FACEBOOK_SECRET'], :scope => ENV['FACEBOOK_SCOPE']
end
config.before_configuration do
env_file = File.join(Rails.root, 'config', 'fb_config.yml')
YAML.load(File.open(env_file)).each do |key, value|
ENV[key.to_s] = value.to_s
end if File.exists?(env_file)
env_file = File.join(Rails.root, 'config', 'devise_config.yml')
YAML.load(File.open(env_file)).each do |key, value|
ENV[key.to_s] = value.to_s
end if File.exists?(env_file)
end
end
end
Thanks to Dmitry Lihachev for his answer https://stackoverflow.com/a/8413724/347501 in a similar problem.
I'm using capybara with minitest on Rails 2.3.14. Like most applications, this one also requires login to do anything inside the site. I'd like to be able to login once per test-suite and use that session throughout all tests that are run. How do I refactor that to the minitest_helper? Right now my helper looks something like this:
#!/usr/bin/env ruby
ENV['RAILS_ENV'] = 'test'
require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
gem 'minitest'
gem 'capybara_minitest_spec'
require 'minitest/unit'
require 'minitest/spec'
require 'minitest/mock'
require 'minitest/autorun'
require 'capybara/rails'
require 'factory_girl'
FactoryGirl.find_definitions
class MiniTest::Spec
include FactoryGirl::Syntax::Methods
include Capybara::DSL
include ActionController::URLWriter
before(:each) do
# .. misc global setup stuff, db cleanup, etc.
end
after(:each) do
# .. more misc stuff
end
end
thanks.
Here’s an example of multiple sessions and custom DSL in an integration test
require 'test_helper'
class UserFlowsTest < ActionDispatch::IntegrationTest
fixtures :users
test "login and browse site" do
# User avs logs in
avs = login(:avs)
# User guest logs in
guest = login(:guest)
# Both are now available in different sessions
assert_equal 'Welcome avs!', avs.flash[:notice]
assert_equal 'Welcome guest!', guest.flash[:notice]
# User avs can browse site
avs.browses_site
# User guest can browse site as well
guest.browses_site
# Continue with other assertions
end
private
module CustomDsl
def browses_site
get "/products/all"
assert_response :success
assert assigns(:products)
end
end
def login(user)
open_session do |sess|
sess.extend(CustomDsl)
u = users(user)
sess.https!
sess.post "/login", :username => u.username, :password => u.password
assert_equal '/welcome', path
sess.https!(false)
end
end
end
Source : http://guides.rubyonrails.org/testing.html#helpers-available-for-integration-tests
Why the error?
Here's the setup:
config/initializers/rack_ip_restrictor.rb
Rack::IpRestrictor.configure do
respond_with [403, {'Content-Type' => 'text/html'}, '']
ips_for :test do
add '127.0.0.1'
add '127.0.0.2/8'
end
restrict /^\/admin/, '/admin', :only => :test
end
config/application.rb
class Application < Rails::Application
...
config.middleware.use Rack::IpRestrictor.middleware
...
end
/lib/rack_ip_restrictor.rb
require 'ipaddr'
require 'active_support/core_ext/array/extract_options'
# namespace Rack
module Rack
# namespace IpRestrictor
module IpRestrictor
class << self
attr_reader :config
# #see Config#initialize
def configure(&block)
#config = IpRestrictor::Config.new
#config.instance_eval &block
end
# Rack middleware
# #return [Middleware] The configured plug & play Rack middleware
def middleware
IpRestrictor::Middleware
end
end
end
end
require 'rack_ip_restrictor/ip_group'
require 'rack_ip_restrictor/middleware'
require 'rack_ip_restrictor/config'
require 'rack_ip_restrictor/restriction'
Any idea why rails can't find Rack::IpRestrictor ?
Thanks
You're not requiring this file anywhere. That is why it cannot find the constant. Files in the lib directory are not automatically loaded in Rails 3. Require this file manually.
I want to test my multidomain RoR3 App.
Here's my test_helper.rb
ENV["RAILS_ENV"] = "test"
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
require 'capybara/rails'
require 'blueprints'
class ActiveSupport::TestCase
end
class ActionDispatch::IntegrationTest
include Capybara
def host
"http://#{subdomain}.lvh.me:3000"
end
def subdomain
#subdomain ? #subdomain : 'demostore'
end
def visit(url)
super("http://#{subdomain}.lvh.me:3000#{url}")
end
end
And my integration test:
require 'test_helper'
class ProductsTest < ActionDispatch::IntegrationTest
def setup
#subdomain = 'demostore'
# creating stuff
end
def teardown
# deleting stuff
end
test "user views product list" do
visit('/')
assert page.has_css?('ul.product-listing')
assert page.has_xpath?("//ul[#class='product-listing']/li", :count => 12)
end
test "user views product page" do
product = Product.first
visit('/')
find(:xpath, "//ul[#class='product-listing']/li/a[1]").click
save_and_open_page
end
end
And I'm sure the link exists. There is problem with clicking and filling stuff.
click_link('Existent link title')
doesn't work too.
I think the default Capybara's driver Rack::Test could have problems with this multidomain stuff?
In your setup, call this rack::test function, which will change your host's value. Well, it changes the host that gets returned about the fake web request.
host! "#{store.subdomain}.example.com"
The problem was that i'm using multidomain stuff so I had to use lvh.me which resolves localhost. You can do the same think by setting in Your /etc/hosts
127.0.0.1 subdomain.yourapp.local
and then use this domain.
I've overwritten Capybara's visit method with sth like that:
def visit(link)
super("mysubdomain.lvh.me:3000#{link}")
end
but problem persisted because when Capybara clicked for example link, the visit method was not used and my host was not requested. Which was? I don't know - probably the default one.
So solution is to set host and port in Capybara settings:
class ActionDispatch::IntegrationTest
include Capybara
Capybara.default_host = "subdomain.yourapp.local"
Capybara.server_port = 3000
# ... rest of stuff here
end
Apparently it's a problem with rack-test.
But there is a fork of it by hassox that just solved it for me.
It's just a couple of commits that really matter, in case you want to check what the changes are.
This is how my Gemfile looks:
group :test, :cucumber do
gem 'rack-test', :git => "https://github.com/hassox/rack-test.git"
gem 'capybara', '= 0.4.1.2'
gem 'capybara-envjs', '= 0.4.0'
gem 'cucumber-rails', '>= 0.3.2'
gem 'pickle', '>= 0.3.4'
end
And then I just make sure to
visit('http://my_subdomain.example.com')
in my steps. Now I'm trying to understand what would make url helpers work with subdomains.
Here's a quick setup that may help you out...
rails 3.2+ testing custom subdomains using cucumber capybara with pow setup:
https://gist.github.com/4465773
I'd like to share what I found to be a great solution for this problem. It involves creating a helper method to prepend URLs with the desired subdomain, doesn't overwrite any Capybara methods, and works with the Rack::Test and capybara-webkit drivers. In fact, it will even work in specs which do not even use Capybara. (source: http://minimul.com/capybara-and-subdomains.html)
The Spec Helper Method
# spec/support/misc.helpers.rb
def hosted_domain(options = {})
path = options[:path] || "/" # use root path by default
subdomain = options[:subdomain] || 'www'
if example.metadata[:js]
port = Capybara.current_session.driver.server_port
url = "http://#{ subdomain }.lvh.me:#{ port }#{ path }"
else
url = "http://#{ subdomain }.example.com#{ path }"
end
end
And to illustrate it's use, here are two examples:
Used in a Feature Spec (with Capybara)
require 'spec_helper'
describe "Accounts" do
# Creates an account using a factory which sequences
# account subdomain names
# Additionally creates users associated with the account
# using FactoryGirl's after callbacks (see FactoryGir docs)
let (:account) { FactoryGirl.create(:account_with_users) })
it "allows users to sign in" do
visit hosted_domain(path: new_sessions_path, subdomain: account.subdomain)
user = account.users.first
fill_in "email", with: user.email
fill_in "password", with: user.password
click_button "commit"
# ... the rest of your specs
end
end
Used in a Request Spec (without Capybara)
#spec/requests/account_management_spec.rb
require "spec_helper"
describe "Account management" do
# creates an account using a factory which sequences
# account subdomain names
let (:account) { FactoryGirl.create(:account) })
it "shows the login page" do
get hosted_domain(path: "/login", subdomain: account.subdomain)
expect(response).to render_template("sessions/new")
end
end
A simple and clean solution is to override the urls you provide to Capybara's visit method. It works well with *.lvh.me domains, which will redirect you to localhost:
describe "Something" do
def with_subdomain(link)
"http://subdomain.lvh.me:3000#{link}"
end
it "should do something" do
visit with_subdomain(some_path)
end
end
Or you could do the same by redefining app_host before a spec:
Capybara.app_host = 'http://sudbomain.lvh.me:3000'
..
visit(some_path)