Rails3 PublicSuffix in application controller - ruby-on-rails

I try to employ the public_suffix gem to set content of a site based on the top level domain but always get the error uninitialized constant ApplicationController::PublicSuffix.
The code of application_controller.rb:
class ApplicationController < ActionController::Base
protect_from_forgery
helper :all
before_filter :set_tld
def set_tld
servername = request.env['SERVER_NAME']
tld = PublicSuffix.parse(servername).tld
end
end
I already tried to do a require 'public_suffix' inside the function as well as include PublicSuffix after protect_from_forgery but to no avail.

After more than a couple of server restarts my code suddenly works - I don't know how but I guess sometimes miracles happen.
Case closed.

Related

How can I extend my controller from installed Spree gem's controller?

I have spree gem installed successfully. I don't need spree_frontend. Here is the Gemfile
gem 'spree_core', '4.2.0.rc2'
gem 'spree_backend', '4.2.0.rc2'
gem 'spree_sample', '4.2.0.rc2'
gem 'spree_cmd', '4.2.0.rc2'
gem 'spree_auth_devise', '~> 4.2'
So I want to extend my ApplicationController from Spree's BaseController. Here is the code:
class ApplicationController < Spree::BaseController
include Spree::Core::ControllerHelpers::Order
end
But I get following errors:
uninitialized constant Spree::BaseController (NameError)
How can I extend my controller from installed Spree gem's controller?
The problem you're running into is that Spree::BaseController already inherits from ApplicationController; see https://github.com/spree/spree/blob/master/core/app/controllers/spree/base_controller.rb. This is to allow your ApplicationController to define things like current_user and similar basic functions before Spree sees it.
Declaring them the other way around as well creates a circular dependency, and the class loading fails as a result. Without changing Spree itself, the only fix is to do something else.
Instead, to have your controllers use Spree::BaseController as a superclass, first define ApplicationController in the more usual fashion e.g.:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# ...
end
then invent a new abstract controller, for your own use, that inherits from Spree, e.g. let's name it StoreBaseController:
# app/controllers/store_base_controller.rb
class StoreBaseController < Spree::BaseController
include Spree::Core::ControllerHelpers::Order
# ...
end
This StoreBaseController can now be used in place of ApplicationController when defining more specific controllers. It works because it doesn't create a loop in the inheritance tree, which now looks like this:
Note: if you're also using the rails generator command to produce controllers or scaffolds from templates, be aware that the generator has ApplicationController hard-coded in the templates, so you'll need to amend them once created.
Is there any reason why you need to extend strictly ApplicationController?
I advise you alternative approach to create a new Base controller class, and then inherit all the children from it and leave ApplicationController to basic rails
app/controller/my_base_controller.rb
class MyBaseController < Spree::BaseController
def foo
# ...
end
end
app/controller/my_resources_controller.rb
class MyResourcesController < MyBaseController
def bar
# ...
end
end
As the errors states, Spree::BaseController is not defined within your app - it is defined in the spree-core gem. If you re-create the filepath to the base controller locally, that is app/controllers/spree/, and copy and paste the code from the controller into a local base_controller.rb, you can edit it and add custom functionality.
Note that it will still inherit from the ApplicationController, but you can place any of the code you wanted to put in the ApplicationController into here and have your classes inherit from Spree::BaseContoller and the effect will be the same.
hmmm, I tried what you want to do but I succeeded (?)
class PagesController < Spree::BaseController
include Spree::Core::ControllerHelpers::Order
end
in the console
2.6.5 :006 > pp PagesController.ancestors
[PagesController,
Spree::Core::ControllerHelpers::Order,
#<Module:0x00007fca27610410>,
Spree::BaseController,
Spree::Core::ControllerHelpers::CurrencyHelpers,
Spree::Core::ControllerHelpers::StrongParameters,
...
I'm using
ruby 2.6.5
rails 6.0.3.4
run bundle update after adding the your spree's gems in the Gemfile
So I think its the requiring or auto-loading problem
what's your rails version? 6? spree >= 4.1 should use rails >= 6
Does Spree::BaseController exist in rails console?
Is Bundler.require(*Rails.groups) in config/application.rb?
Does the gems included in the right group of the Gemfile? ex: spree gems are in :production group.
Does it have config.load_defaults 6.0 in config/application.rb?

skip_before_action callbacks in Rails 5.1 and inheritance

I've just upgraded my app to Rails 5.1 and I've been bitten by the new behaviour of skip_before_action callbacks. i.e. if callback is not defined at the time I try to skip it, it raises an error.
I know that I can pass raise: false like
skip_before_action :authorise, raise: false
But wondering if there's a better way to do it.
My main problem is that with eager load set to true, the new behaviour messes up with modular setup of my controllers.
Basically I've got dir app/controllers/api
with module_controller.rb:
module Api
class ModuleController < ActionController::Base
before_action :authorise
end
end
Then I've got app_chats_controller.rb which skips the authorise callback:
module Api
class AppChatsController < ModuleController
skip_before_action :authorise
end
end
With eager load, app_chats_controller.rb gets loaded first, which means callback is not yet defined and without raise: true error is raised.
If I have to bite the bullet and add raise: false to everything, so be it, but surely there's a better way...
Try adding require 'module_controller' to the top of app_chats_controller.rb

Rails: How to implement protect_from_forgery in Rails API mode

I have a Rails 5 API app (ApplicationController < ActionController::API). The need came up to add a simple GUI form for one endpoint of this API.
Initially, I was getting ActionView::Template::Error undefined method protect_against_forgery? when I tried to render the form. I added include ActionController::RequestForgeryProtection and protect_from_forgery with:exception to that endpoint. Which solved that issue as expected.
However, when I try to submit this form I get: 422 Unprocessable Entity ActionController::InvalidAuthenticityToken. I've added <%= csrf_meta_tags %> and verified that meta: csrf-param and meta: csrf-token are present in my headers, and that authenticity_token is present in my form. (The tokens themselves are different from each other.)
I've tried, protect_from_forgery prepend: true, with:exception, no effect. I can "fix" this issue by commenting out: protect_from_forgery with:exception. But my understanding is that that is turning off CSRF protection on my form. (I want CSRF protection.)
What am I missing?
UPDATE:
To try to make this clear, 99% of this app is a pure JSON RESTful API. The need came up to add one HTML view and form to this app. So for one Controller I want to enable full CSRF protection. The rest of the app doesn't need CSRF and can remain unchanged.
UPDATE 2:
I just compared the page source of this app's HTML form and Header with another conventional Rails 5 app I wrote. The authenticity_token in the Header and the authenticity_token in the form are the same. In the API app I'm having the problem with, they're different. Maybe that's something?
UPDATE 3:
Ok, I don't the the mismatch is the issue. However, in further comparisons between the working and non-working apps I noticed that there's nothing in Network > Cookies. I see a bunch of things like _my_app-session in the cookies of the working app.
Here's what the issue was: Rails 5, when in API mode, logically doesn't include the Cookie middleware. Without it, there's no Session key stored in a Cookie to be used when validating the token I passed with my form.
Somewhat confusingly, changing things in config/initializers/session_store.rb had no effect.
I eventually found the answer to that problem here: Adding cookie session store back to Rails API app, which led me here: https://github.com/rails/rails/pull/28009/files which mentioned exactly the lines I needed to add to application.rb to get working Cookies back:
config.session_store :cookie_store, key: "_YOUR_APP_session_#{Rails.env}"
config.middleware.use ActionDispatch::Cookies # Required for all session management
config.middleware.use ActionDispatch::Session::CookieStore, config.session_options
Those three lines coupled with:
class FooController < ApplicationController
include ActionController::RequestForgeryProtection
protect_from_forgery with: :exception, unless: -> { request.format.json? }
...
And of course a form generated through the proper helpers:
form_tag(FOO_CREATE_path, method: :post)
...
Got me a CSRF protected form in the middle of my Rails API app.
If you're using Rails 5 API mode, you do not use protect_from_forgery or include <%= csrf_meta_tags %> in any view since your API is 'stateless'. If you were going to use full Rails (not API mode) while ALSO using it as a REST API for other apps/clients, then you could do something like this:
protect_from_forgery unless: -> { request.format.json? }
So that protect_from_forgery would be called when appropriate. But I see ActionController::API in your code so it appears you're using API mode in which case you'd remove the method from your application controller altogether
I had this challenge when working on a Rails 6 API only application.
Here's how I solved it:
First, include this in your app/controllers/application_controller.rb file:
class ApplicationController < ActionController::API
include ActionController::RequestForgeryProtection
end
Note: This was added because protect_from_forgery is a class method included in ActionController::RequestForgeryProtection which is not available when working with Rails in API mode.
Next, add the cross-site request forgery protection:
class ApplicationController < ActionController::API
include ActionController::RequestForgeryProtection
protect_from_forgery with: :null_session
end
OR this if you want to protect_from_forgery conditionally based on the request format:
class ApplicationController < ActionController::API
include ActionController::RequestForgeryProtection
protect_from_forgery with: :exception if proc { |c| c.request.format != 'application/json' }
protect_from_forgery with: :null_session if proc { |c| c.request.format == 'application/json' }
end
Finally, add the line below to your config/application.rb file. Add it inside the class Application < Rails::Application class, just at the bottom:
config.middleware.use ActionDispatch::Flash
So it will look like this:
module MyApp
class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version.
config.load_defaults 6.1
# Configuration for the application, engines, and railties goes here.
#
# These settings can be overridden in specific environments using the files
# in config/environments, which are processed later.
#
# config.time_zone = "Central Time (US & Canada)"
# config.eager_load_paths << Rails.root.join("extras")
# Only loads a smaller set of middleware suitable for API only apps.
# Middleware like session, flash, cookies can be added back manually.
# Skip views, helpers and assets when generating a new resource.
config.api_only = true
config.middleware.use ActionDispatch::Flash
end
end
Note: This will prevent the error below:
NoMethodError (undefined method `flash=' for #<ActionDispatch::Request:0x0000558a06b619e0>):
That's all.
I hope this helps
No need of protect_from_forgery for AJAX calls and apis.
If you want to disable it for some action then
protect_from_forgery except: ['action_name']
class Api::ApiController < ApplicationController
skip_before_action :verify_authenticity_token
end
Use as above with rails 5

Active admin error on Rails 4.0.2 application

Got this error while installing active admin on rails 4.0.2 application. I am using rails-api, where I need something like this admin to manage content apart from client.
undefined method `layout' for ActiveAdmin::Devise::SessionsController:Class
I am not sure about this error.
Okay looks like it has been solved by adding this on application controller.
include AbstractController::Layouts
It is now ActionView::Layouts
See https://github.com/rails/rails/issues/14517
When use rails-api(it's merged into rails 5 now), your ApplicationController will inherits from ActionController::API instead of ActionController::Base, while rails admin dependents on ActionController::Base and some other middlewares. To make active admin works with rails api mode, your need to do some additional work:
Make your ApplicationController inherit from ActionController::Base
class ApplicationController < ActionController::Base
Modify your config/application.rb like this
class Application < Rails::Application
# ...
config.middleware.use ActionDispatch::Flash
config.middleware.use Rack::MethodOverride
config.middleware.use ActionDispatch::Cookies
end
References:
1. https://rrott.com/blog/ror/rails-5-api-with-activeadmin-integration.html
2. What is the difference between a regular Rails app and a Rails API?

Rails with ActiveScaffold and Lockdown plugins

Our application is developed using Rails 2.3.5 along with ActiveScaffold. ActiveScaffold adds quite a bit of magic at run time just by declaring as following in a controller:
class SomeController < ApplicationController
active_scaffold :model
end
Just by adding that one line in the controller, all the restful actions and their corresponding views are made available due to ActiveScaffold's meta programming. As most of the code is added at runtime, in development mode requests seems to be little slower as there is no class_caching.
We needed to add a authorization layer and my team has chosen Lockdown plugin which parses an init.rb file where you declare all the authorization rules. The way that Lockdown stores the authorization rules is by parsing the init.rb file and evaluating the controllers declared in the init.rb file. So for every request Lockdown evaluates all the controllers thereby forcing ActiveScaffold to add lot of meta programming which in turn makes db queries to find out the column definitions of every model. This is considerably slowing down the request in development as there is no class_caching. Some times are requesting are taking almost 30-45 seconds.
Is there any way to force ActiveScaffold to do its magic in a before_filter? Something like the following:
class SomeController < ApplicationController
before_filter :init_active_scaffold
private
def init_active_scaffold
self.class_eval do
active_scaffold :model
end
end
end
class SomeController < ApplicationController
before_filter :init_active_scaffold
private
def init_active_scaffold
self.instance_eval do
active_scaffold :model
end
end
end
class SomeController < ApplicationController
before_filter :init_active_scaffold
private
def init_active_scaffold
self.class.class_eval do
active_scaffold :model
end
end
end
class SomeController < ApplicationController
before_filter :init_active_scaffold
private
def init_active_scaffold
self.class.instance_eval do
active_scaffold :model
end
end
end
I tried all the above four options, when I make a request, browser seem to show the loading indicator but nothing is happening.
Any help is appreciated.
Thanks in advance.
Lockdown only reparses init.rb in development mode so you can make changes without restarting the application. It will be slower - a convenience trade off. Good news is that Lockdown will only do this parsing once in production mode.
I don't use ActiveScaffold, so I can't offer any help there, but thought this would be of interest to you.

Resources