From 447046f314f06727420b182ac56273c0b90b5a54 Mon Sep 17 00:00:00 2001 From: Gabriel Zayas Date: Sat, 3 Feb 2024 17:41:43 +0900 Subject: [PATCH 1/9] Super Scaffold slug field partials --- .../app/models/concerns/obfuscates_id.rb | 9 ++++-- .../concerns/obfuscates_id/sluggable.rb | 32 +++++++++++++++++++ .../validators/restricted_paths_validator.rb | 7 ++++ .../config/locales/en/errors.en.yml | 4 +++ .../lib/scaffolding.rb | 1 + .../lib/scaffolding/attribute.rb | 3 ++ .../lib/scaffolding/script.rb | 1 + .../lib/scaffolding/transformer.rb | 30 ++++++++++++++++- .../views/themes/base/fields/_slug.html.erb | 8 +++++ 9 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 bullet_train-obfuscates_id/app/models/concerns/obfuscates_id/sluggable.rb create mode 100644 bullet_train-obfuscates_id/app/validators/restricted_paths_validator.rb create mode 100644 bullet_train-obfuscates_id/config/locales/en/errors.en.yml create mode 100644 bullet_train-themes/app/views/themes/base/fields/_slug.html.erb diff --git a/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id.rb b/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id.rb index 6feafa6f1..9091eeac0 100644 --- a/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id.rb +++ b/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id.rb @@ -23,7 +23,11 @@ def decode_id(id) end def find(*ids) - super(*ids.map { |id| decode_id(id) }) + if self.respond_to?(:slug_attribute) + self.send("find_by_#{slug_attribute}".to_sym, ids.first) + else + super(*ids.map { |id| decode_id(id) }) + end end def relation @@ -46,6 +50,7 @@ def obfuscated_id end def to_param - obfuscated_id + # Even if the `slug` column exists, it might be empty (i.e. - Team). + (defined?(slug) && slug.present?) ? slug : obfuscated_id end end diff --git a/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id/sluggable.rb b/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id/sluggable.rb new file mode 100644 index 000000000..414b2a2c7 --- /dev/null +++ b/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id/sluggable.rb @@ -0,0 +1,32 @@ +module ObfuscatesId + + # We only want to ensure slugging is possible if a developer + # can generate a `slug` field partial with our scaffolder. + if defined?(BulletTrain::SuperScaffolding) + + module Sluggable + extend ActiveSupport::Concern + + included do + # Alphanumeric downcased URL identifier + before_validation :downcase_slug + + validates self.slug_attribute, + uniqueness: true, + length: {minimum: 2, maximum: 30}, + format: {with: /\A[a-zA-Z0-9|-]+\Z/} + + validates_with ::RestrictedPathsValidator + + private + + def downcase_slug + attribute_name = self.class.send(:slug_attribute) + attribute_name_setter = "#{attribute_name}=".to_sym + self.send(attribute_name_setter, self.send(attribute_name).downcase) + end + end + end + + end +end diff --git a/bullet_train-obfuscates_id/app/validators/restricted_paths_validator.rb b/bullet_train-obfuscates_id/app/validators/restricted_paths_validator.rb new file mode 100644 index 000000000..a846be915 --- /dev/null +++ b/bullet_train-obfuscates_id/app/validators/restricted_paths_validator.rb @@ -0,0 +1,7 @@ +class RestrictedPathsValidator < ActiveModel::Validator + def validate(record) + if record.class.restricted_paths.include?(record.slug) + record.errors.add record.class.slug_attribute.to_sym, :restricted_path + end + end +end diff --git a/bullet_train-obfuscates_id/config/locales/en/errors.en.yml b/bullet_train-obfuscates_id/config/locales/en/errors.en.yml new file mode 100644 index 000000000..06e07a4c8 --- /dev/null +++ b/bullet_train-obfuscates_id/config/locales/en/errors.en.yml @@ -0,0 +1,4 @@ +en: + errors: + messages: + restricted_path: "This slug is prohibited" diff --git a/bullet_train-super_scaffolding/lib/scaffolding.rb b/bullet_train-super_scaffolding/lib/scaffolding.rb index 928ce713a..2c0bfe5d2 100644 --- a/bullet_train-super_scaffolding/lib/scaffolding.rb +++ b/bullet_train-super_scaffolding/lib/scaffolding.rb @@ -24,6 +24,7 @@ def self.valid_attribute_type?(type) "options", "password_field", "phone_field", + "slug", "super_select", "text_area", "text_field", diff --git a/bullet_train-super_scaffolding/lib/scaffolding/attribute.rb b/bullet_train-super_scaffolding/lib/scaffolding/attribute.rb index 8f0cf764f..85bd071e7 100644 --- a/bullet_train-super_scaffolding/lib/scaffolding/attribute.rb +++ b/bullet_train-super_scaffolding/lib/scaffolding/attribute.rb @@ -126,6 +126,7 @@ def collection_name def partial_name return options[:attribute] if options[:attribute] + # TODO: We should put these in alphabetical order. case type when "trix_editor", "ckeditor" "html" @@ -169,6 +170,8 @@ def partial_name "number" when "address_field" "address" + when "slug" + "text" else raise "Invalid field type: #{type}." end diff --git a/bullet_train-super_scaffolding/lib/scaffolding/script.rb b/bullet_train-super_scaffolding/lib/scaffolding/script.rb index 2c2709194..a3e85b3b0 100644 --- a/bullet_train-super_scaffolding/lib/scaffolding/script.rb +++ b/bullet_train-super_scaffolding/lib/scaffolding/script.rb @@ -22,6 +22,7 @@ options: "string", password_field: "string", phone_field: "string", + slug: "string", super_select: "string", text_area: "text", text_field: "string", diff --git a/bullet_train-super_scaffolding/lib/scaffolding/transformer.rb b/bullet_train-super_scaffolding/lib/scaffolding/transformer.rb index 59865aa93..b164d2d2b 100644 --- a/bullet_train-super_scaffolding/lib/scaffolding/transformer.rb +++ b/bullet_train-super_scaffolding/lib/scaffolding/transformer.rb @@ -623,7 +623,7 @@ def add_attributes_to_various_views(attributes, scaffolding_options = {}) # 'primary_key' => '', # 'references' => '', "string" => "text_field", - "text" => "text_area" + "text" => "text_area", # 'time' => '', # 'timestamp' => '', } @@ -1294,6 +1294,34 @@ def remove_#{attribute.name} if attribute.is_boolean? scaffold_add_line_to_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", "validates :#{attribute.name}, inclusion: [true, false]", VALIDATIONS_HOOK, prepend: true) end + when "slug" + # TODO: We should really be including Sluggable at the top + # above the concerns hook, not here below the methods. + slug_methods = <<~RUBY + def slug + #{attribute.name} + end + + def self.slug_attribute + :#{attribute.name} + end + + def self.restricted_paths + [ + "admin", + "admins" + ] + end + + include Sluggable + RUBY + + scaffold_add_line_to_file( + "./app/models/scaffolding/completely_concrete/tangible_thing.rb", + slug_methods, + METHODS_HOOK, + prepend: true + ) end end diff --git a/bullet_train-themes/app/views/themes/base/fields/_slug.html.erb b/bullet_train-themes/app/views/themes/base/fields/_slug.html.erb new file mode 100644 index 000000000..33e785b3a --- /dev/null +++ b/bullet_train-themes/app/views/themes/base/fields/_slug.html.erb @@ -0,0 +1,8 @@ + +<% +form ||= current_fields_form +options ||= {} +other_options ||= {} +%> + +<%= render 'shared/fields/field', form: form, method: method, helper: :text_field, options: options, other_options: other_options %> From 7641ba5f0448339167a6449a660de84a676a1152 Mon Sep 17 00:00:00 2001 From: Gabriel Zayas Date: Sat, 3 Feb 2024 17:43:36 +0900 Subject: [PATCH 2/9] Fixing Standard Ruby --- .../app/models/concerns/obfuscates_id.rb | 4 ++-- .../app/models/concerns/obfuscates_id/sluggable.rb | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id.rb b/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id.rb index 9091eeac0..58a60a8d4 100644 --- a/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id.rb +++ b/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id.rb @@ -23,8 +23,8 @@ def decode_id(id) end def find(*ids) - if self.respond_to?(:slug_attribute) - self.send("find_by_#{slug_attribute}".to_sym, ids.first) + if respond_to?(:slug_attribute) + send("find_by_#{slug_attribute}".to_sym, ids.first) else super(*ids.map { |id| decode_id(id) }) end diff --git a/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id/sluggable.rb b/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id/sluggable.rb index 414b2a2c7..f0bcd55eb 100644 --- a/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id/sluggable.rb +++ b/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id/sluggable.rb @@ -1,5 +1,4 @@ module ObfuscatesId - # We only want to ensure slugging is possible if a developer # can generate a `slug` field partial with our scaffolder. if defined?(BulletTrain::SuperScaffolding) @@ -11,7 +10,7 @@ module Sluggable # Alphanumeric downcased URL identifier before_validation :downcase_slug - validates self.slug_attribute, + validates slug_attribute, uniqueness: true, length: {minimum: 2, maximum: 30}, format: {with: /\A[a-zA-Z0-9|-]+\Z/} @@ -23,7 +22,7 @@ module Sluggable def downcase_slug attribute_name = self.class.send(:slug_attribute) attribute_name_setter = "#{attribute_name}=".to_sym - self.send(attribute_name_setter, self.send(attribute_name).downcase) + send(attribute_name_setter, send(attribute_name).downcase) end end end From 9b81d1fb28b156f82093e6d2a6a7942c8a801728 Mon Sep 17 00:00:00 2001 From: Gabriel Zayas Date: Mon, 5 Feb 2024 18:58:29 +0900 Subject: [PATCH 3/9] Update restricted path error locale --- bullet_train-obfuscates_id/config/locales/en/errors.en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bullet_train-obfuscates_id/config/locales/en/errors.en.yml b/bullet_train-obfuscates_id/config/locales/en/errors.en.yml index 06e07a4c8..30e84a687 100644 --- a/bullet_train-obfuscates_id/config/locales/en/errors.en.yml +++ b/bullet_train-obfuscates_id/config/locales/en/errors.en.yml @@ -1,4 +1,4 @@ en: errors: messages: - restricted_path: "This slug is prohibited" + restricted_path: "is restricted, please try another path" From e3a2a644cb66ceca20801c9e2ce78b9204750c73 Mon Sep 17 00:00:00 2001 From: Gabriel Zayas Date: Mon, 5 Feb 2024 21:04:33 +0900 Subject: [PATCH 4/9] Ensure invalid records keep original slug when redirecting to #edit --- .../concerns/obfuscates_id/sluggable.rb | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id/sluggable.rb b/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id/sluggable.rb index f0bcd55eb..0fabd9ead 100644 --- a/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id/sluggable.rb +++ b/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id/sluggable.rb @@ -9,6 +9,7 @@ module Sluggable included do # Alphanumeric downcased URL identifier before_validation :downcase_slug + after_validation :set_to_previous_value, on: :update validates slug_attribute, uniqueness: true, @@ -20,10 +21,30 @@ module Sluggable private def downcase_slug - attribute_name = self.class.send(:slug_attribute) - attribute_name_setter = "#{attribute_name}=".to_sym send(attribute_name_setter, send(attribute_name).downcase) end + + # Since Rails reloads the same invalid object into the form while displaying errors, + # we ensure the slug value being referenced to on reloading is the original value which + # has already been persisted to the database, not the invalid value from the form object. + def set_to_previous_value + if errors.any? + errors.each do |e| + if e.match?(attribute_name) + previous_slug_value = send("#{attribute_name}_was") + send(attribute_name_setter, previous_slug_value) + end + end + end + end + + def attribute_name + self.class.send(:slug_attribute) + end + + def attribute_name_setter + "#{attribute_name}=".to_sym + end end end From d9d749b007a4bc7c03867236c6ed39e75018a9de Mon Sep 17 00:00:00 2001 From: Gabriel Zayas Date: Tue, 6 Feb 2024 13:01:14 +0900 Subject: [PATCH 5/9] Remove useless casting operation --- .../app/validators/restricted_paths_validator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bullet_train-obfuscates_id/app/validators/restricted_paths_validator.rb b/bullet_train-obfuscates_id/app/validators/restricted_paths_validator.rb index a846be915..9ee512327 100644 --- a/bullet_train-obfuscates_id/app/validators/restricted_paths_validator.rb +++ b/bullet_train-obfuscates_id/app/validators/restricted_paths_validator.rb @@ -1,7 +1,7 @@ class RestrictedPathsValidator < ActiveModel::Validator def validate(record) if record.class.restricted_paths.include?(record.slug) - record.errors.add record.class.slug_attribute.to_sym, :restricted_path + record.errors.add record.class.slug_attribute, :restricted_path end end end From 10c088583b3ec4aa8cda8de575ddcfbafe9d4e8c Mon Sep 17 00:00:00 2001 From: Gabriel Zayas Date: Tue, 6 Feb 2024 13:07:23 +0900 Subject: [PATCH 6/9] Remove comment from sluggable --- .../app/models/concerns/obfuscates_id/sluggable.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id/sluggable.rb b/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id/sluggable.rb index 0fabd9ead..2d201209d 100644 --- a/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id/sluggable.rb +++ b/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id/sluggable.rb @@ -7,7 +7,6 @@ module Sluggable extend ActiveSupport::Concern included do - # Alphanumeric downcased URL identifier before_validation :downcase_slug after_validation :set_to_previous_value, on: :update From d060d196c1cc87bf8257f04b73e384fd0c35f344 Mon Sep 17 00:00:00 2001 From: Gabriel Zayas Date: Tue, 6 Feb 2024 15:57:00 +0900 Subject: [PATCH 7/9] Only call slug validation for parent model class --- .../app/models/concerns/obfuscates_id/sluggable.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id/sluggable.rb b/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id/sluggable.rb index 2d201209d..e842979f8 100644 --- a/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id/sluggable.rb +++ b/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id/sluggable.rb @@ -10,10 +10,12 @@ module Sluggable before_validation :downcase_slug after_validation :set_to_previous_value, on: :update - validates slug_attribute, - uniqueness: true, - length: {minimum: 2, maximum: 30}, - format: {with: /\A[a-zA-Z0-9|-]+\Z/} + if respond_to?(:slug_attribute) + validates slug_attribute, + uniqueness: true, + length: {minimum: 2, maximum: 30}, + format: {with: /\A[a-zA-Z0-9|-]+\Z/} + end validates_with ::RestrictedPathsValidator From cd39a974afefb0d743f6f773197fa75882f91357 Mon Sep 17 00:00:00 2001 From: Gabriel Zayas Date: Tue, 6 Feb 2024 15:57:45 +0900 Subject: [PATCH 8/9] Remove Super Scaffolding module conditional on Sluggable module --- .../concerns/obfuscates_id/sluggable.rb | 72 +++++++++---------- 1 file changed, 33 insertions(+), 39 deletions(-) diff --git a/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id/sluggable.rb b/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id/sluggable.rb index e842979f8..0e90d164a 100644 --- a/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id/sluggable.rb +++ b/bullet_train-obfuscates_id/app/models/concerns/obfuscates_id/sluggable.rb @@ -1,53 +1,47 @@ module ObfuscatesId - # We only want to ensure slugging is possible if a developer - # can generate a `slug` field partial with our scaffolder. - if defined?(BulletTrain::SuperScaffolding) - - module Sluggable - extend ActiveSupport::Concern - - included do - before_validation :downcase_slug - after_validation :set_to_previous_value, on: :update - - if respond_to?(:slug_attribute) - validates slug_attribute, - uniqueness: true, - length: {minimum: 2, maximum: 30}, - format: {with: /\A[a-zA-Z0-9|-]+\Z/} - end + module Sluggable + extend ActiveSupport::Concern + + included do + before_validation :downcase_slug + after_validation :set_to_previous_value, on: :update + + if respond_to?(:slug_attribute) + validates slug_attribute, + uniqueness: true, + length: {minimum: 2, maximum: 30}, + format: {with: /\A[a-zA-Z0-9|-]+\Z/} + end - validates_with ::RestrictedPathsValidator + validates_with ::RestrictedPathsValidator - private + private - def downcase_slug - send(attribute_name_setter, send(attribute_name).downcase) - end + def downcase_slug + send(attribute_name_setter, send(attribute_name).downcase) + end - # Since Rails reloads the same invalid object into the form while displaying errors, - # we ensure the slug value being referenced to on reloading is the original value which - # has already been persisted to the database, not the invalid value from the form object. - def set_to_previous_value - if errors.any? - errors.each do |e| - if e.match?(attribute_name) - previous_slug_value = send("#{attribute_name}_was") - send(attribute_name_setter, previous_slug_value) - end + # Since Rails reloads the same invalid object into the form while displaying errors, + # we ensure the slug value being referenced to on reloading is the original value which + # has already been persisted to the database, not the invalid value from the form object. + def set_to_previous_value + if errors.any? + errors.each do |e| + if e.match?(attribute_name) + previous_slug_value = send("#{attribute_name}_was") + send(attribute_name_setter, previous_slug_value) end end end + end - def attribute_name - self.class.send(:slug_attribute) - end + def attribute_name + self.class.send(:slug_attribute) + end - def attribute_name_setter - "#{attribute_name}=".to_sym - end + def attribute_name_setter + "#{attribute_name}=".to_sym end end - end end From 2dbb4664c4cf906e6b3406ed08c14c21131e8c56 Mon Sep 17 00:00:00 2001 From: Gabriel Zayas Date: Tue, 6 Feb 2024 16:02:49 +0900 Subject: [PATCH 9/9] Include the Sluggable module above the concerns hook when scaffolding --- .../lib/scaffolding/transformer.rb | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/bullet_train-super_scaffolding/lib/scaffolding/transformer.rb b/bullet_train-super_scaffolding/lib/scaffolding/transformer.rb index b164d2d2b..c76c06f89 100644 --- a/bullet_train-super_scaffolding/lib/scaffolding/transformer.rb +++ b/bullet_train-super_scaffolding/lib/scaffolding/transformer.rb @@ -1295,8 +1295,6 @@ def remove_#{attribute.name} scaffold_add_line_to_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", "validates :#{attribute.name}, inclusion: [true, false]", VALIDATIONS_HOOK, prepend: true) end when "slug" - # TODO: We should really be including Sluggable at the top - # above the concerns hook, not here below the methods. slug_methods = <<~RUBY def slug #{attribute.name} @@ -1306,16 +1304,22 @@ def self.slug_attribute :#{attribute.name} end + # Define which paths you don't want to show up when defining slugs. def self.restricted_paths [ "admin", "admins" ] end - - include Sluggable RUBY + scaffold_add_line_to_file( + "./app/models/scaffolding/completely_concrete/tangible_thing.rb", + "include Sluggable", + CONCERNS_HOOK, + prepend: true + ) + scaffold_add_line_to_file( "./app/models/scaffolding/completely_concrete/tangible_thing.rb", slug_methods,