avo 1.7.0 → 1.8.0

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +5 -1
  3. data/Gemfile.lock +94 -70
  4. data/README.md +11 -0
  5. data/app/components/avo/fields/common/badge_viewer_component.html.erb +1 -1
  6. data/app/components/avo/fields/common/multiple_file_viewer_component.html.erb +1 -1
  7. data/app/components/avo/fields/common/single_file_viewer_component.html.erb +1 -1
  8. data/app/components/avo/fields/file_field/index_component.html.erb +1 -1
  9. data/app/components/avo/fields/trix_field/edit_component.html.erb +3 -0
  10. data/app/components/avo/index/grid_item_component.html.erb +4 -6
  11. data/app/components/avo/views/resource_index_component.html.erb +48 -46
  12. data/app/components/avo/views/resource_index_component.rb +1 -1
  13. data/app/controllers/avo/actions_controller.rb +2 -2
  14. data/app/controllers/avo/application_controller.rb +11 -10
  15. data/app/controllers/avo/base_controller.rb +20 -4
  16. data/app/controllers/avo/relations_controller.rb +1 -1
  17. data/app/controllers/avo/search_controller.rb +11 -8
  18. data/app/packs/js/controllers/fields/trix_field_controller.js +16 -0
  19. data/app/views/avo/actions/show.html.erb +1 -1
  20. data/app/views/avo/attachments/show.html.erb +1 -1
  21. data/app/views/avo/base/_actions.html.erb +2 -2
  22. data/app/views/avo/home/index.html.erb +2 -2
  23. data/app/views/avo/partials/_javascript.html.erb +1 -1
  24. data/app/views/avo/partials/_resource_search.html.erb +1 -1
  25. data/app/views/avo/relations/new.html.erb +1 -1
  26. data/app/views/avo/sidebar/_sidebar.html.erb +1 -1
  27. data/config/initializers/friendly_id.rb +107 -0
  28. data/db/migrate/20210525143134_add_slug_to_users.rb +6 -0
  29. data/lib/avo/app.rb +3 -1
  30. data/lib/avo/base_resource.rb +23 -3
  31. data/lib/avo/configuration.rb +7 -1
  32. data/lib/avo/fields/belongs_to_field.rb +1 -1
  33. data/lib/avo/fields/file_field.rb +0 -8
  34. data/lib/avo/fields/has_and_belongs_to_many_field.rb +4 -4
  35. data/lib/avo/fields/has_many_field.rb +4 -4
  36. data/lib/avo/fields/has_one_field.rb +4 -4
  37. data/lib/avo/fields/trix_field.rb +6 -0
  38. data/lib/avo/version.rb +1 -1
  39. data/lib/generators/avo/field_generator.rb +1 -1
  40. data/lib/generators/avo/install_generator.rb +1 -1
  41. data/lib/generators/avo/templates/initializer/avo.tt +1 -0
  42. data/lib/generators/avo/templates/tool/sidebar_item.tt +1 -1
  43. data/public/avo-packs/css/application-797341b7.css.map +1 -1
  44. data/public/avo-packs/css/application-797341b7.css.map.br +0 -0
  45. data/public/avo-packs/css/application-797341b7.css.map.gz +0 -0
  46. data/public/avo-packs/js/application-651ed7b9bc727c83f673.js +26 -0
  47. data/public/avo-packs/js/{application-947ed727440d5b5d73ab.js.LICENSE.txt → application-651ed7b9bc727c83f673.js.LICENSE.txt} +0 -0
  48. data/public/avo-packs/js/application-651ed7b9bc727c83f673.js.br +0 -0
  49. data/public/avo-packs/js/application-651ed7b9bc727c83f673.js.gz +0 -0
  50. data/public/avo-packs/js/application-651ed7b9bc727c83f673.js.map +1 -0
  51. data/public/avo-packs/js/application-651ed7b9bc727c83f673.js.map.br +0 -0
  52. data/public/avo-packs/js/application-651ed7b9bc727c83f673.js.map.gz +0 -0
  53. data/public/avo-packs/manifest.json +8 -8
  54. metadata +11 -9
  55. data/public/avo-packs/js/application-947ed727440d5b5d73ab.js +0 -26
  56. data/public/avo-packs/js/application-947ed727440d5b5d73ab.js.br +0 -0
  57. data/public/avo-packs/js/application-947ed727440d5b5d73ab.js.gz +0 -0
  58. data/public/avo-packs/js/application-947ed727440d5b5d73ab.js.map +0 -1
  59. data/public/avo-packs/js/application-947ed727440d5b5d73ab.js.map.br +0 -0
  60. data/public/avo-packs/js/application-947ed727440d5b5d73ab.js.map.gz +0 -0
@@ -77,7 +77,7 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent
77
77
  end
78
78
 
79
79
  def attach_path
80
- "#{Avo.configuration.root_path}#{request.env["PATH_INFO"]}/new"
80
+ "#{Avo::App.root_path}#{request.env["PATH_INFO"]}/new"
81
81
  end
82
82
 
83
83
  def detach_path
@@ -12,7 +12,7 @@ module Avo
12
12
 
13
13
  def handle
14
14
  resource_ids = action_params[:fields][:resource_ids].split(",").map(&:to_i)
15
- models = @resource.model_class.find resource_ids
15
+ models = @resource.class.find_scope.find resource_ids
16
16
 
17
17
  fields = action_params[:fields].select do |key, value|
18
18
  key != "resource_ids"
@@ -33,7 +33,7 @@ module Avo
33
33
  action_class = params[:action_id].gsub("avo_actions_", "").classify.safe_constantize
34
34
 
35
35
  if params[:id].present?
36
- model = @resource.model_class.find params[:id]
36
+ model = @resource.class.find_scope.find params[:id]
37
37
  end
38
38
 
39
39
  @action = action_class.new(model: model, resource: resource, user: _current_user)
@@ -17,7 +17,7 @@ module Avo
17
17
  add_flash_types :info, :warning, :success, :error
18
18
 
19
19
  def init_app
20
- Avo::App.init request: request, context: context, current_user: _current_user
20
+ Avo::App.init request: request, context: context, root_path: avo.root_path.delete_suffix("/"), current_user: _current_user
21
21
 
22
22
  @license = Avo::App.license
23
23
  end
@@ -78,6 +78,7 @@ module Avo
78
78
  existing_params = Addressable::URI.parse(request.fullpath).query_values.symbolize_keys
79
79
  end
80
80
  rescue; end
81
+
81
82
  avo.send :"resources_#{model.model_name.route_key}_path", **existing_params, **args
82
83
  end
83
84
 
@@ -91,7 +92,7 @@ module Avo
91
92
  existing_params = Addressable::URI.parse(request.fullpath).query_values.symbolize_keys
92
93
  end
93
94
  rescue; end
94
- Addressable::Template.new("#{Avo.configuration.root_path}/resources/#{@parent_resource.model.model_name.route_key}/#{@parent_resource.model.id}/#{@resource.route_key}{?query*}")
95
+ Addressable::Template.new("#{Avo::App.root_path}/resources/#{@parent_resource.model.model_name.route_key}/#{@parent_resource.model.id}/#{@resource.route_key}{?query*}")
95
96
  .expand({query: {**existing_params, **args}})
96
97
  .to_str
97
98
  end
@@ -103,7 +104,7 @@ module Avo
103
104
  end
104
105
 
105
106
  def resource_attach_path(model_name, model_id, related_name, related_id = nil)
106
- path = "#{Avo.configuration.root_path}/resources/#{model_name}/#{model_id}/#{related_name}/new"
107
+ path = "#{Avo::App.root_path}/resources/#{model_name}/#{model_id}/#{related_name}/new"
107
108
 
108
109
  path += "/#{related_id}" if related_id.present?
109
110
 
@@ -111,7 +112,7 @@ module Avo
111
112
  end
112
113
 
113
114
  def resource_detach_path(model_name, model_id, related_name, related_id = nil)
114
- path = "#{Avo.configuration.root_path}/resources/#{model_name}/#{model_id}/#{related_name}"
115
+ path = "#{Avo::App.root_path}/resources/#{model_name}/#{model_id}/#{related_name}"
115
116
 
116
117
  path += "/#{related_id}" if related_id.present?
117
118
 
@@ -147,11 +148,11 @@ module Avo
147
148
  end
148
149
 
149
150
  def set_model
150
- @model = eager_load_files(@resource, @resource.model_class).find params[:id]
151
+ @model = eager_load_files(@resource, @resource.class.find_scope).find params[:id]
151
152
  end
152
153
 
153
154
  def set_related_model
154
- @related_model = eager_load_files(@related_resource, @related_resource.model_class).find params[:related_id]
155
+ @related_model = eager_load_files(@related_resource, @related_resource.class.find_scope).find params[:related_id]
155
156
  end
156
157
 
157
158
  def hydrate_resource
@@ -179,7 +180,7 @@ module Avo
179
180
 
180
181
  begin
181
182
  request.path
182
- .match(/\/?#{Avo.configuration.root_path.delete('/')}\/resources\/([a-z1-9\-_]*)\/?/mi)
183
+ .match(/\/?#{Avo::App.root_path.delete('/')}\/resources\/([a-z1-9\-_]*)\/?/mi)
183
184
  .captures
184
185
  .first
185
186
  rescue
@@ -273,15 +274,15 @@ module Avo
273
274
  end
274
275
 
275
276
  def on_root_path
276
- [Avo.configuration.root_path, "#{Avo.configuration.root_path}/"].include?(request.original_fullpath)
277
+ [Avo::App.root_path, "#{Avo::App.root_path}/"].include?(request.original_fullpath)
277
278
  end
278
279
 
279
280
  def on_resources_path
280
- request.original_url.match?(/.*#{Avo.configuration.root_path}\/resources\/.*/)
281
+ request.original_url.match?(/.*#{Avo::App.root_path}\/resources\/.*/)
281
282
  end
282
283
 
283
284
  def on_api_path
284
- request.original_url.match?(/.*#{Avo.configuration.root_path}\/avo_api\/.*/)
285
+ request.original_url.match?(/.*#{Avo::App.root_path}\/avo_api\/.*/)
285
286
  end
286
287
  end
287
288
  end
@@ -7,6 +7,8 @@ module Avo
7
7
  before_action :hydrate_resource
8
8
  before_action :authorize_action
9
9
  before_action :set_model, only: [:show, :edit, :destroy, :update]
10
+ before_action :reset_pagination_if_filters_changed, only: :index
11
+ before_action :cache_applied_filters, only: :index
10
12
 
11
13
  def index
12
14
  @page_title = resource_name.humanize
@@ -18,7 +20,7 @@ module Avo
18
20
 
19
21
  # If we don't get a query object predefined from a child controller like relations, just spin one up
20
22
  unless defined? @query
21
- @query = @authorization.apply_policy @resource.model_class
23
+ @query = @resource.class.query_scope
22
24
  end
23
25
 
24
26
  # Remove default_scope for index view
@@ -62,7 +64,7 @@ module Avo
62
64
  # If we're accessing this resource via another resource add the parent to the breadcrumbs.
63
65
  if params[:via_resource_class].present? && params[:via_resource_id].present?
64
66
  via_resource = Avo::App.get_resource_by_model_name params[:via_resource_class]
65
- via_model = via_resource.model_class.find params[:via_resource_id]
67
+ via_model = via_resource.class.find_scope.find params[:via_resource_id]
66
68
  via_resource.hydrate model: via_model
67
69
 
68
70
  add_breadcrumb via_resource.plural_name, resources_path(via_resource.model_class)
@@ -91,7 +93,7 @@ module Avo
91
93
  # If we're accessing this resource via another resource add the parent to the breadcrumbs.
92
94
  if params[:via_resource_class].present? && params[:via_resource_id].present?
93
95
  via_resource = Avo::App.get_resource_by_model_name params[:via_resource_class]
94
- via_model = via_resource.model_class.find params[:via_resource_id]
96
+ via_model = via_resource.class.find_scope.find params[:via_resource_id]
95
97
  via_resource.hydrate model: via_model
96
98
 
97
99
  add_breadcrumb via_resource.plural_name, resources_path(via_resource.model_class)
@@ -235,7 +237,7 @@ module Avo
235
237
 
236
238
  def set_actions
237
239
  if params[:resource_id].present?
238
- model = @resource.model_class.find params[:resource_id]
240
+ model = @resource.class.find_scope.find params[:resource_id]
239
241
  end
240
242
 
241
243
  @actions = @resource.get_actions.map do |action|
@@ -260,5 +262,19 @@ module Avo
260
262
 
261
263
  filter_defaults
262
264
  end
265
+
266
+ def cache_applied_filters
267
+ ::Avo::App.cache_store.delete applied_filters_cache_key if params[:filters].nil?
268
+
269
+ ::Avo::App.cache_store.write(applied_filters_cache_key, params[:filters], expires_in: 7.days)
270
+ end
271
+
272
+ def reset_pagination_if_filters_changed
273
+ params[:page] = 1 if params[:filters] != ::Avo::App.cache_store.read(applied_filters_cache_key)
274
+ end
275
+
276
+ def applied_filters_cache_key
277
+ "avo.base_controller.#{@resource.route_key}.applied_filters"
278
+ end
263
279
  end
264
280
  end
@@ -15,7 +15,7 @@ module Avo
15
15
  def index
16
16
  @parent_resource = @resource.dup
17
17
  @resource = @related_resource
18
- @parent_model = @parent_resource.model_class.find(params[:id])
18
+ @parent_model = @parent_resource.class.find_scope.find(params[:id])
19
19
  @parent_resource.hydrate(model: @parent_model)
20
20
  @query = @authorization.apply_policy @parent_model.public_send(params[:related_name])
21
21
 
@@ -2,6 +2,8 @@ require_dependency "avo/application_controller"
2
2
 
3
3
  module Avo
4
4
  class SearchController < ApplicationController
5
+ include Rails.application.routes.url_helpers
6
+
5
7
  before_action :set_resource_name, only: [:show]
6
8
  before_action :set_resource, only: [:show]
7
9
 
@@ -18,14 +20,15 @@ module Avo
18
20
  private
19
21
 
20
22
  def search_resources(resources)
21
- resources.map do |resource|
22
- # Apply authorization
23
- next unless @authorization.set_record(resource.model_class).authorize_action(:index, raise_exception: false)
24
- # Filter out the models without a search_query
25
- next if resource.search_query.nil?
23
+ resources
24
+ .map do |resource|
25
+ # Apply authorization
26
+ next unless @authorization.set_record(resource.model_class).authorize_action(:index, raise_exception: false)
27
+ # Filter out the models without a search_query
28
+ next if resource.search_query.nil?
26
29
 
27
- search_resource resource
28
- end
30
+ search_resource resource
31
+ end
29
32
  .select do |payload|
30
33
  payload.present?
31
34
  end
@@ -61,7 +64,7 @@ module Avo
61
64
 
62
65
  if App.license.has_with_trial(:enhanced_search_results)
63
66
  result[:_description] = resource.description
64
- result[:_avatar] = resource.avatar
67
+ result[:_avatar] = resource.avatar.present? ? main_app.url_for(resource.avatar) : nil
65
68
  result[:_avatar_type] = resource.avatar_type
66
69
  end
67
70
 
@@ -21,6 +21,18 @@ export default class extends Controller {
21
21
  return castBoolean(this.controllerTarget.dataset.attachmentsDisabled)
22
22
  }
23
23
 
24
+ get hideAttachmentFilename() {
25
+ return castBoolean(this.controllerTarget.dataset.hideAttachmentFilename)
26
+ }
27
+
28
+ get hideAttachmentFilesize() {
29
+ return castBoolean(this.controllerTarget.dataset.hideAttachmentFilesize)
30
+ }
31
+
32
+ get hideAttachmentUrl() {
33
+ return castBoolean(this.controllerTarget.dataset.hideAttachmentUrl)
34
+ }
35
+
24
36
  get uploadUrl() {
25
37
  return `${window.location.origin}${window.Avo.configuration.root_path}/avo_api/resources/${this.resourceName}/${this.resourceId}/attachments`
26
38
  }
@@ -102,6 +114,10 @@ export default class extends Controller {
102
114
  href: response.href,
103
115
  }
104
116
 
117
+ if (this.hideAttachmentFilename) attributes.filename = null
118
+ if (this.hideAttachmentFilesize) attributes.filesize = null
119
+ if (this.hideAttachmentUrl) attributes.href = null
120
+
105
121
  successCallback(attributes)
106
122
  }
107
123
  })
@@ -7,7 +7,7 @@
7
7
  data-resource-id="<%= params[:id] %>"
8
8
  class="hidden text-blue-gray-800"
9
9
  >
10
- <%= form_with model: @model, scope: 'fields', url: "#{Avo.configuration.root_path}/resources/#{@resource.model_class.model_name.route_key}/actions/#{@action.param_id}", data: {'turbo-frame': '_top', 'action-target': 'form'} do |form| %>
10
+ <%= form_with model: @model, scope: 'fields', url: "#{avo.root_path}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
12
  <% c.heading do %>
13
13
  <%= @action.action_name %>
@@ -1,5 +1,5 @@
1
1
  <%= turbo_frame_tag 'destroy_attachment_form' do %>
2
- <%= form_with url: "#{Avo.configuration.root_path}/resources/#{params[:resource_name]}/#{params[:id]
2
+ <%= form_with url: "#{avo.root_path}resources/#{params[:resource_name]}/#{params[:id]
3
3
  }/active_storage_attachments/#{params[:attachment_name]}/#{params[:signed_attachment_id]}",
4
4
  method: :delete,
5
5
  html: {
@@ -15,8 +15,8 @@
15
15
  <%
16
16
  @actions.each_with_index do |action, index|
17
17
  path = action_name == 'show' ?
18
- "#{Avo.configuration.root_path}/resources/#{@resource.model_class.model_name.route_key}/#{@model.id}/actions/#{action.param_id}" :
19
- "#{Avo.configuration.root_path}/resources/#{@resource.model_class.model_name.route_key}/actions/#{action.param_id}"
18
+ "#{avo.root_path}resources/#{@resource.model_class.model_name.route_key}/#{@model.id}/actions/#{action.param_id}" :
19
+ "#{avo.root_path}resources/#{@resource.model_class.model_name.route_key}/actions/#{action.param_id}"
20
20
  %>
21
21
  <%= link_to path,
22
22
  'data-turbo-frame': 'actions_show',
@@ -28,8 +28,8 @@
28
28
  <li><span class="mr-2">👍</span> Authorization</li>
29
29
  <li><span class="mr-2">👍</span> Localization</li>
30
30
  <li><span class="mr-2">👍</span> Custom tools</li>
31
- <li><span class="mr-2">⏳</span> Custom fields</li>
32
- <li><span class="mr-2">⏳</span> Search</li>
31
+ <li><span class="mr-2">👍</span> Custom fields</li>
32
+ <li><span class="mr-2">👍</span> Search</li>
33
33
  <li><span class="mr-2">🕐</span> Dashboards</li>
34
34
  <li><span class="mr-2">🕐</span> Themes</li>
35
35
  </ul>
@@ -1,5 +1,5 @@
1
1
  <%= javascript_tag nonce: true do %>
2
2
  window.Avo = window.Avo || { configuration: {} }
3
3
  Avo.configuration.timezone = '<%= Avo.configuration.timezone %>'
4
- Avo.configuration.root_path = '<%= Avo.configuration.root_path %>'
4
+ Avo.configuration.root_path = '<%= Avo::App.root_path %>'
5
5
  <% end %>
@@ -6,5 +6,5 @@
6
6
  data-debounce-timeout='<%= Avo.configuration.search_debounce %>'
7
7
  >
8
8
  </div>
9
- <div class="relative inline-flex text-gray-400 text-sm border border-gray-300 rounded-full cursor-pointer" data-search-target="button"></div>
9
+ <div class="hidden relative inline-flex text-gray-400 text-sm border border-gray-300 rounded-full cursor-pointer" data-search-target="button"></div>
10
10
  </div>
@@ -1,6 +1,6 @@
1
1
  <%= turbo_frame_tag 'attach_modal' do %>
2
2
  <%= form_with scope: 'fields',
3
- url: "#{Avo.configuration.root_path}/resources/#{params[:resource_name]}/#{params[:id]}/#{params[:related_name]}/",
3
+ url: "#{avo.root_path}resources/#{params[:resource_name]}/#{params[:id]}/#{params[:related_name]}/",
4
4
  data: {
5
5
  'turbo-frame': '_top'
6
6
  } do |form| %>
@@ -6,7 +6,7 @@
6
6
 
7
7
  <div class="flex-1 flex flex-col justify-between">
8
8
  <div class="tools py-4">
9
- <%= render Avo::NavigationLinkComponent.new label: 'Get started', path: Avo.configuration.root_path, active: :exclusive if Rails.env.development? && Avo.configuration.home_path.nil? %>
9
+ <%= render Avo::NavigationLinkComponent.new label: 'Get started', path: avo.root_path, active: :exclusive if Rails.env.development? && Avo.configuration.home_path.nil? %>
10
10
 
11
11
  <%= render Avo::NavigationHeadingComponent.new label: t('avo.resources') %>
12
12
 
@@ -0,0 +1,107 @@
1
+ # FriendlyId Global Configuration
2
+ #
3
+ # Use this to set up shared configuration options for your entire application.
4
+ # Any of the configuration options shown here can also be applied to single
5
+ # models by passing arguments to the `friendly_id` class method or defining
6
+ # methods in your model.
7
+ #
8
+ # To learn more, check out the guide:
9
+ #
10
+ # http://norman.github.io/friendly_id/file.Guide.html
11
+
12
+ FriendlyId.defaults do |config|
13
+ # ## Reserved Words
14
+ #
15
+ # Some words could conflict with Rails's routes when used as slugs, or are
16
+ # undesirable to allow as slugs. Edit this list as needed for your app.
17
+ config.use :reserved
18
+
19
+ config.reserved_words = %w(new edit index session login logout users admin
20
+ stylesheets assets javascripts images)
21
+
22
+ # This adds an option to treat reserved words as conflicts rather than exceptions.
23
+ # When there is no good candidate, a UUID will be appended, matching the existing
24
+ # conflict behavior.
25
+
26
+ # config.treat_reserved_as_conflict = true
27
+
28
+ # ## Friendly Finders
29
+ #
30
+ # Uncomment this to use friendly finders in all models. By default, if
31
+ # you wish to find a record by its friendly id, you must do:
32
+ #
33
+ # MyModel.friendly.find('foo')
34
+ #
35
+ # If you uncomment this, you can do:
36
+ #
37
+ # MyModel.find('foo')
38
+ #
39
+ # This is significantly more convenient but may not be appropriate for
40
+ # all applications, so you must explicity opt-in to this behavior. You can
41
+ # always also configure it on a per-model basis if you prefer.
42
+ #
43
+ # Something else to consider is that using the :finders addon boosts
44
+ # performance because it will avoid Rails-internal code that makes runtime
45
+ # calls to `Module.extend`.
46
+ #
47
+ # config.use :finders
48
+ #
49
+ # ## Slugs
50
+ #
51
+ # Most applications will use the :slugged module everywhere. If you wish
52
+ # to do so, uncomment the following line.
53
+ #
54
+ # config.use :slugged
55
+ #
56
+ # By default, FriendlyId's :slugged addon expects the slug column to be named
57
+ # 'slug', but you can change it if you wish.
58
+ #
59
+ # config.slug_column = 'slug'
60
+ #
61
+ # By default, slug has no size limit, but you can change it if you wish.
62
+ #
63
+ # config.slug_limit = 255
64
+ #
65
+ # When FriendlyId can not generate a unique ID from your base method, it appends
66
+ # a UUID, separated by a single dash. You can configure the character used as the
67
+ # separator. If you're upgrading from FriendlyId 4, you may wish to replace this
68
+ # with two dashes.
69
+ #
70
+ # config.sequence_separator = '-'
71
+ #
72
+ # Note that you must use the :slugged addon **prior** to the line which
73
+ # configures the sequence separator, or else FriendlyId will raise an undefined
74
+ # method error.
75
+ #
76
+ # ## Tips and Tricks
77
+ #
78
+ # ### Controlling when slugs are generated
79
+ #
80
+ # As of FriendlyId 5.0, new slugs are generated only when the slug field is
81
+ # nil, but if you're using a column as your base method can change this
82
+ # behavior by overriding the `should_generate_new_friendly_id?` method that
83
+ # FriendlyId adds to your model. The change below makes FriendlyId 5.0 behave
84
+ # more like 4.0.
85
+ # Note: Use(include) Slugged module in the config if using the anonymous module.
86
+ # If you have `friendly_id :name, use: slugged` in the model, Slugged module
87
+ # is included after the anonymous module defined in the initializer, so it
88
+ # overrides the `should_generate_new_friendly_id?` method from the anonymous module.
89
+ #
90
+ # config.use :slugged
91
+ # config.use Module.new {
92
+ # def should_generate_new_friendly_id?
93
+ # slug.blank? || <your_column_name_here>_changed?
94
+ # end
95
+ # }
96
+ #
97
+ # FriendlyId uses Rails's `parameterize` method to generate slugs, but for
98
+ # languages that don't use the Roman alphabet, that's not usually sufficient.
99
+ # Here we use the Babosa library to transliterate Russian Cyrillic slugs to
100
+ # ASCII. If you use this, don't forget to add "babosa" to your Gemfile.
101
+ #
102
+ # config.use Module.new {
103
+ # def normalize_friendly_id(text)
104
+ # text.to_slug.normalize! :transliterations => [:russian, :latin]
105
+ # end
106
+ # }
107
+ end
@@ -0,0 +1,6 @@
1
+ class AddSlugToUsers < ActiveRecord::Migration[6.0]
2
+ def change
3
+ add_column :users, :slug, :string
4
+ add_index :users, :slug, unique: true
5
+ end
6
+ end