Module: Slimak::Sluggable
- Extended by:
- ActiveSupport::Concern
- Defined in:
- lib/slimak/sluggable.rb
Overview
Sluggable concern for ActiveRecord models (also usable on plain Ruby objects).
Instance Method Summary collapse
- #_slug ⇒ Object
- #assign_slug_column(slug_col, value) ⇒ Object
- #build_slug_string ⇒ Object
- #build_unique_slug ⇒ Object
- #format_component(str, column) ⇒ Object
- #generate_slug_if_blank ⇒ Object
- #parameterize(str, separator = "-") ⇒ Object
-
#safe_read(name) ⇒ Object
Safe read helper.
- #slug_exists?(candidate, col, scope) ⇒ Boolean
Instance Method Details
#_slug ⇒ Object
64 65 66 67 68 69 70 71 |
# File 'lib/slimak/sluggable.rb', line 64 def _slug col = self.class.[:column] if respond_to?(col) && !send(col).to_s.strip.empty? send(col).to_s else build_slug_string end end |
#assign_slug_column(slug_col, value) ⇒ Object
149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/slimak/sluggable.rb', line 149 def assign_slug_column(slug_col, value) if respond_to?("#{slug_col}=") send("#{slug_col}=", value) elsif respond_to?(:write_attribute) write_attribute(slug_col, value) else (class << self; self; end).class_eval do attr_accessor slug_col unless method_defined?(slug_col) end instance_variable_set("@#{slug_col}", value) end end |
#build_slug_string ⇒ Object
86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/slimak/sluggable.rb', line 86 def build_slug_string parts = self.class._slimak_slug_columns.map do |col| v = safe_read(col) next nil if v.nil? formatted = format_component(v.to_s, col) formatted unless formatted.to_s.empty? end.compact return "" if parts.empty? parameterize(parts.join(" "), self.class.[:separator]) end |
#build_unique_slug ⇒ Object
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/slimak/sluggable.rb', line 99 def build_unique_slug base = build_slug_string return base if base.to_s.strip.empty? return base unless defined?(ActiveRecord) && self.class.respond_to?(:unscoped) # Merge global options with model options so model options override global. opts = Slimak.config.to_hash.merge(self.class. || {}) col = opts[:column] scope = opts[:scope] case opts[:conflict_strategy].to_sym when :random candidate = base.dup while slug_exists?(candidate, col, scope) suffix = SecureRandom.alphanumeric(opts[:random_suffix_length]).downcase candidate = [base, suffix].join(opts[:sequence_separator]) end candidate else candidate = base.dup seq = 2 while slug_exists?(candidate, col, scope) candidate = [base, seq].join(opts[:sequence_separator]) seq += 1 end candidate end end |
#format_component(str, column) ⇒ Object
139 140 141 142 143 144 145 146 147 |
# File 'lib/slimak/sluggable.rb', line 139 def format_component(str, column) s = str.dup s = ActiveSupport::Inflector.transliterate(s) if defined?(ActiveSupport::Inflector) s = s.strip limits = self.class._slimak_slug_column_limits || {} max = limits && limits[column.to_sym] s = s[0, max] if max && max > 0 s.gsub(/[[:space:]]+/, " ") end |
#generate_slug_if_blank ⇒ Object
73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/slimak/sluggable.rb', line 73 def generate_slug_if_blank return unless self.class._slimak_slug_columns.any? slug_col = self.class.[:column] if respond_to?(slug_col) && !send(slug_col).to_s.strip.empty? return end candidate = build_unique_slug assign_slug_column(slug_col, candidate) nil end |
#parameterize(str, separator = "-") ⇒ Object
162 163 164 165 166 167 168 169 |
# File 'lib/slimak/sluggable.rb', line 162 def parameterize(str, separator = "-") s = str.to_s begin s.parameterize(separator: separator) rescue ArgumentError s.parameterize(separator) end end |
#safe_read(name) ⇒ Object
Safe read helper
129 130 131 132 133 134 135 136 137 |
# File 'lib/slimak/sluggable.rb', line 129 def safe_read(name) if respond_to?(name) send(name) else nil end rescue => _ nil end |
#slug_exists?(candidate, col, scope) ⇒ Boolean
171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/slimak/sluggable.rb', line 171 def slug_exists?(candidate, col, scope) return false unless defined?(ActiveRecord) && self.class.respond_to?(:unscoped) rel = self.class.unscoped.where(col => candidate) if scope Array(scope).each do |sc| val = safe_read(sc) rel = rel.where(sc => val) end end if respond_to?(:persisted?) && persisted? rel = rel.where.not(id: id) end rel.exists? end |