slugs 2.0.1 → 4.0.0.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +59 -19
  3. data/Rakefile +1 -19
  4. data/lib/generators/slugs/install/install_generator.rb +24 -0
  5. data/lib/generators/slugs/{templates/configuration.rb → install/templates/initializer.rb} +1 -1
  6. data/lib/generators/slugs/install/templates/migration.rb +15 -0
  7. data/lib/slugs.rb +7 -4
  8. data/lib/slugs/concern.rb +16 -9
  9. data/lib/slugs/configuration.rb +11 -1
  10. data/lib/slugs/extensions/action_dispatch/generator.rb +1 -0
  11. data/lib/slugs/extensions/action_dispatch/optimized_url_helper.rb +1 -0
  12. data/lib/slugs/extensions/active_record/finders.rb +27 -0
  13. data/lib/slugs/railtie.rb +10 -4
  14. data/lib/slugs/slug.rb +9 -0
  15. data/lib/slugs/version.rb +1 -1
  16. data/test/dummy/bin/bundle +1 -0
  17. data/test/dummy/bin/rails +1 -0
  18. data/test/dummy/bin/rake +1 -0
  19. data/test/dummy/bin/setup +1 -0
  20. data/test/dummy/config/database.yml.travis +2 -11
  21. data/test/dummy/config/environments/development.rb +1 -1
  22. data/test/dummy/config/environments/production.rb +1 -1
  23. data/test/dummy/config/environments/test.rb +2 -2
  24. data/test/dummy/config/initializers/slugs.rb +1 -1
  25. data/test/dummy/config/secrets.yml +2 -2
  26. data/test/dummy/db/migrate/20161016174020_create_users.rb +2 -0
  27. data/test/dummy/db/migrate/20161016174126_create_shops.rb +2 -0
  28. data/test/dummy/db/migrate/20161016174202_create_products.rb +2 -0
  29. data/test/dummy/db/migrate/20161016174225_create_categories.rb +2 -0
  30. data/test/dummy/db/migrate/20161124162802_create_slugs.rb +15 -0
  31. data/test/dummy/db/schema.rb +33 -13
  32. data/test/dummy/log/development.log +394 -30
  33. data/test/dummy/log/test.log +3017 -3978
  34. data/test/dummy/public/404.html +57 -63
  35. data/test/dummy/public/422.html +57 -63
  36. data/test/dummy/public/500.html +56 -62
  37. data/test/generator_test.rb +3 -4
  38. data/test/record_test.rb +65 -0
  39. data/test/{routes_test.rb → route_test.rb} +1 -1
  40. data/test/test_helper.rb +2 -2
  41. metadata +14 -9
  42. data/lib/generators/slugs/install_generator.rb +0 -15
  43. data/test/records_test.rb +0 -27
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 21b9bbbfe8b2a44f63ffb7edf8eaa22fe97cf6ce
4
- data.tar.gz: 41e5ec36704f1c403c4cb62030cdbc83dc4e8fb9
3
+ metadata.gz: 40739528bced84f0f44d1d2b4b703195e5200fad
4
+ data.tar.gz: 61ae632c29f54550bed3fc4ecd6e2919fa56d4fe
5
5
  SHA512:
6
- metadata.gz: 1096e6bdcdb42b39622b0a8175c9c2fe7928b1d29e09fd2c4eedf5d1b59745dc871d653f4279cf7277a935632e935ee13dabd573c684034ea4bb6839fd465bc1
7
- data.tar.gz: 072aa30a88b10abad83b03a6b45fb65fc383af4d1c3a9e7495822d7d8778fe13e3f8d727b73a020d439f5347d203a19c4fe24536373ec800f5d4006a0185ac81
6
+ metadata.gz: 454843d24608dad78b5c634201499918bd99d7c1cc59b46d8af85a2197c1be055be353f25ae9449846682a86174e06c1222aa9cf2a1ca41c675c8043ae95bc00
7
+ data.tar.gz: 81f1c28b5d1b2bdc1a482ce3fd5e2775bf63ec85d0f2946ee100d000976aa776880a8c3e5669608eeede8dbcb7ab23e7e26a35f9a2929fb93a75484b3f107e2b
data/README.md CHANGED
@@ -1,12 +1,20 @@
1
1
  [![Gem Version](https://badge.fury.io/rb/slugs.svg)](http://badge.fury.io/rb/slugs)
2
2
  [![Code Climate](https://codeclimate.com/github/mmontossi/slugs/badges/gpa.svg)](https://codeclimate.com/github/mmontossi/slugs)
3
- [![Build Status](https://travis-ci.org/mmontossi/slugs.svg)](https://travis-ci.org/mmontossis/slugs)
3
+ [![Build Status](https://travis-ci.org/mmontossi/slugs.svg)](https://travis-ci.org/mmontossi/slugs)
4
4
  [![Dependency Status](https://gemnasium.com/mmontossi/slugs.svg)](https://gemnasium.com/mmontossi/slugs)
5
5
 
6
6
  # Slugs
7
7
 
8
8
  Manages slugs for records with minimal efford in rails.
9
9
 
10
+ ## Why
11
+
12
+ I did this gem to:
13
+
14
+ - Generalize how to control when routes will use the slug param.
15
+ - Keep old slugs active until the record is destroyed.
16
+ - Ensure unique slugs by appending an index automatically on duplicates.
17
+
10
18
  ## Install
11
19
 
12
20
  Put this line in your Gemfile:
@@ -21,47 +29,79 @@ $ bundle
21
29
 
22
30
  ## Configuration
23
31
 
24
- Generate the slugs configuration file:
32
+ Run the install generator:
25
33
  ```
26
- bundle exec rails g slugs:install
34
+ $ bundle exec rails g slugs:install
27
35
  ```
28
36
 
29
- Add the slug column to the tables of the models you want to have slugs:
37
+ Set the global settings:
38
+ ```ruby
39
+ Slugs.configure do |config|
40
+ config.use_slug? do |record, params|
41
+ params[:controller] != 'admin'
42
+ end
43
+ end
44
+ ```
45
+
46
+ ## Usage
47
+
48
+ ### Definitions
49
+
50
+ Add the column to your tables:
30
51
  ```ruby
31
- t.string :slug
52
+ class AddSlug < ActiveRecord::Migration
53
+ def change
54
+ add_column :products, :slug, :string
55
+ end
56
+ end
32
57
  ```
33
58
 
34
59
  Update your db:
35
60
  ```
36
- bundle exec rake db:migrate
61
+ $ bundle exec rake db:migrate
37
62
  ```
38
63
 
39
- Configure the proc to decide which records will be slugged:
64
+ Define slugs in your models:
40
65
  ```ruby
41
- Slugs.configure do |config|
42
- config.use_slug_proc = Proc.new do |record, params|
43
- params[:controller] != 'admin'
44
- end
66
+ class Product < ActiveRecord::Base
67
+ has_slug :model, :name, scope: :shop_id
45
68
  end
46
69
  ```
47
70
 
48
- ## Usage
71
+ ### Generation
49
72
 
50
- Use has_slug in your models to define what the slug will be:
73
+ A slug will be generated every time you create/update a record:
74
+ ```ruby
75
+ product = Product.create(name: 'Stratocaster', model: 'American Standar', ...)
76
+ product.slug # => 'american-standard-stratocaster'
77
+ ```
78
+
79
+ An index will be appended if another record with the same slug is created:
80
+ ```ruby
81
+ product = Product.create(name: 'Stratocaster', model: 'American Standard', ...)
82
+ product.slug # => 'american-standard-stratocaster-1'
83
+ ```
51
84
 
52
- If you want to use the value of one field:
85
+ Every time you change a record, the slug will be updated:
53
86
  ```ruby
54
- has_slug :prop
87
+ product.update name: 'Strat'
88
+ product.slug # => 'american-standard-strat'
55
89
  ```
56
90
 
57
- To concatenate the value of multiple fields:
91
+ ### Finders
92
+
93
+ The find method of models will start accepting slugs and remember old ones:
58
94
  ```ruby
59
- has_slug :prop1, :prop2, :prop3
95
+ Product.find 'american-standard-stratocaster' # => product
96
+ Product.find 'american-standard-strat' # => product
60
97
  ```
61
98
 
62
- To find a record by slug:
99
+ ### Routes
100
+
101
+ The logic of the use_slug? block is used to determine when to sluggize:
63
102
  ```ruby
64
- Model.find_by slug: 'slug'
103
+ admin_product_path product # => 'admin/products/34443'
104
+ product_path product # => 'products/american-standard-strat'
65
105
  ```
66
106
 
67
107
  ## Credits
data/Rakefile CHANGED
@@ -4,26 +4,8 @@ rescue LoadError
4
4
  puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
5
  end
6
6
 
7
- require 'rdoc/task'
8
-
9
- RDoc::Task.new(:rdoc) do |rdoc|
10
- rdoc.rdoc_dir = 'rdoc'
11
- rdoc.title = 'Slugs'
12
- rdoc.options << '--line-numbers'
13
- rdoc.rdoc_files.include('README.rdoc')
14
- rdoc.rdoc_files.include('lib/**/*.rb')
15
- end
16
-
17
-
18
-
19
-
20
-
21
-
22
7
  Bundler::GemHelper.install_tasks
23
8
 
24
- APP_RAKEFILE = File.expand_path('../test/dummy/Rakefile', __FILE__)
25
- load 'rails/tasks/engine.rake'
26
-
27
9
  require 'rake/testtask'
28
10
 
29
11
  Rake::TestTask.new(:test) do |t|
@@ -31,7 +13,7 @@ Rake::TestTask.new(:test) do |t|
31
13
  t.libs << 'test'
32
14
  t.pattern = 'test/**/*_test.rb'
33
15
  t.verbose = false
16
+ t.warning = false
34
17
  end
35
18
 
36
-
37
19
  task default: :test
@@ -0,0 +1,24 @@
1
+ require 'rails/generators'
2
+
3
+ module Slugs
4
+ module Generators
5
+ class InstallGenerator < ::Rails::Generators::Base
6
+ include Rails::Generators::Migration
7
+
8
+ source_root File.expand_path('../templates', __FILE__)
9
+
10
+ def create_initializer_file
11
+ copy_file 'initializer.rb', 'config/initializers/slugs.rb'
12
+ end
13
+
14
+ def create_migration_file
15
+ migration_template 'migration.rb', 'db/migrate/create_slugs.rb'
16
+ end
17
+
18
+ def self.next_migration_number(path)
19
+ Time.now.utc.strftime '%Y%m%d%H%M%S'
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -1,6 +1,6 @@
1
1
  Slugs.configure do |config|
2
2
 
3
- config.use_slug_proc = Proc.new do |record, params|
3
+ config.use_slug? do |record, params|
4
4
  # Add your code here
5
5
  end
6
6
 
@@ -0,0 +1,15 @@
1
+ class CreateSlugs < ActiveRecord::Migration
2
+ def change
3
+ create_table :slugs do |t|
4
+ t.integer :sluggable_id
5
+ t.string :sluggable_type
6
+ t.string :value
7
+
8
+ t.timestamps null: false
9
+ end
10
+
11
+ add_index :slugs, :sluggable_id
12
+ add_index :slugs, :sluggable_type
13
+ add_index :slugs, :value
14
+ end
15
+ end
data/lib/slugs.rb CHANGED
@@ -1,9 +1,12 @@
1
1
  require 'slugs/extensions/action_dispatch/generator'
2
2
  require 'slugs/extensions/action_dispatch/optimized_url_helper'
3
3
  require 'slugs/extensions/active_record/base'
4
+ require 'slugs/extensions/active_record/finders'
5
+ require 'slugs/slug'
4
6
  require 'slugs/concern'
5
7
  require 'slugs/configuration'
6
8
  require 'slugs/railtie'
9
+ require 'slugs/version'
7
10
 
8
11
  module Slugs
9
12
  class << self
@@ -17,7 +20,7 @@ module Slugs
17
20
  end
18
21
 
19
22
  def parameterize(record, params)
20
- if use_slug_for?(record, params)
23
+ if use_slug?(record, params)
21
24
  if record.slug_changed?
22
25
  record.slug_was
23
26
  else
@@ -28,9 +31,9 @@ module Slugs
28
31
  end
29
32
  end
30
33
 
31
- def use_slug_for?(record, params)
32
- if record.try(:sluggable?) && configuration.use_slug_proc
33
- configuration.use_slug_proc.call record, params
34
+ def use_slug?(record, params)
35
+ if record.try(:sluggable?)
36
+ configuration.use_slug? record, params
34
37
  else
35
38
  false
36
39
  end
data/lib/slugs/concern.rb CHANGED
@@ -3,10 +3,9 @@ module Slugs
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
- before_create :set_slug
7
- after_save :ensure_slug_uniqueness
6
+ has_many :slugs, as: :sluggable, class_name: 'Slugs::Slug'
7
+ before_save :set_slug
8
8
  validates_format_of :slug, with: /\A[a-z0-9\-]+\z/, allow_blank: true
9
- validates_length_of :slug, maximum: 255, allow_blank: true
10
9
  end
11
10
 
12
11
  def sluggable?
@@ -18,10 +17,6 @@ module Slugs
18
17
  def set_slug
19
18
  options = self.class.slug
20
19
  self.slug = slice(*options[:attributes]).values.join(' ').parameterize
21
- end
22
-
23
- def ensure_slug_uniqueness
24
- options = self.class.slug
25
20
  case options[:scope]
26
21
  when Symbol
27
22
  attribute = options[:scope]
@@ -30,8 +25,20 @@ module Slugs
30
25
  attributes = options[:scope]
31
26
  scope = attributes.map{ |a| [a, send(a)] }.to_h
32
27
  end
33
- if self.class.where(scope).where(slug: slug).where.not(id: id).any?
34
- update_column :slug, "#{slug}-#{id}"
28
+ relation = self.class.where(scope).where('slug ~ ?', "^#{slug}(-[0-9]+)?$")
29
+ if persisted?
30
+ relation = relation.where.not(id: id)
31
+ end
32
+ previous_slug = relation.order(slug: :desc).limit(1).pluck(:slug).first
33
+ if previous_slug.present?
34
+ if result = previous_slug.match(/^#{slug}-(\d+)$/)
35
+ self.slug += "-#{result[1].to_i + 1}"
36
+ else
37
+ self.slug += '-1'
38
+ end
39
+ end
40
+ if slug_changed?
41
+ slugs.build value: slug
35
42
  end
36
43
  end
37
44
 
@@ -1,7 +1,17 @@
1
1
  module Slugs
2
2
  class Configuration
3
3
 
4
- attr_accessor :use_slug_proc
4
+ def use_slug?(*args, &block)
5
+ if block_given?
6
+ @use_slug = block
7
+ else
8
+ if @use_slug
9
+ @use_slug.call *args
10
+ else
11
+ false
12
+ end
13
+ end
14
+ end
5
15
 
6
16
  end
7
17
  end
@@ -2,6 +2,7 @@ module Slugs
2
2
  module Extensions
3
3
  module ActionDispatch
4
4
  module Generator
5
+ extend ActiveSupport::Concern
5
6
 
6
7
  def generate
7
8
  @set.formatter.generate(
@@ -2,6 +2,7 @@ module Slugs
2
2
  module Extensions
3
3
  module ActionDispatch
4
4
  module OptimizedUrlHelper
5
+ extend ActiveSupport::Concern
5
6
 
6
7
  private
7
8
 
@@ -0,0 +1,27 @@
1
+ module Slugs
2
+ module Extensions
3
+ module ActiveRecord
4
+ module Finders
5
+ extend ActiveSupport::Concern
6
+
7
+ def find(id)
8
+ if sluggable? && id.is_a?(String) && id !~ /\A\d+\z/
9
+ order = Slugs::Slug.order(id: :desc)
10
+ joins(:slugs).merge(order).find_by! slugs: { value: id }
11
+ else
12
+ super
13
+ end
14
+ end
15
+
16
+ def exists?(value=:none)
17
+ if sluggable? && value.is_a?(String) && value !~ /\A\d+\z/
18
+ joins(:slugs).exists? slugs: { value: value }
19
+ else
20
+ super
21
+ end
22
+ end
23
+
24
+ end
25
+ end
26
+ end
27
+ end
data/lib/slugs/railtie.rb CHANGED
@@ -1,16 +1,22 @@
1
1
  module Slugs
2
2
  class Railtie < Rails::Railtie
3
3
 
4
- initializer 'slugs' do
5
- ::ActionDispatch::Routing::RouteSet::NamedRouteCollection::UrlHelper::OptimizedUrlHelper.prepend(
6
- Slugs::Extensions::ActionDispatch::OptimizedUrlHelper
7
- )
4
+ initializer 'slugs.extensions' do
8
5
  ::ActionDispatch::Routing::RouteSet::Generator.prepend(
9
6
  Slugs::Extensions::ActionDispatch::Generator
10
7
  )
8
+ ::ActionDispatch::Routing::RouteSet::NamedRouteCollection::UrlHelper::OptimizedUrlHelper.prepend(
9
+ Slugs::Extensions::ActionDispatch::OptimizedUrlHelper
10
+ )
11
11
  ::ActiveRecord::Base.include(
12
12
  Slugs::Extensions::ActiveRecord::Base
13
13
  )
14
+ ::ActiveRecord::Base.extend(
15
+ Slugs::Extensions::ActiveRecord::Finders
16
+ )
17
+ ::ActiveRecord::Relation.include(
18
+ Slugs::Extensions::ActiveRecord::Finders
19
+ )
14
20
  end
15
21
 
16
22
  end
data/lib/slugs/slug.rb ADDED
@@ -0,0 +1,9 @@
1
+ module Slugs
2
+ class Slug < ActiveRecord::Base
3
+
4
+ belongs_to :sluggable, polymorphic: true
5
+
6
+ validates_presence_of :sluggable, :value
7
+
8
+ end
9
+ end
data/lib/slugs/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Slugs
2
2
 
3
- VERSION = '2.0.1'
3
+ VERSION = '4.0.0.0'
4
4
 
5
5
  end
@@ -1,3 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
+
2
3
  ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3
4
  load Gem.bin_path('bundler', 'bundle')
data/test/dummy/bin/rails CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+
2
3
  APP_PATH = File.expand_path('../../config/application', __FILE__)
3
4
  require_relative '../config/boot'
4
5
  require 'rails/commands'
data/test/dummy/bin/rake CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+
2
3
  require_relative '../config/boot'
3
4
  require 'rake'
4
5
  Rake.application.run
data/test/dummy/bin/setup CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+
2
3
  require 'pathname'
3
4
 
4
5
  # path to your application root.
@@ -1,12 +1,3 @@
1
- mysql: &mysql
2
- adapter: <%= 'jdbc' if RUBY_ENGINE == 'jruby' %>mysql<%= '2' if RUBY_ENGINE != 'jruby' %>
3
-
4
- postgres: &postgres
5
- adapter: <%= 'jdbc' if RUBY_ENGINE == 'jruby' %>postgresql
6
-
7
- sqlite: &sqlite
8
- adapter: <%= 'jdbc' if RUBY_ENGINE == 'jruby' %>sqlite3
9
-
10
1
  test:
11
- <<: *<%= ENV['DB'] %>
12
- database: <%= ENV['DB'] == 'sqlite' ? 'db/travis.sqlite3' : 'travis' %>
2
+ adapter: <%= 'jdbc' if RUBY_ENGINE == 'jruby' %>postgresql
3
+ database: travis