rails_friendly_urls 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +89 -0
- data/Rakefile +14 -0
- data/lib/generators/rails_friendly_urls/install_generator.rb +19 -0
- data/lib/rails_friendly_urls.rb +34 -0
- data/lib/rails_friendly_urls/friendly_url.rb +21 -0
- data/lib/rails_friendly_urls/manager.rb +55 -0
- data/lib/rails_friendly_urls/route_sets/rails3.rb +45 -0
- data/lib/rails_friendly_urls/route_sets/route_set.rb +97 -0
- data/lib/rails_friendly_urls/urls/rails4_0.rb +36 -0
- data/lib/rails_friendly_urls/urls/rails4_2.rb +26 -0
- data/lib/rails_friendly_urls/version.rb +3 -0
- data/spec/apps/rails3_2.rb +46 -0
- data/spec/apps/rails4.rb +47 -0
- data/spec/generators/install_generator_spec.rb +33 -0
- data/spec/rails_friendly_urls/friendly_url_spec.rb +53 -0
- data/spec/rails_friendly_urls/manager_spec.rb +54 -0
- data/spec/spec_helper.rb +51 -0
- data/spec/support/dummy_friendly_url.rb +19 -0
- data/spec/support/dummy_manager_impl.rb +11 -0
- data/spec/support/routes.rb +33 -0
- data/spec/support/routes/rails3.rb +3 -0
- data/spec/support/routes/rails4.rb +3 -0
- metadata +150 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6e2ea43784b8044cc81c72711d18ca5a4c7b6bf9
|
4
|
+
data.tar.gz: 5d7e8cb96eb2d0b28abd5669bc1283e55bc4e471
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e8291e4dbdf95a576394897f5a73a5d946a41581ab8fcd184cd6c652d7de324b53edaa5b9ae332a29e52f9fd52fa8eeed00ce6dc498ef72b9a0b9ac18b2b627b
|
7
|
+
data.tar.gz: 74465748d887ca85fdbf616b21b418b61d3f0b4a3106e2ba5c0b5299c3e2d3b08734dafd8ab3aa8efcaf555672fe6ca7d738494bb7f79c00cd6a954758a4d95f
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Carlos Alonso Pérez
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
#Rails Friendly Urls Engine
|
2
|
+
[![Build Status](https://travis-ci.org/calonso/rails_friendly_urls.svg?branch=master)](https://travis-ci.org/calonso/rails_friendly_urls) [![Code Climate](https://codeclimate.com/github/calonso/rails_friendly_urls/badges/gpa.svg)](https://codeclimate.com/github/calonso/rails_friendly_urls) [![Test Coverage](https://codeclimate.com/github/calonso/rails_friendly_urls/badges/coverage.svg)](https://codeclimate.com/github/calonso/rails_friendly_urls/coverage) [![calonso/rails_friendly_urls API Documentation](https://www.omniref.com/github/calonso/rails_friendly_urls.png)](https://www.omniref.com/github/calonso/rails_friendly_urls)
|
3
|
+
|
4
|
+
Rails Gem to easily configure any url as a friendlier one.
|
5
|
+
|
6
|
+
##Features
|
7
|
+
|
8
|
+
* Allows customisation of **ABSOLUTELY** any url into a SEO friendlier one.
|
9
|
+
* Takes care of the parameters you defined in your original route and will pass them into the controller when the friendly url is invoked.
|
10
|
+
* Takes care of named routes and both url and path helpers so that there's no need to change a single line of code when adding a new friendly url.
|
11
|
+
* When a friendly URL is defined to substitute another one. The non-friendly one is automatically configured to redirect to the friendly path so that you're not penalised by search engines.
|
12
|
+
* Doesn't force any particular storage for the friendly url's data.
|
13
|
+
|
14
|
+
##Example application
|
15
|
+
|
16
|
+
You can see the gem running live in the following url: [https://rails-friendly-urls-test.herokuapp.com/](https://rails-friendly-urls-test.herokuapp.com/)
|
17
|
+
|
18
|
+
The source code for this example project is available here: [https://github.com/calonso/rails_friendly_urls_test](https://github.com/calonso/rails_friendly_urls_test)
|
19
|
+
|
20
|
+
##Installation
|
21
|
+
|
22
|
+
Installing this gem only requires you to add the following line to your `Gemfile`
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
gem 'rails_friendly_urls'
|
26
|
+
```
|
27
|
+
|
28
|
+
Run
|
29
|
+
|
30
|
+
```
|
31
|
+
$ bundle install
|
32
|
+
$ rails generate rails_friendly_urls:install
|
33
|
+
```
|
34
|
+
|
35
|
+
##Setup
|
36
|
+
|
37
|
+
Here I detail you the steps that I followed to set up the friendly urls engine in the example application, so most of them should be the same for you, some others slightly different, but don't worry, you'll see appropriated explanations while reading this steps.
|
38
|
+
|
39
|
+
###1. Friendly URLs Storage
|
40
|
+
|
41
|
+
First of all we need to decide our urls storage technology, in my case I decided to use a standard activerecord rails model, but I guess that a YAML file could do the job as well or any other persistence technology.
|
42
|
+
|
43
|
+
5 fields are required to be stored to be able to build a friendly url. They are:
|
44
|
+
|
45
|
+
1. Path
|
46
|
+
2. Slug
|
47
|
+
3. Controller
|
48
|
+
4. Action
|
49
|
+
5. Defaults
|
50
|
+
|
51
|
+
Just a reminder that you must make your storage engine to manage the defaults field as a hash.
|
52
|
+
|
53
|
+
Once you've done it, remember to include the `RailsFriendlyUrls::FriendlyUrl` module in that class so that you can use `set_destination_data!` method. That method will complete all your `controller`, `action` and `defaults` fields once you've provided `path` and `slug`.
|
54
|
+
|
55
|
+
###2. The Manager
|
56
|
+
|
57
|
+
After running the bundled installer (`$ rails generate rails_friendly_urls:install`) a new but incomplete file appears at `config/initializers/friendly_urls_manager.rb`. We need to complete the `urls` method in that file to make it return the list of friendly url objects (objects that simply respond to the five methods described above, i.e: path, slug, controller, action and defaults)
|
58
|
+
|
59
|
+
In my example project this is the final implementation:
|
60
|
+
|
61
|
+
```
|
62
|
+
# FriendlyUrls Manager contents
|
63
|
+
class RailsFriendlyUrls::Manager
|
64
|
+
def self.urls
|
65
|
+
::FriendlyUrl.all
|
66
|
+
end
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
Simple, huh?
|
71
|
+
|
72
|
+
###3. URL injection
|
73
|
+
|
74
|
+
As part of the installation process, a new line is inserted on top of the `routes.rb` file that simply invokes the friendly urls engine to do its magic.
|
75
|
+
|
76
|
+
##Caveats
|
77
|
+
|
78
|
+
* At the moment, only GET requests are supported.
|
79
|
+
* At the moment, the rails application has to be restarted for the new urls to start working.
|
80
|
+
|
81
|
+
## Contributing
|
82
|
+
|
83
|
+
1. Fork it ( https://github.com/calonso/rails_friendly_urls/fork )
|
84
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
85
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
86
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
87
|
+
5. Create a new Pull Request
|
88
|
+
|
89
|
+
Released under the MIT-LICENSE.
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler/setup"
|
3
|
+
require 'bundler'
|
4
|
+
require 'appraisal'
|
5
|
+
|
6
|
+
require 'rspec/core/rake_task'
|
7
|
+
|
8
|
+
RSpec::Core::RakeTask.new(:spec)
|
9
|
+
|
10
|
+
task :default => :spec
|
11
|
+
|
12
|
+
if !ENV["APPRAISAL_INITIALIZED"] && !ENV["TRAVIS"]
|
13
|
+
task :default => :appraisal
|
14
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
class RailsFriendlyUrls::InstallGenerator < Rails::Generators::Base
|
4
|
+
desc 'Creates a boilerplate Friendly Urls Manager with an empty implementation for you to complete.'
|
5
|
+
def create_friendly_urls_manager
|
6
|
+
create_file "config/initializers/friendly_urls_manager.rb", <<-EOS
|
7
|
+
# FriendlyUrls Manager contents
|
8
|
+
class RailsFriendlyUrls::Manager
|
9
|
+
def self.urls
|
10
|
+
raise NotImplementedError.new 'RailsFriendlyUrls::Manager::urls not implemented at config/initializers/friendly_urls_manager.rb'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
EOS
|
14
|
+
end
|
15
|
+
|
16
|
+
def inject_urls
|
17
|
+
insert_into_file "config/routes.rb", "\n RailsFriendlyUrls::Manager.inject_urls self\n", :after => ".routes.draw do\n"
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'rails'
|
3
|
+
|
4
|
+
#
|
5
|
+
# Base gem's module
|
6
|
+
#
|
7
|
+
# @author Carlos Alonso
|
8
|
+
#
|
9
|
+
module RailsFriendlyUrls
|
10
|
+
|
11
|
+
#
|
12
|
+
# Method to quickly get the major and minor versions of the current Rails env
|
13
|
+
#
|
14
|
+
# @returns [String] with X.Y format with major and minor versions
|
15
|
+
def self.rails_version
|
16
|
+
"#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}"
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'rails_friendly_urls/manager'
|
22
|
+
require 'rails_friendly_urls/friendly_url'
|
23
|
+
require 'rails_friendly_urls/route_sets/route_set'
|
24
|
+
|
25
|
+
case RailsFriendlyUrls.rails_version
|
26
|
+
when '4.2'
|
27
|
+
require 'rails_friendly_urls/urls/rails4_2'
|
28
|
+
when '4.0', '4.1'
|
29
|
+
require 'rails_friendly_urls/urls/rails4_0'
|
30
|
+
when '3.2'
|
31
|
+
require 'rails_friendly_urls/route_sets/rails3'
|
32
|
+
else
|
33
|
+
raise NotImplementedError.new "Rails Friendly URLs gem doesn't support Rails #{Rails.version}"
|
34
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module RailsFriendlyUrls
|
2
|
+
#
|
3
|
+
# This module is to be included in the client class that represents the Friendly URL
|
4
|
+
#
|
5
|
+
# @author Carlos Alonso
|
6
|
+
#
|
7
|
+
module FriendlyUrl
|
8
|
+
#
|
9
|
+
# This method tries to identify the route contained at self.path to extract
|
10
|
+
# the destination's controller, action and other arguments and save them
|
11
|
+
# into the corresponding controller, action and defaults fields of the
|
12
|
+
# including objects.
|
13
|
+
#
|
14
|
+
def set_destination_data!
|
15
|
+
route_info = Rails.application.routes.recognize_path self.path
|
16
|
+
self.controller = route_info[:controller]
|
17
|
+
self.action = route_info[:action]
|
18
|
+
self.defaults = route_info.reject { |k, v| [:controller, :action].include? k }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module RailsFriendlyUrls
|
2
|
+
#
|
3
|
+
# This class is responsible for orchestrating the whole gem's functions.
|
4
|
+
#
|
5
|
+
# @author Carlos Alonso
|
6
|
+
#
|
7
|
+
class Manager
|
8
|
+
# It is designed to be a Singleton, only one instance will run per process.
|
9
|
+
include Singleton
|
10
|
+
|
11
|
+
#
|
12
|
+
# Injects the defined urls into the given RouteSet. This method should be invoked
|
13
|
+
# first thing in the routes.rb block
|
14
|
+
#
|
15
|
+
# @param [ActionDispatch::Routing::RouteSet] rails' current RouteSet.
|
16
|
+
def self.inject_urls(mapper)
|
17
|
+
list = self.urls || []
|
18
|
+
Rails.logger.warn "Injecting empty Friendly URLs List!!" if list.empty?
|
19
|
+
list.each do |f_url|
|
20
|
+
mapper.get f_url.slug, to: "#{f_url.controller}##{f_url.action}", defaults: f_url.defaults
|
21
|
+
mapper.get f_url.path, to: mapper.redirect(f_url.slug)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
# Invokes Rails' routes reload. Useful to reload the routes after
|
27
|
+
# modifying the list of SEO Friendly defined ones without having to
|
28
|
+
# restart the application server.
|
29
|
+
#
|
30
|
+
def self.apply_changes!
|
31
|
+
Rails.application.reload_routes!
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# This method is used in Rails' URL Helpers to return the corresponding
|
36
|
+
# SEO Friendly URL instead of the default one.
|
37
|
+
#
|
38
|
+
# @param [String] The path we want the URL for.
|
39
|
+
# @returns [String] The SEO Friendly Slug for the required path.
|
40
|
+
def self.url_for(path)
|
41
|
+
self.instance.friendly path
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
# INTERNAL: Searches the list of SEO Friendly defined substitutions for
|
46
|
+
# the corresponding to the given path
|
47
|
+
#
|
48
|
+
# @param [String] The path we want the substitution for.
|
49
|
+
# @returns [String] The SEO Friendly Slugh for the required path.
|
50
|
+
def friendly(path)
|
51
|
+
@all_friendly ||= Hash[*RailsFriendlyUrls::Manager.urls.map { |f_url| [f_url.path, f_url.slug] }.flatten]
|
52
|
+
@all_friendly[path] || path
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module ActionDispatch
|
2
|
+
module Routing
|
3
|
+
#
|
4
|
+
# Monkey Patched Rails' class ActionDispatch::Routing::RouteSet.
|
5
|
+
#
|
6
|
+
# @author Carlos Alonso
|
7
|
+
#
|
8
|
+
class RouteSet
|
9
|
+
#
|
10
|
+
# Monkey Patched Rails' method: Includes a call to RailsFriendlyUrls::Manager.url_for
|
11
|
+
# when the Rails' URL Helper is building a url for a path to use the
|
12
|
+
# configured SEO Friendly substutition if any.
|
13
|
+
#
|
14
|
+
def url_for(options = {})
|
15
|
+
finalize!
|
16
|
+
options = (options || {}).reverse_merge!(default_url_options)
|
17
|
+
|
18
|
+
handle_positional_args(options)
|
19
|
+
|
20
|
+
user, password = extract_authentication(options)
|
21
|
+
path_segments = options.delete(:_path_segments)
|
22
|
+
script_name = options.delete(:script_name)
|
23
|
+
|
24
|
+
path = (script_name.blank? ? _generate_prefix(options) : script_name.chomp('/')).to_s
|
25
|
+
|
26
|
+
path_options = options.except(*RESERVED_OPTIONS)
|
27
|
+
path_options = yield(path_options) if block_given?
|
28
|
+
|
29
|
+
path_addition, params = generate(path_options, path_segments || {})
|
30
|
+
path << path_addition
|
31
|
+
params.merge!(options[:params] || {})
|
32
|
+
|
33
|
+
path = RailsFriendlyUrls::Manager.url_for path
|
34
|
+
|
35
|
+
ActionDispatch::Http::URL.url_for(options.merge!({
|
36
|
+
:path => path,
|
37
|
+
:params => params,
|
38
|
+
:user => user,
|
39
|
+
:password => password
|
40
|
+
}))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module ActionDispatch
|
2
|
+
module Routing
|
3
|
+
#
|
4
|
+
# Monkey Patched Rails' class ActionDispatch::Routing::RouteSet.
|
5
|
+
#
|
6
|
+
# @author Carlos Alonso
|
7
|
+
#
|
8
|
+
class RouteSet
|
9
|
+
#
|
10
|
+
# Monkey Patched Rails' method to recognize redirections as well as, for some
|
11
|
+
# reason, the original Rails' method doesn't.
|
12
|
+
#
|
13
|
+
def recognize_path(path, environment = {})
|
14
|
+
method = (environment[:method] || "GET").to_s.upcase
|
15
|
+
path = Journey::Router::Utils.normalize_path(path) unless path =~ %r{://}
|
16
|
+
extras = environment[:extras] || {}
|
17
|
+
|
18
|
+
begin
|
19
|
+
env = Rack::MockRequest.env_for(path, {:method => method})
|
20
|
+
rescue URI::InvalidURIError => e
|
21
|
+
raise ActionController::RoutingError, e.message
|
22
|
+
end
|
23
|
+
|
24
|
+
req = @request_class.new(env)
|
25
|
+
@router.recognize(req) do |route, _matches, params|
|
26
|
+
params = _matches if params.nil?
|
27
|
+
params.merge!(extras)
|
28
|
+
params.merge!(req.parameters.symbolize_keys)
|
29
|
+
params.each do |key, value|
|
30
|
+
if value.is_a?(String)
|
31
|
+
value = value.dup.force_encoding(Encoding::BINARY)
|
32
|
+
params[key] = URI.parser.unescape(value)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
old_params = env[params_key]
|
37
|
+
env[params_key] = (old_params || {}).merge(params)
|
38
|
+
dispatcher = route.app
|
39
|
+
while dispatcher.is_a?(Mapper::Constraints) && dispatcher.matches?(env) do
|
40
|
+
dispatcher = dispatcher.app
|
41
|
+
end
|
42
|
+
|
43
|
+
if dispatcher.is_a?(Dispatcher)
|
44
|
+
if dispatcher.controller(params, false)
|
45
|
+
dispatcher.prepare_params!(params)
|
46
|
+
return params
|
47
|
+
else
|
48
|
+
raise ActionController::RoutingError, "A route matches #{path.inspect}, but references missing controller: #{params[:controller].camelize}Controller"
|
49
|
+
end
|
50
|
+
elsif dispatcher.is_a?(redirect_class)
|
51
|
+
return { status: 301, path: path_from_dispatcher(dispatcher) }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
raise ActionController::RoutingError, "No route matches #{path.inspect}"
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
#
|
61
|
+
# INTERNAL: Helps deciding which module take the PARAMETERS_KEY constant
|
62
|
+
# from. This constant was moved in Rails 4.2 from one to another and
|
63
|
+
# using this method here allows us to reuse this file for all Rails 4.x
|
64
|
+
#
|
65
|
+
def params_key
|
66
|
+
defined?(::ActionDispatch::Http::Parameters::PARAMETERS_KEY) ?
|
67
|
+
::ActionDispatch::Http::Parameters::PARAMETERS_KEY :
|
68
|
+
::ActionDispatch::Routing::RouteSet::PARAMETERS_KEY
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
# INTERNAL: Helps reusing code by deciding which class to consider
|
73
|
+
# as the redirection depending on the Rails version running.
|
74
|
+
#
|
75
|
+
# @returns [ActionDispatch::Routing::Redirect] or [ActionDispatch::Routing::PathRedirect]
|
76
|
+
def redirect_class
|
77
|
+
Rails::VERSION::MAJOR == 3 ? Redirect : PathRedirect
|
78
|
+
end
|
79
|
+
|
80
|
+
#
|
81
|
+
# INTERNAL: Helps reusing code by obtaining the path from the Rails'
|
82
|
+
# ActionDispatch::Routing::Dispatcher depending on the Rails version
|
83
|
+
# running.
|
84
|
+
#
|
85
|
+
# @param [ActionDispatch::Routing::Dispatcher] in use.
|
86
|
+
# @return [String] the destination path of the redirection.
|
87
|
+
def path_from_dispatcher(dispatcher)
|
88
|
+
if Rails::VERSION::MAJOR == 3
|
89
|
+
dispatcher.block.call({}, nil)
|
90
|
+
else
|
91
|
+
dispatcher.block
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module ActionDispatch
|
2
|
+
module Http
|
3
|
+
#
|
4
|
+
# Monkey Patched Rails' module ActionDispatch::Http::URL
|
5
|
+
#
|
6
|
+
# @author Carlos Alonso
|
7
|
+
#
|
8
|
+
module URL
|
9
|
+
#
|
10
|
+
# Monkey Patched Rails' method: Includes a call to RailsFriendlyUrls::Manager.url_for
|
11
|
+
# when the Rails' URL Helper is building a url for a path to use the
|
12
|
+
# configured SEO Friendly substutition if any.
|
13
|
+
#
|
14
|
+
def self.url_for(options = {})
|
15
|
+
options = options.dup
|
16
|
+
path = options.delete(:script_name).to_s.chomp("/")
|
17
|
+
path << options.delete(:path).to_s
|
18
|
+
|
19
|
+
add_trailing_slash(path) if options[:trailing_slash]
|
20
|
+
|
21
|
+
params = options[:params].is_a?(Hash) ? options[:params] : options.slice(:params)
|
22
|
+
params.reject! { |_,v| v.to_param.nil? }
|
23
|
+
|
24
|
+
result = build_host_url(options)
|
25
|
+
|
26
|
+
path = RailsFriendlyUrls::Manager.url_for path
|
27
|
+
|
28
|
+
result << path
|
29
|
+
|
30
|
+
result << "?#{params.to_query}" unless params.empty?
|
31
|
+
result << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor]
|
32
|
+
result
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ActionDispatch
|
2
|
+
module Http
|
3
|
+
#
|
4
|
+
# Monkey Patched Rails' module ActionDispatch::Http::URL
|
5
|
+
#
|
6
|
+
# @author Carlos Alonso
|
7
|
+
#
|
8
|
+
module URL
|
9
|
+
#
|
10
|
+
# Monkey Patched Rails' method: Includes a call to RailsFriendlyUrls::Manager.url_for
|
11
|
+
# when the Rails' URL Helper is building a url for a path to use the
|
12
|
+
# configured SEO Friendly substutition if any.
|
13
|
+
#
|
14
|
+
def self.path_for(options)
|
15
|
+
path = options[:script_name].to_s.chomp("/")
|
16
|
+
path << options[:path] if options.key?(:path)
|
17
|
+
|
18
|
+
add_trailing_slash(path) if options[:trailing_slash]
|
19
|
+
add_params(path, options[:params]) if options.key?(:params)
|
20
|
+
add_anchor(path, options[:anchor]) if options.key?(:anchor)
|
21
|
+
|
22
|
+
RailsFriendlyUrls::Manager.url_for path
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require "rails"
|
2
|
+
require 'rails/all'
|
3
|
+
require 'action_view/testing/resolvers'
|
4
|
+
|
5
|
+
require 'rails_friendly_urls' # our gem
|
6
|
+
|
7
|
+
class RailsFriendlyUrlsApp < Rails::Application
|
8
|
+
config.root = File.expand_path("../../..", __FILE__)
|
9
|
+
config.cache_classes = true
|
10
|
+
|
11
|
+
config.eager_load = false
|
12
|
+
config.serve_static_assets = true
|
13
|
+
config.static_cache_control = "public, max-age=3600"
|
14
|
+
|
15
|
+
config.consider_all_requests_local = true
|
16
|
+
config.action_controller.perform_caching = false
|
17
|
+
|
18
|
+
config.action_dispatch.show_exceptions = false
|
19
|
+
|
20
|
+
config.action_controller.allow_forgery_protection = false
|
21
|
+
|
22
|
+
config.active_support.deprecation = :stderr
|
23
|
+
|
24
|
+
config.middleware.delete "Rack::Lock"
|
25
|
+
config.middleware.delete "ActionDispatch::Flash"
|
26
|
+
config.middleware.delete "ActionDispatch::BestStandardsSupport"
|
27
|
+
config.secret_token = "49837489qkuweoiuoqwehisuakshdjksadhaisdy78o34y138974xyqp9rmye8yrpiokeuioqwzyoiuxftoyqiuxrhm3iou1hrzmjk"
|
28
|
+
routes.append do
|
29
|
+
get "/" => "welcome#index"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class ApplicationController < ActionController::Base
|
34
|
+
include Rails.application.routes.url_helpers
|
35
|
+
layout 'application'
|
36
|
+
self.view_paths = [ActionView::FixtureResolver.new(
|
37
|
+
"layouts/application.html.erb" => '<%= yield %>',
|
38
|
+
"welcome/index.html.erb"=> 'Hello from index.html.erb',
|
39
|
+
)]
|
40
|
+
|
41
|
+
def index
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
RailsFriendlyUrlsApp.initialize!
|
data/spec/apps/rails4.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require "rails"
|
2
|
+
require 'rails/all'
|
3
|
+
require 'action_view/testing/resolvers'
|
4
|
+
|
5
|
+
require 'rails_friendly_urls' # our gem
|
6
|
+
|
7
|
+
module RailsFriendlyUrlsApp
|
8
|
+
class Application < Rails::Application
|
9
|
+
config.root = File.expand_path("../../..", __FILE__)
|
10
|
+
config.cache_classes = true
|
11
|
+
|
12
|
+
config.eager_load = false
|
13
|
+
config.static_cache_control = "public, max-age=3600"
|
14
|
+
|
15
|
+
config.consider_all_requests_local = true
|
16
|
+
config.action_controller.perform_caching = false
|
17
|
+
|
18
|
+
config.action_dispatch.show_exceptions = false
|
19
|
+
|
20
|
+
config.action_controller.allow_forgery_protection = false
|
21
|
+
|
22
|
+
config.active_support.deprecation = :stderr
|
23
|
+
|
24
|
+
config.middleware.delete "Rack::Lock"
|
25
|
+
config.middleware.delete "ActionDispatch::Flash"
|
26
|
+
config.middleware.delete "ActionDispatch::BestStandardsSupport"
|
27
|
+
config.secret_key_base = '49837489qkuweoiuoqwehisuakshdjksadhaisdy78o34y138974xyqp9rmye8yrpiokeuioqwzyoiuxftoyqiuxrhm3iou1hrzmjk'
|
28
|
+
routes.append do
|
29
|
+
get "/" => "application#index"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class ApplicationController < ActionController::Base
|
35
|
+
include Rails.application.routes.url_helpers
|
36
|
+
layout 'application'
|
37
|
+
self.view_paths = [ActionView::FixtureResolver.new(
|
38
|
+
"layouts/application.html.erb" => '<%= yield %>',
|
39
|
+
"welcome/index.html.erb"=> 'Hello from index.html.erb',
|
40
|
+
)]
|
41
|
+
|
42
|
+
def index
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
RailsFriendlyUrlsApp::Application.initialize!
|
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
require 'generators/rails_friendly_urls/install_generator'
|
3
|
+
|
4
|
+
describe RailsFriendlyUrls::InstallGenerator, type: :generator do
|
5
|
+
destination File.expand_path("../../tmp", __FILE__)
|
6
|
+
|
7
|
+
before do
|
8
|
+
prepare_destination
|
9
|
+
mkdir File.join(destination_root, 'config')
|
10
|
+
cp routes_file, File.join(destination_root, 'config/routes.rb')
|
11
|
+
run_generator
|
12
|
+
end
|
13
|
+
|
14
|
+
after do
|
15
|
+
rm_rf destination_root
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'creates the Rails Friendly Urls Manager' do
|
19
|
+
assert_file "config/initializers/friendly_urls_manager.rb", <<-EOS
|
20
|
+
# FriendlyUrls Manager contents
|
21
|
+
class RailsFriendlyUrls::Manager
|
22
|
+
def self.urls
|
23
|
+
raise NotImplementedError.new 'RailsFriendlyUrls::Manager::urls not implemented at config/initializers/friendly_urls_manager.rb'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
EOS
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'injects the Rails Friendly Urls in routes' do
|
30
|
+
assert_file "config/routes.rb", routes_contents
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
|
2
|
+
describe RailsFriendlyUrls::FriendlyUrl do
|
3
|
+
|
4
|
+
describe '#set_destination_data' do
|
5
|
+
|
6
|
+
let(:path) { '/test' }
|
7
|
+
let(:controller) { 'application' }
|
8
|
+
let(:action) { 'index' }
|
9
|
+
|
10
|
+
before do
|
11
|
+
mapper = ActionDispatch::Routing::Mapper.new Rails.application.routes
|
12
|
+
mapper.get path, to: "#{controller}##{action}"
|
13
|
+
end
|
14
|
+
|
15
|
+
subject { DummyFriendlyURL.new path }
|
16
|
+
|
17
|
+
it 'successfully assigns controller' do
|
18
|
+
expect(subject.controller).to eq controller
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'successfully assigns action' do
|
22
|
+
expect(subject.action).to eq action
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'defaults' do
|
26
|
+
|
27
|
+
context 'with no parameters' do
|
28
|
+
it 'works with no parameters' do
|
29
|
+
expect(subject.defaults).to eq({})
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'using parameters in url' do
|
34
|
+
|
35
|
+
let(:path) { '/:lang/test' }
|
36
|
+
|
37
|
+
subject { DummyFriendlyURL.new '/es-ES/test' }
|
38
|
+
|
39
|
+
it 'successfully assigns defaults' do
|
40
|
+
expect(subject.defaults).to eq lang: 'es-ES'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'using parameters in query string' do
|
45
|
+
subject { DummyFriendlyURL.new '/test?lang=es-ES' }
|
46
|
+
|
47
|
+
it 'successfully assigns defaults' do
|
48
|
+
expect(subject.defaults).to eq lang: 'es-ES'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
|
2
|
+
shared_examples 'a successfully injected friendly url' do
|
3
|
+
let(:route_set) { Rails.application.routes }
|
4
|
+
|
5
|
+
before do
|
6
|
+
subject.urls = [url]
|
7
|
+
subject.inject_urls ActionDispatch::Routing::Mapper.new route_set
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'successfully injects the url' do
|
11
|
+
route_info = Rails.application.routes.recognize_path url.slug
|
12
|
+
expect(route_info.delete(:controller)).to eq url.controller
|
13
|
+
expect(route_info.delete(:action)).to eq url.action
|
14
|
+
expect(route_info).to eq (url.defaults.reject { |k, v| [:controller, :action].include? k })
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'adds the corresponding redirection route' do
|
18
|
+
route_info = Rails.application.routes.recognize_path url.path
|
19
|
+
expect(route_info[:status]).to eq 301
|
20
|
+
expect(route_info[:path]).to eq url.slug
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'keeps path helpers working without changing code' do
|
24
|
+
expect(route_set.url_helpers.a_b_c_path).to eq url.slug
|
25
|
+
end
|
26
|
+
|
27
|
+
after do
|
28
|
+
route_set.clear!
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe RailsFriendlyUrls::Manager do
|
33
|
+
|
34
|
+
describe '#inject_urls' do
|
35
|
+
|
36
|
+
subject { RailsFriendlyUrls::Manager }
|
37
|
+
|
38
|
+
context 'with params in the url' do
|
39
|
+
let(:url) {
|
40
|
+
DummyFriendlyURL.new '/a/b/c', '/friendly1', 'application', 'acti', a:1, b:2
|
41
|
+
}
|
42
|
+
|
43
|
+
include_examples 'a successfully injected friendly url'
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'with no params in the url' do
|
47
|
+
let(:url) {
|
48
|
+
DummyFriendlyURL.new '/a/b/c', '/friendly1', 'application', 'acti', {}
|
49
|
+
}
|
50
|
+
|
51
|
+
include_examples 'a successfully injected friendly url'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# Configure Rails Environment
|
2
|
+
require "codeclimate-test-reporter"
|
3
|
+
CodeClimate::TestReporter.start
|
4
|
+
|
5
|
+
Bundler.setup
|
6
|
+
|
7
|
+
ENV["RAILS_ENV"] = "test"
|
8
|
+
|
9
|
+
require 'rails'
|
10
|
+
|
11
|
+
case "#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}"
|
12
|
+
when '3.2'
|
13
|
+
ENV['DATABASE_URL'] = 'sqlite3://localhost/:memory:'
|
14
|
+
require 'apps/rails3_2'
|
15
|
+
when '4.0'
|
16
|
+
ENV['DATABASE_URL'] = 'sqlite3://localhost/:memory:'
|
17
|
+
require 'apps/rails4'
|
18
|
+
when '4.1'
|
19
|
+
ENV['DATABASE_URL'] = 'sqlite3::memory:'
|
20
|
+
require 'apps/rails4'
|
21
|
+
when '4.2'
|
22
|
+
ENV['DATABASE_URL'] = 'sqlite3::memory:'
|
23
|
+
require 'apps/rails4'
|
24
|
+
else
|
25
|
+
raise NotImplementedError.new "Rails Friendly URLs gem doesn't support Rails #{Rails.version}"
|
26
|
+
end
|
27
|
+
|
28
|
+
Bundler.require :default
|
29
|
+
Bundler.require :development
|
30
|
+
|
31
|
+
require 'rspec/rails'
|
32
|
+
require 'rails_friendly_urls'
|
33
|
+
|
34
|
+
# Load support files
|
35
|
+
Dir["#{File.dirname(__FILE__)}/support/*.rb"].each { |f| require f }
|
36
|
+
|
37
|
+
RSpec.configure do |config|
|
38
|
+
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
|
39
|
+
#config.fixture_path = "#{::Rails.root}/spec/fixtures"
|
40
|
+
|
41
|
+
# If you're not using ActiveRecord, or you'd prefer not to run each of your
|
42
|
+
# examples within a transaction, remove the following line or assign false
|
43
|
+
# instead of true.
|
44
|
+
config.use_transactional_fixtures = true
|
45
|
+
|
46
|
+
# Run specs in random order to surface order dependencies. If you find an
|
47
|
+
# order dependency and want to debug it, you can fix the order by providing
|
48
|
+
# the seed, which is printed after each run.
|
49
|
+
# --seed 1234
|
50
|
+
config.order = :random
|
51
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
class DummyFriendlyURL
|
3
|
+
|
4
|
+
include RailsFriendlyUrls::FriendlyUrl
|
5
|
+
|
6
|
+
attr_accessor :path, :slug, :controller, :action, :defaults
|
7
|
+
|
8
|
+
def initialize(path, slug = nil, controller = nil, action = nil, defaults = nil)
|
9
|
+
@path = path
|
10
|
+
if slug
|
11
|
+
@slug = slug
|
12
|
+
@controller = controller
|
13
|
+
@action = action
|
14
|
+
@defaults = defaults
|
15
|
+
else
|
16
|
+
set_destination_data!
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
if Rails::VERSION::MAJOR == 4
|
2
|
+
require 'support/routes/rails4'
|
3
|
+
else
|
4
|
+
require 'support/routes/rails3'
|
5
|
+
end
|
6
|
+
|
7
|
+
def routes_file
|
8
|
+
if Rails::VERSION::MAJOR == 4
|
9
|
+
'spec/support/routes/rails4.rb'
|
10
|
+
else
|
11
|
+
'spec/support/routes/rails3.rb'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def routes_contents
|
16
|
+
if Rails::VERSION::MAJOR == 3
|
17
|
+
<<-EOS
|
18
|
+
RailsFriendlyUrlsApp.routes.draw do
|
19
|
+
|
20
|
+
RailsFriendlyUrls::Manager.inject_urls self
|
21
|
+
|
22
|
+
end
|
23
|
+
EOS
|
24
|
+
else
|
25
|
+
<<-EOS
|
26
|
+
RailsFriendlyUrlsApp::Application.routes.draw do
|
27
|
+
|
28
|
+
RailsFriendlyUrls::Manager.inject_urls self
|
29
|
+
|
30
|
+
end
|
31
|
+
EOS
|
32
|
+
end
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rails_friendly_urls
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Carlos Alonso
|
8
|
+
- Maria Turnau
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2015-05-28 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rails
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '3.2'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '3.2'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: sqlite3
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '1.3'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '1.3'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rspec
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '3.2'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '3.2'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: rspec-rails
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '3.0'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '3.0'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: generator_spec
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0.9'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0.9'
|
84
|
+
description: Rails Gem to easily configure any url as a friendlier one.
|
85
|
+
email: info@mrcalonso.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- LICENSE
|
91
|
+
- README.md
|
92
|
+
- Rakefile
|
93
|
+
- lib/generators/rails_friendly_urls/install_generator.rb
|
94
|
+
- lib/rails_friendly_urls.rb
|
95
|
+
- lib/rails_friendly_urls/friendly_url.rb
|
96
|
+
- lib/rails_friendly_urls/manager.rb
|
97
|
+
- lib/rails_friendly_urls/route_sets/rails3.rb
|
98
|
+
- lib/rails_friendly_urls/route_sets/route_set.rb
|
99
|
+
- lib/rails_friendly_urls/urls/rails4_0.rb
|
100
|
+
- lib/rails_friendly_urls/urls/rails4_2.rb
|
101
|
+
- lib/rails_friendly_urls/version.rb
|
102
|
+
- spec/apps/rails3_2.rb
|
103
|
+
- spec/apps/rails4.rb
|
104
|
+
- spec/generators/install_generator_spec.rb
|
105
|
+
- spec/rails_friendly_urls/friendly_url_spec.rb
|
106
|
+
- spec/rails_friendly_urls/manager_spec.rb
|
107
|
+
- spec/spec_helper.rb
|
108
|
+
- spec/support/dummy_friendly_url.rb
|
109
|
+
- spec/support/dummy_manager_impl.rb
|
110
|
+
- spec/support/routes.rb
|
111
|
+
- spec/support/routes/rails3.rb
|
112
|
+
- spec/support/routes/rails4.rb
|
113
|
+
homepage: http://mrcalonso.com/rails-friendly-urls-gem/
|
114
|
+
licenses:
|
115
|
+
- MIT
|
116
|
+
metadata: {}
|
117
|
+
post_install_message:
|
118
|
+
rdoc_options: []
|
119
|
+
require_paths:
|
120
|
+
- lib
|
121
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: 1.9.3
|
126
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
requirements: []
|
132
|
+
rubyforge_project:
|
133
|
+
rubygems_version: 2.2.2
|
134
|
+
signing_key:
|
135
|
+
specification_version: 4
|
136
|
+
summary: This is a Rails gem that allows to configure friendly urls for any url in
|
137
|
+
your project.
|
138
|
+
test_files:
|
139
|
+
- spec/apps/rails3_2.rb
|
140
|
+
- spec/apps/rails4.rb
|
141
|
+
- spec/generators/install_generator_spec.rb
|
142
|
+
- spec/rails_friendly_urls/friendly_url_spec.rb
|
143
|
+
- spec/rails_friendly_urls/manager_spec.rb
|
144
|
+
- spec/spec_helper.rb
|
145
|
+
- spec/support/dummy_friendly_url.rb
|
146
|
+
- spec/support/dummy_manager_impl.rb
|
147
|
+
- spec/support/routes/rails3.rb
|
148
|
+
- spec/support/routes/rails4.rb
|
149
|
+
- spec/support/routes.rb
|
150
|
+
has_rdoc:
|