From c437b9f041436e7c896c27b12e71399b9d055429 Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Mon, 26 Feb 2018 12:11:15 +0100 Subject: [PATCH 1/3] Introduce a taggable module This module handles all tag related stuff for our models. --- app/models/alchemy/attachment.rb | 3 +-- app/models/alchemy/element.rb | 3 +-- app/models/alchemy/page.rb | 2 +- app/models/alchemy/picture.rb | 3 +-- lib/alchemy/taggable.rb | 9 +++++++++ lib/alchemy_cms.rb | 1 + .../alchemy/admin/pictures_controller_spec.rb | 13 +++++++++++++ spec/features/admin/resources_integration_spec.rb | 2 +- 8 files changed, 28 insertions(+), 8 deletions(-) create mode 100644 lib/alchemy/taggable.rb diff --git a/app/models/alchemy/attachment.rb b/app/models/alchemy/attachment.rb index 2a8366cb83..bc9a760bff 100644 --- a/app/models/alchemy/attachment.rb +++ b/app/models/alchemy/attachment.rb @@ -21,10 +21,9 @@ module Alchemy class Attachment < BaseRecord include Alchemy::Filetypes include Alchemy::NameConversions + include Alchemy::Taggable include Alchemy::Touching - acts_as_taggable - dragonfly_accessor :file, app: :alchemy_attachments do after_assign { |f| write_attribute(:file_mime_type, f.mime_type) } end diff --git a/app/models/alchemy/element.rb b/app/models/alchemy/element.rb index 8e561dd516..77fe9e3004 100644 --- a/app/models/alchemy/element.rb +++ b/app/models/alchemy/element.rb @@ -23,6 +23,7 @@ module Alchemy class Element < BaseRecord include Alchemy::Logger + include Alchemy::Taggable include Alchemy::Touching include Alchemy::Hints @@ -46,8 +47,6 @@ class Element < BaseRecord "updater_id" ].freeze - acts_as_taggable - # All Elements that share the same page id, cell id and parent element id are considered a list. # # If cell id and parent element id are nil (typical case for a simple page), diff --git a/app/models/alchemy/page.rb b/app/models/alchemy/page.rb index 952bfe9829..ca7d79a1da 100644 --- a/app/models/alchemy/page.rb +++ b/app/models/alchemy/page.rb @@ -40,6 +40,7 @@ module Alchemy class Page < BaseRecord include Alchemy::Hints include Alchemy::Logger + include Alchemy::Taggable include Alchemy::Touching DEFAULT_ATTRIBUTES_FOR_COPY = { @@ -82,7 +83,6 @@ class Page < BaseRecord :layoutpage ] - acts_as_taggable acts_as_nested_set(dependent: :destroy) stampable stamper_class_name: Alchemy.user_class_name diff --git a/app/models/alchemy/picture.rb b/app/models/alchemy/picture.rb index 21e0d6b0a4..057c53eb8a 100644 --- a/app/models/alchemy/picture.rb +++ b/app/models/alchemy/picture.rb @@ -25,6 +25,7 @@ class Picture < BaseRecord CONVERTIBLE_FILE_FORMATS = %w(gif jpg jpeg png).freeze include Alchemy::NameConversions + include Alchemy::Taggable include Alchemy::Touching include Alchemy::Picture::Transformations include Alchemy::Picture::Url @@ -69,8 +70,6 @@ def allowed_filetypes case_sensitive: false, message: Alchemy.t("not a valid image") - acts_as_taggable - stampable stamper_class_name: Alchemy.user_class_name scope :named, ->(name) { diff --git a/lib/alchemy/taggable.rb b/lib/alchemy/taggable.rb new file mode 100644 index 0000000000..11a34397ab --- /dev/null +++ b/lib/alchemy/taggable.rb @@ -0,0 +1,9 @@ +module Alchemy + # ActsAsTaggableOn interface + # Include this module to add tagging support to your model. + module Taggable + def self.included(base) + base.acts_as_taggable + end + end +end diff --git a/lib/alchemy_cms.rb b/lib/alchemy_cms.rb index 10312bb558..b2156dadb1 100644 --- a/lib/alchemy_cms.rb +++ b/lib/alchemy_cms.rb @@ -51,6 +51,7 @@ module Alchemy require_relative 'alchemy/ssl_protection' require_relative 'alchemy/resource' require_relative 'alchemy/tinymce' +require_relative 'alchemy/taggable' require_relative 'alchemy/touching' # Require hacks diff --git a/spec/controllers/alchemy/admin/pictures_controller_spec.rb b/spec/controllers/alchemy/admin/pictures_controller_spec.rb index 3308fb86ae..3f89b2a0d6 100644 --- a/spec/controllers/alchemy/admin/pictures_controller_spec.rb +++ b/spec/controllers/alchemy/admin/pictures_controller_spec.rb @@ -50,11 +50,24 @@ module Alchemy context 'with tag params' do let!(:picture_1) { create(:alchemy_picture, tag_list: %w(water)) } let!(:picture_2) { create(:alchemy_picture, tag_list: %w(kitten)) } + let!(:picture_3) { create(:alchemy_picture, tag_list: %w(water nature)) } it 'assigns @pictures with filtered pictures' do get :index, params: {tagged_with: 'water'} expect(assigns(:pictures)).to include(picture_1) expect(assigns(:pictures)).to_not include(picture_2) + expect(assigns(:pictures)).to include(picture_3) + end + end + + context 'with multiple tag params' do + let!(:picture_1) { create(:alchemy_picture, tag_list: %w(water)) } + let!(:picture_2) { create(:alchemy_picture, tag_list: %w(water nature)) } + + it 'assigns @pictures with filtered pictures' do + get :index, params: {tagged_with: 'water,nature'} + expect(assigns(:pictures)).to_not include(picture_1) + expect(assigns(:pictures)).to include(picture_2) end end diff --git a/spec/features/admin/resources_integration_spec.rb b/spec/features/admin/resources_integration_spec.rb index 1c80da186c..5173a1e764 100644 --- a/spec/features/admin/resources_integration_spec.rb +++ b/spec/features/admin/resources_integration_spec.rb @@ -136,7 +136,7 @@ def reload_event_class context "with event that acts_as_taggable" do around do |example| - Event.class_eval { acts_as_taggable } + Event.class_eval { include Alchemy::Taggable } example.run reload_event_class end From d82bb5e714aa8083c5cbc3f3653de7c61cd176aa Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Mon, 26 Feb 2018 12:08:47 +0100 Subject: [PATCH 2/3] Use Gutentag for tags Gutentag is a small but capable library for managing tags in activerecord models. ActsAsTaggableOn is full of features we don't use and not very well maintained lately. It also lacks support for Rails 5.2 --- alchemy_cms.gemspec | 2 +- .../alchemy/admin/tags_controller.rb | 14 +++--- app/helpers/alchemy/admin/tags_helper.rb | 4 +- app/models/alchemy/picture.rb | 2 +- app/models/alchemy/tag.rb | 6 +-- app/views/alchemy/admin/tags/index.html.erb | 4 +- config/locales/alchemy.de.yml | 4 +- config/locales/alchemy.en.yml | 4 +- config/locales/alchemy.es.yml | 4 +- config/locales/alchemy.fr.yml | 4 +- config/locales/alchemy.it.yml | 4 +- config/locales/alchemy.nl.yml | 4 +- config/locales/alchemy.ru.yml | 4 +- ...20180227224537_migrate_tags_to_gutentag.rb | 37 +++++++++++++++ lib/alchemy/engine.rb | 7 +++ lib/alchemy/resource.rb | 2 +- lib/alchemy/taggable.rb | 35 +++++++++++++- lib/alchemy_cms.rb | 2 +- .../alchemy/admin/tags_controller_spec.rb | 12 ++--- ...20180227224537_migrate_tags_to_gutentag.rb | 1 + spec/dummy/db/schema.rb | 47 +++++++------------ .../helpers/alchemy/admin/tags_helper_spec.rb | 6 +-- spec/models/alchemy/tag_spec.rb | 4 +- spec/spec_helper.rb | 5 -- 24 files changed, 139 insertions(+), 79 deletions(-) create mode 100644 db/migrate/20180227224537_migrate_tags_to_gutentag.rb create mode 120000 spec/dummy/db/migrate/20180227224537_migrate_tags_to_gutentag.rb diff --git a/alchemy_cms.gemspec b/alchemy_cms.gemspec index 50976944e6..bd011cd707 100644 --- a/alchemy_cms.gemspec +++ b/alchemy_cms.gemspec @@ -20,12 +20,12 @@ Gem::Specification.new do |gem| gem.add_runtime_dependency 'active_model_serializers', ['~> 0.9.0'] gem.add_runtime_dependency 'acts_as_list', ['~> 0.3'] - gem.add_runtime_dependency 'acts-as-taggable-on', ['~> 5.0'] gem.add_runtime_dependency 'awesome_nested_set', ['~> 3.1'] gem.add_runtime_dependency 'cancancan', ['~> 1.9'] gem.add_runtime_dependency 'coffee-rails', ['~> 4.0'] gem.add_runtime_dependency 'dragonfly', ['~> 1.0', '>= 1.0.7'] gem.add_runtime_dependency 'dragonfly_svg', ['~> 0.0.4'] + gem.add_runtime_dependency 'gutentag', ['~> 2.1'] gem.add_runtime_dependency 'handlebars_assets', ['~> 0.23'] gem.add_runtime_dependency 'jquery-rails', ['~> 4.0'] gem.add_runtime_dependency 'jquery-ui-rails', ['~> 5.0.0'] diff --git a/app/controllers/alchemy/admin/tags_controller.rb b/app/controllers/alchemy/admin/tags_controller.rb index a0cd7804db..42250b73b3 100644 --- a/app/controllers/alchemy/admin/tags_controller.rb +++ b/app/controllers/alchemy/admin/tags_controller.rb @@ -6,7 +6,7 @@ class TagsController < ResourcesController before_action :load_tag, only: [:edit, :update, :destroy] def index - @query = ActsAsTaggableOn::Tag.ransack(params[:q]) + @query = Gutentag::Tag.ransack(params[:q]) @tags = @query .result .page(params[:page] || 1) @@ -15,21 +15,21 @@ def index end def new - @tag = ActsAsTaggableOn::Tag.new + @tag = Gutentag::Tag.new end def create - @tag = ActsAsTaggableOn::Tag.create(tag_params) + @tag = Gutentag::Tag.create(tag_params) render_errors_or_redirect @tag, admin_tags_path, Alchemy.t('New Tag Created') end def edit - @tags = ActsAsTaggableOn::Tag.order("name ASC").to_a - [@tag] + @tags = Gutentag::Tag.order("name ASC").to_a - [@tag] end def update if tag_params[:merge_to] - @new_tag = ActsAsTaggableOn::Tag.find(tag_params[:merge_to]) + @new_tag = Gutentag::Tag.find(tag_params[:merge_to]) Tag.replace(@tag, @new_tag) operation_text = Alchemy.t('Replaced Tag') % {old_tag: @tag.name, new_tag: @new_tag.name} @tag.destroy @@ -57,7 +57,7 @@ def autocomplete private def load_tag - @tag = ActsAsTaggableOn::Tag.find(params[:id]) + @tag = Gutentag::Tag.find(params[:id]) end def tag_params @@ -66,7 +66,7 @@ def tag_params def tags_from_term(term) return [] if term.blank? - ActsAsTaggableOn::Tag.where(['LOWER(name) LIKE ?', "#{term.downcase}%"]) + Gutentag::Tag.where(['LOWER(name) LIKE ?', "#{term.downcase}%"]) end def json_for_autocomplete(items, attribute) diff --git a/app/helpers/alchemy/admin/tags_helper.rb b/app/helpers/alchemy/admin/tags_helper.rb index c2d8686f65..23e3133bb1 100644 --- a/app/helpers/alchemy/admin/tags_helper.rb +++ b/app/helpers/alchemy/admin/tags_helper.rb @@ -16,7 +16,7 @@ def render_tag_list(class_name) sorted_tags_from(class_name: class_name).map do |tag| content_tag('li', name: tag.name, class: filtered_by_tag?(tag) ? 'active' : nil) do link_to( - "#{tag.name} (#{tag.count})", + "#{tag.name} (#{tag.taggings_count})", url_for( search_filter_params.except(:page, :tagged_with).merge( tagged_with: tags_for_filter(current: tag).presence @@ -36,7 +36,7 @@ def filtered_by_tag?(tag) # Returns the tags from params suitable for the tags filter. # - # @param current [ActsAsTaggableOn::Tag] - The current tag that will be added or removed if already present + # @param current [Gutentag::Tag] - The current tag that will be added or removed if already present # @returns [String] def tags_for_filter(current:) if filtered_by_tag?(current) diff --git a/app/models/alchemy/picture.rb b/app/models/alchemy/picture.rb index 057c53eb8a..169131dfee 100644 --- a/app/models/alchemy/picture.rb +++ b/app/models/alchemy/picture.rb @@ -85,7 +85,7 @@ def allowed_filetypes } scope :without_tag, -> { - where("#{table_name}.cached_tag_list IS NULL OR #{table_name}.cached_tag_list = ''") + left_outer_joins(:taggings).where(gutentag_taggings: {id: nil}) } after_update :touch_contents diff --git a/app/models/alchemy/tag.rb b/app/models/alchemy/tag.rb index 8f2ac41487..a0a312a072 100644 --- a/app/models/alchemy/tag.rb +++ b/app/models/alchemy/tag.rb @@ -2,7 +2,7 @@ # == Schema Information # -# Table name: tags +# Table name: gutentag_tags # # id :integer not null, primary key # name :string @@ -10,9 +10,9 @@ # # Just holds some useful tag methods. -# The original Tag model is ActsAsTaggableOn::Tag +# The original Tag model is Gutentag::Tag module Alchemy - class Tag < ActsAsTaggableOn::Tag + class Tag < Gutentag::Tag # Replaces tag with new tag on all models tagged with tag. def self.replace(tag, new_tag) tag.taggings.collect(&:taggable).each do |taggable| diff --git a/app/views/alchemy/admin/tags/index.html.erb b/app/views/alchemy/admin/tags/index.html.erb index ecddf7eed6..6a75f2b5d7 100644 --- a/app/views/alchemy/admin/tags/index.html.erb +++ b/app/views/alchemy/admin/tags/index.html.erb @@ -18,7 +18,7 @@

<%= @tags.total_count %> - <%= ActsAsTaggableOn::Tag.model_name.human(count: @tags.total_count) %> + <%= Gutentag::Tag.model_name.human(count: @tags.total_count) %>

<% if @tags.any? %> @@ -27,7 +27,7 @@ <%= sort_link(@query, :name, hide_indicator: true) %> - <%= ActsAsTaggableOn::Tag.human_attribute_name(:taggings_types) %> + <%= Gutentag::Tag.human_attribute_name(:taggings_types) %> <%= sort_link(@query, :taggings_count, hide_indicator: true) %> diff --git a/config/locales/alchemy.de.yml b/config/locales/alchemy.de.yml index d58775612e..aeb08f33cc 100644 --- a/config/locales/alchemy.de.yml +++ b/config/locales/alchemy.de.yml @@ -798,7 +798,7 @@ de: activerecord: models: - acts_as_taggable_on/tag: + gutentag/tag: one: Tag other: Tags @@ -832,7 +832,7 @@ de: attributes: - acts_as_taggable_on/tag: + gutentag/tag: taggings_types: Verwendet an taggings_count: Anzahl Verwendung diff --git a/config/locales/alchemy.en.yml b/config/locales/alchemy.en.yml index d53d09ba21..7270925815 100644 --- a/config/locales/alchemy.en.yml +++ b/config/locales/alchemy.en.yml @@ -776,7 +776,7 @@ en: activerecord: models: - acts_as_taggable_on/tag: + gutentag/tag: one: Tag other: Tags @@ -810,7 +810,7 @@ en: attributes: - acts_as_taggable_on/tag: + gutentag/tag: taggings_types: Used for taggings_count: Usage count diff --git a/config/locales/alchemy.es.yml b/config/locales/alchemy.es.yml index be86c40118..10a15d90d0 100644 --- a/config/locales/alchemy.es.yml +++ b/config/locales/alchemy.es.yml @@ -827,7 +827,7 @@ es: activerecord: models: - acts_as_taggable_on/tag: + gutentag/tag: one: Etiqueta other: Etiquetas @@ -861,7 +861,7 @@ es: attributes: - acts_as_taggable_on/tag: + gutentag/tag: taggings_types: Usado por taggings_count: Contador de uso name: Nombre diff --git a/config/locales/alchemy.fr.yml b/config/locales/alchemy.fr.yml index ca9a25478a..3724c89139 100644 --- a/config/locales/alchemy.fr.yml +++ b/config/locales/alchemy.fr.yml @@ -812,7 +812,7 @@ fr: activerecord: models: - acts_as_taggable_on/tag: + gutentag/tag: one: Tag other: Tags @@ -845,7 +845,7 @@ fr: attributes: - acts_as_taggable_on/tag: + gutentag/tag: taggings_types: Posté le taggings_count: Nombres d´utilisations diff --git a/config/locales/alchemy.it.yml b/config/locales/alchemy.it.yml index a41d73a580..e967380003 100644 --- a/config/locales/alchemy.it.yml +++ b/config/locales/alchemy.it.yml @@ -807,7 +807,7 @@ it: activerecord: models: - acts_as_taggable_on/tag: + gutentag/tag: one: Tag other: Tag @@ -841,7 +841,7 @@ it: attributes: - acts_as_taggable_on/tag: + gutentag/tag: taggings_types: Usato per taggings_count: Numero utilizzi diff --git a/config/locales/alchemy.nl.yml b/config/locales/alchemy.nl.yml index 5e3eb45202..54d1bc49c9 100755 --- a/config/locales/alchemy.nl.yml +++ b/config/locales/alchemy.nl.yml @@ -788,7 +788,7 @@ nl: activerecord: models: - acts_as_taggable_on/tag: + gutentag/tag: one: Tag other: Tags @@ -822,7 +822,7 @@ nl: attributes: - acts_as_taggable_on/tag: + gutentag/tag: taggings_types: Gebruikt voor taggings_count: Aantal in gebruik diff --git a/config/locales/alchemy.ru.yml b/config/locales/alchemy.ru.yml index 953b3f3e0b..f65298cbc7 100644 --- a/config/locales/alchemy.ru.yml +++ b/config/locales/alchemy.ru.yml @@ -683,7 +683,7 @@ ru: activerecord: models: - acts_as_taggable_on/tag: + gutentag/tag: one: "Метка" few: "Метки" many: "Меток" @@ -733,7 +733,7 @@ ru: attributes: - acts_as_taggable_on/tag: + gutentag/tag: name: "Название" taggings_types: Используется для taggings_count: Количество использований diff --git a/db/migrate/20180227224537_migrate_tags_to_gutentag.rb b/db/migrate/20180227224537_migrate_tags_to_gutentag.rb new file mode 100644 index 0000000000..e58209963f --- /dev/null +++ b/db/migrate/20180227224537_migrate_tags_to_gutentag.rb @@ -0,0 +1,37 @@ +class MigrateTagsToGutentag < ActiveRecord::Migration[5.0] + def change + remove_index :taggings, :taggable_id + remove_column :taggings, :tagger_id, :integer + remove_index :taggings, :taggable_type + remove_column :taggings, :tagger_type, :string + remove_index :taggings, column: [:taggable_id, :taggable_type, :context], name: 'index_taggings_on_taggable_id_and_taggable_type_and_context' + remove_column :taggings, :context, :string, limit: 128 + if index_exists? :taggings, [:tag_id, :taggable_id, :taggable_type], unique: true, name: 'taggings_idx' + rename_index :taggings, 'taggings_idx', 'unique_taggings' + else + add_index :taggings, [:taggable_type, :taggable_id, :tag_id], unique: true, name: 'unique_taggings' + end + if index_exists? :taggings, [:taggable_id, :taggable_type], name: 'taggings_idy' + rename_index :taggings, 'taggings_idy', 'index_gutentag_taggings_on_taggable_id_and_taggable_type' + else + add_index :taggings, [:taggable_type, :taggable_id] + end + add_column :taggings, :updated_at, :datetime + change_column_null :taggings, :tag_id, false + change_column_null :taggings, :taggable_id, false + change_column_null :taggings, :taggable_type, false + change_column_null :taggings, :created_at, false, Time.current + change_column_null :taggings, :updated_at, false, Time.current + rename_table :taggings, :gutentag_taggings + + change_column_null :tags, :name, false + add_index :tags, :taggings_count + rename_table :tags, :gutentag_tags + + %i(alchemy_attachments alchemy_elements alchemy_pages alchemy_pictures).each do |table| + if column_exists? table, :cached_tag_list + remove_column table, :cached_tag_list + end + end + end +end diff --git a/lib/alchemy/engine.rb b/lib/alchemy/engine.rb index bcae0aaa03..7a089a2728 100644 --- a/lib/alchemy/engine.rb +++ b/lib/alchemy/engine.rb @@ -14,6 +14,13 @@ class Engine < Rails::Engine NonStupidDigestAssets.whitelist += [/^tinymce\//] end + # Gutentag downcases all tgas before save. + # We support having tags with uppercase characters. + # The Gutentag search is case insensitive. + initializer 'alchemy.gutentag_normalizer' do + Gutentag.normaliser = ->(value) { value.to_s } + end + # We need to reload each essence class in development mode on every request, # so it can register itself as essence relation on Page and Element models # diff --git a/lib/alchemy/resource.rb b/lib/alchemy/resource.rb index 2460e6b023..a0d202a652 100644 --- a/lib/alchemy/resource.rb +++ b/lib/alchemy/resource.rb @@ -95,7 +95,7 @@ module Alchemy # If you don't want to stick with these conventions you can separate model and controller by providing # a model class (for example used by Alchemy's Tags admin interface): # - # resource = Resource.new('/admin/tags', {"engine_name"=>"alchemy"}, ActsAsTaggableOn::Tag) + # resource = Resource.new('/admin/tags', {"engine_name"=>"alchemy"}, Gutentag::Tag) # class Resource attr_accessor :resource_relations, :model_associations diff --git a/lib/alchemy/taggable.rb b/lib/alchemy/taggable.rb index 11a34397ab..b948eddb60 100644 --- a/lib/alchemy/taggable.rb +++ b/lib/alchemy/taggable.rb @@ -1,9 +1,40 @@ module Alchemy - # ActsAsTaggableOn interface + # ActsAsTaggableOn to Gutentag interface compatibility module # Include this module to add tagging support to your model. module Taggable def self.included(base) - base.acts_as_taggable + Gutentag::ActiveRecord.call base + base.extend ClassMethods + base.send(:alias_method, :tag_list, :tag_names) + end + + # Set a list of tags + # Pass a String with comma separated tag names or + # an Array of tag names + def tag_list=(tags) + case tags + when String + self.tag_names = tags.split(/,\s*/) + when Array + self.tag_names = tags + end + end + + module ClassMethods + # Find all records matching all of the given tags. + # Separate multiple tags by comma. + def tagged_with(names) + if names.is_a? String + names = names.split(/,\s*/) + end + super(names: names, match: :all) + end + + # Returns all unique tags + def tag_counts + Gutentag::Tag.distinct.joins(:taggings) + .where(gutentag_taggings: {taggable_type: name}) + end end end end diff --git a/lib/alchemy_cms.rb b/lib/alchemy_cms.rb index b2156dadb1..1a706a9379 100644 --- a/lib/alchemy_cms.rb +++ b/lib/alchemy_cms.rb @@ -4,12 +4,12 @@ module Alchemy # Require globally used external libraries require 'acts_as_list' -require 'acts-as-taggable-on' require 'action_view/dependency_tracker' require 'active_model_serializers' require 'awesome_nested_set' require 'cancan' require 'dragonfly' +require 'gutentag' require 'handlebars_assets' require 'jquery-rails' require 'jquery-ui-rails' diff --git a/spec/controllers/alchemy/admin/tags_controller_spec.rb b/spec/controllers/alchemy/admin/tags_controller_spec.rb index 969d667f04..8394686b75 100644 --- a/spec/controllers/alchemy/admin/tags_controller_spec.rb +++ b/spec/controllers/alchemy/admin/tags_controller_spec.rb @@ -21,15 +21,15 @@ module Admin it "creates tag and redirects to tags view" do expect { post :create, params: {tag: {name: 'Foo'}} - }.to change { ActsAsTaggableOn::Tag.count }.by(1) + }.to change { Gutentag::Tag.count }.by(1) expect(response).to redirect_to admin_tags_path end end end describe '#edit' do - let(:tag) { ActsAsTaggableOn::Tag.create(name: 'Sputz') } - let(:another_tag) { ActsAsTaggableOn::Tag.create(name: 'Hutzl') } + let(:tag) { Gutentag::Tag.create(name: 'Sputz') } + let(:another_tag) { Gutentag::Tag.create(name: 'Hutzl') } before { another_tag; tag } @@ -41,7 +41,7 @@ module Admin end describe '#update' do - let(:tag) { ActsAsTaggableOn::Tag.create(name: 'Sputz') } + let(:tag) { Gutentag::Tag.create(name: 'Sputz') } it "changes tags name" do put :update, params: {id: tag.id, tag: {name: 'Foo'}} @@ -50,11 +50,11 @@ module Admin end context 'with merg_to param given' do - let(:another_tag) { ActsAsTaggableOn::Tag.create(name: 'Hutzl') } + let(:another_tag) { Gutentag::Tag.create(name: 'Hutzl') } it "replaces tag with other tag" do expect(Alchemy::Tag).to receive(:replace) - expect_any_instance_of(ActsAsTaggableOn::Tag).to receive(:destroy) + expect_any_instance_of(Gutentag::Tag).to receive(:destroy) put :update, params: {id: tag.id, tag: {merge_to: another_tag.id}} expect(response).to redirect_to(admin_tags_path) end diff --git a/spec/dummy/db/migrate/20180227224537_migrate_tags_to_gutentag.rb b/spec/dummy/db/migrate/20180227224537_migrate_tags_to_gutentag.rb new file mode 120000 index 0000000000..6cb63ccb9e --- /dev/null +++ b/spec/dummy/db/migrate/20180227224537_migrate_tags_to_gutentag.rb @@ -0,0 +1 @@ +../../../../db/migrate/20180227224537_migrate_tags_to_gutentag.rb \ No newline at end of file diff --git a/spec/dummy/db/schema.rb b/spec/dummy/db/schema.rb index 894bfe5293..6696b46e71 100644 --- a/spec/dummy/db/schema.rb +++ b/spec/dummy/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180226123013) do +ActiveRecord::Schema.define(version: 20180227224537) do create_table "alchemy_attachments", force: :cascade do |t| t.string "name" @@ -21,7 +21,6 @@ t.integer "updater_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.text "cached_tag_list" t.string "file_uid" t.index ["file_uid"], name: "index_alchemy_attachments_on_file_uid" end @@ -60,7 +59,6 @@ t.integer "creator_id" t.integer "updater_id" t.integer "cell_id" - t.text "cached_tag_list" t.integer "parent_element_id" t.index ["cell_id"], name: "index_alchemy_elements_on_cell_id" t.index ["page_id", "parent_element_id"], name: "index_alchemy_elements_on_page_id_and_parent_element_id" @@ -232,7 +230,6 @@ t.integer "creator_id" t.integer "updater_id" t.integer "language_id" - t.text "cached_tag_list" t.datetime "published_at" t.datetime "public_on" t.datetime "public_until" @@ -255,7 +252,6 @@ t.integer "creator_id" t.integer "updater_id" t.string "upload_hash" - t.text "cached_tag_list" t.string "image_file_uid" t.integer "image_file_size" t.string "image_file_format" @@ -298,35 +294,28 @@ t.datetime "updated_at", null: false end - create_table "locations", force: :cascade do |t| - t.string "name" - t.datetime "created_at" - t.datetime "updated_at" + create_table "gutentag_taggings", force: :cascade do |t| + t.integer "tag_id", null: false + t.string "taggable_type", null: false + t.integer "taggable_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["tag_id", "taggable_id", "taggable_type"], name: "unique_taggings", unique: true + t.index ["tag_id"], name: "index_gutentag_taggings_on_tag_id" + t.index ["taggable_id", "taggable_type"], name: "index_gutentag_taggings_on_taggable_id_and_taggable_type" end - create_table "taggings", force: :cascade do |t| - t.integer "tag_id" - t.string "taggable_type" - t.integer "taggable_id" - t.string "tagger_type" - t.integer "tagger_id" - t.string "context", limit: 128 - t.datetime "created_at" - t.index ["context"], name: "index_taggings_on_context" - t.index ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], name: "taggings_idx", unique: true - t.index ["tag_id"], name: "index_taggings_on_tag_id" - t.index ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context" - t.index ["taggable_id", "taggable_type", "tagger_id", "context"], name: "taggings_idy" - t.index ["taggable_id"], name: "index_taggings_on_taggable_id" - t.index ["taggable_type"], name: "index_taggings_on_taggable_type" - t.index ["tagger_id", "tagger_type"], name: "index_taggings_on_tagger_id_and_tagger_type" - t.index ["tagger_id"], name: "index_taggings_on_tagger_id" + create_table "gutentag_tags", force: :cascade do |t| + t.string "name", null: false + t.integer "taggings_count", default: 0 + t.index ["name"], name: "index_gutentag_tags_on_name", unique: true + t.index ["taggings_count"], name: "index_gutentag_tags_on_taggings_count" end - create_table "tags", force: :cascade do |t| + create_table "locations", force: :cascade do |t| t.string "name" - t.integer "taggings_count", default: 0 - t.index ["name"], name: "index_tags_on_name", unique: true + t.datetime "created_at" + t.datetime "updated_at" end end diff --git a/spec/helpers/alchemy/admin/tags_helper_spec.rb b/spec/helpers/alchemy/admin/tags_helper_spec.rb index 98f8d59496..511080d1ea 100644 --- a/spec/helpers/alchemy/admin/tags_helper_spec.rb +++ b/spec/helpers/alchemy/admin/tags_helper_spec.rb @@ -2,8 +2,8 @@ module Alchemy describe Admin::TagsHelper do - let(:tag) { mock_model(ActsAsTaggableOn::Tag, name: 'foo', count: 1) } - let(:tag2) { mock_model(ActsAsTaggableOn::Tag, name: 'abc', count: 1) } + let(:tag) { mock_model(Gutentag::Tag, name: 'foo', count: 1) } + let(:tag2) { mock_model(Gutentag::Tag, name: 'abc', count: 1) } let(:params) do ActionController::Parameters.new(tagged_with: 'foo') @@ -38,7 +38,7 @@ module Alchemy end context "with lowercase and uppercase tag names mixed" do - let(:tag) { mock_model(ActsAsTaggableOn::Tag, name: 'Foo', count: 1) } + let(:tag) { mock_model(Gutentag::Tag, name: 'Foo', count: 1) } it "tags are sorted alphabetically correctly" do is_expected.to match(/li.+name="#{tag2.name}.+li.+name="#{tag.name}/) diff --git a/spec/models/alchemy/tag_spec.rb b/spec/models/alchemy/tag_spec.rb index 86a46e5d75..3d102cd32a 100644 --- a/spec/models/alchemy/tag_spec.rb +++ b/spec/models/alchemy/tag_spec.rb @@ -14,8 +14,8 @@ module Alchemy allow(picture).to receive(:save).and_return(true) allow(element).to receive(:save).and_return(true) allow(tag).to receive(:taggings).and_return([ - mock_model(ActsAsTaggableOn::Tagging, taggable: picture), - mock_model(ActsAsTaggableOn::Tagging, taggable: element) + mock_model(Gutentag::Tagging, taggable: picture), + mock_model(Gutentag::Tagging, taggable: element) ]) end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 0eac506cbe..8658a8866f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -44,11 +44,6 @@ # Disable rails loggin for faster IO. Remove this if you want to have a test.log Rails.logger.level = 4 -# Fix for MySQL with ActsAsTaggableOn 5 -if ENV['DB'] == 'mysql' - ActsAsTaggableOn.force_binary_collation = true -end - # Configure capybara for integration testing Capybara.register_driver :selenium_chrome_headless do |app| browser_options = ::Selenium::WebDriver::Chrome::Options.new From daa312f709f8e641bf9bab26e9e98fb4c4d3b13c Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Mon, 26 Feb 2018 12:13:54 +0100 Subject: [PATCH 3/3] Add a upgrader for tags Since the acts-as-taggable-on migrations make use of the ActsAsTaggableOn class, we want to skip the tasks inside of these migrations. We renamed the tables anyway, but in order to be able to re-run these migrations without harm it makes sense to skip these migrations. --- lib/alchemy/upgrader/four_point_one.rb | 29 +++++++++++++++++++ .../harden_acts_as_taggable_on_migrations.rb | 27 +++++++++++++++++ lib/tasks/alchemy/upgrade.rake | 25 +++++++++++++++- 3 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 lib/alchemy/upgrader/four_point_one.rb create mode 100644 lib/alchemy/upgrader/tasks/harden_acts_as_taggable_on_migrations.rb diff --git a/lib/alchemy/upgrader/four_point_one.rb b/lib/alchemy/upgrader/four_point_one.rb new file mode 100644 index 0000000000..0349506f7c --- /dev/null +++ b/lib/alchemy/upgrader/four_point_one.rb @@ -0,0 +1,29 @@ +require_relative 'tasks/harden_acts_as_taggable_on_migrations' + +module Alchemy + class Upgrader::FourPointOne < Upgrader + class << self + def harden_acts_as_taggable_on_migrations + desc 'Harden `acts_as_taggable_on_migrations`' + `bundle exec rake railties:install:migrations FROM=acts_as_taggable_on_engine` + Alchemy::Upgrader::Tasks::HardenActsAsTaggableOnMigrations.new.patch_migrations + `bundle exec rake db:migrate` + end + + def alchemy_4_1_todos + notice = <<-NOTE + + Changed tagging provider to Gutentag + ------------------------------------ + + The automatic updater that just ran updated all existing `acts_as_taggable_on_migrations`, + so that they don't blow up if the `acts_as_taggable_on` gem is no longer available. + + All your existing tags have been migrated to `Gutentag::Tag`s. + + NOTE + todo notice, 'Alchemy v4.1 changes' + end + end + end +end diff --git a/lib/alchemy/upgrader/tasks/harden_acts_as_taggable_on_migrations.rb b/lib/alchemy/upgrader/tasks/harden_acts_as_taggable_on_migrations.rb new file mode 100644 index 0000000000..06f16581be --- /dev/null +++ b/lib/alchemy/upgrader/tasks/harden_acts_as_taggable_on_migrations.rb @@ -0,0 +1,27 @@ +require 'thor' + +module Alchemy::Upgrader::Tasks + class HardenActsAsTaggableOnMigrations < Thor + include Thor::Actions + + no_tasks do + def patch_migrations + sentinel = /add_column.*/ + aato_file = Dir.glob('db/migrate/*_add_taggings_counter_cache_to_tags.*.rb').first + if aato_file + inject_into_file aato_file, + "\n\n # inserted by Alchemy CMS upgrader\n return unless defined?(ActsAsTaggableOn)", + { after: sentinel, verbose: true } + end + + sentinel = /def up/ + aato_file = Dir.glob('db/migrate/*_change_collation_for_tag_names.*.rb').first + if aato_file + inject_into_file aato_file, + "\n # inserted by Alchemy CMS upgrader\n return unless defined?(ActsAsTaggableOn)\n", + { after: sentinel, verbose: true } + end + end + end + end +end diff --git a/lib/tasks/alchemy/upgrade.rake b/lib/tasks/alchemy/upgrade.rake index 24bd39c0f1..311ec5492a 100644 --- a/lib/tasks/alchemy/upgrade.rake +++ b/lib/tasks/alchemy/upgrade.rake @@ -4,7 +4,8 @@ require 'alchemy/version' namespace :alchemy do desc "Upgrades your app to AlchemyCMS v#{Alchemy::VERSION}." task upgrade: [ - 'alchemy:upgrade:prepare' + 'alchemy:upgrade:prepare', + 'alchemy:upgrade:4.1:run', 'alchemy:upgrade:4.1:todo' ] do Alchemy::Upgrader.display_todos end @@ -33,5 +34,27 @@ namespace :alchemy do picture.update_column(:image_file_format, picture.image_file_format.to_s.chomp) end end + + desc 'Upgrade Alchemy to v4.1' + task '4.1' => [ + 'alchemy:upgrade:prepare', + 'alchemy:upgrade:4.1:run', + 'alchemy:upgrade:4.1:todo' + ] do + Alchemy::Upgrader.display_todos + end + + namespace '4.1' do + task run: ['alchemy:upgrade:4.1:harden_acts_as_taggable_on_migrations'] + + desc 'Harden acts_as_taggable_on migrations' + task harden_acts_as_taggable_on_migrations: [:environment] do + Alchemy::Upgrader::FourPointOne.harden_acts_as_taggable_on_migrations + end + + task :todo do + Alchemy::Upgrader::FourPointOne.alchemy_4_1_todos + end + end end end