avo 1.0.4 → 1.3.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of avo might be problematic. Click here for more details.

Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +5 -1
  3. data/Gemfile.lock +11 -3
  4. data/README.md +3 -1
  5. data/app/components/avo/common/key_value_component.html.erb +53 -0
  6. data/app/components/avo/common/key_value_component.rb +11 -0
  7. data/app/components/avo/edit/fields/belongs_to_field_component.html.erb +0 -1
  8. data/app/components/avo/edit/fields/belongs_to_field_component.rb +7 -0
  9. data/app/components/avo/edit/fields/key_value_field_component.html.erb +3 -0
  10. data/app/components/avo/edit/fields/key_value_field_component.rb +4 -0
  11. data/app/components/avo/index/grid_cover_empty_state_component.html.erb +3 -0
  12. data/app/components/avo/index/grid_cover_empty_state_component.rb +4 -0
  13. data/app/components/avo/index/grid_item_component.html.erb +4 -4
  14. data/app/components/avo/modal_component.rb +2 -1
  15. data/app/components/avo/panel_component.html.erb +7 -2
  16. data/app/components/avo/panel_component.rb +4 -1
  17. data/app/components/avo/show/fields/has_one_field_component.html.erb +3 -3
  18. data/app/components/avo/show/fields/key_value_field_component.html.erb +3 -0
  19. data/app/components/avo/show/fields/key_value_field_component.rb +4 -0
  20. data/app/components/avo/views/resource_edit_component.html.erb +3 -3
  21. data/app/components/avo/views/resource_index_component.html.erb +4 -4
  22. data/app/components/avo/views/resource_new_component.html.erb +3 -3
  23. data/app/components/avo/views/resource_show_component.html.erb +2 -2
  24. data/app/controllers/avo/application_controller.rb +12 -8
  25. data/app/controllers/avo/base_controller.rb +16 -0
  26. data/app/controllers/avo/home_controller.rb +2 -0
  27. data/app/helpers/avo/application_helper.rb +0 -30
  28. data/app/packs/entrypoints/application.css +1 -0
  29. data/app/packs/entrypoints/application.js +14 -10
  30. data/app/packs/js/controllers/fields/key_value_controller.js +132 -0
  31. data/app/packs/stylesheets/breadcrumbs.css +12 -0
  32. data/app/packs/svgs/chevron-right.svg +3 -0
  33. data/app/views/avo/actions/show.html.erb +2 -2
  34. data/app/views/avo/home/index.html.erb +3 -3
  35. data/{lib/generators/avo/templates → app/views/avo}/partials/_scripts.html.erb +0 -0
  36. data/app/views/avo/relations/new.html.erb +2 -2
  37. data/app/views/avo/sidebar/_sidebar.html.erb +11 -2
  38. data/app/views/layouts/avo/application.html.erb +5 -4
  39. data/avo.gemspec +2 -0
  40. data/lib/avo/app.rb +28 -9
  41. data/lib/avo/base_resource.rb +12 -12
  42. data/lib/avo/configuration.rb +11 -1
  43. data/lib/avo/fields/belongs_to_field.rb +0 -2
  44. data/lib/avo/fields/key_value_field.rb +24 -1
  45. data/lib/avo/grid_collector.rb +1 -1
  46. data/lib/avo/version.rb +1 -1
  47. data/lib/generators/avo/eject_generator.rb +55 -0
  48. data/lib/generators/avo/templates/tool/controller.tt +2 -0
  49. data/lib/generators/avo/templates/tool/sidebar_item.tt +1 -0
  50. data/lib/generators/avo/templates/tool/view.tt +27 -0
  51. data/lib/generators/avo/tool_generator.rb +62 -0
  52. data/public/avo-packs/css/{application-9d115b7e.css → application-5af67922.css} +132 -24
  53. data/public/avo-packs/css/application-5af67922.css.br +0 -0
  54. data/public/avo-packs/css/application-5af67922.css.gz +0 -0
  55. data/public/avo-packs/css/application-5af67922.css.map +1 -0
  56. data/public/avo-packs/css/application-5af67922.css.map.br +0 -0
  57. data/public/avo-packs/css/application-5af67922.css.map.gz +0 -0
  58. data/public/avo-packs/js/application-a48a99244065c8d7a1d1.js +26 -0
  59. data/public/avo-packs/js/{application-e351ef988a3cb9687542.js.LICENSE.txt → application-a48a99244065c8d7a1d1.js.LICENSE.txt} +0 -0
  60. data/public/avo-packs/js/application-a48a99244065c8d7a1d1.js.br +0 -0
  61. data/public/avo-packs/js/application-a48a99244065c8d7a1d1.js.gz +0 -0
  62. data/public/avo-packs/js/application-a48a99244065c8d7a1d1.js.map +1 -0
  63. data/public/avo-packs/js/application-a48a99244065c8d7a1d1.js.map.br +0 -0
  64. data/public/avo-packs/js/application-a48a99244065c8d7a1d1.js.map.gz +0 -0
  65. data/public/avo-packs/manifest.json +15 -15
  66. metadata +62 -22
  67. data/lib/generators/avo/partials_generator.rb +0 -14
  68. data/lib/generators/avo/templates/partials/_footer.html.erb +0 -3
  69. data/lib/generators/avo/templates/partials/_header.html.erb +0 -1
  70. data/lib/generators/avo/templates/partials/_logo.html.erb +0 -1
  71. data/public/avo-packs/css/application-9d115b7e.css.br +0 -0
  72. data/public/avo-packs/css/application-9d115b7e.css.gz +0 -0
  73. data/public/avo-packs/css/application-9d115b7e.css.map +0 -1
  74. data/public/avo-packs/css/application-9d115b7e.css.map.br +0 -0
  75. data/public/avo-packs/css/application-9d115b7e.css.map.gz +0 -0
  76. data/public/avo-packs/js/application-e351ef988a3cb9687542.js +0 -26
  77. data/public/avo-packs/js/application-e351ef988a3cb9687542.js.br +0 -0
  78. data/public/avo-packs/js/application-e351ef988a3cb9687542.js.gz +0 -0
  79. data/public/avo-packs/js/application-e351ef988a3cb9687542.js.map +0 -1
  80. data/public/avo-packs/js/application-e351ef988a3cb9687542.js.map.br +0 -0
  81. data/public/avo-packs/js/application-e351ef988a3cb9687542.js.map.gz +0 -0
@@ -9,6 +9,9 @@ module Avo
9
9
  before_action :set_model, only: [:show, :edit, :destroy, :update]
10
10
 
11
11
  def index
12
+ @page_title = resource_name.humanize
13
+ add_breadcrumb resource_name.humanize
14
+
12
15
  set_index_params
13
16
  set_filters
14
17
  set_actions
@@ -48,15 +51,28 @@ module Avo
48
51
  set_actions
49
52
 
50
53
  @resource = @resource.hydrate(model: @model, view: :show, user: _current_user, params: params)
54
+
55
+ @page_title = @resource.default_panel_name
56
+ add_breadcrumb resource_name.humanize, resources_path(@resource.model_class)
57
+ add_breadcrumb @resource.model_title
51
58
  end
52
59
 
53
60
  def new
54
61
  @model = @resource.model_class.new
55
62
  @resource = @resource.hydrate(model: @model, view: :new, user: _current_user)
63
+
64
+ @page_title = @resource.default_panel_name
65
+ add_breadcrumb resource_name.humanize, resources_path(@resource.model_class)
66
+ add_breadcrumb t("avo.new").humanize
56
67
  end
57
68
 
58
69
  def edit
59
70
  @resource = @resource.hydrate(model: @model, view: :edit, user: _current_user)
71
+
72
+ @page_title = @resource.default_panel_name
73
+ add_breadcrumb resource_name.humanize, resources_path(@resource.model_class)
74
+ add_breadcrumb @resource.model_title, resource_path(@resource.model)
75
+ add_breadcrumb t("avo.edit").humanize
60
76
  end
61
77
 
62
78
  def create
@@ -3,6 +3,8 @@ require_dependency "avo/application_controller"
3
3
  module Avo
4
4
  class HomeController < ApplicationController
5
5
  def index
6
+ @page_title = "Get started"
7
+
6
8
  unless Rails.env.development?
7
9
  redirect_to resources_path Avo::App.get_resources.min_by { |resource| resource.route_key }.model_class
8
10
  end
@@ -7,30 +7,6 @@ module Avo
7
7
  Avo.webpacker
8
8
  end
9
9
 
10
- def render_logo
11
- render partial: "vendor/avo/partials/logo"
12
- rescue
13
- render partial: "avo/partials/logo"
14
- end
15
-
16
- def render_header
17
- render partial: "vendor/avo/partials/header"
18
- rescue
19
- render partial: "avo/partials/header"
20
- end
21
-
22
- def render_footer
23
- render partial: "vendor/avo/partials/footer"
24
- rescue
25
- render partial: "avo/partials/footer"
26
- end
27
-
28
- def render_scripts
29
- render partial: "vendor/avo/partials/scripts"
30
- rescue
31
- ""
32
- end
33
-
34
10
  def render_license_warnings
35
11
  render partial: "avo/sidebar/license_warnings", locals: {
36
12
  license: Avo::App.license.properties
@@ -45,12 +21,6 @@ module Avo
45
21
  }
46
22
  end
47
23
 
48
- def panel(**args, &block)
49
- render(Avo::PanelComponent.new(**args)) do |component|
50
- capture(component, &block)
51
- end
52
- end
53
-
54
24
  def empty_state(resource_name)
55
25
  render partial: "avo/partials/empty_state", locals: {resource_name: resource_name}
56
26
  end
@@ -14,6 +14,7 @@
14
14
  @import './../stylesheets/alerts.css';
15
15
  @import './../stylesheets/loader.css';
16
16
  @import './../stylesheets/pagination.css';
17
+ @import './../stylesheets/breadcrumbs.css';
17
18
 
18
19
  @import './../stylesheets/components/status.css';
19
20
  @import './../stylesheets/components/code.css';
@@ -19,6 +19,19 @@ window.Turbolinks = Turbo
19
19
 
20
20
  Mousetrap.bind('r r r', () => Turbo.visit(window.location.href, { action: 'replace' }))
21
21
 
22
+ function initTippy() {
23
+ tippy('[data-tippy="tooltip"]', {
24
+ theme: 'light',
25
+ content(reference) {
26
+ const title = reference.getAttribute('title')
27
+ reference.removeAttribute('title')
28
+
29
+ return title
30
+ },
31
+ })
32
+ }
33
+ window.initTippy = initTippy
34
+
22
35
  const application = Application.start()
23
36
 
24
37
  const context = require.context('./../js/controllers', true, /\.js$/)
@@ -29,16 +42,7 @@ application.load(definitionsFromContext(fieldsContext))
29
42
 
30
43
  document.addEventListener('turbo:load', () => {
31
44
  document.body.classList.remove('turbo-loading')
32
-
33
- tippy('[data-tippy="tooltip"]', {
34
- theme: 'light',
35
- content(reference) {
36
- const title = reference.getAttribute('title')
37
- reference.removeAttribute('title')
38
-
39
- return title
40
- },
41
- })
45
+ initTippy()
42
46
  })
43
47
  document.addEventListener('turbo:visit', () => document.body.classList.add('turbo-loading'))
44
48
  document.addEventListener('turbo:submit-start', () => document.body.classList.add('turbo-loading'))
@@ -0,0 +1,132 @@
1
+ import { Controller } from 'stimulus'
2
+ import { castBoolean } from '@/js/helpers/cast_boolean'
3
+
4
+ export default class extends Controller {
5
+ static targets = ['input', 'controller', 'rows']
6
+
7
+ fieldValue = []
8
+
9
+ options = {}
10
+
11
+ get keyInputDisabled() {
12
+ return !this.options.editable || this.options.disable_editing_keys
13
+ }
14
+
15
+ get valueInputDisabled() {
16
+ return !this.options.editable
17
+ }
18
+
19
+ connect() {
20
+ this.setOptions()
21
+
22
+ try {
23
+ const objectValue = JSON.parse(this.inputTarget.value)
24
+ Object.keys(objectValue).forEach((key) => this.fieldValue.push([key, objectValue[key]]))
25
+ } catch (error) {
26
+ this.fieldValue = []
27
+ }
28
+
29
+ this.updateKeyValueComponent()
30
+ }
31
+
32
+ addRow() {
33
+ if (this.options.disable_adding_rows || !this.options.editable) return
34
+ this.fieldValue.push(['', ''])
35
+ this.updateKeyValueComponent()
36
+ }
37
+
38
+ deleteRow(event) {
39
+ if (this.options.disable_deleting_rows || !this.options.editable) return
40
+ const { index } = event.target.dataset
41
+ this.fieldValue.splice(index, 1)
42
+ this.updateTextareaInput()
43
+ this.updateKeyValueComponent()
44
+ }
45
+
46
+ valueFieldUpdated(event) {
47
+ const { value } = event.target
48
+ const { index } = event.target.dataset
49
+ this.fieldValue[index][1] = value
50
+
51
+ this.updateTextareaInput()
52
+ }
53
+
54
+ keyFieldUpdated(event) {
55
+ const { value } = event.target
56
+ const { index } = event.target.dataset
57
+ this.fieldValue[index][0] = value
58
+
59
+ this.updateTextareaInput()
60
+ }
61
+
62
+ updateTextareaInput() {
63
+ if (!this.hasInputTarget) return
64
+ let result = {}
65
+ if (this.fieldValue && this.fieldValue.length > 0) {
66
+ result = Object.assign(...this.fieldValue.map(([key, val]) => ({ [key]: val })))
67
+ }
68
+ this.inputTarget.value = JSON.stringify(result)
69
+ }
70
+
71
+ updateKeyValueComponent() {
72
+ let result = ''
73
+ let index = 0
74
+ this.fieldValue.forEach((row) => {
75
+ const [key, value] = row
76
+ result += this.interpolatedRow(key, value, index)
77
+ index++
78
+ })
79
+ this.rowsTarget.innerHTML = result
80
+ window.initTippy()
81
+ }
82
+
83
+ interpolatedRow(key, value, index) {
84
+ let result = `<div class="flex"><input type="text"
85
+ class="${this.options.inputClasses} border-gray-600 border-r border-l-0 border-b-0 border-t-0 focus:border-gray-700 w-1/2 rounded-none focus:outline-none outline-none focus:border-none"
86
+ data-action="input->key-value#keyFieldUpdated"
87
+ placeholder="${this.options.key_label}"
88
+ data-index="${index}"
89
+ ${this.keyInputDisabled ? "disabled='disabled'" : ''}
90
+ value="${key}"
91
+ autofocus
92
+ />
93
+ <input type="text"
94
+ class="${this.options.inputClasses} border-gray-600 border-r border-l-0 border-b-0 border-t-0 focus:border-gray-700 w-1/2 rounded-none focus:outline-none outline-none focus:border-none"
95
+ data-action="input->key-value#valueFieldUpdated"
96
+ placeholder="${this.options.value_label}"
97
+ data-index="${index}"
98
+ ${this.valueInputDisabled ? "disabled='disabled'" : ''}
99
+ value="${value}"
100
+ />`
101
+ if (this.options.editable) {
102
+ result += `<a
103
+ href="javascript:void(0);"
104
+ data-index="${index}"
105
+ data-action="click->key-value#deleteRow"
106
+ title="${this.options.delete_text}"
107
+ data-tippy="tooltip"
108
+ data-button="delete-row"
109
+ ${this.options.disable_deleting_rows ? "disabled='disabled'" : ''}
110
+ class="flex items-center justify-center p-2 px-3 border-l ${this.options.disable_deleting_rows ? 'cursor-not-allowed' : ''}"
111
+ ><svg class="pointer-events-none text-gray-500 h-5 hover:text-gray-500" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor"><path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg></a>`
112
+ }
113
+ result += '</div>'
114
+
115
+ return result
116
+ }
117
+
118
+ setOptions() {
119
+ let fieldOptions
120
+
121
+ try {
122
+ fieldOptions = JSON.parse(this.controllerTarget.dataset.options)
123
+ } catch (error) {
124
+ fieldOptions = {}
125
+ }
126
+ this.options = {
127
+ ...fieldOptions,
128
+ inputClasses: this.controllerTarget.dataset.inputClasses,
129
+ editable: castBoolean(this.controllerTarget.dataset.editable),
130
+ }
131
+ }
132
+ }
@@ -0,0 +1,12 @@
1
+ .breadcrumbs {
2
+ @apply relative text-sm text-gray-400 font-bold;
3
+
4
+ a,
5
+ span {
6
+ @apply inline-block text-gray-400;
7
+ }
8
+
9
+ a {
10
+ @apply hover:text-gray-500;
11
+ }
12
+ }
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
2
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
3
+ </svg>
@@ -9,7 +9,7 @@
9
9
  >
10
10
  <%= form_with model: @model, scope: 'fields', url: "/avo/resources/#{@resource.model_class.model_name.route_key}/actions/#{@action.param_id}", data: {'turbo-frame': '_top', 'action-target': 'form'} do |form| %>
11
11
  <%= render Avo::ModalComponent.new do |c| %>
12
- <% c.with :heading do %>
12
+ <% c.heading do %>
13
13
  <%= @action.name %>
14
14
  <% end %>
15
15
 
@@ -25,7 +25,7 @@
25
25
  <% end %>
26
26
  <% end %>
27
27
 
28
- <% c.with :controls do %>
28
+ <% c.controls do %>
29
29
  <%= a_button @action.cancel_button_label, 'data-action': 'click->modal#close', size: :sm %>
30
30
  <%= a_button @action.confirm_button_label, type: :submit, color: :green, size: :sm %>
31
31
  <% end %>
@@ -1,10 +1,10 @@
1
1
  <div class="flex flex-col">
2
- <%= panel title: 'Welcome to Avo' do |c| %>
3
- <% c.with :tools do %>
2
+ <%= render Avo::PanelComponent.new title: 'Welcome to Avo' do |c| %>
3
+ <% c.tools do %>
4
4
  <div class="text-sm italic">This page is visible only in development. It will be hidden in other environments.</div>
5
5
  <% end %>
6
6
 
7
- <% c.with :body do %>
7
+ <% c.body do %>
8
8
  <div class="flex flex-col justify-between py-6 min-h-24">
9
9
  <div class="px-6 space-y-4">
10
10
  <h3>About</h3>
@@ -5,7 +5,7 @@
5
5
  'turbo-frame': '_top'
6
6
  } do |form| %>
7
7
  <%= render Avo::ModalComponent.new do |c| %>
8
- <% c.with :heading do %>
8
+ <% c.heading do %>
9
9
  <%= t 'avo.choose_item', item: params[:related_name].downcase %>
10
10
  <% end %>
11
11
 
@@ -24,7 +24,7 @@
24
24
 
25
25
  <%#= form.hidden_field :resource_ids, value: params[:resource_ids], 'data-action-target': 'resourceIds' %>
26
26
 
27
- <% c.with :controls do %>
27
+ <% c.controls do %>
28
28
  <%= a_button t('avo.cancel'), 'data-action': 'click->modal#close', size: :sm %>
29
29
  <%= a_button t('avo.attach'), type: :submit, color: :green, size: :sm %>
30
30
  <% end %>
@@ -1,7 +1,7 @@
1
1
  <div class="application-sidebar flex h-full bg-white text-white w-56 z-50 border-r border-gray-300">
2
2
  <div class="flex flex-col w-full">
3
3
  <%= link_to root_path, class: 'logo-placeholder h-16 bg-white p-2 flex justify-center' do %>
4
- <%= render_logo %>
4
+ <%= render partial: "avo/partials/logo" %>
5
5
  <% end %>
6
6
 
7
7
  <div class="flex-1 flex flex-col justify-between">
@@ -10,9 +10,18 @@
10
10
  <%= render Avo::NavigationHeadingComponent.new label: t('avo.resources') %>
11
11
 
12
12
  <div class="w-full">
13
- <% Avo::App.get_navigation_items(_current_user).each do |resource| %>
13
+ <% Avo::App.resources_navigation(_current_user).each do |resource| %>
14
14
  <%= render Avo::NavigationLinkComponent.new label: resource.name.pluralize.humanize, path: resources_path(resource.model_class) %>
15
15
  <% end %>
16
+
17
+ <% sidebar_partials = Avo::App.get_sidebar_partials %>
18
+ <% if sidebar_partials.present? %>
19
+ <%= render Avo::NavigationHeadingComponent.new label: t('avo.tools') %>
20
+
21
+ <% sidebar_partials.each do |partial| %>
22
+ <%= render partial: "/avo/sidebar/items/#{partial}" %>
23
+ <% end %>
24
+ <% end %>
16
25
  </div>
17
26
 
18
27
  </div>
@@ -1,7 +1,7 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
3
  <head>
4
- <title>Avo Hotwire</title>
4
+ <%= display_meta_tags site: Avo.configuration.app_name, reverse: true, separator: "—" %>
5
5
 
6
6
  <%= csrf_meta_tags %>
7
7
  <%= csp_meta_tag %>
@@ -20,7 +20,7 @@
20
20
  <div class="flex-1 flex flex-col h-full overflow-auto">
21
21
  <div class="relative bg-white p-2 shadow-md h-16 w-full flex flex-shrink-0 items-center z-50" v-if="layout !== 'blank'">
22
22
  <div class="ml-6">
23
- <%= render_header %>
23
+ <%= render partial: "avo/partials/header" %>
24
24
  </div>
25
25
  <div class="flex-1 flex justify-center">
26
26
  <div class="w-64">
@@ -31,7 +31,7 @@
31
31
 
32
32
  <div class="content p-8 flex-1 flex flex-col justify-between items-stretch <%= @container_classes %>">
33
33
  <%= yield %>
34
- <%= render_footer %>
34
+ <%= render partial: "avo/partials/footer" %>
35
35
  </div>
36
36
  </div>
37
37
  </div>
@@ -44,6 +44,7 @@
44
44
  <% end %>
45
45
  </div>
46
46
 
47
- <%= render_scripts %>
47
+ <%= render partial: "avo/partials/scripts" %>
48
+ <!-- Avo version: <%= Avo::VERSION %> -->
48
49
  </body>
49
50
  </html>
data/avo.gemspec CHANGED
@@ -43,4 +43,6 @@ Gem::Specification.new do |spec|
43
43
  spec.add_dependency "view_component"
44
44
  spec.add_dependency "hotwire-rails"
45
45
  spec.add_dependency "addressable"
46
+ spec.add_dependency "meta-tags"
47
+ spec.add_dependency "breadcrumbs_on_rails"
46
48
  end
data/lib/avo/app.rb CHANGED
@@ -129,10 +129,28 @@ module Avo
129
129
  .sort_by { |r| r.name }
130
130
  end
131
131
 
132
- def get_navigation_items(user = nil)
132
+ def resources_navigation(user = nil)
133
133
  get_available_resources(user).select do |resource|
134
134
  resource.model_class.present?
135
135
  end
136
+ .select do |resource|
137
+ resource.visible_on_sidebar
138
+ end
139
+ end
140
+
141
+ # Insert any partials that we find in app/views/avo/sidebar/items.
142
+ def get_sidebar_partials
143
+ Dir.glob(Rails.root.join("app", "views", "avo", "sidebar", "items", "*.html.erb"))
144
+ .map do |path|
145
+ File.basename path
146
+ end
147
+ .map do |filename|
148
+ # remove the leading underscore (_)
149
+ filename[0] = ""
150
+ # remove the extension
151
+ filename.gsub!('.html.erb', '')
152
+ filename
153
+ end
136
154
  end
137
155
 
138
156
  def draw_routes
@@ -144,16 +162,17 @@ module Avo
144
162
  .select do |resource|
145
163
  resource != :BaseResource
146
164
  end
165
+ .select do |resource|
166
+ resource.is_a? Class
167
+ end
147
168
  .map do |resource|
148
- if resource.is_a? Class
149
- route_key = if resource.model_class.present?
150
- resource.model_class.model_name.route_key
151
- else
152
- resource.to_s.underscore.gsub("_resource", "").downcase.pluralize.to_sym
153
- end
154
-
155
- resources route_key
169
+ route_key = if resource.model_class.present?
170
+ resource.model_class.model_name.route_key
171
+ else
172
+ resource.to_s.underscore.gsub("_resource", "").downcase.pluralize.to_sym
156
173
  end
174
+
175
+ resources route_key
157
176
  end
158
177
  end
159
178
  end