ress 0.0.1
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/.gitignore +17 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +151 -0
- data/Rakefile +1 -0
- data/lib/generators/ress/install_generator.rb +17 -0
- data/lib/generators/ress/templates/README +19 -0
- data/lib/generators/ress/templates/ress.rb +4 -0
- data/lib/ress.rb +33 -0
- data/lib/ress/alternate_version.rb +40 -0
- data/lib/ress/canonical_version.rb +38 -0
- data/lib/ress/category_collection.rb +23 -0
- data/lib/ress/controller_additions.rb +32 -0
- data/lib/ress/engine.rb +6 -0
- data/lib/ress/version.rb +3 -0
- data/lib/ress/view_helpers.rb +21 -0
- data/ress.gemspec +23 -0
- data/spec/ress/alternate_version_spec.rb +80 -0
- data/spec/ress/canonical_version_spec.rb +75 -0
- data/spec/ress/category_collection_spec.rb +40 -0
- data/spec/ress/controller_additions_spec.rb +72 -0
- data/spec/ress/view_helpers_spec.rb +49 -0
- data/spec/ress_spec.rb +38 -0
- data/vendor/assets/javascripts/modernizr.js +4 -0
- data/vendor/assets/javascripts/ress.js +168 -0
- metadata +109 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm gemset use ress
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Matthew Robertson
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
# Ress
|
2
|
+
|
3
|
+
Ress implements a set of best practices that allow you to progressively
|
4
|
+
enhance you Rails application to improve the user experience on mobile
|
5
|
+
devices.
|
6
|
+
|
7
|
+
## Background
|
8
|
+
|
9
|
+
Ress is an extension of the [devicejs](https://github.com/borismus/device.js)
|
10
|
+
library written by [Boris Smus](http://smus.com/). It adds a back end for
|
11
|
+
adapting server responses based on client side feature detection. Ress allows
|
12
|
+
you to specify alternate versions of your website, along with media queries
|
13
|
+
for which devices should be redirected to which version.
|
14
|
+
|
15
|
+
## How it Works
|
16
|
+
|
17
|
+
### HTML Annotations
|
18
|
+
|
19
|
+
When you register alternate mobile versions of your website, Ress adds annotations
|
20
|
+
to the `<head>` of your document that describes where these pages are located and
|
21
|
+
which devices should be redirected to them.
|
22
|
+
|
23
|
+
For example, a typical alternate version for a mobile site might include a tag
|
24
|
+
like this:
|
25
|
+
|
26
|
+
```html
|
27
|
+
<link rel="alternate" media="only screen and (max-width: 640px)" href="http://m.example.com/page-1" >
|
28
|
+
```
|
29
|
+
|
30
|
+
The mobile version of the page would then have a link pointing back the canonical
|
31
|
+
version:
|
32
|
+
|
33
|
+
```html
|
34
|
+
<link rel="canonical" href="http://www.example.com/page-1" >
|
35
|
+
```
|
36
|
+
|
37
|
+
These annotations conform to SEO best practices for mobile optimized websites
|
38
|
+
as documented by Google
|
39
|
+
[here](https://developers.google.com/webmasters/smartphone-sites/details).
|
40
|
+
|
41
|
+
### Feature Detection
|
42
|
+
|
43
|
+
semantic, media query-based device detection
|
44
|
+
|
45
|
+
Device.js will read all of the version links in your markup, and
|
46
|
+
redirect you to the appropriate URL that serves the correct version of
|
47
|
+
your webapp.
|
48
|
+
|
49
|
+
When a request comes into your site, ress runs a some javascript if there is an
|
50
|
+
alternate version available that matches the client. If there is, the user is
|
51
|
+
redirected to that site.
|
52
|
+
|
53
|
+
### Server Side Components
|
54
|
+
|
55
|
+
Ress allows you to customize how your Rails application responds to requests in
|
56
|
+
two ways:
|
57
|
+
|
58
|
+
1. It adds controller and helper methods to detect which version of your site has
|
59
|
+
been requested. This is useful for small tweeks in html or behaviour, eg:
|
60
|
+
|
61
|
+
```erb
|
62
|
+
<% if mobile_request? %>
|
63
|
+
<%= image_tag 'low-res.png' %>
|
64
|
+
<% else %>
|
65
|
+
<%= image_tag 'high-res.png' %>
|
66
|
+
<% end %>
|
67
|
+
```
|
68
|
+
|
69
|
+
2. It prepends a view path for each alternate version of your site, so that you can
|
70
|
+
override which templates or partials are rendered for certain requests. For example if
|
71
|
+
you want to render a different html form for creating users on the mobile version of your
|
72
|
+
site you could create `app/mobile_views/users/_form.html.erb` and Ress would have Rails
|
73
|
+
select that template over `app/views/users/_form.html.erb` when a request comes in to the
|
74
|
+
mobile version.
|
75
|
+
|
76
|
+
|
77
|
+
## Installation
|
78
|
+
|
79
|
+
Add this line to your application's Gemfile:
|
80
|
+
|
81
|
+
gem 'ress'
|
82
|
+
|
83
|
+
And then execute:
|
84
|
+
|
85
|
+
$ bundle install
|
86
|
+
|
87
|
+
Run the installation generator:
|
88
|
+
|
89
|
+
$ rails g ress:install
|
90
|
+
|
91
|
+
## Usage
|
92
|
+
|
93
|
+
### Version override
|
94
|
+
|
95
|
+
You can manually override the detector and load a particular version of
|
96
|
+
the site by passing in the `device` GET parameter with the ID of the
|
97
|
+
version you'd like to load. This will look up the `link` tag based on
|
98
|
+
the specified ID and load that version. For example, if you are on
|
99
|
+
desktop but want the tablet version, visiting
|
100
|
+
`http://foo.com/?version=tablet` will redirect to the tablet version at
|
101
|
+
`http://tablet.foo.com`.
|
102
|
+
|
103
|
+
Relatedly, you can prevent redirection completely, by specifying the
|
104
|
+
`force=1` GET parameter. For example, if you are on desktop and know the
|
105
|
+
URL of the tablet site, you can load `http://tablet.foo.com/?force=1`.
|
106
|
+
|
107
|
+
```html
|
108
|
+
<!-- Include a way to manually switch between device types -->
|
109
|
+
<footer>
|
110
|
+
<ul>
|
111
|
+
<li><a href="?device=desktop">Desktop</a></li>
|
112
|
+
<li><a href="?device=tablet">Tablet</a></li>
|
113
|
+
<li><a href="?device=phone">Phone</a></li>
|
114
|
+
</ul>
|
115
|
+
</footer>
|
116
|
+
```
|
117
|
+
|
118
|
+
### Dependencies
|
119
|
+
|
120
|
+
TODO: Modernizr
|
121
|
+
|
122
|
+
|
123
|
+
## Performance considerations
|
124
|
+
|
125
|
+
The javascript included by Ress does some checks and will use client-side
|
126
|
+
redirection to point users to the right version of your webapp. Client-side
|
127
|
+
redirection can have a performance overhead (though I haven't measured it).
|
128
|
+
If you find this is true, you can keep your DOM the same, still using the
|
129
|
+
SEO-friendly `<link rel="alternate">` tags, but simply remove the
|
130
|
+
device.js script and do your own server-side UA-based pushing.
|
131
|
+
|
132
|
+
## Browser support
|
133
|
+
|
134
|
+
Device.js should work in all browsers that support
|
135
|
+
`document.querySelectorAll`. Notably, this excludes IE7. If you want it
|
136
|
+
to work in IE7 and below, please include a [polyfill](https://gist.github.com/2724353).
|
137
|
+
|
138
|
+
## Contributing
|
139
|
+
|
140
|
+
The goal of Ress is to provide a SEO-compatible best practice and
|
141
|
+
starting point for reliable cross-device, cross-browser redirection.
|
142
|
+
|
143
|
+
Given how many browsers and devices we have these days, there are bound
|
144
|
+
to be bugs. If you find them, please report them and (ideally) fix them
|
145
|
+
in a pull request.
|
146
|
+
|
147
|
+
1. Fork it
|
148
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
149
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
150
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
151
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Ress
|
2
|
+
module Generators
|
3
|
+
class InstallGenerator < ::Rails::Generators::Base
|
4
|
+
source_root File.expand_path("../templates", __FILE__)
|
5
|
+
|
6
|
+
desc "Creates a Ress initializer for your application."
|
7
|
+
|
8
|
+
def copy_initializer
|
9
|
+
template "ress.rb", "config/initializers/ress.rb"
|
10
|
+
end
|
11
|
+
|
12
|
+
def show_readme
|
13
|
+
readme "README" if behavior == :invoke
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
===============================================================================
|
2
|
+
|
3
|
+
There is some setup you must do manually if you haven't yet:
|
4
|
+
|
5
|
+
1. Include the Javascript files in your application.js:
|
6
|
+
|
7
|
+
//= require ress
|
8
|
+
|
9
|
+
2. Call Ress in the head of your layout to render out the version annotations.
|
10
|
+
For example:
|
11
|
+
|
12
|
+
<head>
|
13
|
+
<title>My App</title>
|
14
|
+
<%= stylesheet_link_tag "application", :media => "all" %>
|
15
|
+
<%= ress_link_tags %>
|
16
|
+
<%= csrf_meta_tags %>
|
17
|
+
</head>
|
18
|
+
|
19
|
+
===============================================================================
|
data/lib/ress.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require "action_view"
|
2
|
+
|
3
|
+
require "ress/version"
|
4
|
+
require "ress/alternate_version"
|
5
|
+
require "ress/canonical_version"
|
6
|
+
require "ress/category_collection"
|
7
|
+
require "ress/controller_additions"
|
8
|
+
require "ress/view_helpers"
|
9
|
+
|
10
|
+
if defined? Rails
|
11
|
+
require "ress/engine"
|
12
|
+
end
|
13
|
+
|
14
|
+
module Ress
|
15
|
+
extend self
|
16
|
+
|
17
|
+
def category_collection
|
18
|
+
@categories ||= CategoryCollection.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def canonical_version
|
22
|
+
category_collection.canonical_version
|
23
|
+
end
|
24
|
+
|
25
|
+
def alternate_versions
|
26
|
+
category_collection.alternate_versions
|
27
|
+
end
|
28
|
+
|
29
|
+
def configure
|
30
|
+
yield(category_collection)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Ress
|
2
|
+
|
3
|
+
class AlternateVersion
|
4
|
+
|
5
|
+
attr_reader :name, :subdomain, :media_query, :view_path
|
6
|
+
|
7
|
+
def initialize(name, media_query, options = {})
|
8
|
+
@name = name
|
9
|
+
@media_query = media_query
|
10
|
+
@subdomain = options.fetch(:subdomain, name)
|
11
|
+
@view_path = options.fetch(:view_path, default_view_path)
|
12
|
+
end
|
13
|
+
|
14
|
+
def matches?(subdomain)
|
15
|
+
self.subdomain == subdomain.split('.').first
|
16
|
+
end
|
17
|
+
|
18
|
+
# Create a tag of this format:
|
19
|
+
# `<link rel="alternate" href="http://foo.com" id="desktop" media="only screen and (touch-enabled: 0)">`
|
20
|
+
def link_tag(protocol, base_url, view)
|
21
|
+
view.tag :link, :rel => 'alternate', :href => href(protocol, base_url), :id => id, :media => media_query
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def default_view_path
|
27
|
+
File.join('app', "#{name}_views")
|
28
|
+
end
|
29
|
+
|
30
|
+
def href(protocol, base_url)
|
31
|
+
"#{protocol}#{subdomain}.#{base_url}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def id
|
35
|
+
name
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Ress
|
2
|
+
|
3
|
+
class CanonicalVersion
|
4
|
+
|
5
|
+
attr_reader :subdomain
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
@subdomain = options.fetch(:subdomain, false)
|
9
|
+
end
|
10
|
+
|
11
|
+
def matches?(subdomain)
|
12
|
+
if self.subdomain
|
13
|
+
self.subdomain == subdomain
|
14
|
+
else
|
15
|
+
subdomain.empty?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Create a tag of this format:
|
20
|
+
# `<link rel="canonical" href="http://www.example.com/page-1" >`
|
21
|
+
def link_tag(protocol, fullpath, subdomain, view)
|
22
|
+
view.tag :link, :rel => 'canonical', :href => href(protocol, fullpath, subdomain)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def href(protocol, fullpath, subdomain)
|
28
|
+
fullpath = fullpath[(subdomain.length + 1)..-1] unless subdomain.empty?
|
29
|
+
if self.subdomain
|
30
|
+
"#{protocol}#{self.subdomain}.#{fullpath}"
|
31
|
+
else
|
32
|
+
"#{protocol}#{fullpath}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Ress
|
2
|
+
|
3
|
+
class CategoryCollection
|
4
|
+
|
5
|
+
attr_reader :canonical_version, :alternate_versions
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@alternate_versions = []
|
9
|
+
@canonical_version = CanonicalVersion.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def set_canonical(options = {})
|
13
|
+
@canonical_version = CanonicalVersion.new(options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_alternate(options)
|
17
|
+
version = AlternateVersion.new(options.delete(:name), options.delete(:media_type), options)
|
18
|
+
alternate_versions << version
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Ress
|
2
|
+
|
3
|
+
# This module is automatically included into all controllers.
|
4
|
+
module ControllerAdditions
|
5
|
+
module ClassMethods
|
6
|
+
# class methods go here
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.included(base)
|
10
|
+
base.extend ClassMethods
|
11
|
+
base.helper_method :canonical_request?
|
12
|
+
base.before_filter :prepend_category_view_path
|
13
|
+
end
|
14
|
+
|
15
|
+
def prepend_category_view_path
|
16
|
+
Ress.alternate_versions.each do |cat|
|
17
|
+
prepend_view_path(cat.view_path) if cat.matches?(request.subdomain)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def canonical_request?
|
22
|
+
Ress.canonical_version.matches?(request.subdomain)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
if defined? ActionController::Base
|
29
|
+
ActionController::Base.class_eval do
|
30
|
+
include Ress::ControllerAdditions
|
31
|
+
end
|
32
|
+
end
|
data/lib/ress/engine.rb
ADDED
data/lib/ress/version.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module Ress
|
2
|
+
|
3
|
+
module ViewHelpers
|
4
|
+
|
5
|
+
def ress_link_tags
|
6
|
+
path = "#{request.host_with_port}#{request.fullpath}"
|
7
|
+
if canonical_request?
|
8
|
+
Ress.alternate_versions.map do |category|
|
9
|
+
category.link_tag(request.protocol, path, self)
|
10
|
+
end.join.html_safe
|
11
|
+
else
|
12
|
+
Ress.canonical_version.link_tag(request.protocol, path, request.subdomain, self)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
ActionView::Base.send :include, Ress::ViewHelpers
|
data/ress.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ress/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "ress"
|
8
|
+
gem.version = Ress::VERSION
|
9
|
+
gem.authors = ["Matthew Robertson"]
|
10
|
+
gem.email = ["matthewrobertson03@gmail.com"]
|
11
|
+
gem.description = %q{Progressively enhance the mobile user experience of your Rails application.}
|
12
|
+
gem.summary = %q{Progressively enhance the mobile user experience of your Rails application.}
|
13
|
+
gem.homepage = "https://github.com/matthewrobertson/ress"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency("actionpack", "~> 3.0")
|
21
|
+
|
22
|
+
gem.add_development_dependency("rspec")
|
23
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require_relative '../../lib/ress/alternate_version'
|
2
|
+
|
3
|
+
describe Ress::AlternateVersion do
|
4
|
+
|
5
|
+
describe '#subdomain' do
|
6
|
+
|
7
|
+
it 'defaults to the name of the category' do
|
8
|
+
category = Ress::AlternateVersion.new('mobile', 'some media query')
|
9
|
+
category.subdomain.should == 'mobile'
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'can be overridden by an optional parameter' do
|
13
|
+
category = Ress::AlternateVersion.new('mobile', 'some media query', :subdomain => 'foo')
|
14
|
+
category.subdomain.should == 'foo'
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#view_path' do
|
20
|
+
|
21
|
+
it 'defaults to app/[NAME]_views' do
|
22
|
+
category = Ress::AlternateVersion.new('mobile', 'some media query')
|
23
|
+
category.view_path.should == 'app/mobile_views'
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'can be overridden by an optional parameter' do
|
27
|
+
category = Ress::AlternateVersion.new('mobile', 'some media query', :view_path => 'foo')
|
28
|
+
category.view_path.should == 'foo'
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#matches?' do
|
34
|
+
|
35
|
+
let(:category) { Ress::AlternateVersion.new('mobile', 'some media query', :subdomain => 'foo') }
|
36
|
+
|
37
|
+
it 'returns true if the subdomain matches' do
|
38
|
+
category.matches?('foo').should be_true
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'returns false if the subdomain does not match' do
|
42
|
+
category.matches?('bar').should be_false
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'when there are multiple subdomains' do
|
46
|
+
|
47
|
+
it 'returns true if the first subdomain matches' do
|
48
|
+
category.matches?('foo.bar').should be_true
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'returns false if the first subdomain does not match' do
|
52
|
+
category.matches?('bar.baz').should be_false
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '#link_tag' do
|
60
|
+
|
61
|
+
let(:category) { Ress::AlternateVersion.new('mobile', 'some media query') }
|
62
|
+
let(:view) { stub('view') }
|
63
|
+
|
64
|
+
def link_tag(base_url, protocol, view)
|
65
|
+
view.tag :link, :rel => 'alternate', :href => href(base_url, protocol), :id => id, :media => media_query
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'passes args to the view tag helper' do
|
69
|
+
view.should_receive(:tag).with(:link, {
|
70
|
+
:rel => "alternate",
|
71
|
+
:href => "http://mobile.foo.com",
|
72
|
+
:id => "mobile",
|
73
|
+
:media => "some media query"
|
74
|
+
}).and_return('<link>')
|
75
|
+
category.link_tag('http://', 'foo.com', view).should == '<link>'
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require_relative '../../lib/ress/canonical_version'
|
2
|
+
|
3
|
+
describe Ress::CanonicalVersion do
|
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
|
+
describe '#matches?' do
|
20
|
+
|
21
|
+
context 'with no canonical subdomain' do
|
22
|
+
let(:version) { Ress::CanonicalVersion.new }
|
23
|
+
|
24
|
+
it 'returns true if there is no subdomain' do
|
25
|
+
version.matches?('').should be_true
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'returns false if there is a subdomain' do
|
29
|
+
version.matches?('blah').should be_false
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'with a canonical subdomain' do
|
35
|
+
|
36
|
+
let(:version) { Ress::CanonicalVersion.new(:subdomain => 'foo') }
|
37
|
+
|
38
|
+
it 'returns true if the subdomain matches the configured' do
|
39
|
+
version.matches?('foo').should be_true
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'returns false if the subdomain does not match' do
|
43
|
+
version.matches?('bar').should be_false
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '#link_tag' do
|
51
|
+
|
52
|
+
let(:view) { stub('view') }
|
53
|
+
|
54
|
+
|
55
|
+
it 'constructs the correct url when there is no canonical subdomain ' do
|
56
|
+
version = Ress::CanonicalVersion.new
|
57
|
+
view.should_receive(:tag).with(:link, {
|
58
|
+
:rel => "canonical",
|
59
|
+
:href => "http://foo.com"
|
60
|
+
}).and_return('<link>')
|
61
|
+
version.link_tag('http://', 'm.foo.com', 'm', view).should == '<link>'
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'constructs the correct url when there is a canonical subdomain ' do
|
65
|
+
version = Ress::CanonicalVersion.new(:subdomain => 'secure')
|
66
|
+
view.should_receive(:tag).with(:link, {
|
67
|
+
:rel => "canonical",
|
68
|
+
:href => "http://secure.foo.com"
|
69
|
+
}).and_return('<link>')
|
70
|
+
version.link_tag('http://', 'm.secure.foo.com', 'm.secure', view).should == '<link>'
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require_relative '../../lib/ress'
|
2
|
+
|
3
|
+
describe Ress::CategoryCollection do
|
4
|
+
|
5
|
+
let(:collection) { Ress::CategoryCollection.new }
|
6
|
+
|
7
|
+
describe '#add_alternate' do
|
8
|
+
|
9
|
+
it 'adds an item to the alternate_versions' do
|
10
|
+
expect {
|
11
|
+
collection.add_alternate({:name => 'stuff', :media_query => 'foo'})
|
12
|
+
}.to change { collection.alternate_versions.size }.by(1)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'sets the name and media_query of the alternate_version' do
|
16
|
+
collection.add_alternate({:name => 'stuff', :media_query => 'foo'})
|
17
|
+
collection.alternate_versions.last.name.should == 'stuff'
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'passes options to the alternate_version' do
|
21
|
+
collection.add_alternate({:name => 'stuff', :media_query => 'foo', :subdomain => 'm'})
|
22
|
+
collection.alternate_versions.last.subdomain.should == 'm'
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
describe 'canonical_version' do
|
28
|
+
|
29
|
+
it 'defaults to a canonical_version with no subdomain' do
|
30
|
+
collection.canonical_version.subdomain.should be_false
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'can be altered through set_canonical' do
|
34
|
+
collection.set_canonical :subdomain => 'foo'
|
35
|
+
collection.canonical_version.subdomain.should == 'foo'
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require_relative '../../lib/ress'
|
2
|
+
|
3
|
+
class ActionControllerStub
|
4
|
+
|
5
|
+
attr_accessor :request
|
6
|
+
|
7
|
+
def self.before_filter(action)
|
8
|
+
@@action = action
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.action
|
12
|
+
@@action
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.helper_method(*splat) ; end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
ActionControllerStub.class_eval do
|
20
|
+
include Ress::ControllerAdditions
|
21
|
+
end
|
22
|
+
|
23
|
+
describe Ress::ControllerAdditions do
|
24
|
+
|
25
|
+
it 'adds a before_filter to all actions when it is included' do
|
26
|
+
ActionControllerStub.action.should == :prepend_category_view_path
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '#prepend_category_view_path' do
|
30
|
+
|
31
|
+
let(:controller) { ActionControllerStub.new }
|
32
|
+
|
33
|
+
before do
|
34
|
+
request = stub('request', :subdomain => 'foo')
|
35
|
+
controller.request = request
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'prepends view paths of matching alternate_versions' do
|
39
|
+
category = stub('category', :matches? => true, :view_path => 'foo/bar')
|
40
|
+
Ress.stub(:alternate_versions => [category])
|
41
|
+
|
42
|
+
controller.should_receive(:prepend_view_path).with('foo/bar')
|
43
|
+
controller.prepend_category_view_path
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'does not prepend view paths of alternate_versions that dont match' do
|
47
|
+
category = stub('category', :matches? => false, :view_path => 'foo/bar')
|
48
|
+
Ress.stub(:alternate_versions => [category])
|
49
|
+
|
50
|
+
controller.should_not_receive(:prepend_view_path)
|
51
|
+
controller.prepend_category_view_path
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '#canonical_request?' do
|
57
|
+
|
58
|
+
let(:controller) { ActionControllerStub.new }
|
59
|
+
|
60
|
+
before do
|
61
|
+
request = stub(:subdomain => 'foo')
|
62
|
+
controller.stub(:request => request)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'returns true if the request subdomain matches the canonical' do
|
66
|
+
Ress.canonical_version.stub(:matches? => true)
|
67
|
+
controller.canonical_request?.should be_true
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require_relative '../../lib/ress'
|
3
|
+
|
4
|
+
describe ActionView::Base do
|
5
|
+
|
6
|
+
describe '#ress_link_tags' do
|
7
|
+
|
8
|
+
let(:view) { ActionView::Base.new }
|
9
|
+
let(:request) { stub('request', :protocol => 'http://', :host_with_port => 'x.foo.com', :fullpath => '/bar', :subdomain => 'x') }
|
10
|
+
let(:category) { Ress::AlternateVersion.new('m', 'stuff') }
|
11
|
+
|
12
|
+
before do
|
13
|
+
view.stub(:request => request)
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'alternate request' do
|
17
|
+
|
18
|
+
before { view.stub(:canonical_request? => false) }
|
19
|
+
|
20
|
+
it 'returns the canonical link' do
|
21
|
+
view.ress_link_tags.should == "<link href=\"http://foo.com/bar\" rel=\"canonical\" />"
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'canonical request' do
|
27
|
+
|
28
|
+
let(:request) { stub('request', :protocol => 'http://', :host_with_port => 'foo.com', :fullpath => '/bar', :subdomain => '') }
|
29
|
+
before { view.stub(:canonical_request? => true) }
|
30
|
+
|
31
|
+
it 'returns an empty string if there are no registered categories' do
|
32
|
+
view.ress_link_tags.should == ''
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'generates the link tags when there is one category' do
|
36
|
+
|
37
|
+
Ress.configure do |r|
|
38
|
+
r.add_alternate :name => 'm', :media_type => 'stuff'
|
39
|
+
end
|
40
|
+
|
41
|
+
view.ress_link_tags.should ==
|
42
|
+
"<link href=\"http://m.foo.com/bar\" id=\"m\" media=\"stuff\" rel=\"alternate\" />"
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
data/spec/ress_spec.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require_relative '../lib/ress'
|
2
|
+
|
3
|
+
describe Ress do
|
4
|
+
|
5
|
+
describe '.configure' do
|
6
|
+
|
7
|
+
it 'yields the default category collection' do
|
8
|
+
Ress.configure { |r| r.should be_a(Ress::CategoryCollection) }
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '.canonical_version' do
|
14
|
+
|
15
|
+
it 'returns a Ress::CanonicalVersion' do
|
16
|
+
Ress.canonical_version.should be_a(Ress::CanonicalVersion)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'can be altered through Ress.configure' do
|
20
|
+
Ress.configure { |r| r.set_canonical :subdomain => 'foo' }
|
21
|
+
Ress.canonical_version.subdomain.should == 'foo'
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '.alternate_versions' do
|
27
|
+
|
28
|
+
it 'can be altered through Ress.configure' do
|
29
|
+
expect {
|
30
|
+
Ress.configure { |r| r.add_alternate :name => 'foo' }
|
31
|
+
}.to change {
|
32
|
+
Ress.alternate_versions.length
|
33
|
+
}.by(1)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,4 @@
|
|
1
|
+
/* Modernizr 2.5.3 (Custom Build) | MIT & BSD
|
2
|
+
* Build: http://modernizr.com/download/#-touch-mq-teststyles-prefixes
|
3
|
+
*/
|
4
|
+
;window.Modernizr=function(a,b,c){function w(a){i.cssText=a}function x(a,b){return w(l.join(a+";")+(b||""))}function y(a,b){return typeof a===b}function z(a,b){return!!~(""+a).indexOf(b)}function A(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:y(f,"function")?f.bind(d||b):f}return!1}var d="2.5.3",e={},f=b.documentElement,g="modernizr",h=b.createElement(g),i=h.style,j,k={}.toString,l=" -webkit- -moz- -o- -ms- ".split(" "),m={},n={},o={},p=[],q=p.slice,r,s=function(a,c,d,e){var h,i,j,k=b.createElement("div"),l=b.body,m=l?l:b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:g+(d+1),k.appendChild(j);return h=["­","<style>",a,"</style>"].join(""),k.id=g,(l?k:m).innerHTML+=h,m.appendChild(k),l||(m.style.background="",f.appendChild(m)),i=c(k,a),l?k.parentNode.removeChild(k):m.parentNode.removeChild(m),!!i},t=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return s("@media "+b+" { #"+g+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},u={}.hasOwnProperty,v;!y(u,"undefined")&&!y(u.call,"undefined")?v=function(a,b){return u.call(a,b)}:v=function(a,b){return b in a&&y(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=q.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(q.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(q.call(arguments)))};return e});var B=function(c,d){var f=c.join(""),g=d.length;s(f,function(c,d){var f=b.styleSheets[b.styleSheets.length-1],h=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"",i=c.childNodes,j={};while(g--)j[i[g].id]=i[g];e.touch="ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch||(j.touch&&j.touch.offsetTop)===9},g,d)}([,["@media (",l.join("touch-enabled),("),g,")","{#touch{top:9px;position:absolute}}"].join("")],[,"touch"]);m.touch=function(){return e.touch};for(var C in m)v(m,C)&&(r=C.toLowerCase(),e[r]=m[C](),p.push((e[r]?"":"no-")+r));return w(""),h=j=null,e._version=d,e._prefixes=l,e.mq=t,e.testStyles=s,e}(this,this.document);
|
@@ -0,0 +1,168 @@
|
|
1
|
+
/**
|
2
|
+
* This file has been adapted from device.js:
|
3
|
+
* https://github.com/borismus/device.js
|
4
|
+
*/
|
5
|
+
(function(exports) {
|
6
|
+
|
7
|
+
var MQ_TOUCH = /\(touch-enabled: (.*?)\)/;
|
8
|
+
var FORCE_COOKIE = 'force-cannonical';
|
9
|
+
|
10
|
+
/**
|
11
|
+
* Class responsible for deciding which version of the application to
|
12
|
+
* load.
|
13
|
+
*/
|
14
|
+
function VersionManager() {
|
15
|
+
// Get a list of all versions.
|
16
|
+
this.versions = this.getVersions();
|
17
|
+
}
|
18
|
+
|
19
|
+
/**
|
20
|
+
* Parse all of the <link> elements in the <head>.
|
21
|
+
*/
|
22
|
+
VersionManager.prototype.getVersions = function() {
|
23
|
+
var versions = [];
|
24
|
+
// Get all of the link rel alternate elements from the head.
|
25
|
+
var links = document.querySelectorAll('head link[rel="alternate"]');
|
26
|
+
// For each link element, get href, media and id.
|
27
|
+
for (var i = 0; i < links.length; i++) {
|
28
|
+
var href = links[i].getAttribute('href');
|
29
|
+
var media = links[i].getAttribute('media');
|
30
|
+
var id = links[i].getAttribute('id');
|
31
|
+
versions.push(new Version(href, media, id));
|
32
|
+
}
|
33
|
+
// Return an array of Version objects.
|
34
|
+
return versions;
|
35
|
+
};
|
36
|
+
|
37
|
+
/**
|
38
|
+
* Device which version to load.
|
39
|
+
*/
|
40
|
+
VersionManager.prototype.redirectIfNeeded = function() {
|
41
|
+
if(this.shouldNotRedirect()) {
|
42
|
+
return;
|
43
|
+
}
|
44
|
+
var version = this.findVersion(Version.prototype.matches);
|
45
|
+
if (version) {
|
46
|
+
version.redirect();
|
47
|
+
}
|
48
|
+
};
|
49
|
+
|
50
|
+
VersionManager.prototype.shouldNotRedirect = function() {
|
51
|
+
return this.versions.length === 0 ||
|
52
|
+
this.readCookie(FORCE_COOKIE) === 'true';
|
53
|
+
};
|
54
|
+
|
55
|
+
VersionManager.prototype.readCookie = function() {
|
56
|
+
var nameEQ = name + "=";
|
57
|
+
var ca = document.cookie.split(';');
|
58
|
+
for(var i=0;i < ca.length;i++) {
|
59
|
+
var c = ca[i];
|
60
|
+
while (c.charAt(0)==' ') c = c.substring(1,c.length);
|
61
|
+
if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length,c.length);
|
62
|
+
}
|
63
|
+
return null;
|
64
|
+
};
|
65
|
+
|
66
|
+
VersionManager.prototype.findVersion = function(criteria) {
|
67
|
+
// Go through configured versions.
|
68
|
+
for (var i = 0; i < this.versions.length; i++) {
|
69
|
+
var v = this.versions[i];
|
70
|
+
// Check if this version matches based on criteria.
|
71
|
+
if (criteria.call(v)) {
|
72
|
+
return v;
|
73
|
+
}
|
74
|
+
}
|
75
|
+
return null;
|
76
|
+
};
|
77
|
+
|
78
|
+
function Version(href, mediaQuery, id) {
|
79
|
+
this.mediaQuery = mediaQuery;
|
80
|
+
this.url = href;
|
81
|
+
this.id = id;
|
82
|
+
}
|
83
|
+
|
84
|
+
/**
|
85
|
+
* Check if this version matches the current one.
|
86
|
+
*/
|
87
|
+
Version.prototype.matches = function() {
|
88
|
+
// Apply a polyfill for the touch-enabled media query (not currently
|
89
|
+
// standardized, only implemented in Firefox: http://goo.gl/LrmIa)
|
90
|
+
var mqParser = new MQParser(this.mediaQuery);
|
91
|
+
return mqParser.evaluate();
|
92
|
+
};
|
93
|
+
|
94
|
+
/**
|
95
|
+
* Redirect to the current version.
|
96
|
+
*/
|
97
|
+
Version.prototype.redirect = function() {
|
98
|
+
// prevent infinite redirect loops
|
99
|
+
if(window.location.href != this.url) {
|
100
|
+
window.location.href = this.url;
|
101
|
+
}
|
102
|
+
};
|
103
|
+
|
104
|
+
|
105
|
+
function MQParser(mq) {
|
106
|
+
this.mq = mq;
|
107
|
+
this.segments = [];
|
108
|
+
this.standardSegments = [];
|
109
|
+
this.specialSegments = [];
|
110
|
+
|
111
|
+
this.parse();
|
112
|
+
}
|
113
|
+
|
114
|
+
MQParser.prototype.parse = function() {
|
115
|
+
// Split the Media Query into segments separated by 'and'.
|
116
|
+
this.segments = this.mq.split(/\s*and\s*/);
|
117
|
+
// Look for segments that contain touch checks.
|
118
|
+
for (var i = 0; i < this.segments.length; i++) {
|
119
|
+
var seg = this.segments[i];
|
120
|
+
// TODO: replace this check with something that checks generally for
|
121
|
+
// unknown MQ properties.
|
122
|
+
var match = seg.match(MQ_TOUCH);
|
123
|
+
if (match) {
|
124
|
+
this.specialSegments.push(seg);
|
125
|
+
} else {
|
126
|
+
// If there's no touch MQ, we're dealing with something standard.
|
127
|
+
this.standardSegments.push(seg);
|
128
|
+
}
|
129
|
+
}
|
130
|
+
};
|
131
|
+
|
132
|
+
/**
|
133
|
+
* Check if touch support matches the media query.
|
134
|
+
*/
|
135
|
+
MQParser.prototype.evaluateTouch = function() {
|
136
|
+
var out = true;
|
137
|
+
for (var i = 0; i < this.specialSegments.length; i++) {
|
138
|
+
var match = this.specialSegments[i].match(MQ_TOUCH);
|
139
|
+
var touchValue = match[1];
|
140
|
+
if (touchValue !== "0" && touchValue !== "1") {
|
141
|
+
console.error('Invalid value for "touch-enabled" media query.');
|
142
|
+
}
|
143
|
+
var touchExpected = parseInt(touchValue, 10) === 1 ? true : false;
|
144
|
+
out = out && (touchExpected == Modernizr.touch);
|
145
|
+
}
|
146
|
+
return out;
|
147
|
+
};
|
148
|
+
|
149
|
+
/**
|
150
|
+
* Returns the valid media query (without touch stuff).
|
151
|
+
*/
|
152
|
+
MQParser.prototype.getMediaQuery = function() {
|
153
|
+
return this.standardSegments.join(' and ');
|
154
|
+
};
|
155
|
+
|
156
|
+
/**
|
157
|
+
* Evaluates the media query with matchMedia.
|
158
|
+
*/
|
159
|
+
MQParser.prototype.evaluate = function() {
|
160
|
+
return Modernizr.mq(this.getMediaQuery()) &&
|
161
|
+
this.evaluateTouch();
|
162
|
+
|
163
|
+
};
|
164
|
+
|
165
|
+
var vermgr = new VersionManager();
|
166
|
+
vermgr.redirectIfNeeded();
|
167
|
+
|
168
|
+
})(window);
|
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ress
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Matthew Robertson
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-01-28 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
version_requirements: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.0'
|
20
|
+
none: false
|
21
|
+
name: actionpack
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
requirement: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '3.0'
|
29
|
+
none: false
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
version_requirements: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - ! '>='
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '0'
|
36
|
+
none: false
|
37
|
+
name: rspec
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
requirement: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
none: false
|
46
|
+
description: Progressively enhance the mobile user experience of your Rails application.
|
47
|
+
email:
|
48
|
+
- matthewrobertson03@gmail.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- .gitignore
|
54
|
+
- .rvmrc
|
55
|
+
- Gemfile
|
56
|
+
- LICENSE.txt
|
57
|
+
- README.md
|
58
|
+
- Rakefile
|
59
|
+
- lib/generators/ress/install_generator.rb
|
60
|
+
- lib/generators/ress/templates/README
|
61
|
+
- lib/generators/ress/templates/ress.rb
|
62
|
+
- lib/ress.rb
|
63
|
+
- lib/ress/alternate_version.rb
|
64
|
+
- lib/ress/canonical_version.rb
|
65
|
+
- lib/ress/category_collection.rb
|
66
|
+
- lib/ress/controller_additions.rb
|
67
|
+
- lib/ress/engine.rb
|
68
|
+
- lib/ress/version.rb
|
69
|
+
- lib/ress/view_helpers.rb
|
70
|
+
- ress.gemspec
|
71
|
+
- spec/ress/alternate_version_spec.rb
|
72
|
+
- spec/ress/canonical_version_spec.rb
|
73
|
+
- spec/ress/category_collection_spec.rb
|
74
|
+
- spec/ress/controller_additions_spec.rb
|
75
|
+
- spec/ress/view_helpers_spec.rb
|
76
|
+
- spec/ress_spec.rb
|
77
|
+
- vendor/assets/javascripts/modernizr.js
|
78
|
+
- vendor/assets/javascripts/ress.js
|
79
|
+
homepage: https://github.com/matthewrobertson/ress
|
80
|
+
licenses: []
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options: []
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
none: false
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ! '>='
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
none: false
|
97
|
+
requirements: []
|
98
|
+
rubyforge_project:
|
99
|
+
rubygems_version: 1.8.24
|
100
|
+
signing_key:
|
101
|
+
specification_version: 3
|
102
|
+
summary: Progressively enhance the mobile user experience of your Rails application.
|
103
|
+
test_files:
|
104
|
+
- spec/ress/alternate_version_spec.rb
|
105
|
+
- spec/ress/canonical_version_spec.rb
|
106
|
+
- spec/ress/category_collection_spec.rb
|
107
|
+
- spec/ress/controller_additions_spec.rb
|
108
|
+
- spec/ress/view_helpers_spec.rb
|
109
|
+
- spec/ress_spec.rb
|