slugs 2.0.1 → 4.0.0.0

Sign up to get free protection for your applications and to get access to all the features.
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