acts-as-taggable-on-mongoid 6.0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +63 -0
  3. data/.gitignore +54 -0
  4. data/.reek.yml +8 -0
  5. data/.rspec +2 -0
  6. data/.rubocop.yml +59 -0
  7. data/.ruby-version +1 -0
  8. data/CODE_OF_CONDUCT.md +74 -0
  9. data/Gemfile +10 -0
  10. data/Gemfile.lock +203 -0
  11. data/LICENSE.txt +21 -0
  12. data/PULL_REQUEST_TEMPLATE.md +11 -0
  13. data/README.md +741 -0
  14. data/Rakefile +8 -0
  15. data/acts-as-taggable-on-mongoid.gemspec +54 -0
  16. data/bin/console +14 -0
  17. data/bin/setup +8 -0
  18. data/codecov.yml +3 -0
  19. data/config/pronto-circleci.yml +7 -0
  20. data/lib/acts-as-taggable-on-mongoid.rb +80 -0
  21. data/lib/acts_as_taggable_on_mongoid/configuration.rb +94 -0
  22. data/lib/acts_as_taggable_on_mongoid/default_parser.rb +120 -0
  23. data/lib/acts_as_taggable_on_mongoid/errors/duplicate_tag_error.rb +9 -0
  24. data/lib/acts_as_taggable_on_mongoid/generic_parser.rb +44 -0
  25. data/lib/acts_as_taggable_on_mongoid/models/tag.rb +103 -0
  26. data/lib/acts_as_taggable_on_mongoid/models/tagging.rb +80 -0
  27. data/lib/acts_as_taggable_on_mongoid/tag_list.rb +169 -0
  28. data/lib/acts_as_taggable_on_mongoid/taggable.rb +131 -0
  29. data/lib/acts_as_taggable_on_mongoid/taggable/changeable.rb +71 -0
  30. data/lib/acts_as_taggable_on_mongoid/taggable/core.rb +219 -0
  31. data/lib/acts_as_taggable_on_mongoid/taggable/list_tags.rb +45 -0
  32. data/lib/acts_as_taggable_on_mongoid/taggable/tag_type_definition.rb +189 -0
  33. data/lib/acts_as_taggable_on_mongoid/taggable/tag_type_definition/attributes.rb +77 -0
  34. data/lib/acts_as_taggable_on_mongoid/taggable/tag_type_definition/changeable.rb +140 -0
  35. data/lib/acts_as_taggable_on_mongoid/taggable/tag_type_definition/names.rb +39 -0
  36. data/lib/acts_as_taggable_on_mongoid/taggable/utils/tag_list_diff.rb +121 -0
  37. data/lib/acts_as_taggable_on_mongoid/version.rb +5 -0
  38. metadata +352 -0
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -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
@@ -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__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,3 @@
1
+ codecov:
2
+ ci:
3
+ - CircleCI
@@ -0,0 +1,7 @@
1
+ github:
2
+ org: "CardTapp"
3
+ repo: "acts-as-taggable-on-mongoid"
4
+ pronto:
5
+ comments_on_diff: true
6
+ reviews_on_diff: true
7
+ report_status: true
@@ -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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsTaggableOnMongoid
4
+ module Errors
5
+ # Error raised if a Tag already exists but cannot be found appropriately when trying to create new tags.
6
+ class DuplicateTagError < StandardError
7
+ end
8
+ end
9
+ 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