acts-as-taggable-on-mongoid 6.0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.circleci/config.yml +63 -0
- data/.gitignore +54 -0
- data/.reek.yml +8 -0
- data/.rspec +2 -0
- data/.rubocop.yml +59 -0
- data/.ruby-version +1 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +203 -0
- data/LICENSE.txt +21 -0
- data/PULL_REQUEST_TEMPLATE.md +11 -0
- data/README.md +741 -0
- data/Rakefile +8 -0
- data/acts-as-taggable-on-mongoid.gemspec +54 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/codecov.yml +3 -0
- data/config/pronto-circleci.yml +7 -0
- data/lib/acts-as-taggable-on-mongoid.rb +80 -0
- data/lib/acts_as_taggable_on_mongoid/configuration.rb +94 -0
- data/lib/acts_as_taggable_on_mongoid/default_parser.rb +120 -0
- data/lib/acts_as_taggable_on_mongoid/errors/duplicate_tag_error.rb +9 -0
- data/lib/acts_as_taggable_on_mongoid/generic_parser.rb +44 -0
- data/lib/acts_as_taggable_on_mongoid/models/tag.rb +103 -0
- data/lib/acts_as_taggable_on_mongoid/models/tagging.rb +80 -0
- data/lib/acts_as_taggable_on_mongoid/tag_list.rb +169 -0
- data/lib/acts_as_taggable_on_mongoid/taggable.rb +131 -0
- data/lib/acts_as_taggable_on_mongoid/taggable/changeable.rb +71 -0
- data/lib/acts_as_taggable_on_mongoid/taggable/core.rb +219 -0
- data/lib/acts_as_taggable_on_mongoid/taggable/list_tags.rb +45 -0
- data/lib/acts_as_taggable_on_mongoid/taggable/tag_type_definition.rb +189 -0
- data/lib/acts_as_taggable_on_mongoid/taggable/tag_type_definition/attributes.rb +77 -0
- data/lib/acts_as_taggable_on_mongoid/taggable/tag_type_definition/changeable.rb +140 -0
- data/lib/acts_as_taggable_on_mongoid/taggable/tag_type_definition/names.rb +39 -0
- data/lib/acts_as_taggable_on_mongoid/taggable/utils/tag_list_diff.rb +121 -0
- data/lib/acts_as_taggable_on_mongoid/version.rb +5 -0
- metadata +352 -0
data/Rakefile
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path("lib", __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require "acts_as_taggable_on_mongoid/version"
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = "acts-as-taggable-on-mongoid"
|
9
|
+
spec.version = ActsAsTaggableOnMongoid::VERSION
|
10
|
+
spec.authors = ["RealNobody"]
|
11
|
+
spec.email = ["admin@cardtapp.com"]
|
12
|
+
|
13
|
+
spec.summary = "A partial mongoid implementation of tagging based on/inspired by acts-as-taggable-on."
|
14
|
+
spec.description = "A partial mongoid implementation of tagging based on/inspired by acts-as-taggable-on."
|
15
|
+
spec.homepage = "http://www.cardtapp.com"
|
16
|
+
spec.license = "MIT"
|
17
|
+
|
18
|
+
# # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
19
|
+
# # to allow pushing to a single host or delete this section to allow pushing to any host.
|
20
|
+
# if spec.respond_to?(:metadata)
|
21
|
+
# spec.metadata["allowed_push_host"] = "http://RubyGems.org"
|
22
|
+
# else
|
23
|
+
# raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
24
|
+
# end
|
25
|
+
|
26
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
f.match(%r{^(test|spec|features)/})
|
28
|
+
end
|
29
|
+
|
30
|
+
spec.bindir = "exe"
|
31
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
32
|
+
spec.require_paths = ["lib"]
|
33
|
+
|
34
|
+
spec.add_dependency "activesupport", "~> 4.2"
|
35
|
+
spec.add_dependency "mongoid", "~> 5.2"
|
36
|
+
|
37
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
38
|
+
spec.add_development_dependency "codecov", "~> 0.1", "~> 0.1.0"
|
39
|
+
spec.add_development_dependency "cornucopia"
|
40
|
+
spec.add_development_dependency "database_cleaner"
|
41
|
+
spec.add_development_dependency "pronto"
|
42
|
+
spec.add_development_dependency "pronto-brakeman"
|
43
|
+
spec.add_development_dependency "pronto-circleci"
|
44
|
+
spec.add_development_dependency "pronto-fasterer"
|
45
|
+
spec.add_development_dependency "pronto-rails_best_practices"
|
46
|
+
spec.add_development_dependency "pronto-reek"
|
47
|
+
spec.add_development_dependency "pronto-rubocop"
|
48
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
49
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
50
|
+
spec.add_development_dependency "rspec_junit_formatter", "~> 0.3.0"
|
51
|
+
spec.add_development_dependency "rubocop"
|
52
|
+
spec.add_development_dependency "simplecov"
|
53
|
+
spec.add_development_dependency "simplecov-rcov"
|
54
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "acts-as-taggable-on-mongoid"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/codecov.yml
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "acts_as_taggable_on_mongoid/version"
|
4
|
+
|
5
|
+
# The base module for the gem under which all classes are namespaced.
|
6
|
+
module ActsAsTaggableOnMongoid
|
7
|
+
extend ::ActiveSupport::Autoload
|
8
|
+
|
9
|
+
# rubocop:disable Metrics/BlockLength
|
10
|
+
|
11
|
+
eager_autoload do
|
12
|
+
autoload :Configuration
|
13
|
+
autoload :TagList
|
14
|
+
autoload :GenericParser
|
15
|
+
autoload :DefaultParser
|
16
|
+
# autoload :TagsHelper
|
17
|
+
|
18
|
+
autoload_under "taggable/tag_type_definition" do
|
19
|
+
autoload :Attributes
|
20
|
+
autoload "Changeable"
|
21
|
+
autoload :Names
|
22
|
+
end
|
23
|
+
|
24
|
+
autoload_under "taggable/utils" do
|
25
|
+
autoload :TagListDiff
|
26
|
+
end
|
27
|
+
|
28
|
+
autoload_under :Taggable do
|
29
|
+
# autoload :Cache
|
30
|
+
# autoload :Collection
|
31
|
+
autoload :Core
|
32
|
+
autoload :Changeable
|
33
|
+
autoload :TagTypeDefinition
|
34
|
+
autoload :ListTags
|
35
|
+
# autoload :Ownership
|
36
|
+
# autoload :Related
|
37
|
+
end
|
38
|
+
|
39
|
+
autoload :Taggable
|
40
|
+
|
41
|
+
autoload_under :Models do
|
42
|
+
autoload :Tag
|
43
|
+
autoload :Tagging
|
44
|
+
# autoload :Tagger
|
45
|
+
end
|
46
|
+
|
47
|
+
autoload_under :Errors do
|
48
|
+
autoload :DuplicateTagError
|
49
|
+
end
|
50
|
+
|
51
|
+
# autoload :Utils
|
52
|
+
# autoload :Compatibility
|
53
|
+
end
|
54
|
+
|
55
|
+
# rubocop:enable Metrics/BlockLength
|
56
|
+
|
57
|
+
def self.configuration
|
58
|
+
@configuration ||= Configuration.new
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.configure
|
62
|
+
yield configuration if block_given?
|
63
|
+
end
|
64
|
+
|
65
|
+
# :reek:ManualDispatch
|
66
|
+
def self.method_missing(method_name, *args, &block)
|
67
|
+
configuration.respond_to?(method_name) ? configuration.public_send(method_name, *args, &block) : super
|
68
|
+
end
|
69
|
+
|
70
|
+
# :reek:BooleanParameter
|
71
|
+
# :reek:ManualDispatch
|
72
|
+
def self.respond_to_missing?(method_name, _include_private = false)
|
73
|
+
configuration.respond_to?(method_name) || super
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
::ActiveSupport.on_load(:mongoid) do
|
78
|
+
include ActsAsTaggableOnMongoid::Taggable
|
79
|
+
# include ActsAsTaggableOn::Tagger
|
80
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActsAsTaggableOnMongoid
|
4
|
+
# A configuration class for the ActsAsTaggableOnMongoid gem.
|
5
|
+
#
|
6
|
+
# These global configurations are default configuration settings for the gem and will drive individual
|
7
|
+
# tag definitions if the tag does not specify a different value when the tag is defined/added to a model.
|
8
|
+
#
|
9
|
+
# The configuration options are:
|
10
|
+
# * force_lowercase - If set, values stored into the tags and taggings table will be downcased before being saved.
|
11
|
+
# this allows for case-insensitive comparisons of values in the database.
|
12
|
+
#
|
13
|
+
# Because Mongo does not support methods on database calls like SQL does, the strict_case_match
|
14
|
+
# option from ActsAsTaggableOn is not supported. All comparisons are done case sensitive.
|
15
|
+
# * force_parameterize - If set, values stored into the tags and taggings table will be parameterized before being saved.
|
16
|
+
# * remove_unused_tags - If set when a Tagging is destroyed, the correlated Tag will also be destroyed if there are no
|
17
|
+
# other Taggings associated with that Tag.
|
18
|
+
# * default_parser - The parser class to be used to convert strings to arrays of values and to convert arrays of
|
19
|
+
# values back to strings. See GenericParser for details on creating your own parser.
|
20
|
+
# * tags_table - The model class to be used to store Tag objects.
|
21
|
+
# Custom classes can be used if you wish to store Tags in different tables for some tags if desired,
|
22
|
+
# but it is expected that the Tags table will have equivalent fields, scopes and methods as the
|
23
|
+
# ActsAsTaggableOn::Models::Tag class. In most cases, you can derive from the Tag class and simply
|
24
|
+
# override the relations or methods to achieve any differences you want/need.
|
25
|
+
# * taggings_table - The model class to be used to store Tagging objects.
|
26
|
+
# Custom classes can be used if you wish to store Taggings in different tables for some tags if desired,
|
27
|
+
# but it is expected that the Taggings table will have equivalent fields, scopes and methods as the
|
28
|
+
# ActsAsTaggableOn::Models::Tagging class. In most cases, you can derive from the Tagging class and simply
|
29
|
+
# override the relations or methods to achieve any differences you want/need.
|
30
|
+
#
|
31
|
+
# Unsupported configurations:
|
32
|
+
# * strict_case_match - This is not supported. Use the `forc_lowercase` option to achieve case insensitive data storage and
|
33
|
+
# comparisons.
|
34
|
+
# * tags_counter - This is defined in the Tags and Taggings models, and is not configured through the configuration
|
35
|
+
# nor tag defnitions.
|
36
|
+
#
|
37
|
+
# See spec tests for examples in creating your own Tag or Tagging models to customize this behavior.
|
38
|
+
|
39
|
+
# :reek:UtilityFunction
|
40
|
+
# :reek:Attribute
|
41
|
+
# :reek:TooManyInstanceVariables
|
42
|
+
class Configuration
|
43
|
+
attr_accessor :force_lowercase,
|
44
|
+
:force_parameterize,
|
45
|
+
:remove_unused_tags,
|
46
|
+
:default_parser,
|
47
|
+
:tags_table,
|
48
|
+
:taggings_table,
|
49
|
+
:preserve_tag_order
|
50
|
+
|
51
|
+
# For duck compatibility with ActsAsTaggableOn. Do not use.
|
52
|
+
def tags_counter
|
53
|
+
Mongoid.logger.warn "tags_counter is not supported."
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def strict_case_match
|
58
|
+
Mongoid.logger.warn "strict_case_match= is not supported."
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
|
62
|
+
# rubocop:disable Lint/Void
|
63
|
+
|
64
|
+
def tags_counter=(_value)
|
65
|
+
Mongoid.logger.warn "tags_counter is not supported."
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
|
69
|
+
def strict_case_match=(_value)
|
70
|
+
Mongoid.logger.warn "strict_case_match= is not supported."
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
|
74
|
+
# rubocop:enable Lint/Void
|
75
|
+
|
76
|
+
alias force_lowercase? force_lowercase
|
77
|
+
alias force_parameterize? force_parameterize
|
78
|
+
alias preserve_tag_order? preserve_tag_order
|
79
|
+
alias remove_unused_tags? remove_unused_tags
|
80
|
+
alias strict_case_match? strict_case_match
|
81
|
+
alias tags_counter? tags_counter
|
82
|
+
|
83
|
+
def initialize
|
84
|
+
@force_lowercase = false
|
85
|
+
@force_parameterize = false
|
86
|
+
@preserve_tag_order = false
|
87
|
+
@remove_unused_tags = false
|
88
|
+
@tags_counter = true
|
89
|
+
@default_parser = ActsAsTaggableOnMongoid::DefaultParser
|
90
|
+
@tags_table = ActsAsTaggableOnMongoid::Models::Tag
|
91
|
+
@taggings_table = ActsAsTaggableOnMongoid::Models::Tagging
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActsAsTaggableOnMongoid
|
4
|
+
##
|
5
|
+
# Returns a new Array using the given tag string.
|
6
|
+
#
|
7
|
+
# Parsing is done based on an array of delimiters that is set at the class level. Parsing will split
|
8
|
+
# on any delimiter value that is found. By default strings are split by commas (,).
|
9
|
+
#
|
10
|
+
# To allow more complex strings, parsing will parse out quoted strings (either single or double quoted)as a block.
|
11
|
+
# (This is only partially implemented for quick not accurate/complete implementation that is
|
12
|
+
# "good enough" for most expected tags.)
|
13
|
+
#
|
14
|
+
# examples:
|
15
|
+
#
|
16
|
+
# # Delimiters
|
17
|
+
# # You can set the delimiters to a single value:
|
18
|
+
# DefaultParser.delimiter = "\\|"
|
19
|
+
#
|
20
|
+
# # You can set the delimiters to an array value:
|
21
|
+
# DefaultParser.delimiter = %w[\\| , break_here]
|
22
|
+
#
|
23
|
+
# # Parsing a string by multiple delimters
|
24
|
+
# DefaultParser.new("a|stupid,stringbreak_hereparses").parse
|
25
|
+
# # > ["a", "stupid", "string", "parses"]
|
26
|
+
#
|
27
|
+
# # Parsing works with simple quoted strings:
|
28
|
+
# DefaultParser.new("a,\"more,interesting\",string").parse
|
29
|
+
# # > ["a", "more,interesting", "string"]
|
30
|
+
class DefaultParser < GenericParser
|
31
|
+
class_attribute :delimiter
|
32
|
+
|
33
|
+
def parse
|
34
|
+
@tags = [].tap do |tag_list|
|
35
|
+
tags.each do |tag|
|
36
|
+
string = tag.to_s.dup
|
37
|
+
|
38
|
+
extract_quoted_strings(string, tag_list, double_quote_pattern)
|
39
|
+
extract_quoted_strings(string, tag_list, single_quote_pattern)
|
40
|
+
|
41
|
+
# split the string by the delimiter
|
42
|
+
# and add to the tag_list
|
43
|
+
tag_list.concat(string.split(delimiter_regex))
|
44
|
+
end
|
45
|
+
end.flatten
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_s
|
49
|
+
tag_list = tags.frozen? ? tags.dup : tags
|
50
|
+
|
51
|
+
join_delimiter = ActsAsTaggableOnMongoid::DefaultParser.delimiters.first
|
52
|
+
|
53
|
+
tag_list.map do |name|
|
54
|
+
name.index(escape_regex) ? "\"#{name}\"" : name
|
55
|
+
end.join(join_delimiter)
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.delimiters
|
59
|
+
Array.wrap(delimiter.presence || DEFAULT_DELIMITER)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def escape_regex
|
65
|
+
@escape_regex ||= Regexp.new((Array.wrap(ActsAsTaggableOnMongoid::DefaultParser.delimiters) + %w[" ']).join("|"))
|
66
|
+
end
|
67
|
+
|
68
|
+
# :reek:UtilityFunction
|
69
|
+
def extract_quoted_strings(string, tag_list, quote_pattern)
|
70
|
+
string.gsub!(quote_pattern) do
|
71
|
+
# Append the matched tag to the tag list
|
72
|
+
tag_list << Regexp.last_match[2]
|
73
|
+
# Return the matched delimiter ($3) to replace the matched items
|
74
|
+
""
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def delimiter
|
79
|
+
# Parse the quoted tags
|
80
|
+
delimiter_list = self.class.delimiters
|
81
|
+
# Separate multiple delimiters by bitwise operator
|
82
|
+
delimiter_list = delimiter_list.join("|") if delimiter_list.is_a?(Array)
|
83
|
+
delimiter_list
|
84
|
+
end
|
85
|
+
|
86
|
+
def delimiter_regex
|
87
|
+
Regexp.new(delimiter)
|
88
|
+
end
|
89
|
+
|
90
|
+
# ( # Tag start delimiter ($1)
|
91
|
+
# \A | # Either string start or
|
92
|
+
# #{delimiter} # a delimiter
|
93
|
+
# )
|
94
|
+
# \s*" # quote (") optionally preceded by whitespace
|
95
|
+
# (.*?) # Tag ($2)
|
96
|
+
# "\s* # quote (") optionally followed by whitespace
|
97
|
+
# (?= # Tag end delimiter (not consumed; is zero-length lookahead)
|
98
|
+
# #{delimiter}\s* | # Either a delimiter optionally followed by whitespace or
|
99
|
+
# \z # string end
|
100
|
+
# )
|
101
|
+
def double_quote_pattern
|
102
|
+
/(\A|#{delimiter})\s*"(.*?)"\s*(?=#{delimiter}\s*|\z)/
|
103
|
+
end
|
104
|
+
|
105
|
+
# ( # Tag start delimiter ($1)
|
106
|
+
# \A | # Either string start or
|
107
|
+
# #{delimiter} # a delimiter
|
108
|
+
# )
|
109
|
+
# \s*' # quote (') optionally preceded by whitespace
|
110
|
+
# (.*?) # Tag ($2)
|
111
|
+
# '\s* # quote (') optionally followed by whitespace
|
112
|
+
# (?= # Tag end delimiter (not consumed; is zero-length lookahead)
|
113
|
+
# #{delimiter}\s* | delimiter_list # Either a delimiter optionally followed by whitespace or
|
114
|
+
# \z # string end
|
115
|
+
# )
|
116
|
+
def single_quote_pattern
|
117
|
+
/(\A|#{delimiter})\s*'(.*?)'\s*(?=#{delimiter}\s*|\z)/
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActsAsTaggableOnMongoid
|
4
|
+
##
|
5
|
+
# Returns a new list of tags (array of strings) using the given tag string.
|
6
|
+
#
|
7
|
+
# Example:
|
8
|
+
# tag_list = ActsAsTaggableOn::GenericParser.new.parse("One , Two, Three")
|
9
|
+
# tag_list # ["One ", " Two", " Three"]
|
10
|
+
#
|
11
|
+
# All parsers are required to support two methods:
|
12
|
+
# * parse - parse the tag_list into an array of strings
|
13
|
+
# * to_s - return a parsed array of tags (may be passed in parsed) in a format that
|
14
|
+
# is suitable for parsing.
|
15
|
+
#
|
16
|
+
# NOTE: The ablitity to parse a list of tags and convert it to a string then back to
|
17
|
+
# the same list of tags is dependent on the complexity of the parser. This is
|
18
|
+
# not actually assumed to be true, though it is best if it is.
|
19
|
+
#
|
20
|
+
# Cleansing the list of tags for the tag is the responsibility of the tags TagList which knows
|
21
|
+
# if the tags need to be stripped, downcased, etc. The parser need only return an array of
|
22
|
+
# strings that are split out.
|
23
|
+
class GenericParser
|
24
|
+
attr_reader :tags
|
25
|
+
|
26
|
+
DEFAULT_DELIMITER = ","
|
27
|
+
|
28
|
+
def initialize(*tag_list)
|
29
|
+
@tags = tag_list.flatten
|
30
|
+
end
|
31
|
+
|
32
|
+
def parse
|
33
|
+
@tags = [].tap do |tag_list|
|
34
|
+
tags.each do |tag|
|
35
|
+
tag_list.concat tag.split(DEFAULT_DELIMITER).map(&:strip).reject(&:empty?)
|
36
|
+
end
|
37
|
+
end.flatten
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_s
|
41
|
+
tags.join(DEFAULT_DELIMITER)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|