make_taggable 0.6.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +47 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.standard.yml +18 -0
- data/.standard_todo.yml +5 -0
- data/.travis.yml +36 -0
- data/Appraisals +11 -0
- data/CHANGELOG.md +0 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/CONTRIBUTING.md +57 -0
- data/Gemfile +16 -0
- data/LICENSE.md +20 -0
- data/LICENSE.txt +21 -0
- data/README.md +478 -0
- data/Rakefile +7 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/db/migrate/1_create_make_taggable_tags.rb +10 -0
- data/db/migrate/2_create_make_taggable_taggings.rb +12 -0
- data/db/migrate/3_add_index_to_tags.rb +5 -0
- data/db/migrate/4_add_index_to_taggings.rb +12 -0
- data/gemfiles/rails_5.gemfile +9 -0
- data/gemfiles/rails_6.gemfile +9 -0
- data/gemfiles/rails_master.gemfile +9 -0
- data/lib/make_taggable.rb +134 -0
- data/lib/make_taggable/default_parser.rb +75 -0
- data/lib/make_taggable/engine.rb +4 -0
- data/lib/make_taggable/generic_parser.rb +19 -0
- data/lib/make_taggable/tag.rb +131 -0
- data/lib/make_taggable/tag_list.rb +102 -0
- data/lib/make_taggable/taggable.rb +100 -0
- data/lib/make_taggable/taggable/cache.rb +90 -0
- data/lib/make_taggable/taggable/collection.rb +183 -0
- data/lib/make_taggable/taggable/core.rb +323 -0
- data/lib/make_taggable/taggable/ownership.rb +137 -0
- data/lib/make_taggable/taggable/related.rb +71 -0
- data/lib/make_taggable/taggable/tag_list_type.rb +4 -0
- data/lib/make_taggable/taggable/tagged_with_query.rb +16 -0
- data/lib/make_taggable/taggable/tagged_with_query/all_tags_query.rb +111 -0
- data/lib/make_taggable/taggable/tagged_with_query/any_tags_query.rb +68 -0
- data/lib/make_taggable/taggable/tagged_with_query/exclude_tags_query.rb +81 -0
- data/lib/make_taggable/taggable/tagged_with_query/query_base.rb +61 -0
- data/lib/make_taggable/tagger.rb +89 -0
- data/lib/make_taggable/tagging.rb +32 -0
- data/lib/make_taggable/tags_helper.rb +15 -0
- data/lib/make_taggable/utils.rb +34 -0
- data/lib/make_taggable/version.rb +4 -0
- data/lib/tasks/tags_collate_utf8.rake +17 -0
- data/make_taggable.gemspec +26 -0
- data/spec/dummy/README.md +24 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/config/manifest.js +2 -0
- data/spec/dummy/app/channels/application_cable/channel.rb +4 -0
- data/spec/dummy/app/channels/application_cable/connection.rb +4 -0
- data/spec/dummy/app/controllers/application_controller.rb +2 -0
- data/spec/dummy/app/controllers/concerns/.keep +0 -0
- data/spec/dummy/app/jobs/application_job.rb +7 -0
- data/spec/dummy/app/mailers/application_mailer.rb +4 -0
- data/spec/dummy/app/models/altered_inheriting_taggable_model.rb +5 -0
- data/spec/dummy/app/models/application_record.rb +3 -0
- data/spec/dummy/app/models/cached_model.rb +3 -0
- data/spec/dummy/app/models/cached_model_with_array.rb +11 -0
- data/spec/dummy/app/models/columns_override_model.rb +5 -0
- data/spec/dummy/app/models/company.rb +15 -0
- data/spec/dummy/app/models/concerns/.keep +0 -0
- data/spec/dummy/app/models/inheriting_taggable_model.rb +4 -0
- data/spec/dummy/app/models/market.rb +2 -0
- data/spec/dummy/app/models/non_standard_id_taggable_model.rb +8 -0
- data/spec/dummy/app/models/ordered_taggable_model.rb +4 -0
- data/spec/dummy/app/models/other_cached_model.rb +3 -0
- data/spec/dummy/app/models/other_taggable_model.rb +4 -0
- data/spec/dummy/app/models/student.rb +4 -0
- data/spec/dummy/app/models/taggable_model.rb +14 -0
- data/spec/dummy/app/models/untaggable_model.rb +3 -0
- data/spec/dummy/app/models/user.rb +3 -0
- data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
- data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +33 -0
- data/spec/dummy/config.ru +5 -0
- data/spec/dummy/config/application.rb +19 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/cable.yml +10 -0
- data/spec/dummy/config/credentials.yml.enc +1 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +52 -0
- data/spec/dummy/config/environments/production.rb +105 -0
- data/spec/dummy/config/environments/test.rb +49 -0
- data/spec/dummy/config/initializers/application_controller_renderer.rb +8 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cors.rb +16 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +33 -0
- data/spec/dummy/config/master.key +1 -0
- data/spec/dummy/config/puma.rb +38 -0
- data/spec/dummy/config/routes.rb +3 -0
- data/spec/dummy/config/spring.rb +6 -0
- data/spec/dummy/config/storage.yml +34 -0
- data/spec/dummy/db/migrate/20201119220853_create_taggable_models.rb +8 -0
- data/spec/dummy/db/migrate/20201119221037_create_columns_override_models.rb +9 -0
- data/spec/dummy/db/migrate/20201119221121_create_non_standard_id_taggable_models.rb +8 -0
- data/spec/dummy/db/migrate/20201119221228_create_untaggable_models.rb +8 -0
- data/spec/dummy/db/migrate/20201119221247_create_cached_models.rb +9 -0
- data/spec/dummy/db/migrate/20201119221314_create_other_cached_models.rb +11 -0
- data/spec/dummy/db/migrate/20201119221343_create_companies.rb +7 -0
- data/spec/dummy/db/migrate/20201119221416_create_users.rb +7 -0
- data/spec/dummy/db/migrate/20201119221434_create_other_taggable_models.rb +8 -0
- data/spec/dummy/db/migrate/20201119221507_create_ordered_taggable_models.rb +8 -0
- data/spec/dummy/db/migrate/20201119221530_create_cache_methods_injected_models.rb +7 -0
- data/spec/dummy/db/migrate/20201119221629_create_other_cached_with_array_models.rb +11 -0
- data/spec/dummy/db/migrate/20201119221746_create_taggable_model_with_jsons.rb +9 -0
- data/spec/dummy/db/migrate/20201119222429_create_make_taggable_tags.make_taggable_engine.rb +11 -0
- data/spec/dummy/db/migrate/20201119222430_create_make_taggable_taggings.make_taggable_engine.rb +13 -0
- data/spec/dummy/db/migrate/20201119222431_add_index_to_tags.make_taggable_engine.rb +6 -0
- data/spec/dummy/db/migrate/20201119222432_add_index_to_taggings.make_taggable_engine.rb +13 -0
- data/spec/dummy/db/schema.rb +117 -0
- data/spec/dummy/db/seeds.rb +7 -0
- data/spec/dummy/lib/tasks/.keep +0 -0
- data/spec/dummy/log/.keep +0 -0
- data/spec/dummy/public/robots.txt +1 -0
- data/spec/dummy/storage/.keep +0 -0
- data/spec/dummy/test/channels/application_cable/connection_test.rb +11 -0
- data/spec/dummy/test/controllers/.keep +0 -0
- data/spec/dummy/test/fixtures/.keep +0 -0
- data/spec/dummy/test/fixtures/files/.keep +0 -0
- data/spec/dummy/test/integration/.keep +0 -0
- data/spec/dummy/test/mailers/.keep +0 -0
- data/spec/dummy/test/models/.keep +0 -0
- data/spec/dummy/test/test_helper.rb +13 -0
- data/spec/dummy/vendor/.keep +0 -0
- data/spec/make_taggable/acts_as_tagger_spec.rb +112 -0
- data/spec/make_taggable/caching_spec.rb +123 -0
- data/spec/make_taggable/default_parser_spec.rb +45 -0
- data/spec/make_taggable/dirty_spec.rb +140 -0
- data/spec/make_taggable/generic_parser_spec.rb +13 -0
- data/spec/make_taggable/make_taggable_spec.rb +260 -0
- data/spec/make_taggable/related_spec.rb +93 -0
- data/spec/make_taggable/single_table_inheritance_spec.rb +220 -0
- data/spec/make_taggable/tag_list_spec.rb +169 -0
- data/spec/make_taggable/tag_spec.rb +297 -0
- data/spec/make_taggable/taggable_spec.rb +804 -0
- data/spec/make_taggable/tagger_spec.rb +149 -0
- data/spec/make_taggable/tagging_spec.rb +115 -0
- data/spec/make_taggable/tags_helper_spec.rb +43 -0
- data/spec/make_taggable/utils_spec.rb +22 -0
- data/spec/make_taggable_spec.rb +5 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/support/array.rb +9 -0
- data/spec/support/helpers.rb +31 -0
- metadata +391 -0
@@ -0,0 +1,71 @@
|
|
1
|
+
module MakeTaggable::Taggable
|
2
|
+
module Related
|
3
|
+
def self.included(base)
|
4
|
+
base.extend MakeTaggable::Taggable::Related::ClassMethods
|
5
|
+
base.initialize_make_taggable_related
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def initialize_make_taggable_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 make_taggable(*args)
|
25
|
+
super(*args)
|
26
|
+
initialize_make_taggable_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} = #{MakeTaggable::Tagging.table_name}.taggable_id AND #{MakeTaggable::Tagging.table_name}.taggable_type = '#{klass.base_class}' AND #{MakeTaggable::Tagging.table_name}.tag_id = #{MakeTaggable::Tag.table_name}.#{MakeTaggable::Tag.primary_key} AND #{MakeTaggable::Tag.table_name}.name IN (?) AND #{MakeTaggable::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} = #{MakeTaggable::Tagging.table_name}.taggable_id AND #{MakeTaggable::Tagging.table_name}.taggable_type = '#{klass.base_class}' AND #{MakeTaggable::Tagging.table_name}.tag_id = #{MakeTaggable::Tag.table_name}.#{MakeTaggable::Tag.primary_key} AND #{MakeTaggable::Tag.table_name}.name IN (?) AND #{MakeTaggable::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 MakeTaggable::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(#{MakeTaggable::Tag.table_name}.#{MakeTaggable::Tag.primary_key}) AS count")
|
65
|
+
.from("#{klass.table_name}, #{MakeTaggable::Tag.table_name}, #{MakeTaggable::Tagging.table_name}")
|
66
|
+
.group(group_columns(klass))
|
67
|
+
.order("count DESC")
|
68
|
+
.where(conditions)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
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 MakeTaggable::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 MakeTaggable::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
|
+
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_#{MakeTaggable::Utils.sha_prefix(tag)}")
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module MakeTaggable::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
|
+
adjust_taggings_alias(
|
64
|
+
"#{alias_base_name[0..4]}#{taggings_context[0..6]}_taggings_#{MakeTaggable::Utils.sha_prefix(tag_list.join("_"))}"
|
65
|
+
)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module MakeTaggable::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
|
+
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
|
+
def owning_to_tagger
|
28
|
+
return [] unless options[:owned_by].present?
|
29
|
+
|
30
|
+
owner = options[:owned_by]
|
31
|
+
|
32
|
+
arel_join = taggable_arel_table
|
33
|
+
.join(tagging_arel_table)
|
34
|
+
.on(
|
35
|
+
tagging_arel_table[:tagger_id].eq(owner.id)
|
36
|
+
.and(tagging_arel_table[:tagger_type].eq(owner.class.base_class.to_s))
|
37
|
+
.and(tagging_arel_table[:taggable_id].eq(taggable_arel_table[taggable_model.primary_key]))
|
38
|
+
.and(tagging_arel_table[:taggable_type].eq(taggable_model.base_class.name))
|
39
|
+
)
|
40
|
+
|
41
|
+
if options[:match_all].present?
|
42
|
+
arel_join = arel_join
|
43
|
+
.join(tagging_arel_table, Arel::Nodes::OuterJoin)
|
44
|
+
.on(
|
45
|
+
match_all_on_conditions
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
arel_join.join_sources
|
50
|
+
end
|
51
|
+
|
52
|
+
def match_all_on_conditions
|
53
|
+
on_condition = tagging_arel_table[:taggable_id].eq(taggable_arel_table[taggable_model.primary_key])
|
54
|
+
.and(tagging_arel_table[:taggable_type].eq(taggable_model.base_class.name))
|
55
|
+
|
56
|
+
if options[:start_at].present?
|
57
|
+
on_condition = on_condition.and(tagging_arel_table[:created_at].gteq(options[:start_at]))
|
58
|
+
end
|
59
|
+
|
60
|
+
if options[:end_at].present?
|
61
|
+
on_condition = on_condition.and(tagging_arel_table[:created_at].lteq(options[:end_at]))
|
62
|
+
end
|
63
|
+
|
64
|
+
if options[:on].present?
|
65
|
+
on_condition = on_condition.and(tagging_arel_table[:context].eq(options[:on]))
|
66
|
+
end
|
67
|
+
|
68
|
+
on_condition
|
69
|
+
end
|
70
|
+
|
71
|
+
def tags_that_matches_count
|
72
|
+
return [] unless options[:match_all].present?
|
73
|
+
|
74
|
+
taggable_model.find_by_sql(tag_arel_table.project(Arel.star.count).where(tags_match_type).to_sql)
|
75
|
+
|
76
|
+
tagging_arel_table[:taggable_id].count.eq(
|
77
|
+
tag_arel_table.project(Arel.star.count).where(tags_match_type)
|
78
|
+
)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module MakeTaggable::Taggable::TaggedWithQuery
|
2
|
+
class QueryBase
|
3
|
+
def initialize(taggable_model, tag_model, tagging_model, tag_list, options)
|
4
|
+
@taggable_model = taggable_model
|
5
|
+
@tag_model = tag_model
|
6
|
+
@tagging_model = tagging_model
|
7
|
+
@tag_list = tag_list
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
attr_reader :taggable_model, :tag_model, :tagging_model, :tag_list, :options
|
14
|
+
|
15
|
+
def taggable_arel_table
|
16
|
+
@taggable_arel_table ||= taggable_model.arel_table
|
17
|
+
end
|
18
|
+
|
19
|
+
def tag_arel_table
|
20
|
+
@tag_arel_table ||= tag_model.arel_table
|
21
|
+
end
|
22
|
+
|
23
|
+
def tagging_arel_table
|
24
|
+
@tagging_arel_table ||= tagging_model.arel_table
|
25
|
+
end
|
26
|
+
|
27
|
+
def tag_match_type(tag)
|
28
|
+
matches_attribute = tag_arel_table[:name]
|
29
|
+
matches_attribute = matches_attribute.lower unless MakeTaggable.strict_case_match
|
30
|
+
|
31
|
+
if options[:wild].present?
|
32
|
+
matches_attribute.matches("%#{escaped_tag(tag)}%", "!")
|
33
|
+
else
|
34
|
+
matches_attribute.matches(escaped_tag(tag), "!")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def tags_match_type
|
39
|
+
matches_attribute = tag_arel_table[:name]
|
40
|
+
matches_attribute = matches_attribute.lower unless MakeTaggable.strict_case_match
|
41
|
+
|
42
|
+
if options[:wild].present?
|
43
|
+
matches_attribute.matches_any(tag_list.map { |tag| "%#{escaped_tag(tag)}%" }, "!")
|
44
|
+
else
|
45
|
+
matches_attribute.matches_any(tag_list.map { |tag| escaped_tag(tag).to_s }, "!")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def escaped_tag(tag)
|
50
|
+
tag = tag.downcase unless MakeTaggable.strict_case_match
|
51
|
+
MakeTaggable::Utils.escape_like(tag)
|
52
|
+
end
|
53
|
+
|
54
|
+
def adjust_taggings_alias(taggings_alias)
|
55
|
+
if taggings_alias.size > 75
|
56
|
+
taggings_alias = "taggings_alias_" + Digest::SHA1.hexdigest(taggings_alias)
|
57
|
+
end
|
58
|
+
taggings_alias
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module MakeTaggable
|
2
|
+
module Tagger
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
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: "::MakeTaggable::Tagging",
|
24
|
+
dependent: :destroy
|
25
|
+
)
|
26
|
+
|
27
|
+
has_many :owned_tags, -> { distinct },
|
28
|
+
class_name: "::MakeTaggable::Tag",
|
29
|
+
source: :tag,
|
30
|
+
through: :owned_taggings
|
31
|
+
end
|
32
|
+
|
33
|
+
include MakeTaggable::Tagger::InstanceMethods
|
34
|
+
extend MakeTaggable::Tagger::SingletonMethods
|
35
|
+
end
|
36
|
+
|
37
|
+
def tagger?
|
38
|
+
false
|
39
|
+
end
|
40
|
+
|
41
|
+
def is_tagger?
|
42
|
+
tagger?
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module InstanceMethods
|
47
|
+
##
|
48
|
+
# Tag a taggable model with tags that are owned by the tagger.
|
49
|
+
#
|
50
|
+
# @param taggable The object that will be tagged
|
51
|
+
# @param [Hash] options An hash with options. Available options are:
|
52
|
+
# * <tt>:with</tt> - The tags that you want to
|
53
|
+
# * <tt>:on</tt> - The context on which you want to tag
|
54
|
+
#
|
55
|
+
# Example:
|
56
|
+
# @user.tag(@photo, :with => "paris, normandy", :on => :locations)
|
57
|
+
def tag(taggable, opts = {})
|
58
|
+
opts.reverse_merge!(force: true)
|
59
|
+
skip_save = opts.delete(:skip_save)
|
60
|
+
return false unless taggable.respond_to?(:is_taggable?) && taggable.is_taggable?
|
61
|
+
|
62
|
+
fail "You need to specify a tag context using :on" unless opts.key?(:on)
|
63
|
+
fail "You need to specify some tags using :with" unless opts.key?(:with)
|
64
|
+
fail "No context :#{opts[:on]} defined in #{taggable.class}" unless opts[:force] || taggable.tag_types.include?(opts[:on])
|
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
|
+
def is_tagger?
|
75
|
+
tagger?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
module SingletonMethods
|
80
|
+
def tagger?
|
81
|
+
true
|
82
|
+
end
|
83
|
+
|
84
|
+
def is_tagger?
|
85
|
+
tagger?
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|