nano-max-tool 0.0.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 (37) hide show
  1. checksums.yaml +7 -0
  2. data/acts-as-taggable-on-13.0.0/LICENSE.md +20 -0
  3. data/acts-as-taggable-on-13.0.0/db/migrate/1_acts_as_taggable_on_migration.rb +33 -0
  4. data/acts-as-taggable-on-13.0.0/db/migrate/2_add_missing_unique_indices.rb +23 -0
  5. data/acts-as-taggable-on-13.0.0/db/migrate/3_add_taggings_counter_cache_to_tags.rb +16 -0
  6. data/acts-as-taggable-on-13.0.0/db/migrate/4_add_missing_taggable_index.rb +12 -0
  7. data/acts-as-taggable-on-13.0.0/db/migrate/5_change_collation_for_tag_names.rb +12 -0
  8. data/acts-as-taggable-on-13.0.0/db/migrate/6_add_missing_indexes_on_taggings.rb +24 -0
  9. data/acts-as-taggable-on-13.0.0/db/migrate/7_add_tenant_to_taggings.rb +13 -0
  10. data/acts-as-taggable-on-13.0.0/lib/acts-as-taggable-on/default_parser.rb +77 -0
  11. data/acts-as-taggable-on-13.0.0/lib/acts-as-taggable-on/engine.rb +6 -0
  12. data/acts-as-taggable-on-13.0.0/lib/acts-as-taggable-on/generic_parser.rb +21 -0
  13. data/acts-as-taggable-on-13.0.0/lib/acts-as-taggable-on/tag.rb +138 -0
  14. data/acts-as-taggable-on-13.0.0/lib/acts-as-taggable-on/tag_list.rb +103 -0
  15. data/acts-as-taggable-on-13.0.0/lib/acts-as-taggable-on/taggable/caching.rb +46 -0
  16. data/acts-as-taggable-on-13.0.0/lib/acts-as-taggable-on/taggable/collection.rb +220 -0
  17. data/acts-as-taggable-on-13.0.0/lib/acts-as-taggable-on/taggable/core.rb +333 -0
  18. data/acts-as-taggable-on-13.0.0/lib/acts-as-taggable-on/taggable/ownership.rb +146 -0
  19. data/acts-as-taggable-on-13.0.0/lib/acts-as-taggable-on/taggable/related.rb +84 -0
  20. data/acts-as-taggable-on-13.0.0/lib/acts-as-taggable-on/taggable/tag_list_type.rb +8 -0
  21. data/acts-as-taggable-on-13.0.0/lib/acts-as-taggable-on/taggable/tagged_with_query/all_tags_query.rb +115 -0
  22. data/acts-as-taggable-on-13.0.0/lib/acts-as-taggable-on/taggable/tagged_with_query/any_tags_query.rb +74 -0
  23. data/acts-as-taggable-on-13.0.0/lib/acts-as-taggable-on/taggable/tagged_with_query/exclude_tags_query.rb +85 -0
  24. data/acts-as-taggable-on-13.0.0/lib/acts-as-taggable-on/taggable/tagged_with_query/query_base.rb +78 -0
  25. data/acts-as-taggable-on-13.0.0/lib/acts-as-taggable-on/taggable/tagged_with_query.rb +17 -0
  26. data/acts-as-taggable-on-13.0.0/lib/acts-as-taggable-on/taggable.rb +119 -0
  27. data/acts-as-taggable-on-13.0.0/lib/acts-as-taggable-on/tagger.rb +85 -0
  28. data/acts-as-taggable-on-13.0.0/lib/acts-as-taggable-on/tagging.rb +40 -0
  29. data/acts-as-taggable-on-13.0.0/lib/acts-as-taggable-on/tags_helper.rb +17 -0
  30. data/acts-as-taggable-on-13.0.0/lib/acts-as-taggable-on/utils.rb +35 -0
  31. data/acts-as-taggable-on-13.0.0/lib/acts-as-taggable-on/version.rb +5 -0
  32. data/acts-as-taggable-on-13.0.0/lib/acts-as-taggable-on.rb +118 -0
  33. data/acts-as-taggable-on-13.0.0/lib/tasks/example/acts-as-taggable-on.rb.example +8 -0
  34. data/acts-as-taggable-on-13.0.0/lib/tasks/install_initializer.rake +23 -0
  35. data/acts-as-taggable-on-13.0.0/lib/tasks/tags_collate_utf8.rake +21 -0
  36. data/nano-max-tool.gemspec +12 -0
  37. metadata +76 -0
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsTaggableOn
4
+ module Taggable
5
+ module Related
6
+ def self.included(base)
7
+ base.extend ActsAsTaggableOn::Taggable::Related::ClassMethods
8
+ base.initialize_acts_as_taggable_on_related
9
+ end
10
+
11
+ module ClassMethods
12
+ def initialize_acts_as_taggable_on_related
13
+ tag_types.map(&:to_s).each do |tag_type|
14
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
15
+ def find_related_#{tag_type}(options = {})
16
+ related_tags_for('#{tag_type}', self.class, options)
17
+ end
18
+ alias_method :find_related_on_#{tag_type}, :find_related_#{tag_type}
19
+
20
+ def find_related_#{tag_type}_for(klass, options = {})
21
+ related_tags_for('#{tag_type}', klass, options)
22
+ end
23
+ RUBY
24
+ end
25
+ end
26
+
27
+ def acts_as_taggable_on(*args)
28
+ super(*args)
29
+ initialize_acts_as_taggable_on_related
30
+ end
31
+ end
32
+
33
+ def find_matching_contexts(search_context, result_context, options = {})
34
+ matching_contexts_for(search_context.to_s, result_context.to_s, self.class, options)
35
+ end
36
+
37
+ def find_matching_contexts_for(klass, search_context, result_context, options = {})
38
+ matching_contexts_for(search_context.to_s, result_context.to_s, klass, options)
39
+ end
40
+
41
+ def matching_contexts_for(search_context, result_context, klass, _options = {})
42
+ tags_to_find = tags_on(search_context).map(&:name)
43
+ related_where(klass,
44
+ [
45
+ "#{exclude_self(klass,
46
+ id)} #{klass.table_name}.#{klass.primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.base_class}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key} AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?) AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", tags_to_find, result_context
47
+ ])
48
+ end
49
+
50
+ def related_tags_for(context, klass, options = {})
51
+ tags_to_ignore = Array.wrap(options[:ignore]).map(&:to_s) || []
52
+ tags_to_find = tags_on(context).map(&:name).reject { |t| tags_to_ignore.include? t }
53
+ related_where(klass,
54
+ [
55
+ "#{exclude_self(klass,
56
+ id)} #{klass.table_name}.#{klass.primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.base_class}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key} AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?) AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", tags_to_find, context
57
+ ])
58
+ end
59
+
60
+ private
61
+
62
+ def exclude_self(klass, id)
63
+ "#{klass.arel_table[klass.primary_key].not_eq(id).to_sql} AND" if [self.class.base_class,
64
+ self.class].include? klass
65
+ end
66
+
67
+ def group_columns(klass)
68
+ if ActsAsTaggableOn::Utils.using_postgresql?
69
+ grouped_column_names_for(klass)
70
+ else
71
+ "#{klass.table_name}.#{klass.primary_key}"
72
+ end
73
+ end
74
+
75
+ def related_where(klass, conditions)
76
+ klass.select("#{klass.table_name}.*, COUNT(#{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key}) AS count")
77
+ .from("#{klass.table_name}, #{ActsAsTaggableOn::Tag.table_name}, #{ActsAsTaggableOn::Tagging.table_name}")
78
+ .group(group_columns(klass))
79
+ .order('count DESC')
80
+ .where(conditions)
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsTaggableOn
4
+ module Taggable
5
+ class TagListType < ActiveModel::Type::Value
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsTaggableOn
4
+ module Taggable
5
+ module TaggedWithQuery
6
+ class AllTagsQuery < QueryBase
7
+ def build
8
+ taggable_model.joins(each_tag_in_list)
9
+ .group(by_taggable)
10
+ .having(tags_that_matches_count)
11
+ .order(order_conditions)
12
+ .readonly(false)
13
+ end
14
+
15
+ private
16
+
17
+ def each_tag_in_list
18
+ arel_join = taggable_arel_table
19
+
20
+ tag_list.each do |tag|
21
+ tagging_alias = tagging_arel_table.alias(tagging_alias(tag))
22
+ arel_join = arel_join
23
+ .join(tagging_alias)
24
+ .on(on_conditions(tag, tagging_alias))
25
+ end
26
+
27
+ if options[:match_all].present?
28
+ arel_join = arel_join
29
+ .join(tagging_arel_table, Arel::Nodes::OuterJoin)
30
+ .on(
31
+ match_all_on_conditions
32
+ )
33
+ end
34
+
35
+ arel_join.join_sources
36
+ end
37
+
38
+ def on_conditions(tag, tagging_alias)
39
+ on_condition = tagging_alias[:taggable_id].eq(taggable_arel_table[taggable_model.primary_key])
40
+ .and(tagging_alias[:taggable_type].eq(taggable_model.base_class.name))
41
+ .and(
42
+ tagging_alias[:tag_id].in(
43
+ tag_arel_table.project(tag_arel_table[:id]).where(tag_match_type(tag))
44
+ )
45
+ )
46
+
47
+ if options[:start_at].present?
48
+ on_condition = on_condition.and(tagging_alias[:created_at].gteq(options[:start_at]))
49
+ end
50
+
51
+ if options[:end_at].present?
52
+ on_condition = on_condition.and(tagging_alias[:created_at].lteq(options[:end_at]))
53
+ end
54
+
55
+ on_condition = on_condition.and(tagging_alias[:context].eq(options[:on])) if options[:on].present?
56
+
57
+ if (owner = options[:owned_by]).present?
58
+ on_condition = on_condition.and(tagging_alias[:tagger_id].eq(owner.id))
59
+ .and(tagging_alias[:tagger_type].eq(owner.class.base_class.to_s))
60
+ end
61
+
62
+ on_condition
63
+ end
64
+
65
+ def match_all_on_conditions
66
+ on_condition = tagging_arel_table[:taggable_id].eq(taggable_arel_table[taggable_model.primary_key])
67
+ .and(tagging_arel_table[:taggable_type].eq(taggable_model.base_class.name))
68
+
69
+ if options[:start_at].present?
70
+ on_condition = on_condition.and(tagging_arel_table[:created_at].gteq(options[:start_at]))
71
+ end
72
+
73
+ if options[:end_at].present?
74
+ on_condition = on_condition.and(tagging_arel_table[:created_at].lteq(options[:end_at]))
75
+ end
76
+
77
+ on_condition = on_condition.and(tagging_arel_table[:context].eq(options[:on])) if options[:on].present?
78
+
79
+ on_condition
80
+ end
81
+
82
+ def by_taggable
83
+ return [] if options[:match_all].blank?
84
+
85
+ taggable_arel_table[taggable_model.primary_key]
86
+ end
87
+
88
+ def tags_that_matches_count
89
+ return [] if options[:match_all].blank?
90
+
91
+ taggable_model.find_by_sql(tag_arel_table.project(Arel.star.count).where(tags_match_type).to_sql)
92
+
93
+ tagging_arel_table[:taggable_id].count.eq(
94
+ tag_arel_table.project(Arel.star.count).where(tags_match_type)
95
+ )
96
+ end
97
+
98
+ def order_conditions
99
+ order_by = []
100
+ if options[:order_by_matching_tag_count].present? && options[:match_all].blank?
101
+ order_by << tagging_arel_table.project(tagging_arel_table[Arel.star].count.as('taggings_count')).order('taggings_count DESC').to_sql
102
+ end
103
+
104
+ order_by << options[:order] if options[:order].present?
105
+ order_by.join(', ')
106
+ end
107
+
108
+ def tagging_alias(tag)
109
+ alias_base_name = taggable_model.base_class.name.downcase
110
+ adjust_taggings_alias("#{alias_base_name[0..11]}_taggings_#{ActsAsTaggableOn::Utils.sha_prefix(tag)}")
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsTaggableOn
4
+ module Taggable
5
+ module TaggedWithQuery
6
+ class AnyTagsQuery < QueryBase
7
+ def build
8
+ taggable_model.select(all_fields)
9
+ .where(model_has_at_least_one_tag)
10
+ .order(Arel.sql(order_conditions))
11
+ .readonly(false)
12
+ end
13
+
14
+ private
15
+
16
+ def all_fields
17
+ taggable_arel_table[Arel.star]
18
+ end
19
+
20
+ def model_has_at_least_one_tag
21
+ tagging_arel_table.project(Arel.star).where(at_least_one_tag).exists
22
+ end
23
+
24
+ def at_least_one_tag
25
+ exists_contition = tagging_arel_table[:taggable_id].eq(taggable_arel_table[taggable_model.primary_key])
26
+ .and(tagging_arel_table[:taggable_type].eq(taggable_model.base_class.name))
27
+ .and(
28
+ tagging_arel_table[:tag_id].in(
29
+ tag_arel_table.project(tag_arel_table[:id]).where(tags_match_type)
30
+ )
31
+ )
32
+
33
+ if options[:start_at].present?
34
+ exists_contition = exists_contition.and(tagging_arel_table[:created_at].gteq(options[:start_at]))
35
+ end
36
+
37
+ if options[:end_at].present?
38
+ exists_contition = exists_contition.and(tagging_arel_table[:created_at].lteq(options[:end_at]))
39
+ end
40
+
41
+ if options[:on].present?
42
+ exists_contition = exists_contition.and(tagging_arel_table[:context].eq(options[:on]))
43
+ end
44
+
45
+ if (owner = options[:owned_by]).present?
46
+ exists_contition = exists_contition.and(tagging_arel_table[:tagger_id].eq(owner.id))
47
+ .and(tagging_arel_table[:tagger_type].eq(owner.class.base_class.to_s))
48
+ end
49
+
50
+ exists_contition
51
+ end
52
+
53
+ def order_conditions
54
+ order_by = []
55
+ if options[:order_by_matching_tag_count].present?
56
+ order_by << "(SELECT count(*) FROM #{tagging_model.table_name} WHERE #{at_least_one_tag.to_sql}) desc"
57
+ end
58
+
59
+ order_by << options[:order] if options[:order].present?
60
+ order_by.join(', ')
61
+ end
62
+
63
+ def alias_name(tag_list)
64
+ alias_base_name = taggable_model.base_class.name.downcase
65
+ taggings_context = options[:on] ? "_#{options[:on]}" : ''
66
+
67
+ adjust_taggings_alias(
68
+ "#{alias_base_name[0..4]}#{taggings_context[0..6]}_taggings_#{ActsAsTaggableOn::Utils.sha_prefix(tag_list.join('_'))}"
69
+ )
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsTaggableOn
4
+ module Taggable
5
+ module TaggedWithQuery
6
+ class ExcludeTagsQuery < QueryBase
7
+ def build
8
+ taggable_model.joins(owning_to_tagger)
9
+ .where(tags_not_in_list)
10
+ .having(tags_that_matches_count)
11
+ .readonly(false)
12
+ end
13
+
14
+ private
15
+
16
+ def tags_not_in_list
17
+ taggable_arel_table[:id].not_in(
18
+ tagging_arel_table
19
+ .project(tagging_arel_table[:taggable_id])
20
+ .join(tag_arel_table)
21
+ .on(
22
+ tagging_arel_table[:tag_id].eq(tag_arel_table[:id])
23
+ .and(tagging_arel_table[:taggable_type].eq(taggable_model.base_class.name))
24
+ .and(tags_match_type)
25
+ )
26
+ )
27
+
28
+ # FIXME: missing time scope, this is also missing in the original implementation
29
+ end
30
+
31
+ def owning_to_tagger
32
+ return [] if options[:owned_by].blank?
33
+
34
+ owner = options[:owned_by]
35
+
36
+ arel_join = taggable_arel_table
37
+ .join(tagging_arel_table)
38
+ .on(
39
+ tagging_arel_table[:tagger_id].eq(owner.id)
40
+ .and(tagging_arel_table[:tagger_type].eq(owner.class.base_class.to_s))
41
+ .and(tagging_arel_table[:taggable_id].eq(taggable_arel_table[taggable_model.primary_key]))
42
+ .and(tagging_arel_table[:taggable_type].eq(taggable_model.base_class.name))
43
+ )
44
+
45
+ if options[:match_all].present?
46
+ arel_join = arel_join
47
+ .join(tagging_arel_table, Arel::Nodes::OuterJoin)
48
+ .on(
49
+ match_all_on_conditions
50
+ )
51
+ end
52
+
53
+ arel_join.join_sources
54
+ end
55
+
56
+ def match_all_on_conditions
57
+ on_condition = tagging_arel_table[:taggable_id].eq(taggable_arel_table[taggable_model.primary_key])
58
+ .and(tagging_arel_table[:taggable_type].eq(taggable_model.base_class.name))
59
+
60
+ if options[:start_at].present?
61
+ on_condition = on_condition.and(tagging_arel_table[:created_at].gteq(options[:start_at]))
62
+ end
63
+
64
+ if options[:end_at].present?
65
+ on_condition = on_condition.and(tagging_arel_table[:created_at].lteq(options[:end_at]))
66
+ end
67
+
68
+ on_condition = on_condition.and(tagging_arel_table[:context].eq(options[:on])) if options[:on].present?
69
+
70
+ on_condition
71
+ end
72
+
73
+ def tags_that_matches_count
74
+ return [] if options[:match_all].blank?
75
+
76
+ taggable_model.find_by_sql(tag_arel_table.project(Arel.star.count).where(tags_match_type).to_sql)
77
+
78
+ tagging_arel_table[:taggable_id].count.eq(
79
+ tag_arel_table.project(Arel.star.count).where(tags_match_type)
80
+ )
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsTaggableOn
4
+ module Taggable
5
+ module TaggedWithQuery
6
+ class QueryBase
7
+ def initialize(taggable_model, tag_model, tagging_model, tag_list, options)
8
+ @taggable_model = taggable_model
9
+ @tag_model = tag_model
10
+ @tagging_model = tagging_model
11
+ @tag_list = tag_list
12
+ @options = options
13
+ end
14
+
15
+ private
16
+
17
+ attr_reader :taggable_model, :tag_model, :tagging_model, :tag_list, :options
18
+
19
+ def taggable_arel_table
20
+ @taggable_arel_table ||= taggable_model.arel_table
21
+ end
22
+
23
+ def tag_arel_table
24
+ @tag_arel_table ||= tag_model.arel_table
25
+ end
26
+
27
+ def tagging_arel_table
28
+ @tagging_arel_table ||= tagging_model.arel_table
29
+ end
30
+
31
+ def tag_match_type(tag)
32
+ matches_attribute = tag_arel_table[:name]
33
+ matches_attribute = matches_attribute.lower unless ActsAsTaggableOn.strict_case_match
34
+
35
+ if options[:wild].present?
36
+ matches_attribute.matches(wildcard_escaped_tag(tag), '!', ActsAsTaggableOn.strict_case_match)
37
+ else
38
+ matches_attribute.matches(escaped_tag(tag), '!', ActsAsTaggableOn.strict_case_match)
39
+ end
40
+ end
41
+
42
+ def tags_match_type
43
+ matches_attribute = tag_arel_table[:name]
44
+ matches_attribute = matches_attribute.lower unless ActsAsTaggableOn.strict_case_match
45
+
46
+ if options[:wild].present?
47
+ matches_attribute.matches_any(tag_list.map do |tag|
48
+ wildcard_escaped_tag(tag)
49
+ end, '!', ActsAsTaggableOn.strict_case_match)
50
+ else
51
+ matches_attribute.matches_any(tag_list.map do |tag|
52
+ escaped_tag(tag).to_s
53
+ end, '!', ActsAsTaggableOn.strict_case_match)
54
+ end
55
+ end
56
+
57
+ def escaped_tag(tag)
58
+ tag = tag.downcase unless ActsAsTaggableOn.strict_case_match
59
+ ActsAsTaggableOn::Utils.escape_like(tag)
60
+ end
61
+
62
+ def wildcard_escaped_tag(tag)
63
+ case options[:wild]
64
+ when :suffix then "#{escaped_tag(tag)}%"
65
+ when :prefix then "%#{escaped_tag(tag)}"
66
+ when true then "%#{escaped_tag(tag)}%"
67
+ else escaped_tag(tag)
68
+ end
69
+ end
70
+
71
+ def adjust_taggings_alias(taggings_alias)
72
+ taggings_alias = "taggings_alias_#{Digest::SHA1.hexdigest(taggings_alias)}" if taggings_alias.size > 75
73
+ taggings_alias
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsTaggableOn
4
+ module Taggable
5
+ module TaggedWithQuery
6
+ def self.build(taggable_model, tag_model, tagging_model, tag_list, options)
7
+ if options[:exclude].present?
8
+ ExcludeTagsQuery.new(taggable_model, tag_model, tagging_model, tag_list, options).build
9
+ elsif options[:any].present?
10
+ AnyTagsQuery.new(taggable_model, tag_model, tagging_model, tag_list, options).build
11
+ else
12
+ AllTagsQuery.new(taggable_model, tag_model, tagging_model, tag_list, options).build
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsTaggableOn
4
+ module Taggable
5
+ def taggable?
6
+ false
7
+ end
8
+
9
+ ##
10
+ # This is an alias for calling <tt>acts_as_taggable_on :tags</tt>.
11
+ #
12
+ # Example:
13
+ # class Book < ActiveRecord::Base
14
+ # acts_as_taggable
15
+ # end
16
+ def acts_as_taggable
17
+ acts_as_taggable_on :tags
18
+ end
19
+
20
+ ##
21
+ # This is an alias for calling <tt>acts_as_ordered_taggable_on :tags</tt>.
22
+ #
23
+ # Example:
24
+ # class Book < ActiveRecord::Base
25
+ # acts_as_ordered_taggable
26
+ # end
27
+ def acts_as_ordered_taggable
28
+ acts_as_ordered_taggable_on :tags
29
+ end
30
+
31
+ ##
32
+ # Make a model taggable on specified contexts.
33
+ #
34
+ # @param [Array] tag_types An array of taggable contexts
35
+ #
36
+ # Example:
37
+ # class User < ActiveRecord::Base
38
+ # acts_as_taggable_on :languages, :skills
39
+ # end
40
+ def acts_as_taggable_on(*tag_types)
41
+ taggable_on(false, tag_types)
42
+ end
43
+
44
+ ##
45
+ # Make a model taggable on specified contexts
46
+ # and preserves the order in which tags are created
47
+ #
48
+ # @param [Array] tag_types An array of taggable contexts
49
+ #
50
+ # Example:
51
+ # class User < ActiveRecord::Base
52
+ # acts_as_ordered_taggable_on :languages, :skills
53
+ # end
54
+ def acts_as_ordered_taggable_on(*tag_types)
55
+ taggable_on(true, tag_types)
56
+ end
57
+
58
+ def acts_as_taggable_tenant(tenant)
59
+ if taggable?
60
+ else
61
+ class_attribute :tenant_column
62
+ end
63
+ self.tenant_column = tenant
64
+
65
+ # each of these add context-specific methods and must be
66
+ # called on each call of taggable_on
67
+ include Core
68
+ include Collection
69
+ include Caching
70
+ include Ownership
71
+ include Related
72
+ end
73
+
74
+ private
75
+
76
+ # Make a model taggable on specified contexts
77
+ # and optionally preserves the order in which tags are created
78
+ #
79
+ # Separate methods used above for backwards compatibility
80
+ # so that the original acts_as_taggable_on method is unaffected
81
+ # as it's not possible to add another argument to the method
82
+ # without the tag_types being enclosed in square brackets
83
+ #
84
+ # NB: method overridden in core module in order to create tag type
85
+ # associations and methods after this logic has executed
86
+ #
87
+ def taggable_on(preserve_tag_order, *tag_types)
88
+ tag_types = tag_types.to_a.flatten.compact.map(&:to_sym)
89
+
90
+ if taggable?
91
+ self.tag_types = (self.tag_types + tag_types).uniq
92
+ self.preserve_tag_order = preserve_tag_order
93
+ else
94
+ class_eval do
95
+ class_attribute :tag_types
96
+ class_attribute :preserve_tag_order
97
+ class_attribute :tenant_column
98
+ self.tag_types = tag_types
99
+ self.preserve_tag_order = preserve_tag_order
100
+
101
+ has_many :taggings, as: :taggable, dependent: :destroy, class_name: '::ActsAsTaggableOn::Tagging'
102
+ has_many :base_tags, through: :taggings, source: :tag, class_name: '::ActsAsTaggableOn::Tag'
103
+
104
+ def self.taggable?
105
+ true
106
+ end
107
+ end
108
+ end
109
+
110
+ # each of these add context-specific methods and must be
111
+ # called on each call of taggable_on
112
+ include Core
113
+ include Collection
114
+ include Caching
115
+ include Ownership
116
+ include Related
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsTaggableOn
4
+ module Tagger
5
+ extend ActiveSupport::Concern
6
+
7
+ class_methods do
8
+ ##
9
+ # Make a model a tagger. This allows an instance of a model to claim ownership
10
+ # of tags.
11
+ #
12
+ # Example:
13
+ # class User < ActiveRecord::Base
14
+ # acts_as_tagger
15
+ # end
16
+ def acts_as_tagger(opts = {})
17
+ class_eval do
18
+ owned_taggings_scope = opts.delete(:scope)
19
+
20
+ has_many :owned_taggings, owned_taggings_scope,
21
+ **opts.merge(
22
+ as: :tagger,
23
+ class_name: '::ActsAsTaggableOn::Tagging',
24
+ dependent: :destroy
25
+ )
26
+
27
+ has_many :owned_tags, -> { distinct },
28
+ class_name: '::ActsAsTaggableOn::Tag',
29
+ source: :tag,
30
+ through: :owned_taggings
31
+ end
32
+
33
+ include ActsAsTaggableOn::Tagger::InstanceMethods
34
+ extend ActsAsTaggableOn::Tagger::SingletonMethods
35
+ end
36
+
37
+ def tagger?
38
+ false
39
+ end
40
+
41
+ alias is_tagger? tagger?
42
+ end
43
+
44
+ module InstanceMethods
45
+ ##
46
+ # Tag a taggable model with tags that are owned by the tagger.
47
+ #
48
+ # @param taggable The object that will be tagged
49
+ # @param [Hash] options An hash with options. Available options are:
50
+ # * <tt>:with</tt> - The tags that you want to
51
+ # * <tt>:on</tt> - The context on which you want to tag
52
+ #
53
+ # Example:
54
+ # @user.tag(@photo, :with => "paris, normandy", :on => :locations)
55
+ def tag(taggable, opts = {})
56
+ opts.reverse_merge!(force: true)
57
+ skip_save = opts.delete(:skip_save)
58
+ return false unless taggable.respond_to?(:is_taggable?) && taggable.is_taggable?
59
+
60
+ raise 'You need to specify a tag context using :on' unless opts.key?(:on)
61
+ raise 'You need to specify some tags using :with' unless opts.key?(:with)
62
+ unless opts[:force] || taggable.tag_types.include?(opts[:on])
63
+ raise "No context :#{opts[:on]} defined in #{taggable.class}"
64
+ end
65
+
66
+ taggable.set_owner_tag_list_on(self, opts[:on].to_s, opts[:with])
67
+ taggable.save unless skip_save
68
+ end
69
+
70
+ def tagger?
71
+ self.class.is_tagger?
72
+ end
73
+
74
+ alias is_tagger? tagger?
75
+ end
76
+
77
+ module SingletonMethods
78
+ def tagger?
79
+ true
80
+ end
81
+
82
+ alias is_tagger? tagger?
83
+ end
84
+ end
85
+ end