make_taggable 0.6.3
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.
- 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
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "make_taggable"
|
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__)
|
data/bin/setup
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
class CreateMakeTaggableTaggings < ActiveRecord::Migration[5.2]
|
2
|
+
def change
|
3
|
+
create_table MakeTaggable.taggings_table do |t|
|
4
|
+
t.references :tag, foreign_key: {to_table: MakeTaggable.tags_table}
|
5
|
+
t.references :taggable, polymorphic: true
|
6
|
+
t.references :tagger, polymorphic: true
|
7
|
+
t.string :context, limit: 128
|
8
|
+
|
9
|
+
t.timestamps
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class AddIndexToTaggings < ActiveRecord::Migration[5.2]
|
2
|
+
def change
|
3
|
+
add_index MakeTaggable.taggings_table, :taggable_id
|
4
|
+
add_index MakeTaggable.taggings_table, :tagger_id
|
5
|
+
add_index MakeTaggable.taggings_table, :taggable_type
|
6
|
+
add_index MakeTaggable.taggings_table, :context
|
7
|
+
add_index MakeTaggable.taggings_table, [:tagger_id, :tagger_type]
|
8
|
+
add_index MakeTaggable.taggings_table, [:tag_id, :taggable_id, :taggable_type, :context, :tagger_id, :tagger_type], unique: true, name: "taggings_idx"
|
9
|
+
add_index MakeTaggable.taggings_table, [:taggable_id, :taggable_type, :context], name: "taggings_taggable_context_idx"
|
10
|
+
add_index MakeTaggable.taggings_table, [:taggable_id, :taggable_type, :tagger_id, :context], name: "taggings_idy"
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require "make_taggable/version"
|
2
|
+
require "active_record"
|
3
|
+
require "active_record/version"
|
4
|
+
require "active_support/core_ext/module"
|
5
|
+
|
6
|
+
begin
|
7
|
+
require "rails/engine"
|
8
|
+
require "make_taggable/engine"
|
9
|
+
rescue LoadError
|
10
|
+
end
|
11
|
+
|
12
|
+
require "digest/sha1"
|
13
|
+
|
14
|
+
module MakeTaggable
|
15
|
+
extend ActiveSupport::Autoload
|
16
|
+
|
17
|
+
autoload :Tag
|
18
|
+
autoload :TagList
|
19
|
+
autoload :GenericParser
|
20
|
+
autoload :DefaultParser
|
21
|
+
autoload :Taggable
|
22
|
+
autoload :Tagger
|
23
|
+
autoload :Tagging
|
24
|
+
autoload :TagsHelper
|
25
|
+
autoload :VERSION
|
26
|
+
|
27
|
+
autoload_under "taggable" do
|
28
|
+
autoload :Cache
|
29
|
+
autoload :Collection
|
30
|
+
autoload :Core
|
31
|
+
autoload :Dirty
|
32
|
+
autoload :Ownership
|
33
|
+
autoload :Related
|
34
|
+
autoload :TagListType
|
35
|
+
end
|
36
|
+
|
37
|
+
autoload :Utils
|
38
|
+
autoload :Compatibility
|
39
|
+
|
40
|
+
class DuplicateTagError < StandardError
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.setup
|
44
|
+
@configuration ||= Configuration.new
|
45
|
+
yield @configuration if block_given?
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.method_missing(method_name, *args, &block)
|
49
|
+
if @configuration.respond_to?(method_name)
|
50
|
+
@configuration.send(method_name, *args, &block)
|
51
|
+
else
|
52
|
+
super
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.respond_to_missing?(method_name, include_private = false)
|
57
|
+
@configuration.respond_to? method_name
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.glue
|
61
|
+
setting = @configuration.delimiter
|
62
|
+
delimiter = setting.is_a?(Array) ? setting[0] : setting
|
63
|
+
delimiter.ends_with?(" ") ? delimiter : "#{delimiter} "
|
64
|
+
end
|
65
|
+
|
66
|
+
class Configuration
|
67
|
+
attr_accessor :force_lowercase, :force_parameterize,
|
68
|
+
:remove_unused_tags, :default_parser,
|
69
|
+
:tags_counter, :tags_table,
|
70
|
+
:taggings_table
|
71
|
+
attr_reader :delimiter, :strict_case_match
|
72
|
+
|
73
|
+
def initialize
|
74
|
+
@delimiter = ","
|
75
|
+
@force_lowercase = false
|
76
|
+
@force_parameterize = false
|
77
|
+
@strict_case_match = false
|
78
|
+
@remove_unused_tags = false
|
79
|
+
@tags_counter = true
|
80
|
+
@default_parser = DefaultParser
|
81
|
+
@force_binary_collation = false
|
82
|
+
@tags_table = :tags
|
83
|
+
@taggings_table = :taggings
|
84
|
+
end
|
85
|
+
|
86
|
+
def strict_case_match=(force_cs)
|
87
|
+
@strict_case_match = force_cs unless @force_binary_collation
|
88
|
+
end
|
89
|
+
|
90
|
+
def delimiter=(string)
|
91
|
+
ActiveRecord::Base.logger.warn <<~WARNING
|
92
|
+
MakeTaggable.delimiter is deprecated \
|
93
|
+
and will be removed from v4.0+, use \
|
94
|
+
a MakeTaggable.default_parser instead
|
95
|
+
WARNING
|
96
|
+
@delimiter = string
|
97
|
+
end
|
98
|
+
|
99
|
+
def force_binary_collation=(force_bin)
|
100
|
+
if Utils.using_mysql?
|
101
|
+
if force_bin
|
102
|
+
Configuration.apply_binary_collation(true)
|
103
|
+
@force_binary_collation = true
|
104
|
+
@strict_case_match = true
|
105
|
+
else
|
106
|
+
Configuration.apply_binary_collation(false)
|
107
|
+
@force_binary_collation = false
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.apply_binary_collation(bincoll)
|
113
|
+
if Utils.using_mysql?
|
114
|
+
coll = "utf8_general_ci"
|
115
|
+
coll = "utf8_bin" if bincoll
|
116
|
+
begin
|
117
|
+
ActiveRecord::Migration.execute("ALTER TABLE #{Tag.table_name} MODIFY name varchar(255) CHARACTER SET utf8 COLLATE #{coll};")
|
118
|
+
rescue => e
|
119
|
+
puts "Trapping #{e.class}: collation parameter ignored while migrating for the first time."
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
setup
|
125
|
+
end
|
126
|
+
|
127
|
+
ActiveSupport.on_load(:active_record) do
|
128
|
+
extend MakeTaggable::Taggable
|
129
|
+
include MakeTaggable::Tagger
|
130
|
+
end
|
131
|
+
|
132
|
+
ActiveSupport.on_load(:action_view) do
|
133
|
+
include MakeTaggable::TagsHelper
|
134
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module MakeTaggable
|
2
|
+
##
|
3
|
+
# Returns a new TagList using the given tag string.
|
4
|
+
#
|
5
|
+
# Example:
|
6
|
+
# tag_list = MakeTaggable::DefaultParser.parse("One , Two, Three")
|
7
|
+
# tag_list # ["One", "Two", "Three"]
|
8
|
+
class DefaultParser < GenericParser
|
9
|
+
def parse
|
10
|
+
string = @tag_list
|
11
|
+
|
12
|
+
string = string.join(MakeTaggable.glue) if string.respond_to?(:join)
|
13
|
+
TagList.new.tap do |tag_list|
|
14
|
+
string = string.to_s.dup
|
15
|
+
|
16
|
+
string.gsub!(double_quote_pattern) do
|
17
|
+
# Append the matched tag to the tag list
|
18
|
+
tag_list << Regexp.last_match[2]
|
19
|
+
# Return the matched delimiter ($3) to replace the matched items
|
20
|
+
""
|
21
|
+
end
|
22
|
+
|
23
|
+
string.gsub!(single_quote_pattern) do
|
24
|
+
# Append the matched tag ($2) to the tag list
|
25
|
+
tag_list << Regexp.last_match[2]
|
26
|
+
# Return an empty string to replace the matched items
|
27
|
+
""
|
28
|
+
end
|
29
|
+
|
30
|
+
# split the string by the delimiter
|
31
|
+
# and add to the tag_list
|
32
|
+
tag_list.add(string.split(Regexp.new(delimiter)))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# private
|
37
|
+
def delimiter
|
38
|
+
# Parse the quoted tags
|
39
|
+
d = MakeTaggable.delimiter
|
40
|
+
# Separate multiple delimiters by bitwise operator
|
41
|
+
d = d.join("|") if d.is_a?(Array)
|
42
|
+
d
|
43
|
+
end
|
44
|
+
|
45
|
+
# ( # Tag start delimiter ($1)
|
46
|
+
# \A | # Either string start or
|
47
|
+
# #{delimiter} # a delimiter
|
48
|
+
# )
|
49
|
+
# \s*" # quote (") optionally preceded by whitespace
|
50
|
+
# (.*?) # Tag ($2)
|
51
|
+
# "\s* # quote (") optionally followed by whitespace
|
52
|
+
# (?= # Tag end delimiter (not consumed; is zero-length lookahead)
|
53
|
+
# #{delimiter}\s* | # Either a delimiter optionally followed by whitespace or
|
54
|
+
# \z # string end
|
55
|
+
# )
|
56
|
+
def double_quote_pattern
|
57
|
+
/(\A|#{delimiter})\s*"(.*?)"\s*(?=#{delimiter}\s*|\z)/
|
58
|
+
end
|
59
|
+
|
60
|
+
# ( # Tag start delimiter ($1)
|
61
|
+
# \A | # Either string start or
|
62
|
+
# #{delimiter} # a delimiter
|
63
|
+
# )
|
64
|
+
# \s*' # quote (') optionally preceded by whitespace
|
65
|
+
# (.*?) # Tag ($2)
|
66
|
+
# '\s* # quote (') optionally followed by whitespace
|
67
|
+
# (?= # Tag end delimiter (not consumed; is zero-length lookahead)
|
68
|
+
# #{delimiter}\s* | d # Either a delimiter optionally followed by whitespace or
|
69
|
+
# \z # string end
|
70
|
+
# )
|
71
|
+
def single_quote_pattern
|
72
|
+
/(\A|#{delimiter})\s*'(.*?)'\s*(?=#{delimiter}\s*|\z)/
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module MakeTaggable
|
2
|
+
##
|
3
|
+
# Returns a new TagList using the given tag string.
|
4
|
+
#
|
5
|
+
# Example:
|
6
|
+
# tag_list = MakeTaggable::GenericParser.new.parse("One , Two, Three")
|
7
|
+
# tag_list # ["One", "Two", "Three"]
|
8
|
+
class GenericParser
|
9
|
+
def initialize(tag_list)
|
10
|
+
@tag_list = tag_list
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse
|
14
|
+
TagList.new.tap do |tag_list|
|
15
|
+
tag_list.add @tag_list.split(",").map(&:strip).reject(&:empty?)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
module MakeTaggable
|
2
|
+
class Tag < ::ActiveRecord::Base
|
3
|
+
self.table_name = MakeTaggable.tags_table
|
4
|
+
|
5
|
+
### ASSOCIATIONS:
|
6
|
+
has_many :taggings, dependent: :destroy, class_name: "::MakeTaggable::Tagging"
|
7
|
+
|
8
|
+
### VALIDATIONS:
|
9
|
+
validates_presence_of :name
|
10
|
+
validates_uniqueness_of :name, if: :validates_name_uniqueness?
|
11
|
+
validates_length_of :name, maximum: 255
|
12
|
+
|
13
|
+
# Monkey patch this method if don't need name uniqueness validation
|
14
|
+
def validates_name_uniqueness?
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
### SCOPES:
|
19
|
+
scope :most_used, ->(limit = 20) { order("taggings_count desc").limit(limit) }
|
20
|
+
scope :least_used, ->(limit = 20) { order("taggings_count asc").limit(limit) }
|
21
|
+
|
22
|
+
def self.named(name)
|
23
|
+
if MakeTaggable.strict_case_match
|
24
|
+
where(["name = #{binary}?", as_8bit_ascii(name)])
|
25
|
+
else
|
26
|
+
where(["LOWER(name) = LOWER(?)", as_8bit_ascii(unicode_downcase(name))])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.named_any(list)
|
31
|
+
clause = list.map { |tag|
|
32
|
+
sanitize_sql_for_named_any(tag).force_encoding("BINARY")
|
33
|
+
}.join(" OR ")
|
34
|
+
where(clause)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.named_like(name)
|
38
|
+
clause = ["name #{MakeTaggable::Utils.like_operator} ? ESCAPE '!'", "%#{MakeTaggable::Utils.escape_like(name)}%"]
|
39
|
+
where(clause)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.named_like_any(list)
|
43
|
+
clause = list.map { |tag|
|
44
|
+
sanitize_sql(["name #{MakeTaggable::Utils.like_operator} ? ESCAPE '!'", "%#{MakeTaggable::Utils.escape_like(tag.to_s)}%"])
|
45
|
+
}.join(" OR ")
|
46
|
+
where(clause)
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.for_context(context)
|
50
|
+
joins(:taggings)
|
51
|
+
.where(["#{MakeTaggable.taggings_table}.context = ?", context])
|
52
|
+
.select("DISTINCT #{MakeTaggable.tags_table}.*")
|
53
|
+
end
|
54
|
+
|
55
|
+
### CLASS METHODS:
|
56
|
+
def self.find_or_create_with_like_by_name(name)
|
57
|
+
if MakeTaggable.strict_case_match
|
58
|
+
find_or_create_all_with_like_by_name([name]).first
|
59
|
+
else
|
60
|
+
named_like(name).first || create(name: name)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.find_or_create_all_with_like_by_name(*list)
|
65
|
+
list = Array(list).flatten
|
66
|
+
|
67
|
+
return [] if list.empty?
|
68
|
+
|
69
|
+
existing_tags = named_any(list)
|
70
|
+
list.map do |tag_name|
|
71
|
+
tries ||= 3
|
72
|
+
comparable_tag_name = comparable_name(tag_name)
|
73
|
+
existing_tag = existing_tags.find { |tag| comparable_name(tag.name) == comparable_tag_name }
|
74
|
+
existing_tag || create(name: tag_name)
|
75
|
+
rescue ActiveRecord::RecordNotUnique
|
76
|
+
if (tries -= 1).positive?
|
77
|
+
ActiveRecord::Base.connection.execute "ROLLBACK"
|
78
|
+
existing_tags = named_any(list)
|
79
|
+
retry
|
80
|
+
end
|
81
|
+
|
82
|
+
raise DuplicateTagError.new("'#{tag_name}' has already been taken")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
### INSTANCE METHODS:
|
87
|
+
def ==(other)
|
88
|
+
super || (other.is_a?(Tag) && name == other.name)
|
89
|
+
end
|
90
|
+
|
91
|
+
def to_s
|
92
|
+
name
|
93
|
+
end
|
94
|
+
|
95
|
+
def count
|
96
|
+
read_attribute(:count).to_i
|
97
|
+
end
|
98
|
+
|
99
|
+
class << self
|
100
|
+
private
|
101
|
+
|
102
|
+
def comparable_name(str)
|
103
|
+
if MakeTaggable.strict_case_match
|
104
|
+
str
|
105
|
+
else
|
106
|
+
unicode_downcase(str.to_s)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def binary
|
111
|
+
MakeTaggable::Utils.using_mysql? ? "BINARY " : nil
|
112
|
+
end
|
113
|
+
|
114
|
+
def as_8bit_ascii(string)
|
115
|
+
string.to_s.mb_chars
|
116
|
+
end
|
117
|
+
|
118
|
+
def unicode_downcase(string)
|
119
|
+
as_8bit_ascii(string).downcase
|
120
|
+
end
|
121
|
+
|
122
|
+
def sanitize_sql_for_named_any(tag)
|
123
|
+
if MakeTaggable.strict_case_match
|
124
|
+
sanitize_sql(["name = #{binary}?", as_8bit_ascii(tag)])
|
125
|
+
else
|
126
|
+
sanitize_sql(["LOWER(name) = LOWER(?)", as_8bit_ascii(unicode_downcase(tag))])
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|