administrate
administrate copied to clipboard
Namespaced models not working
Per #871 and #179, it looks like namespaced models were worked on before, but I'm still getting the same issue. Going to the "New
uninitialized constant PaperTrail::VersionDashboard
Which looks to be the same namespacing issue as before. I'm on version 0.11.0 of the gem, as well as Rails 5.2.1 and Ruby 2.5.1.
@TrevorHinesley can you provide an example app where you get that error?
The example app included in this repo has namespaced models (blog/post.rb and dashboards/blog/post_dashboard.rb and it seems to be working fine.
If you were to bundle PaperTrail into that example app and include it in a model, you'd get the same problem. Maybe it's PaperTrail specific, but it seems tied to namespacing somehow, because PaperTrail isn't doing anything special under the hood. Perhaps it's namespaced associations that are the problem?
I'm running into the same issue at the moment. I've created an example app that only includes paper_trail and administrate here: https://github.com/coffeejunk/administrate-paper_trail
Notes:
-
rails generate administrate:install
skips thePaperTrail::Version
model (see message here) however, creates the association in the generated dashboard (see diff). - manually generating the dashboard for
PaperTrail::Version
works (see here)- however, after adding the route for the versions dashboard trying to access it, the lookup doesn't work:
- however, after adding the route for the versions dashboard trying to access it, the lookup doesn't work:
@coffeejunk Can you try following routes. You can create new folder paper_trail
and keep your files inside that folder i-e app/controllers/admin/paper_trail/versions_controller.rb
namespace :admin do
namespace :paper_trail do
resources :versions
end
end
@usman-ahmad interesting.. after moving the VersionsController
into the paper_trail
folder as you suggested I get this error on first load:
and upon a refresh I don't get the error anymore:

@coffeejunk This is what I am experiencing as well. However on production (Heroku) the page is not working no matter how many times I might refresh. Seems like a loading issue. I am not sure if this is a gem-specific issue.
EDIT: Following the blog/posts way as in the example application made it work fine in production. In development I get the refresh issue, but I don't really mind as long as it works fine in production.
@TrevorHinesley can you provide an example app where you get that error?
The example app included in this repo has namespaced models (blog/post.rb and dashboards/blog/post_dashboard.rb and it seems to be working fine.
I had the same issue as in the OP and could fix it in my case with same code as suggested (and didn't encounter any refresh issues so far):
# config/routes.rb
namespace :admin do
...
namespace :paper_trail do
resources :versions
end
end
# app/controllers/admin/paper_trail/versions_controller.rb
module Admin
class PaperTrail::VersionsController < Admin::ApplicationController
# To customize the behavior of this controller,
# you can overwrite any of the RESTful actions. For example:
#
# def index
# super
# @resources = PaperTrail::Version.
# page(params[:page]).
# per(10)
# end
# Define a custom finder by overriding the `find_resource` method:
# def find_resource(param)
# PaperTrail::Version.find_by!(slug: param)
# end
# See https://administrate-prototype.herokuapp.com/customizing_controller_actions
# for more information
end
end
# app/dashboards/paper_trail/version_dashboard.rb
require "administrate/base_dashboard"
class PaperTrail::VersionDashboard < Administrate::BaseDashboard
# ATTRIBUTE_TYPES
# a hash that describes the type of each of the model's fields.
#
# Each different type represents an Administrate::Field object,
# which determines how the attribute is displayed
# on pages throughout the dashboard.
ATTRIBUTE_TYPES = {
item: Field::Polymorphic,
id: Field::Number,
event: Field::String,
whodunnit: Field::String,
object: Field::Text,
created_at: Field::DateTime,
}.freeze
# COLLECTION_ATTRIBUTES
# an array of attributes that will be displayed on the model's index page.
#
# By default, it's limited to four items to reduce clutter on index pages.
# Feel free to add, remove, or rearrange items.
COLLECTION_ATTRIBUTES = [
:item,
:id,
:event,
:whodunnit,
].freeze
# SHOW_PAGE_ATTRIBUTES
# an array of attributes that will be displayed on the model's show page.
SHOW_PAGE_ATTRIBUTES = [
:item,
:id,
:event,
:whodunnit,
:object,
:created_at,
].freeze
# FORM_ATTRIBUTES
# an array of attributes that will be displayed
# on the model's form (`new` and `edit`) pages.
FORM_ATTRIBUTES = [
:item,
:event,
:whodunnit,
:object,
].freeze
# Overwrite this method to customize how versions are displayed
# across all pages of the admin dashboard.
#
# def display_resource(version)
# "PaperTrail::Version ##{version.id}"
# end
end
Should Administrate generate this automatically?
I have problem when namespaces are plurals. In my case shops
This is my very bad fix inside the gem
Looks like I reached a workable state in a project combining Globalize v5.3.0, PaperTrail v10.3.1 (which both use namespaced models) with Administrate v0.12.0, using the following steps:
- Remove all previous admin-related config (I had a mix of custom admin pages and RailsAdmin). In particular in routes.rb, removed my old
namespace :admin do root ‘pages#home’ ...
otherwise got errorPage
not found when running generator. - Run the generator:
rails generate administrate:install
. NB: it worked but gaveWARNING: Unable to generate a dashboard for PaperTrail::Version. Administrate does not yet support namespaced models. WARNING: Unable to generate a dashboard for Thought::Translation. Administrate does not yet support namespaced models
, etc (1 warning about PaperTrail and several others about Globalized models). - For each of these warnings:
- run something like
rails generate administrate:dashboard PaperTrail::Version
,rails generate administrate:dashboard Thought::Translation
, etc - A dashboard configuration file like
dashboards/translations_dashboard.rb
gets generated successfully with a class such asThought::TranslationDashboard
and various configurations inside, but it is in the wrong directorydashboards
--> move it to a new sub-directory which matches the namespace:dashboards/thoughts/translations_dashboard.rb
, etc - similarly, an admin controller gets generated at the wrong level, move it like this:
controllers/translations_controllers.rb
-->controllers/thought/translations_controllers.rb
- Add to routes.rb inside the newly generated admin namespace:
namespace :paper_trail do resources :versions end
,namespace :post do resources :translations end
, etc
- run something like
- At this point the app can be started, and all these routes work without giving an error page: http://localhost:3000/admin, http://localhost:3000/admin/thoughts, http://localhost:3000/admin/thoughts/25, http://localhost:3000/admin/thoughts/25/edit, http://localhost:3000/admin/thought/translations, http://localhost:3000/admin/thought/translations/27.
- When viewing http://localhost:3000/admin/thought/translations/27/edit (editing a thought translation) there was the error
undefined method 'globalized_model_id'
, this is becauseThought::TranslationDashboard
was generated withFORM_ATTRIBUTES = %i[ globalized_model thought_id locale quote ]
. I removedglobalized_model
from this array and then the translation edit page could be loaded correctly.
Some other configurations were then also needed (e.g for models using ActiveStorage attachments, or models with custom to_param
method, etc), but these were not related to this namespaced models issue.
In case anyone else is experiencing issues with getting the namespaced models to work both on development and in production, here's what works for me on Rails 5.2.2
Let's say you have a namespaced model called Foo::Bar
Your controller should look like this:
# app/controllers/admin/foo/bars_controller.rb
module Admin::Foo
class BarsController < Admin::ApplicationController
end
end
If you are using administrate in namespaced mode (eg. if it's accessible via /manager
instead of /admin
), then just replace Admin
with Manager
and move the controller to app/controllers/manager/foo/bars_controller.rb
**NOTE**: If you define your controller as below, it won't work on production, at least not on rails 5.2.2
# app/controllers/admin/foo/bars_controller.rb
module Admin
class Foo::BarsController < Admin::ApplicationController
end
end
Your dashboard should look like this:
# app/dashboards/foo/bar_dashboard.rb
require 'administrate/base_dashboard'
class Foo::BarDashboard < Administrate::BaseDashboard
ATTRIBUTE_TYPES = {...}.freeze
COLLECTION_ATTRIBUTES = [...].freeze
SHOW_PAGE_ATTRIBUTES = [...].freeze
FORM_ATTRIBUTES = [...].freeze
end
Your routes should look like this
# config/routes.rb
namespace :admin do
namespace :foo do
resources :bars
end
end
Adding a bit more of context.
The main issue is that Administrate assumes the names of models following Rails conventions only. There isn't an official way of telling a dashboard that it should be serving a model different from the one named in the current URL.
Due to this, if you want to provide a dashboard for a model, you must ensure that all necessary pieces are in place, and have the expected names.
For example, for PaperTrail::Version
you will need the following.
The admin controller will be under the Admin
namespace (or whatever you call it in your app), as well as the PaperTrail
namespace because it's part of the model name:
### app/controllers/admin/paper_trail/versions_controller.rb
module Admin
module PaperTrail
class VersionsController < Admin::ApplicationController
end
end
end
The route will reflect this namespacing too:
### config/routes.rb
Rails.application.routes.draw do
namespace :admin do
resources :products
namespace :paper_trail do
resources :versions
end
end
end
The dashboard will also need the PaperTrail
namespace:
### app/dashboards/paper_trail/version_dashboard.rb
require "administrate/base_dashboard"
module PaperTrail
class VersionDashboard < Administrate::BaseDashboard
ATTRIBUTE_TYPES = {
event: Field::String,
object: Field::Text,
}.freeze
COLLECTION_ATTRIBUTES = ATTRIBUTE_TYPES.keys
SHOW_PAGE_ATTRIBUTES = ATTRIBUTE_TYPES.keys
FORM_ATTRIBUTES = ATTRIBUTE_TYPES.keys
end
end
With any other dashboards referring to your model via associations, use the option class_name
to specify the model class:
### app/dashboards/product_dashboard.rb
ATTRIBUTE_TYPES = {
id: Field::Number,
name: Field::String,
code: Field::String,
price_in_cents: Field::Number,
stock: Field::Number,
versions: Field::HasMany.with_options(class_name: 'PaperTrail::Version'),
created_at: Field::DateTime,
updated_at: Field::DateTime,
}.freeze
And that should be it.
The situation is not ideal, and it stems from the way that ResourceResolver
works, which I think is a bit too clever. It's related to https://github.com/thoughtbot/administrate/issues/405, where I propose a potential solution (which would need a PR). Still, this doesn't mean yet that such a solution is going to happen any time soon. It requires a bit of thought, and for this we require a bit of time. Personally I'd rather prune the long list of issues a bit before introducing big changes. That will help us understand better what sort of use cases people are getting into.
Or, someone could try implement the solution at #405, and at least we'll have real code to discuss. Doesn't mean it will definitely be merged! But at least it helps understanding the problem better.
In Rails 6.0.3.2 the above solution does not appear to quite work
I'm getting: NoMethodError (undefined method `admin_tenants_path' for #Admin::Manyhands::TenantsController:0x00007f959144c1a8):
This is in the _form.html.erb file, which is receiving:
["admin", #<Manyhands::Tenant: id: nil, created_at: nil, updated_at: nil>]
The problem is here: actionpack-6.0.3.2/lib/action_dispatch/routing/polymorphic_routes.rb:312
if model.persisted?
args << model
model.model_name.singular_route_key
else
@key_strategy.call model.model_name
end
For the 'new' routes, it'll use the key_strategy lambda, which uses the ActiveModel::Name.param_key value ('tenant') in this case.
For routes where the mode is persisted, it'd use singular_route_key (also 'tenant')
Near as I can see, I have 2 options:
- I could override the model_name on all of my models. This seems like a terribly confusing plan
- I can override the _form.html.erb from administrate, and specify the url: parameter eg:
<%= form_for([namespace, page.resource], html: { class: "form" }) do |f| %>
becomes
<%= form_for(page.resource, url: admin_manyhands_tenants_path, html: { class: "form" }) do |f| %>
NOTE If you're coming behind me w/ the same problem, note that the solution above is not complete, because you have to discern between the plural (POST) and singular (PUT/PATCH etc) paths. If this is the direction I go, I guess I'll create some kind of helper to generate this.
Is there a better way to do this that I'm not seeing?
When setting up for ActiveStorage
the instructions from Pablo above worked well but I had to scope the routes as follows:
namespace :admin do
scope module: :active_storage do
resources :attachments
resources :blobs
end
end
@andrewdsmith - when I change from:
namespace :admin do
namespace :manyhands do
resources :tenants
end
end
to
namespace :admin do
scope module: :manyhands do
resources :tenants
end
end
I wind up with an error on /admin/tenants, where it tells me: undefined method
new_admin_manyhands_tenant_path' for #<#Class:0x00007fed7130b2e8:0x00007fed71309a10>` that's true, because the scope call removes it.
This is called from: app/views/administrate/application/index.html.erb:44
<%= link_to(
t(
"administrate.actions.new_resource",
name: page.resource_name.titleize.downcase
),
[:new, namespace, page.resource_path],
class: "button",
) if valid_action?(:new) && show_action?(:new, new_resource) %>
@jeffdeville My solution probably only works because I'm not allowing the creation of new attachments or blobs, so I don't encounter the code that's causing an error in your case. I was really just commenting above to add an option for people to try who end up at this issue while trying to problem solve. I'm just baffled by why I had to switch to scope
in my case.
@jeffdeville - Thank you for your description of the issue. If you run the following on the console, do you get the same I get?
> model = Manyhands::Tenant.first
Manyhands::Tenant Load (0.8ms) SELECT "tenants".* FROM "tenants" ORDER BY "tenants"."id" ASC LIMIT $1 [["LIMIT", 1]]
=> #<Manyhands::Tenant id: 1, name: "Foo">
> model.model_name.singular_route_key
=> "manyhands_tenant"
> model.model_name.param_key
=> "manyhands_tenant"
From what you say, it looks like instead you get "tenant"
in both cases. Is that correct?
I'm surprised as I don't think that's the default behaviour. Is it possible that there's something else in your models that makes the result be different?
Related/possible fix for the use cases mentioned here as well: https://github.com/thoughtbot/administrate/issues/1037#issuecomment-1260082686
I researched this area recently. The reason why scope module: :active_storage
works has to do with complex Rails internals.
- ActiveStorage uses
isolate_namespace
(just as Administrate does). - Among other things, this monkeypatches the target module (
ActiveStorage
) so thatuse_relative_model_naming?
returnstrue
.- See https://github.com/rails/rails/blob/v6.1.6/railties/lib/rails/engine.rb#L401
- This is used by
ActiveModel::Name
to tell whether the "route key" of a model should include its namespace. For ActiveStorage, it causesActiveStorage::Blob.model_name.route_key
to beblobs
, instead ofactive_storage_blobs
.- See https://github.com/rails/rails/blob/v6.1.6/activemodel/lib/active_model/naming.rb#L297
- In turn, this is used by
polymorphic_path
. Therefore, this generates things like the routes forActiveStorage::Blob
turning up likeblob_path
instead ofactive_storage_blob_path
.
So using scope module: :active_storage
makes it so that Rails understands that the resource is ActiveStorage::Blob
while not creating a routing namespace. Therefore the routes can be just blob
.
Another workaround I have found is using namespace
, along with the little known method direct
in the router, in order to declare the routes of these de-namespaced models explicitly:
# config/routes.rb
direct(:admin_blob) { |blob, options| route_for(:admin_active_storage_blob, blob, options) }
direct(:admin_blobs) { |opts| route_for(:admin_active_storage_blobs, opts) }
direct(:edit_admin_blob) { |blob, opts| route_for(:edit_admin_active_storage_blob, blob, opts) }
direct(:admin_attachment) { |attachment, options| route_for(:admin_active_storage_attachment, attachment, options) }
direct(:admin_attachments) { |opts| route_for(:admin_active_storage_attachments, opts) }
direct(:edit_admin_attachment) { |attachment, opts| route_for(:edit_admin_active_storage_attachment, attachment, opts) }
Regarding the problem with Manyhands::Tenant
, it may have been because of the plural namespace. This has been fixed now with https://github.com/thoughtbot/administrate/pull/2261
By the way, I think the "solution" for this issue is documenting it, probably in the Guides, which I should write more for...
I'll leave this open until I get around to doing it. Also this comment will remind me of what I'm supposed to do.