permalink 1.2.0 → 2.1.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 (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