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.
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