ress 0.0.6 → 0.0.7
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.
- 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
|