permalink 1.2.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.github/FUNDING.yml +3 -0
  3. data/.gitignore +3 -1
  4. data/.rubocop.yml +13 -0
  5. data/.travis.yml +7 -0
  6. data/Gemfile +3 -1
  7. data/README.md +118 -0
  8. data/Rakefile +14 -4
  9. data/lib/permalink.rb +38 -8
  10. data/lib/permalink/active_record.rb +127 -0
  11. data/lib/permalink/normalizations/contraction.rb +11 -0
  12. data/lib/permalink/normalizations/downcase.rb +11 -0
  13. data/lib/permalink/normalizations/leading_trailing_dashes.rb +11 -0
  14. data/lib/permalink/normalizations/multiple_dashes.rb +11 -0
  15. data/lib/permalink/normalizations/non_alphanumeric.rb +12 -0
  16. data/lib/permalink/normalizations/transliteration.rb +14 -0
  17. data/lib/permalink/version.rb +4 -2
  18. data/permalink.gemspec +15 -13
  19. data/test/permalink/active_record_test.rb +175 -0
  20. data/test/permalink/normalizations_test.rb +48 -0
  21. data/test/permalink/permalink_test.rb +22 -0
  22. data/test/support/post.rb +5 -0
  23. data/test/support/schema.rb +11 -0
  24. data/test/support/user.rb +5 -0
  25. data/test/test_helper.rb +17 -0
  26. metadata +94 -106
  27. data/.rspec +0 -1
  28. data/Gemfile.lock +0 -79
  29. data/README.markdown +0 -79
  30. data/lib/permalink/orm/active_record.rb +0 -26
  31. data/lib/permalink/orm/base.rb +0 -83
  32. data/lib/permalink/orm/mongo_mapper.rb +0 -28
  33. data/lib/permalink/orm/mongoid.rb +0 -24
  34. data/lib/permalink/string_ext.rb +0 -12
  35. data/spec/permalink/active_record_spec.rb +0 -13
  36. data/spec/permalink/mongo_mapper_spec.rb +0 -13
  37. data/spec/permalink/mongoid_spec.rb +0 -13
  38. data/spec/permalink/string_spec.rb +0 -23
  39. data/spec/spec_helper.rb +0 -24
  40. data/spec/support/article.rb +0 -4
  41. data/spec/support/page.rb +0 -5
  42. data/spec/support/post.rb +0 -2
  43. data/spec/support/schema.rb +0 -5
  44. data/spec/support/shared.rb +0 -95
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b59f20670f6bcfbd6b204ad5e23aefcce7abf477b6b574d165d87e892e9b6afc
4
+ data.tar.gz: dfdee1e9df89d3d11e9d9240811c00a4307abf1a22accc6346e992b092326225
5
+ SHA512:
6
+ metadata.gz: 79d9f75edc2fd001ffe2cc4544ba5bd0fbc38a7a842641f5d656136961233aa926a72e9ad2e5d387641e354d5b4dd919f92567289d84a137644ed7edab782857
7
+ data.tar.gz: '080cb2da608b09403a9e4aae8d42f1465a0cb33b3902aadf2f4747c50c72bd21bd361f91dc1abce55503df7d5488d5332099e20cc9755938ae54f8a5045cd9a3'
@@ -0,0 +1,3 @@
1
+ ---
2
+ github: [fnando]
3
+ custom: ["https://www.paypal.me/nandovieira/🍕"]
data/.gitignore CHANGED
@@ -1 +1,3 @@
1
- pkg
1
+ pkg
2
+ *.lock
3
+ coverage
@@ -0,0 +1,13 @@
1
+ ---
2
+ inherit_gem:
3
+ rubocop-fnando: .rubocop.yml
4
+
5
+ AllCops:
6
+ TargetRubyVersion: 2.7
7
+ NewCops: enable
8
+
9
+ Metrics/MethodLength:
10
+ Enabled: false
11
+
12
+ Metrics/ClassLength:
13
+ Enabled: false
@@ -0,0 +1,7 @@
1
+ cache: bundler
2
+ sudo: false
3
+ rvm:
4
+ - "2.7.1"
5
+ - "2.6.5"
6
+ notifications:
7
+ email: false
data/Gemfile CHANGED
@@ -1,2 +1,4 @@
1
- source :rubygems
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
2
4
  gemspec
@@ -0,0 +1,118 @@
1
+ # Permalink
2
+
3
+ [![Code Climate](https://codeclimate.com/github/fnando/permalink.svg)](https://codeclimate.com/github/fnando/permalink)
4
+ [![Build Status](https://travis-ci.org/fnando/permalink.svg)](https://travis-ci.org/fnando/permalink)
5
+ [![Gem](https://img.shields.io/gem/v/permalink.svg)](https://rubygems.org/gems/permalink)
6
+ [![Gem](https://img.shields.io/gem/dt/permalink.svg)](https://rubygems.org/gems/permalink)
7
+
8
+ ## Installation
9
+
10
+ gem install permalink
11
+
12
+ ## Usage
13
+
14
+ Add the method call `permalink` to your model. Your model should have a
15
+ `permalink` attribute.
16
+
17
+ ```ruby
18
+ class Page < ActiveRecord::Base
19
+ permalink :title
20
+ end
21
+ ```
22
+
23
+ You can specify the permalink field:
24
+
25
+ ```ruby
26
+ class page < ActiveRecord::Base
27
+ permalink :title, to: "title_permalink"
28
+ end
29
+ ```
30
+
31
+ If you don't want to use `permalink`, you can call
32
+ `Permalink.generate("some text")` string method and manage the permalink process
33
+ by yourself.
34
+
35
+ Permalinks are not unique by default. `permalink` overrides `to_param` as
36
+ following:
37
+
38
+ ```ruby
39
+ def to_param
40
+ "#{id}-#{permalink}"
41
+ end
42
+ ```
43
+
44
+ You can define the `to_param` format:
45
+
46
+ ```ruby
47
+ class Page < ActiveRecord::Base
48
+ permalink :title, to_param: %w(id permalink page)
49
+ end
50
+ ```
51
+
52
+ The above settings will generate something link `100-some-title-page`. By
53
+ overriding `to_param` method you don't have to change a thing on your app
54
+ routes.
55
+
56
+ If you want to generate unique permalink, use the `:unique` option:
57
+
58
+ ```ruby
59
+ class Page < ActiveRecord::Base
60
+ permalink :title, unique: true, to_param: "permalink"
61
+ end
62
+ ```
63
+
64
+ The permalink can be tied to a given scope. Let's say you want to have unique
65
+ permalinks by user. Just set the `:scope` option.
66
+
67
+ ```ruby
68
+ class Page < ActiveRecord::Base
69
+ belongs_to :user
70
+ permalink :title, unique: true, scope: "user_id"
71
+ end
72
+
73
+ user = User.first
74
+ another_user = User.last
75
+
76
+ page = user.pages.create(title: 'Hello')
77
+ page.permalink #=> hello
78
+
79
+ another_page = another_user.pages.create(title: 'Hello')
80
+ another_page.permalink #=> hello
81
+ ```
82
+
83
+ The permalink is generated using `ActiveSupport::Multibyte::Chars` class; this
84
+ means that characters will properly replaced from `áéíó` to `aeio`, for
85
+ instance.
86
+
87
+ The permalink is created when `before_validation` callback is evaluated. This
88
+ plugin also tries to generate a permalink when `before_save` callback is
89
+ evaluated and the instance has no permalink set.
90
+
91
+ You can force the permalink generation by setting the `:force` option.
92
+
93
+ ```ruby
94
+ class Page < ActiveRecord::Base
95
+ permalink :title, force: true
96
+ end
97
+ ```
98
+
99
+ ## License
100
+
101
+ Copyright (c) 2011-2015 Nando Vieira, released under the MIT license
102
+
103
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
104
+ this software and associated documentation files (the "Software"), to deal in
105
+ the Software without restriction, including without limitation the rights to
106
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
107
+ the Software, and to permit persons to whom the Software is furnished to do so,
108
+ subject to the following conditions:
109
+
110
+ The above copyright notice and this permission notice shall be included in all
111
+ copies or substantial portions of the Software.
112
+
113
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
114
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
115
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
116
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
117
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
118
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile CHANGED
@@ -1,5 +1,15 @@
1
- require "bundler"
2
- Bundler::GemHelper.install_tasks
1
+ # frozen_string_literal: true
3
2
 
4
- require "rspec/core/rake_task"
5
- RSpec::Core::RakeTask.new
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+ require "rubocop/rake_task"
6
+
7
+ Rake::TestTask.new(:test) do |t|
8
+ t.libs << "test"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ t.warning = false
11
+ end
12
+
13
+ RuboCop::RakeTask.new
14
+
15
+ task default: %i[test rubocop]
@@ -1,8 +1,38 @@
1
- require "permalink/string_ext"
2
- require "permalink/orm/base"
3
- require "permalink/orm/active_record"
4
- require "permalink/orm/mongo_mapper"
5
- require "permalink/orm/mongoid"
6
-
7
- ActiveRecord::Base.send(:include, Permalink::Orm::ActiveRecord) if defined?(ActiveRecord)
8
- Mongoid::Document::ClassMethods.send(:include, Permalink::Orm::Mongoid) if defined?(Mongoid)
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record"
4
+ require "permalink/active_record"
5
+ require "permalink/normalizations/contraction"
6
+ require "permalink/normalizations/downcase"
7
+ require "permalink/normalizations/leading_trailing_dashes"
8
+ require "permalink/normalizations/multiple_dashes"
9
+ require "permalink/normalizations/non_alphanumeric"
10
+ require "permalink/normalizations/transliteration"
11
+
12
+ module Permalink
13
+ DEFAULT_NORMALIZATIONS = [
14
+ Normalizations::Transliteration,
15
+ Normalizations::Downcase,
16
+ Normalizations::Contraction,
17
+ Normalizations::NonAlphanumeric,
18
+ Normalizations::MultipleDashes,
19
+ Normalizations::LeadingTrailingDashes
20
+ ].freeze
21
+
22
+ DEFAULT_OPTIONS = {
23
+ normalizations: DEFAULT_NORMALIZATIONS,
24
+ separator: "-"
25
+ }.freeze
26
+
27
+ def self.generate(input, options = DEFAULT_OPTIONS)
28
+ options = DEFAULT_OPTIONS.merge(options)
29
+
30
+ options[:normalizations].each do |normalization|
31
+ input = normalization.call(input, options)
32
+ end
33
+
34
+ input
35
+ end
36
+ end
37
+
38
+ ActiveRecord::Base.include Permalink::ActiveRecord
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Permalink
4
+ module ActiveRecord
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ base.extend(Permalink::ActiveRecord::ClassMethods)
8
+ base.cattr_accessor :permalink_options
9
+ end
10
+
11
+ module ClassMethods
12
+ # permalink :title
13
+ # permalink :title, to: :custom_permalink_field
14
+ # permalink :title, to: :permalink, to_param: [:id, :permalink]
15
+ # permalink :title, unique: true
16
+ # permalink :title, separator: "_"
17
+ # permalink :title, normalizations: array_of_normalizations
18
+ def permalink(from_column, options = {})
19
+ include InstanceMethods
20
+
21
+ options = options.reverse_merge(
22
+ to_param: %i[id permalink],
23
+ to: :permalink,
24
+ unique: false,
25
+ force: false,
26
+ separator: "-",
27
+ normalizations: Permalink::DEFAULT_NORMALIZATIONS
28
+ )
29
+
30
+ self.permalink_options = {
31
+ from_column_name: from_column,
32
+ to_column_name: options[:to],
33
+ to_param: [options[:to_param]].flatten,
34
+ unique: options[:unique],
35
+ force: options[:force],
36
+ scope: options[:scope],
37
+ separator: options[:separator],
38
+ normalizations: options[:normalizations]
39
+ }
40
+
41
+ before_validation :create_permalink
42
+ before_save :create_permalink
43
+ end
44
+ end
45
+
46
+ module InstanceMethods
47
+ def to_param
48
+ to_param_option = permalink_options[:to_param]
49
+
50
+ to_param_option.compact.map do |name|
51
+ respond_to?(name) ? public_send(name).to_s : name.to_s
52
+ end.reject(&:blank?).join(permalink_options[:separator])
53
+ end
54
+
55
+ private def next_available_permalink(permalink)
56
+ unique_permalink = permalink
57
+ scope = build_scope_for_permalink
58
+
59
+ if permalink_options[:unique]
60
+ suffix = 2
61
+
62
+ while scope.where(to_permalink_name => unique_permalink).first
63
+ unique_permalink =
64
+ [permalink, suffix].join(permalink_options[:separator])
65
+
66
+ suffix += 1
67
+ end
68
+ end
69
+
70
+ unique_permalink
71
+ end
72
+
73
+ private def build_scope_for_permalink
74
+ search_scope = permalink_options[:scope]
75
+ scope = self.class.unscoped
76
+
77
+ if search_scope
78
+ scope = scope.where(search_scope => public_send(search_scope))
79
+ end
80
+
81
+ scope
82
+ end
83
+
84
+ private def from_permalink_name
85
+ permalink_options[:from_column_name]
86
+ end
87
+
88
+ private def to_permalink_name
89
+ permalink_options[:to_column_name]
90
+ end
91
+
92
+ private def from_permalink_value
93
+ read_attribute(from_permalink_name)
94
+ end
95
+
96
+ private def to_permalink_value
97
+ read_attribute(to_permalink_name)
98
+ end
99
+
100
+ private def update_permalink?
101
+ changes[from_permalink_name] &&
102
+ (permalink_options[:force] || to_permalink_value.blank?)
103
+ end
104
+
105
+ private def create_permalink
106
+ return unless update_permalink?
107
+
108
+ permalink = Permalink.generate(
109
+ from_permalink_value.to_s,
110
+ permalink_generator_options
111
+ )
112
+
113
+ write_attribute(
114
+ to_permalink_name,
115
+ next_available_permalink(permalink)
116
+ )
117
+ end
118
+
119
+ private def permalink_generator_options
120
+ {
121
+ separator: permalink_options[:separator],
122
+ normalizations: permalink_options[:normalizations]
123
+ }
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Permalink
4
+ module Normalizations
5
+ module Contraction
6
+ def self.call(input, _options = nil)
7
+ input.gsub(/['’]/, "")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Permalink
4
+ module Normalizations
5
+ module Downcase
6
+ def self.call(input, _options = nil)
7
+ input.downcase
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Permalink
4
+ module Normalizations
5
+ module LeadingTrailingDashes
6
+ def self.call(input, _options = nil)
7
+ input.gsub(/^-?(.*?)-?$/, '\1')
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Permalink
4
+ module Normalizations
5
+ module MultipleDashes
6
+ def self.call(input, options = DEFAULT_OPTIONS)
7
+ input.gsub(/-+/sm, options[:separator])
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Permalink
4
+ module Normalizations
5
+ module NonAlphanumeric
6
+ def self.call(input, options = DEFAULT_OPTIONS)
7
+ regex = /[^#{options[:separator]}a-z0-9]/sim
8
+ input.gsub(regex, options[:separator])
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Permalink
4
+ module Normalizations
5
+ module Transliteration
6
+ def self.call(input, _options = nil)
7
+ input
8
+ .unicode_normalize(:nfkd)
9
+ .gsub(/[^\x00-\x7F]/, "")
10
+ .to_s
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Permalink
2
4
  module Version
3
- MAJOR = 1
4
- MINOR = 2
5
+ MAJOR = 2
6
+ MINOR = 1
5
7
  PATCH = 0
6
8
  STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
7
9
  end