From 0608e4157d358d92f1f6ab16ba72809a37c22511 Mon Sep 17 00:00:00 2001 From: ProGM Date: Mon, 25 Aug 2014 11:45:03 +0200 Subject: [PATCH] Adding configurable custom parsers for tags. --- CHANGELOG.md | 2 + README.md | 37 +++++++++ lib/acts-as-taggable-on.rb | 14 +++- lib/acts_as_taggable_on/default_parser.rb | 79 +++++++++++++++++++ lib/acts_as_taggable_on/generic_parser.rb | 19 +++++ lib/acts_as_taggable_on/tag_list.rb | 12 ++- lib/acts_as_taggable_on/tag_list_parser.rb | 71 ++--------------- lib/acts_as_taggable_on/taggable/core.rb | 8 +- lib/acts_as_taggable_on/taggable/ownership.rb | 2 +- .../default_parser_spec.rb | 47 +++++++++++ .../generic_parser_spec.rb | 14 ++++ spec/acts_as_taggable_on/tag_list_spec.rb | 29 +++++++ 12 files changed, 260 insertions(+), 74 deletions(-) create mode 100644 lib/acts_as_taggable_on/default_parser.rb create mode 100644 lib/acts_as_taggable_on/generic_parser.rb create mode 100644 spec/acts_as_taggable_on/default_parser_spec.rb create mode 100644 spec/acts_as_taggable_on/generic_parser_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 59d5ffdc0..938b79437 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ As such, a _Feature_ would map to either major or minor. A _bug fix_ to a patch. * Breaking Changes * Features + * [@ProGM Support for custom parsers for tags](https://github.com/mbleigh/acts-as-taggable-on/pull/579) + * Fixes * Performance * Misc diff --git a/README.md b/README.md index 53a84dd1e..2a7cb175c 100644 --- a/README.md +++ b/README.md @@ -237,6 +237,43 @@ to allow for dynamic tag contexts (this could be user generated tag contexts!) User.tagged_with("same", :on => :customs) # => [@user] ``` +### Tag Parsers + +If you want to change how tags are parsed, you can define a your own implementation: + +```ruby +class MyParser < ActsAsTaggableOn::GenericParser + def parse + TagList.new.tap do |tag_list| + tag_list.add @tag_list.split('|') + end + end +end +``` + +Now you can use this parser, passing it as parameter: + +```ruby +@user = User.new(:name => "Bobby") +@user.tag_list = "east, south" +@user.tag_list.add("north|west", parser: MyParser) +@user.tag_list # => ["north", "east", "south", "west"] + +# Or also: +@user.tag_list.parser = MyParser +@user.tag_list.add("north|west") +@user.tag_list # => ["north", "east", "south", "west"] +``` + +Or change it globally: + +```ruby +ActsAsTaggable.default_parser = MyParser +@user = User.new(:name => "Bobby") +@user.tag_list = "east|south" +@user.tag_list # => ["east", "south"] +``` + ### Tag Ownership Tags can have owners: diff --git a/lib/acts-as-taggable-on.rb b/lib/acts-as-taggable-on.rb index e125d92a6..30e4b85e0 100644 --- a/lib/acts-as-taggable-on.rb +++ b/lib/acts-as-taggable-on.rb @@ -12,6 +12,8 @@ module ActsAsTaggableOn autoload :Tag autoload :TagList + autoload :GenericParser + autoload :DefaultParser autoload :TagListParser autoload :Taggable autoload :Tagger @@ -57,7 +59,7 @@ def self.glue class Configuration attr_accessor :delimiter, :force_lowercase, :force_parameterize, - :strict_case_match, :remove_unused_tags + :strict_case_match, :remove_unused_tags, :default_parser def initialize @delimiter = ',' @@ -65,6 +67,16 @@ def initialize @force_parameterize = false @strict_case_match = false @remove_unused_tags = false + @default_parser = DefaultParser + end + + def delimiter=(string) + ActiveRecord::Base.logger.warn < foo ) # Users that are tagged with just awesome and cool by 'foo' # User.tagged_with(["awesome", "cool"], :owned_by => foo, :start_at => Date.today ) # Users that are tagged with just awesome, cool by 'foo' and starting today def tagged_with(tags, options = {}) - tag_list = ActsAsTaggableOn::TagListParser.parse(tags) + tag_list = ActsAsTaggableOn.default_parser.new(tags).parse options = options.dup empty_result = where('1 = 0') @@ -278,7 +278,7 @@ def tag_list_cache_on(context) if instance_variable_get(variable_name) instance_variable_get(variable_name) elsif cached_tag_list_on(context) && self.class.caching_tag_list_on?(context) - instance_variable_set(variable_name, ActsAsTaggableOn::TagListParser.parse(cached_tag_list_on(context))) + instance_variable_set(variable_name, ActsAsTaggableOn.default_parser.new(cached_tag_list_on(context)).parse) else instance_variable_set(variable_name, ActsAsTaggableOn::TagList.new(tags_on(context).map(&:name))) end @@ -328,7 +328,7 @@ def set_tag_list_on(context, new_list) variable_name = "@#{context.to_s.singularize}_list" process_dirty_object(context, new_list) unless custom_contexts.include?(context.to_s) - instance_variable_set(variable_name, ActsAsTaggableOn::TagListParser.parse(new_list)) + instance_variable_set(variable_name, ActsAsTaggableOn.default_parser.new(new_list).parse) end def tagging_contexts @@ -348,7 +348,7 @@ def process_dirty_object(context, new_list) if self.class.preserve_tag_order changed_attributes[attrib] = old if old.to_s != value.to_s else - changed_attributes[attrib] = old.to_s if old.sort != ActsAsTaggableOn::TagListParser.parse(value).sort + changed_attributes[attrib] = old.to_s if old.sort != ActsAsTaggableOn.default_parser.new(value).parse.sort end end end diff --git a/lib/acts_as_taggable_on/taggable/ownership.rb b/lib/acts_as_taggable_on/taggable/ownership.rb index a893fe9fb..ad63f7c7f 100644 --- a/lib/acts_as_taggable_on/taggable/ownership.rb +++ b/lib/acts_as_taggable_on/taggable/ownership.rb @@ -63,7 +63,7 @@ def set_owner_tag_list_on(owner, context, new_list) cache = cached_owned_tag_list_on(context) - cache[owner] = ActsAsTaggableOn::TagListParser.parse(new_list) + cache[owner] = ActsAsTaggableOn.default_parser.new(new_list).parse end def reload(*args) diff --git a/spec/acts_as_taggable_on/default_parser_spec.rb b/spec/acts_as_taggable_on/default_parser_spec.rb new file mode 100644 index 000000000..3957ce044 --- /dev/null +++ b/spec/acts_as_taggable_on/default_parser_spec.rb @@ -0,0 +1,47 @@ +# -*- encoding : utf-8 -*- +require 'spec_helper' + +describe ActsAsTaggableOn::DefaultParser do + it '#parse should return empty array if empty array is passed' do + parser = ActsAsTaggableOn::DefaultParser.new([]) + expect(parser.parse).to be_empty + end + + describe 'Multiple Delimiter' do + before do + @old_delimiter = ActsAsTaggableOn.delimiter + end + + after do + ActsAsTaggableOn.delimiter = @old_delimiter + end + + it 'should separate tags by delimiters' do + ActsAsTaggableOn.delimiter = [',', ' ', '\|'] + parser = ActsAsTaggableOn::DefaultParser.new('cool, data|I have') + expect(parser.parse.to_s).to eq('cool, data, I, have') + end + + it 'should escape quote' do + ActsAsTaggableOn.delimiter = [',', ' ', '\|'] + parser = ActsAsTaggableOn::DefaultParser.new("'I have'|cool, data") + expect(parser.parse.to_s).to eq('"I have", cool, data') + + parser = ActsAsTaggableOn::DefaultParser.new('"I, have"|cool, data') + expect(parser.parse.to_s).to eq('"I, have", cool, data') + end + + it 'should work for utf8 delimiter and long delimiter' do + ActsAsTaggableOn.delimiter = [',', '的', '可能是'] + parser = ActsAsTaggableOn::DefaultParser.new('我的东西可能是不见了,还好有备份') + expect(parser.parse.to_s).to eq('我, 东西, 不见了, 还好有备份') + end + + it 'should work for multiple quoted tags' do + ActsAsTaggableOn.delimiter = [','] + parser = ActsAsTaggableOn::DefaultParser.new('"Ruby Monsters","eat Katzenzungen"') + expect(parser.parse.to_s).to eq('Ruby Monsters, eat Katzenzungen') + end + end + +end diff --git a/spec/acts_as_taggable_on/generic_parser_spec.rb b/spec/acts_as_taggable_on/generic_parser_spec.rb new file mode 100644 index 000000000..e131b5c7b --- /dev/null +++ b/spec/acts_as_taggable_on/generic_parser_spec.rb @@ -0,0 +1,14 @@ +# -*- encoding : utf-8 -*- +require 'spec_helper' + +describe ActsAsTaggableOn::GenericParser do + it '#parse should return empty array if empty tag string is passed' do + tag_list = ActsAsTaggableOn::GenericParser.new('') + expect(tag_list.parse).to be_empty + end + + it '#parse should separate tags by comma' do + tag_list = ActsAsTaggableOn::GenericParser.new('cool,data,,I,have') + expect(tag_list.parse).to eq(%w(cool data I have)) + end +end diff --git a/spec/acts_as_taggable_on/tag_list_spec.rb b/spec/acts_as_taggable_on/tag_list_spec.rb index 2f8644cbd..50e39f86e 100644 --- a/spec/acts_as_taggable_on/tag_list_spec.rb +++ b/spec/acts_as_taggable_on/tag_list_spec.rb @@ -114,7 +114,36 @@ ActsAsTaggableOn.force_lowercase = false end + end + + describe 'custom parser' do + let(:parser) { double(parse: %w(cool wicked)) } + let(:parser_class) { stub_const('MyParser', Class) } + + it 'should use a the default parser if none is set as parameter' do + allow(ActsAsTaggableOn.default_parser).to receive(:new).and_return(parser) + ActsAsTaggableOn::TagList.new('cool, wicked', parse: true) + + expect(parser).to have_received(:parse) + end + + it 'should use the custom parser passed as parameter' do + allow(parser_class).to receive(:new).and_return(parser) + ActsAsTaggableOn::TagList.new('cool, wicked', parser: parser_class) + + expect(parser).to have_received(:parse) + end + + it 'should use the parser setted as attribute' do + allow(parser_class).to receive(:new).with('new, tag').and_return(parser) + + tag_list = ActsAsTaggableOn::TagList.new('example') + tag_list.parser = parser_class + tag_list.add('new, tag', parse: true) + + expect(parser).to have_received(:parse) + end end