ress 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +48 -32
- data/lib/ress.rb +1 -0
- data/lib/ress/canonical_version.rb +14 -18
- data/lib/ress/subdomain.rb +78 -0
- data/lib/ress/version.rb +1 -1
- data/spec/ress/canonical_version_spec.rb +1 -15
- data/spec/ress/category_collection_spec.rb +2 -2
- data/spec/ress/subdomain_spec.rb +112 -0
- data/spec/ress_spec.rb +1 -1
- metadata +5 -2
data/README.md
CHANGED
@@ -16,7 +16,7 @@ for which devices should be redirected to which version.
|
|
16
16
|
### HTML Annotations
|
17
17
|
|
18
18
|
When you register alternate mobile versions of your website, Ress adds annotations
|
19
|
-
to the `<head>` of your document that
|
19
|
+
to the `<head>` of your document that describe where these pages are located and
|
20
20
|
which devices should be redirected to them.
|
21
21
|
|
22
22
|
For example, a typical alternate version for a mobile site might include a tag
|
@@ -34,22 +34,18 @@ version:
|
|
34
34
|
```
|
35
35
|
|
36
36
|
These annotations conform to SEO best practices for mobile optimized websites
|
37
|
-
as documented by Google
|
38
|
-
[here](https://developers.google.com/webmasters/smartphone-sites/details).
|
37
|
+
[as documented by Google](https://developers.google.com/webmasters/smartphone-sites/details).
|
39
38
|
|
40
39
|
### Feature Detection
|
41
40
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
When a request comes into your site, ress runs a some javascript to determine
|
47
|
-
if there is an alternate version available that matches the client. If there
|
48
|
-
is, the user is redirected to the url for that version.
|
41
|
+
When a request comes into your site, the javascript included with ress will parse
|
42
|
+
all of the `[rel="alternate"]` links in your markup, and evalute their media queries
|
43
|
+
to determine if there is an alternate version available that matches the client.
|
44
|
+
If there is, the user is redirected to the url for that version.
|
49
45
|
|
50
46
|
### Server Side Components
|
51
47
|
|
52
|
-
Ress allows you to customize how your Rails application responds to requests in
|
48
|
+
Ress allows you to customize how your Rails application responds to mobile requests in
|
53
49
|
two ways:
|
54
50
|
|
55
51
|
1. It adds controller and helper methods to detect which version of your site has
|
@@ -64,7 +60,7 @@ been requested. This is useful for small tweeks in html or behaviour, eg:
|
|
64
60
|
```
|
65
61
|
|
66
62
|
2. It prepends a view path for each alternate version of your site, so that you can
|
67
|
-
override
|
63
|
+
override the templates or partials that are rendered for certain requests. For example if
|
68
64
|
you want to render a different html form for creating users on the mobile version of your
|
69
65
|
site you could create `app/mobile_views/users/_form.html.erb` and Ress would have Rails
|
70
66
|
select that template over `app/views/users/_form.html.erb` when a request comes in to the
|
@@ -81,34 +77,38 @@ And then execute:
|
|
81
77
|
|
82
78
|
$ bundle install
|
83
79
|
|
84
|
-
Run the
|
80
|
+
Run the generator:
|
85
81
|
|
86
82
|
$ rails g ress:install
|
87
83
|
|
88
84
|
## Usage
|
89
85
|
|
90
|
-
###
|
86
|
+
### Adding Alternate Versions
|
91
87
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
the specified ID and load that version. For example, if you are on
|
96
|
-
desktop but want the tablet version, visiting
|
97
|
-
`http://foo.com/?version=tablet` will redirect to the tablet version at
|
98
|
-
`http://tablet.foo.com`.
|
88
|
+
Alternate versions of an application are registered using the `#add_alternate` method in the
|
89
|
+
`ress.rb` initializer that is generated by the `ress:install` generator. The configurabe options
|
90
|
+
available are all documented in the [comments of that file](https://github.com/matthewrobertson/ress/blob/master/lib/generators/ress/templates/ress.rb).
|
99
91
|
|
100
|
-
|
101
|
-
`force=1` GET parameter. For example, if you are on desktop and know the
|
102
|
-
URL of the tablet site, you can load `http://tablet.foo.com/?force=1`.
|
92
|
+
### Version override
|
103
93
|
|
104
|
-
|
105
|
-
|
94
|
+
You can manually override the version detector javascript and allow mobile
|
95
|
+
clients to visit the canonical version of the app by passing in a the GET
|
96
|
+
url parameter `force_canonical=1`. This sets a session cookie in a `before_filter`
|
97
|
+
that stops the version detection scipt from redirecting users, so it only has to be
|
98
|
+
done once per session. Ress includes a helper / controller method `force_canonical` that returns
|
99
|
+
a link back to the canonical version of the current page with this query param appended.
|
100
|
+
For, example you may include something like this in your `<footer>` to let mobile users
|
101
|
+
access the canonical site.
|
102
|
+
|
103
|
+
```erb
|
104
|
+
<!-- Let mobile devices access the canonical site -->
|
106
105
|
<footer>
|
107
|
-
|
108
|
-
<
|
109
|
-
|
110
|
-
|
111
|
-
|
106
|
+
<% unless canonical_request? %>
|
107
|
+
<div>
|
108
|
+
You are currently viewing the mobile version of this site.
|
109
|
+
<%= link_to 'View the desktop version', force_canonical_url %>
|
110
|
+
</div>
|
111
|
+
<% end %>
|
112
112
|
</footer>
|
113
113
|
```
|
114
114
|
|
@@ -130,7 +130,7 @@ redirection to point users to the right version of your webapp. Client-side
|
|
130
130
|
redirection can have a performance overhead (though I haven't measured it).
|
131
131
|
If you find this is true, you can keep your DOM the same, still using the
|
132
132
|
SEO-friendly `<link rel="alternate">` tags, but simply remove the
|
133
|
-
ress.js script and do your own server-side UA-based
|
133
|
+
ress.js script and do your own server-side UA-based redirection.
|
134
134
|
|
135
135
|
## Browser support
|
136
136
|
|
@@ -149,3 +149,19 @@ in a pull request.
|
|
149
149
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
150
150
|
4. Push to the branch (`git push origin my-new-feature`)
|
151
151
|
5. Create new Pull Request
|
152
|
+
|
153
|
+
## Resources
|
154
|
+
|
155
|
+
Ress is the compilation of a few different ideas packaged up for Ruby on Rails. You
|
156
|
+
may want to look at the following articles for more info:
|
157
|
+
|
158
|
+
- [devicejs](https://github.com/borismus/device.js) - a javascript library for client
|
159
|
+
side feature direction and the main inspiration for this gem. Boris has also provided
|
160
|
+
[a good write up about it](http://www.html5rocks.com/en/mobile/cross-device/) on
|
161
|
+
http://www.html5rocks.com/
|
162
|
+
- [Building Smartphone-Optimized Websites](https://developers.google.com/webmasters/smartphone-sites/details) - Google's
|
163
|
+
recommendations and best practices for building Mobile Optimized web apps
|
164
|
+
- [RESS: Responsive Design + Server Side Components](http://www.lukew.com/ff/entry.asp?1392) - an
|
165
|
+
article by Luke Wroblewski about combining Responsive Design and Server Side Components (the
|
166
|
+
inspiration for the name RESS).
|
167
|
+
|
data/lib/ress.rb
CHANGED
@@ -2,35 +2,31 @@ module Ress
|
|
2
2
|
|
3
3
|
class CanonicalVersion
|
4
4
|
|
5
|
-
attr_reader :subdomain
|
6
|
-
|
7
5
|
def initialize(options = {})
|
8
|
-
@subdomain = options.fetch(:subdomain,
|
6
|
+
@subdomain = Ress::Subdomain.create(options.fetch(:subdomain, nil))
|
9
7
|
end
|
10
8
|
|
11
|
-
def matches?(
|
12
|
-
|
13
|
-
self.subdomain == subdomain
|
14
|
-
else
|
15
|
-
subdomain.empty?
|
16
|
-
end
|
9
|
+
def matches?(req_subdomain)
|
10
|
+
subdomain.matches?(req_subdomain)
|
17
11
|
end
|
18
12
|
|
19
13
|
# Create a tag of this format:
|
20
14
|
# `<link rel="canonical" href="http://www.example.com/page-1" >`
|
21
|
-
def link_tag(protocol, fullpath,
|
22
|
-
|
15
|
+
def link_tag(protocol, fullpath, req_subdomain, view)
|
16
|
+
|
17
|
+
view.tag :link, :rel => 'canonical', :href => url(protocol, fullpath, req_subdomain)
|
23
18
|
end
|
24
19
|
|
25
|
-
def url(protocol, fullpath,
|
26
|
-
|
27
|
-
if self.subdomain
|
28
|
-
"#{protocol}#{self.subdomain}.#{fullpath}"
|
29
|
-
else
|
30
|
-
"#{protocol}#{fullpath}"
|
31
|
-
end
|
20
|
+
def url(protocol, fullpath, req_subdomain)
|
21
|
+
subdomain.url(protocol, fullpath, req_subdomain)
|
32
22
|
end
|
33
23
|
|
24
|
+
private
|
25
|
+
|
26
|
+
def subdomain
|
27
|
+
@subdomain
|
28
|
+
end
|
29
|
+
|
34
30
|
end
|
35
31
|
|
36
32
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Ress
|
2
|
+
|
3
|
+
class Subdomain
|
4
|
+
|
5
|
+
# this class is a factory, don't instantiate it directly
|
6
|
+
private_class_method :new
|
7
|
+
|
8
|
+
def self.create(subdomain)
|
9
|
+
case subdomain
|
10
|
+
when String
|
11
|
+
StringSubdomain.new(subdomain)
|
12
|
+
when Regexp
|
13
|
+
RegexpSubdomain.new(subdomain)
|
14
|
+
else
|
15
|
+
NilSubdomain.new
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class NilSubdomain
|
20
|
+
def matches?(subdomain)
|
21
|
+
subdomain.empty?
|
22
|
+
end
|
23
|
+
|
24
|
+
def url(protocol, fullpath, subdomain)
|
25
|
+
fullpath = fullpath[(subdomain.length + 1)..-1] unless subdomain.empty?
|
26
|
+
"#{protocol}#{fullpath}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class StringSubdomain
|
31
|
+
|
32
|
+
attr_reader :subdomain
|
33
|
+
|
34
|
+
def initialize(subdomain)
|
35
|
+
@subdomain = subdomain
|
36
|
+
end
|
37
|
+
|
38
|
+
def matches?(subdomain)
|
39
|
+
self.subdomain == subdomain
|
40
|
+
end
|
41
|
+
|
42
|
+
def url(protocol, fullpath, subdomain)
|
43
|
+
fullpath = fullpath[(subdomain.length + 1)..-1] unless subdomain.empty?
|
44
|
+
"#{protocol}#{self.subdomain}.#{fullpath}"
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
# The main inspiration for this class is to match xip.io
|
50
|
+
# subdomains for many devs working in development mode with
|
51
|
+
# the same configuration.
|
52
|
+
class RegexpSubdomain
|
53
|
+
|
54
|
+
attr_reader :subdomain
|
55
|
+
|
56
|
+
def initialize(subdomain)
|
57
|
+
@subdomain = subdomain
|
58
|
+
end
|
59
|
+
|
60
|
+
def matches?(subdomain)
|
61
|
+
self.subdomain =~ subdomain
|
62
|
+
end
|
63
|
+
|
64
|
+
def url(protocol, fullpath, subdomain)
|
65
|
+
fullpath = fullpath[(subdomain.length + 1)..-1]
|
66
|
+
begin
|
67
|
+
if matches?(subdomain)
|
68
|
+
return "#{protocol}#{subdomain}.#{fullpath}"
|
69
|
+
end
|
70
|
+
subdomain = subdomain.split('.')[1..-1].join('.')
|
71
|
+
end while(!subdomain.empty?)
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
data/lib/ress/version.rb
CHANGED
@@ -1,21 +1,7 @@
|
|
1
|
-
require_relative '../../lib/ress
|
1
|
+
require_relative '../../lib/ress'
|
2
2
|
|
3
3
|
describe Ress::CanonicalVersion do
|
4
4
|
|
5
|
-
describe '#subdomain' do
|
6
|
-
|
7
|
-
it 'defaults to false' do
|
8
|
-
category = Ress::CanonicalVersion.new
|
9
|
-
category.subdomain.should == false
|
10
|
-
end
|
11
|
-
|
12
|
-
it 'can be overridden by an optional parameter' do
|
13
|
-
category = Ress::CanonicalVersion.new(:subdomain => 'foo')
|
14
|
-
category.subdomain.should == 'foo'
|
15
|
-
end
|
16
|
-
|
17
|
-
end
|
18
|
-
|
19
5
|
describe '#matches?' do
|
20
6
|
|
21
7
|
context 'with no canonical subdomain' do
|
@@ -27,12 +27,12 @@ describe Ress::CategoryCollection do
|
|
27
27
|
describe 'canonical_version' do
|
28
28
|
|
29
29
|
it 'defaults to a canonical_version with no subdomain' do
|
30
|
-
collection.canonical_version.
|
30
|
+
collection.canonical_version.matches?('').should be_true
|
31
31
|
end
|
32
32
|
|
33
33
|
it 'can be altered through set_canonical' do
|
34
34
|
collection.set_canonical :subdomain => 'foo'
|
35
|
-
collection.canonical_version.
|
35
|
+
collection.canonical_version.matches?('foo').should be_true
|
36
36
|
end
|
37
37
|
|
38
38
|
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require_relative '../../lib/ress/subdomain'
|
2
|
+
|
3
|
+
describe Ress::Subdomain do
|
4
|
+
|
5
|
+
describe '.create' do
|
6
|
+
|
7
|
+
it 'returns a NilSubdomain when the subdomain is nil' do
|
8
|
+
Ress::Subdomain.create(nil).should be_a(Ress::Subdomain::NilSubdomain)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'returns a StringSubdomain when the subdomain is a String' do
|
12
|
+
Ress::Subdomain.create('foo').should be_a(Ress::Subdomain::StringSubdomain)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'returns a RegexpSubdomain when the subdomain is a Regex' do
|
16
|
+
Ress::Subdomain.create(/foo/).should be_a(Ress::Subdomain::RegexpSubdomain)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
describe Ress::Subdomain::NilSubdomain do
|
22
|
+
|
23
|
+
let(:subdomain) { Ress::Subdomain::NilSubdomain.new }
|
24
|
+
|
25
|
+
describe '#matches?' do
|
26
|
+
it 'matches the empty string' do
|
27
|
+
subdomain.matches?('').should be_true
|
28
|
+
subdomain.matches?('s').should be_false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '#url' do
|
33
|
+
it 'strips urls from the fullpath if there is one' do
|
34
|
+
subdomain.url('http://', 'foo.bar.com/some/stuff', 'foo').should ==
|
35
|
+
'http://bar.com/some/stuff'
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'concatenates the protocol and fullpath together if there is no subdomain' do
|
39
|
+
subdomain.url('http://', 'bar.com/some/stuff', '').should ==
|
40
|
+
'http://bar.com/some/stuff'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe Ress::Subdomain::StringSubdomain do
|
46
|
+
|
47
|
+
let(:subdomain) { Ress::Subdomain::StringSubdomain.new('foo') }
|
48
|
+
|
49
|
+
describe '#matches?' do
|
50
|
+
|
51
|
+
it 'returns true if the url is exact match of that passed to the ctor' do
|
52
|
+
subdomain.matches?('foo').should be_true
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'returns false if the url is not an exact match of that passed to the ctor' do
|
56
|
+
subdomain.matches?('boo').should be_false
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '#url' do
|
61
|
+
|
62
|
+
it 'strips off extra subdomains from the fullpath' do
|
63
|
+
subdomain.url('http://', 'm.foo.bar.com/some/stuff', 'm.foo').should ==
|
64
|
+
'http://foo.bar.com/some/stuff'
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'handles cannonical fullpaths' do
|
68
|
+
subdomain.url('http://', 'foo.bar.com/some/stuff', 'foo').should ==
|
69
|
+
'http://foo.bar.com/some/stuff'
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe Ress::Subdomain::RegexpSubdomain do
|
76
|
+
describe '#matches?' do
|
77
|
+
let(:subdomain) { Ress::Subdomain::RegexpSubdomain.new(/[fb]oo/) }
|
78
|
+
|
79
|
+
it 'returns true if the subdomain matches regex that passed to the ctor' do
|
80
|
+
subdomain.matches?('foo').should be_true
|
81
|
+
subdomain.matches?('boo').should be_true
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'returns false if the subdomain doesnt matches regex that is passed to the ctor' do
|
85
|
+
subdomain.matches?('baz').should be_false
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'can handle the xip.io format' do
|
89
|
+
subdomain = Ress::Subdomain::RegexpSubdomain.new(/^([0-9]+\.){3}([0-9]+)/)
|
90
|
+
subdomain.matches?('172.30.251.3').should be_true
|
91
|
+
|
92
|
+
subdomain.matches?('mobile.172.30.251.3').should be_false
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe '#url' do
|
97
|
+
|
98
|
+
let(:subdomain) { Ress::Subdomain::RegexpSubdomain.new(/^[fb]oo/) }
|
99
|
+
|
100
|
+
it 'echos back the url for cannonical requests' do
|
101
|
+
subdomain.url('http://', 'foo.bar.com/some/stuff', 'foo').should ==
|
102
|
+
'http://foo.bar.com/some/stuff'
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'strips off prepended subdomains that dont matche the regex' do
|
106
|
+
subdomain.url('http://', 'm.a.foo.bar.com/some/stuff', 'm.a.foo').should ==
|
107
|
+
'http://foo.bar.com/some/stuff'
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
data/spec/ress_spec.rb
CHANGED
metadata
CHANGED
@@ -2,14 +2,14 @@
|
|
2
2
|
name: ress
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.0.
|
5
|
+
version: 0.0.7
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Matthew Robertson
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-02-05 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -65,6 +65,7 @@ files:
|
|
65
65
|
- lib/ress/category_collection.rb
|
66
66
|
- lib/ress/controller_additions.rb
|
67
67
|
- lib/ress/engine.rb
|
68
|
+
- lib/ress/subdomain.rb
|
68
69
|
- lib/ress/version.rb
|
69
70
|
- lib/ress/view_helpers.rb
|
70
71
|
- ress.gemspec
|
@@ -72,6 +73,7 @@ files:
|
|
72
73
|
- spec/ress/canonical_version_spec.rb
|
73
74
|
- spec/ress/category_collection_spec.rb
|
74
75
|
- spec/ress/controller_additions_spec.rb
|
76
|
+
- spec/ress/subdomain_spec.rb
|
75
77
|
- spec/ress/view_helpers_spec.rb
|
76
78
|
- spec/ress_spec.rb
|
77
79
|
- vendor/assets/javascripts/ress.js
|
@@ -105,5 +107,6 @@ test_files:
|
|
105
107
|
- spec/ress/canonical_version_spec.rb
|
106
108
|
- spec/ress/category_collection_spec.rb
|
107
109
|
- spec/ress/controller_additions_spec.rb
|
110
|
+
- spec/ress/subdomain_spec.rb
|
108
111
|
- spec/ress/view_helpers_spec.rb
|
109
112
|
- spec/ress_spec.rb
|