sb-acts-as-taggable-on 6.5.0

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 (87) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +39 -0
  5. data/Appraisals +15 -0
  6. data/CHANGELOG.md +330 -0
  7. data/CONTRIBUTING.md +57 -0
  8. data/Gemfile +11 -0
  9. data/Guardfile +5 -0
  10. data/LICENSE.md +20 -0
  11. data/README.md +555 -0
  12. data/Rakefile +21 -0
  13. data/UPGRADING.md +8 -0
  14. data/acts-as-taggable-on.gemspec +32 -0
  15. data/db/migrate/1_acts_as_taggable_on_migration.rb +36 -0
  16. data/db/migrate/2_add_missing_unique_indices.rb +25 -0
  17. data/db/migrate/3_add_taggings_counter_cache_to_tags.rb +19 -0
  18. data/db/migrate/4_add_missing_taggable_index.rb +14 -0
  19. data/db/migrate/5_change_collation_for_tag_names.rb +14 -0
  20. data/db/migrate/6_add_missing_indexes_on_taggings.rb +22 -0
  21. data/gemfiles/activerecord_5.0.gemfile +21 -0
  22. data/gemfiles/activerecord_5.1.gemfile +21 -0
  23. data/gemfiles/activerecord_5.2.gemfile +21 -0
  24. data/gemfiles/activerecord_6.0.gemfile +21 -0
  25. data/lib/acts-as-taggable-on.rb +133 -0
  26. data/lib/acts_as_taggable_on.rb +6 -0
  27. data/lib/acts_as_taggable_on/default_parser.rb +79 -0
  28. data/lib/acts_as_taggable_on/engine.rb +4 -0
  29. data/lib/acts_as_taggable_on/generic_parser.rb +19 -0
  30. data/lib/acts_as_taggable_on/tag.rb +139 -0
  31. data/lib/acts_as_taggable_on/tag_list.rb +106 -0
  32. data/lib/acts_as_taggable_on/taggable.rb +101 -0
  33. data/lib/acts_as_taggable_on/taggable/cache.rb +90 -0
  34. data/lib/acts_as_taggable_on/taggable/collection.rb +183 -0
  35. data/lib/acts_as_taggable_on/taggable/core.rb +322 -0
  36. data/lib/acts_as_taggable_on/taggable/ownership.rb +136 -0
  37. data/lib/acts_as_taggable_on/taggable/related.rb +71 -0
  38. data/lib/acts_as_taggable_on/taggable/tag_list_type.rb +4 -0
  39. data/lib/acts_as_taggable_on/taggable/tagged_with_query.rb +16 -0
  40. data/lib/acts_as_taggable_on/taggable/tagged_with_query/all_tags_query.rb +111 -0
  41. data/lib/acts_as_taggable_on/taggable/tagged_with_query/any_tags_query.rb +70 -0
  42. data/lib/acts_as_taggable_on/taggable/tagged_with_query/exclude_tags_query.rb +82 -0
  43. data/lib/acts_as_taggable_on/taggable/tagged_with_query/query_base.rb +61 -0
  44. data/lib/acts_as_taggable_on/tagger.rb +89 -0
  45. data/lib/acts_as_taggable_on/tagging.rb +36 -0
  46. data/lib/acts_as_taggable_on/tags_helper.rb +15 -0
  47. data/lib/acts_as_taggable_on/utils.rb +37 -0
  48. data/lib/acts_as_taggable_on/version.rb +3 -0
  49. data/lib/tasks/tags_collate_utf8.rake +21 -0
  50. data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +285 -0
  51. data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +112 -0
  52. data/spec/acts_as_taggable_on/caching_spec.rb +129 -0
  53. data/spec/acts_as_taggable_on/default_parser_spec.rb +47 -0
  54. data/spec/acts_as_taggable_on/dirty_spec.rb +142 -0
  55. data/spec/acts_as_taggable_on/generic_parser_spec.rb +14 -0
  56. data/spec/acts_as_taggable_on/related_spec.rb +99 -0
  57. data/spec/acts_as_taggable_on/single_table_inheritance_spec.rb +231 -0
  58. data/spec/acts_as_taggable_on/tag_list_spec.rb +176 -0
  59. data/spec/acts_as_taggable_on/tag_spec.rb +340 -0
  60. data/spec/acts_as_taggable_on/taggable_spec.rb +817 -0
  61. data/spec/acts_as_taggable_on/tagger_spec.rb +153 -0
  62. data/spec/acts_as_taggable_on/tagging_spec.rb +117 -0
  63. data/spec/acts_as_taggable_on/tags_helper_spec.rb +45 -0
  64. data/spec/acts_as_taggable_on/utils_spec.rb +23 -0
  65. data/spec/internal/app/models/altered_inheriting_taggable_model.rb +5 -0
  66. data/spec/internal/app/models/cached_model.rb +3 -0
  67. data/spec/internal/app/models/cached_model_with_array.rb +11 -0
  68. data/spec/internal/app/models/columns_override_model.rb +5 -0
  69. data/spec/internal/app/models/company.rb +15 -0
  70. data/spec/internal/app/models/inheriting_taggable_model.rb +4 -0
  71. data/spec/internal/app/models/market.rb +2 -0
  72. data/spec/internal/app/models/non_standard_id_taggable_model.rb +8 -0
  73. data/spec/internal/app/models/ordered_taggable_model.rb +4 -0
  74. data/spec/internal/app/models/other_cached_model.rb +3 -0
  75. data/spec/internal/app/models/other_taggable_model.rb +4 -0
  76. data/spec/internal/app/models/student.rb +4 -0
  77. data/spec/internal/app/models/taggable_model.rb +14 -0
  78. data/spec/internal/app/models/untaggable_model.rb +3 -0
  79. data/spec/internal/app/models/user.rb +3 -0
  80. data/spec/internal/config/database.yml.sample +19 -0
  81. data/spec/internal/db/schema.rb +110 -0
  82. data/spec/spec_helper.rb +20 -0
  83. data/spec/support/0-helpers.rb +32 -0
  84. data/spec/support/array.rb +9 -0
  85. data/spec/support/database.rb +36 -0
  86. data/spec/support/database_cleaner.rb +21 -0
  87. metadata +269 -0
@@ -0,0 +1,136 @@
1
+ module ActsAsTaggableOn::Taggable
2
+ module Ownership
3
+ def self.included(base)
4
+ base.extend ActsAsTaggableOn::Taggable::Ownership::ClassMethods
5
+
6
+ base.class_eval do
7
+ after_save :save_owned_tags
8
+ end
9
+
10
+ base.initialize_acts_as_taggable_on_ownership
11
+ end
12
+
13
+ module ClassMethods
14
+ def acts_as_taggable_on(*args)
15
+ initialize_acts_as_taggable_on_ownership
16
+ super(*args)
17
+ end
18
+
19
+ def initialize_acts_as_taggable_on_ownership
20
+ tag_types.map(&:to_s).each do |tag_type|
21
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
22
+ def #{tag_type}_from(owner)
23
+ owner_tag_list_on(owner, '#{tag_type}')
24
+ end
25
+ RUBY
26
+ end
27
+ end
28
+ end
29
+
30
+ def owner_tags(owner)
31
+ if owner.nil?
32
+ scope = base_tags
33
+ else
34
+ scope = base_tags.where(
35
+ "#{ActsAsTaggableOn::Tagging.table_name}" => {
36
+ tagger_id: owner.id,
37
+ tagger_type: owner.class.base_class.to_s
38
+ }
39
+ )
40
+ end
41
+
42
+ # when preserving tag order, return tags in created order
43
+ # if we added the order to the association this would always apply
44
+ if self.class.preserve_tag_order?
45
+ scope.order("#{ActsAsTaggableOn::Tagging.table_name}.id")
46
+ else
47
+ scope
48
+ end
49
+ end
50
+
51
+ def owner_tags_on(owner, context)
52
+ owner_tags(owner).where(
53
+ "#{ActsAsTaggableOn::Tagging.table_name}" => {
54
+ context: context
55
+ }
56
+ )
57
+ end
58
+
59
+ def cached_owned_tag_list_on(context)
60
+ variable_name = "@owned_#{context}_list"
61
+ (instance_variable_defined?(variable_name) && instance_variable_get(variable_name)) || instance_variable_set(variable_name, {})
62
+ end
63
+
64
+ def owner_tag_list_on(owner, context)
65
+ add_custom_context(context)
66
+
67
+ cache = cached_owned_tag_list_on(context)
68
+
69
+ cache[owner] ||= ActsAsTaggableOn::TagList.new(*owner_tags_on(owner, context).map(&:name))
70
+ end
71
+
72
+ def set_owner_tag_list_on(owner, context, new_list)
73
+ add_custom_context(context)
74
+
75
+ cache = cached_owned_tag_list_on(context)
76
+
77
+ cache[owner] = ActsAsTaggableOn.default_parser.new(new_list).parse
78
+ end
79
+
80
+ def reload(*args)
81
+ self.class.tag_types.each do |context|
82
+ instance_variable_set("@owned_#{context}_list", nil)
83
+ end
84
+
85
+ super(*args)
86
+ end
87
+
88
+ def save_owned_tags
89
+ tagging_contexts.each do |context|
90
+ cached_owned_tag_list_on(context).each do |owner, tag_list|
91
+
92
+ # Find existing tags or create non-existing tags:
93
+ tags = find_or_create_tags_from_list_with_context(tag_list.uniq, context)
94
+
95
+ # Tag objects for owned tags
96
+ owned_tags = owner_tags_on(owner, context).to_a
97
+
98
+ # Tag maintenance based on whether preserving the created order of tags
99
+ if self.class.preserve_tag_order?
100
+ old_tags, new_tags = owned_tags - tags, tags - owned_tags
101
+
102
+ shared_tags = owned_tags & tags
103
+
104
+ if shared_tags.any? && tags[0...shared_tags.size] != shared_tags
105
+ index = shared_tags.each_with_index { |_, i| break i unless shared_tags[i] == tags[i] }
106
+
107
+ # Update arrays of tag objects
108
+ old_tags |= owned_tags.from(index)
109
+ new_tags |= owned_tags.from(index) & shared_tags
110
+
111
+ # Order the array of tag objects to match the tag list
112
+ new_tags = tags.map { |t| new_tags.find { |n| n.name.downcase == t.name.downcase } }.compact
113
+ end
114
+ else
115
+ # Delete discarded tags and create new tags
116
+ old_tags = owned_tags - tags
117
+ new_tags = tags - owned_tags
118
+ end
119
+
120
+ # Find all taggings that belong to the taggable (self), are owned by the owner,
121
+ # have the correct context, and are removed from the list.
122
+ ActsAsTaggableOn::Tagging.where(taggable_id: id, taggable_type: self.class.base_class.to_s,
123
+ tagger_type: owner.class.base_class.to_s, tagger_id: owner.id,
124
+ tag_id: old_tags, context: context).destroy_all if old_tags.present?
125
+
126
+ # Create new taggings:
127
+ new_tags.each do |tag|
128
+ taggings.create!(tag_id: tag.id, context: context.to_s, tagger: owner, taggable: self)
129
+ end
130
+ end
131
+ end
132
+
133
+ true
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,71 @@
1
+ module ActsAsTaggableOn::Taggable
2
+ module Related
3
+ def self.included(base)
4
+ base.extend ActsAsTaggableOn::Taggable::Related::ClassMethods
5
+ base.initialize_acts_as_taggable_on_related
6
+ end
7
+
8
+ module ClassMethods
9
+ def initialize_acts_as_taggable_on_related
10
+ tag_types.map(&:to_s).each do |tag_type|
11
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
12
+ def find_related_#{tag_type}(options = {})
13
+ related_tags_for('#{tag_type}', self.class, options)
14
+ end
15
+ alias_method :find_related_on_#{tag_type}, :find_related_#{tag_type}
16
+
17
+ def find_related_#{tag_type}_for(klass, options = {})
18
+ related_tags_for('#{tag_type}', klass, options)
19
+ end
20
+ RUBY
21
+ end
22
+ end
23
+
24
+ def acts_as_taggable_on(*args)
25
+ super(*args)
26
+ initialize_acts_as_taggable_on_related
27
+ end
28
+ end
29
+
30
+ def find_matching_contexts(search_context, result_context, options = {})
31
+ matching_contexts_for(search_context.to_s, result_context.to_s, self.class, options)
32
+ end
33
+
34
+ def find_matching_contexts_for(klass, search_context, result_context, options = {})
35
+ matching_contexts_for(search_context.to_s, result_context.to_s, klass, options)
36
+ end
37
+
38
+ def matching_contexts_for(search_context, result_context, klass, options = {})
39
+ tags_to_find = tags_on(search_context).map { |t| t.name }
40
+ related_where(klass, ["#{exclude_self(klass, 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])
41
+ end
42
+
43
+ def related_tags_for(context, klass, options = {})
44
+ tags_to_ignore = Array.wrap(options[:ignore]).map(&:to_s) || []
45
+ tags_to_find = tags_on(context).map { |t| t.name }.reject { |t| tags_to_ignore.include? t }
46
+ related_where(klass, ["#{exclude_self(klass, 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])
47
+ end
48
+
49
+ private
50
+
51
+ def exclude_self(klass, id)
52
+ "#{klass.arel_table[klass.primary_key].not_eq(id).to_sql} AND" if [self.class.base_class, self.class].include? klass
53
+ end
54
+
55
+ def group_columns(klass)
56
+ if ActsAsTaggableOn::Utils.using_postgresql?
57
+ grouped_column_names_for(klass)
58
+ else
59
+ "#{klass.table_name}.#{klass.primary_key}"
60
+ end
61
+ end
62
+
63
+ def related_where(klass, conditions)
64
+ klass.select("#{klass.table_name}.*, COUNT(#{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key}) AS count")
65
+ .from("#{klass.table_name}, #{ActsAsTaggableOn::Tag.table_name}, #{ActsAsTaggableOn::Tagging.table_name}")
66
+ .group(group_columns(klass))
67
+ .order('count DESC')
68
+ .where(conditions)
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,4 @@
1
+ module ActsAsTaggableOn::Taggable
2
+ class TagListType < ActiveModel::Type::Value
3
+ end
4
+ end
@@ -0,0 +1,16 @@
1
+ require_relative 'tagged_with_query/query_base'
2
+ require_relative 'tagged_with_query/exclude_tags_query'
3
+ require_relative 'tagged_with_query/any_tags_query'
4
+ require_relative 'tagged_with_query/all_tags_query'
5
+
6
+ module ActsAsTaggableOn::Taggable::TaggedWithQuery
7
+ def self.build(taggable_model, tag_model, tagging_model, tag_list, options)
8
+ if options[:exclude].present?
9
+ ExcludeTagsQuery.new(taggable_model, tag_model, tagging_model, tag_list, options).build
10
+ elsif options[:any].present?
11
+ AnyTagsQuery.new(taggable_model, tag_model, tagging_model, tag_list, options).build
12
+ else
13
+ AllTagsQuery.new(taggable_model, tag_model, tagging_model, tag_list, options).build
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,111 @@
1
+ module ActsAsTaggableOn::Taggable::TaggedWithQuery
2
+ class AllTagsQuery < QueryBase
3
+ def build
4
+ taggable_model.joins(each_tag_in_list)
5
+ .group(by_taggable)
6
+ .having(tags_that_matches_count)
7
+ .order(order_conditions)
8
+ .readonly(false)
9
+ end
10
+
11
+ private
12
+
13
+ def each_tag_in_list
14
+ arel_join = taggable_arel_table
15
+
16
+ tag_list.each do |tag|
17
+ tagging_alias = tagging_arel_table.alias(tagging_alias(tag))
18
+ arel_join = arel_join
19
+ .join(tagging_alias)
20
+ .on(on_conditions(tag, tagging_alias))
21
+ end
22
+
23
+ if options[:match_all].present?
24
+ arel_join = arel_join
25
+ .join(tagging_arel_table, Arel::Nodes::OuterJoin)
26
+ .on(
27
+ match_all_on_conditions
28
+ )
29
+ end
30
+
31
+ return arel_join.join_sources
32
+ end
33
+
34
+ def on_conditions(tag, tagging_alias)
35
+ on_condition = tagging_alias[:taggable_id].eq(taggable_arel_table[taggable_model.primary_key])
36
+ .and(tagging_alias[:taggable_type].eq(taggable_model.base_class.name))
37
+ .and(
38
+ tagging_alias[:tag_id].in(
39
+ tag_arel_table.project(tag_arel_table[:id]).where(tag_match_type(tag))
40
+ )
41
+ )
42
+
43
+ if options[:start_at].present?
44
+ on_condition = on_condition.and(tagging_alias[:created_at].gteq(options[:start_at]))
45
+ end
46
+
47
+ if options[:end_at].present?
48
+ on_condition = on_condition.and(tagging_alias[:created_at].lteq(options[:end_at]))
49
+ end
50
+
51
+ if options[:on].present?
52
+ on_condition = on_condition.and(tagging_alias[:context].eq(options[:on]))
53
+ end
54
+
55
+ if (owner = options[:owned_by]).present?
56
+ on_condition = on_condition.and(tagging_alias[:tagger_id].eq(owner.id))
57
+ .and(tagging_alias[:tagger_type].eq(owner.class.base_class.to_s))
58
+ end
59
+
60
+ on_condition
61
+ end
62
+
63
+ def match_all_on_conditions
64
+ on_condition = tagging_arel_table[:taggable_id].eq(taggable_arel_table[taggable_model.primary_key])
65
+ .and(tagging_arel_table[:taggable_type].eq(taggable_model.base_class.name))
66
+
67
+ if options[:start_at].present?
68
+ on_condition = on_condition.and(tagging_arel_table[:created_at].gteq(options[:start_at]))
69
+ end
70
+
71
+ if options[:end_at].present?
72
+ on_condition = on_condition.and(tagging_arel_table[:created_at].lteq(options[:end_at]))
73
+ end
74
+
75
+ if options[:on].present?
76
+ on_condition = on_condition.and(tagging_arel_table[:context].eq(options[:on]))
77
+ end
78
+
79
+ on_condition
80
+ end
81
+
82
+ def by_taggable
83
+ return [] unless options[:match_all].present?
84
+
85
+ taggable_arel_table[taggable_model.primary_key]
86
+ end
87
+
88
+ def tags_that_matches_count
89
+ return [] unless options[:match_all].present?
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
+ order_by << tagging_arel_table.project(tagging_arel_table[Arel.star].count.as('taggings_count')).order('taggings_count DESC').to_sql if options[:order_by_matching_tag_count].present? && options[:match_all].blank?
101
+
102
+ order_by << options[:order] if options[:order].present?
103
+ order_by.join(', ')
104
+ end
105
+
106
+ def tagging_alias(tag)
107
+ alias_base_name = taggable_model.base_class.name.downcase
108
+ adjust_taggings_alias("#{alias_base_name[0..11]}_taggings_#{ActsAsTaggableOn::Utils.sha_prefix(tag)}")
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,70 @@
1
+ module ActsAsTaggableOn::Taggable::TaggedWithQuery
2
+ class AnyTagsQuery < QueryBase
3
+ def build
4
+ taggable_model.select(all_fields)
5
+ .where(model_has_at_least_one_tag)
6
+ .order(Arel.sql(order_conditions))
7
+ .readonly(false)
8
+ end
9
+
10
+ private
11
+
12
+ def all_fields
13
+ taggable_arel_table[Arel.star]
14
+ end
15
+
16
+ def model_has_at_least_one_tag
17
+ tagging_arel_table.project(Arel.star).where(at_least_one_tag).exists
18
+ end
19
+
20
+ def at_least_one_tag
21
+ exists_contition = tagging_arel_table[:taggable_id].eq(taggable_arel_table[taggable_model.primary_key])
22
+ .and(tagging_arel_table[:taggable_type].eq(taggable_model.base_class.name))
23
+ .and(
24
+ tagging_arel_table[:tag_id].in(
25
+ tag_arel_table.project(tag_arel_table[:id]).where(tags_match_type)
26
+ )
27
+ )
28
+
29
+ if options[:start_at].present?
30
+ exists_contition = exists_contition.and(tagging_arel_table[:created_at].gteq(options[:start_at]))
31
+ end
32
+
33
+ if options[:end_at].present?
34
+ exists_contition = exists_contition.and(tagging_arel_table[:created_at].lteq(options[:end_at]))
35
+ end
36
+
37
+ if options[:on].present?
38
+ exists_contition = exists_contition.and(tagging_arel_table[:context].eq(options[:on]))
39
+ end
40
+
41
+ if (owner = options[:owned_by]).present?
42
+ exists_contition = exists_contition.and(tagging_arel_table[:tagger_id].eq(owner.id))
43
+ .and(tagging_arel_table[:tagger_type].eq(owner.class.base_class.to_s))
44
+ end
45
+
46
+ exists_contition
47
+ end
48
+
49
+ def order_conditions
50
+ order_by = []
51
+ if options[:order_by_matching_tag_count].present?
52
+ order_by << "(SELECT count(*) FROM #{tagging_model.table_name} WHERE #{at_least_one_tag.to_sql}) desc"
53
+ end
54
+
55
+ order_by << options[:order] if options[:order].present?
56
+ order_by.join(', ')
57
+ end
58
+
59
+ def alias_name(tag_list)
60
+ alias_base_name = taggable_model.base_class.name.downcase
61
+ taggings_context = options[:on] ? "_#{options[:on]}" : ''
62
+
63
+ taggings_alias = adjust_taggings_alias(
64
+ "#{alias_base_name[0..4]}#{taggings_context[0..6]}_taggings_#{ActsAsTaggableOn::Utils.sha_prefix(tag_list.join('_'))}"
65
+ )
66
+
67
+ taggings_alias
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,82 @@
1
+ module ActsAsTaggableOn::Taggable::TaggedWithQuery
2
+ class ExcludeTagsQuery < QueryBase
3
+ def build
4
+ taggable_model.joins(owning_to_tagger)
5
+ .where(tags_not_in_list)
6
+ .having(tags_that_matches_count)
7
+ .readonly(false)
8
+ end
9
+
10
+ private
11
+
12
+ def tags_not_in_list
13
+ return taggable_arel_table[:id].not_in(
14
+ tagging_arel_table
15
+ .project(tagging_arel_table[:taggable_id])
16
+ .join(tag_arel_table)
17
+ .on(
18
+ tagging_arel_table[:tag_id].eq(tag_arel_table[:id])
19
+ .and(tagging_arel_table[:taggable_type].eq(taggable_model.base_class.name))
20
+ .and(tags_match_type)
21
+ )
22
+ )
23
+
24
+ # FIXME: missing time scope, this is also missing in the original implementation
25
+ end
26
+
27
+
28
+ def owning_to_tagger
29
+ return [] unless options[:owned_by].present?
30
+
31
+ owner = options[:owned_by]
32
+
33
+ arel_join = taggable_arel_table
34
+ .join(tagging_arel_table)
35
+ .on(
36
+ tagging_arel_table[:tagger_id].eq(owner.id)
37
+ .and(tagging_arel_table[:tagger_type].eq(owner.class.base_class.to_s))
38
+ .and(tagging_arel_table[:taggable_id].eq(taggable_arel_table[taggable_model.primary_key]))
39
+ .and(tagging_arel_table[:taggable_type].eq(taggable_model.base_class.name))
40
+ )
41
+
42
+ if options[:match_all].present?
43
+ arel_join = arel_join
44
+ .join(tagging_arel_table, Arel::Nodes::OuterJoin)
45
+ .on(
46
+ match_all_on_conditions
47
+ )
48
+ end
49
+
50
+ return arel_join.join_sources
51
+ end
52
+
53
+ def match_all_on_conditions
54
+ on_condition = tagging_arel_table[:taggable_id].eq(taggable_arel_table[taggable_model.primary_key])
55
+ .and(tagging_arel_table[:taggable_type].eq(taggable_model.base_class.name))
56
+
57
+ if options[:start_at].present?
58
+ on_condition = on_condition.and(tagging_arel_table[:created_at].gteq(options[:start_at]))
59
+ end
60
+
61
+ if options[:end_at].present?
62
+ on_condition = on_condition.and(tagging_arel_table[:created_at].lteq(options[:end_at]))
63
+ end
64
+
65
+ if options[:on].present?
66
+ on_condition = on_condition.and(tagging_arel_table[:context].eq(options[:on]))
67
+ end
68
+
69
+ on_condition
70
+ end
71
+
72
+ def tags_that_matches_count
73
+ return [] unless options[:match_all].present?
74
+
75
+ taggable_model.find_by_sql(tag_arel_table.project(Arel.star.count).where(tags_match_type).to_sql)
76
+
77
+ tagging_arel_table[:taggable_id].count.eq(
78
+ tag_arel_table.project(Arel.star.count).where(tags_match_type)
79
+ )
80
+ end
81
+ end
82
+ end