crystalmeta 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/.gitignore +6 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +4 -0
  4. data/Gemfile.lock +130 -0
  5. data/Rakefile +1 -0
  6. data/Readme.markdown +210 -0
  7. data/crystalmeta.gemspec +23 -0
  8. data/lib/crystal/controller_extensions.rb +19 -0
  9. data/lib/crystal/hash_with_stringify_keys.rb +52 -0
  10. data/lib/crystal/interpolates_tags.rb +19 -0
  11. data/lib/crystal/meta.rb +29 -0
  12. data/lib/crystal/options_for_controller.rb +52 -0
  13. data/lib/crystal/railtie.rb +17 -0
  14. data/lib/crystal/tag.rb +26 -0
  15. data/lib/crystal/tags.rb +52 -0
  16. data/lib/crystal/version.rb +3 -0
  17. data/lib/crystal/view_helpers.rb +13 -0
  18. data/lib/crystalmeta.rb +24 -0
  19. data/spec/dummy/Rakefile +7 -0
  20. data/spec/dummy/app/assets/javascripts/application.js +12 -0
  21. data/spec/dummy/app/assets/stylesheets/application.css +11 -0
  22. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  23. data/spec/dummy/app/controllers/movies_controller.rb +5 -0
  24. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  25. data/spec/dummy/app/mailers/.gitkeep +0 -0
  26. data/spec/dummy/app/models/.gitkeep +0 -0
  27. data/spec/dummy/app/views/layouts/application.html.erb +11 -0
  28. data/spec/dummy/app/views/movies/index.html.erb +1 -0
  29. data/spec/dummy/app/views/movies/show.html.erb +0 -0
  30. data/spec/dummy/config.ru +4 -0
  31. data/spec/dummy/config/application.rb +64 -0
  32. data/spec/dummy/config/boot.rb +10 -0
  33. data/spec/dummy/config/environment.rb +5 -0
  34. data/spec/dummy/config/environments/development.rb +37 -0
  35. data/spec/dummy/config/environments/production.rb +67 -0
  36. data/spec/dummy/config/environments/test.rb +37 -0
  37. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  38. data/spec/dummy/config/initializers/inflections.rb +15 -0
  39. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  40. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  41. data/spec/dummy/config/initializers/session_store.rb +8 -0
  42. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  43. data/spec/dummy/config/locales/en.yml +18 -0
  44. data/spec/dummy/config/routes.rb +3 -0
  45. data/spec/dummy/lib/assets/.gitkeep +0 -0
  46. data/spec/dummy/log/.gitkeep +0 -0
  47. data/spec/dummy/script/rails +6 -0
  48. data/spec/features/meta_tags_spec.rb +52 -0
  49. data/spec/spec_helper.rb +14 -0
  50. data/spec/support/tag.rb +3 -0
  51. data/spec/unit/hash_with_stringify_keys_spec.rb +31 -0
  52. data/spec/unit/meta_spec.rb +41 -0
  53. data/spec/unit/tag_spec.rb +31 -0
  54. data/spec/unit/tags_spec.rb +132 -0
  55. metadata +185 -0
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ .bundle/
2
+ pkg/
3
+ spec/dummy/db/*.sqlite3
4
+ spec/dummy/log/*.log
5
+ spec/dummy/tmp/
6
+ spec/dummy/.sass-cache
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in opengraphy.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,130 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ crystalmeta (0.9.0)
5
+ rails (>= 3.0.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ actionmailer (3.2.9)
11
+ actionpack (= 3.2.9)
12
+ mail (~> 2.4.4)
13
+ actionpack (3.2.9)
14
+ activemodel (= 3.2.9)
15
+ activesupport (= 3.2.9)
16
+ builder (~> 3.0.0)
17
+ erubis (~> 2.7.0)
18
+ journey (~> 1.0.4)
19
+ rack (~> 1.4.0)
20
+ rack-cache (~> 1.2)
21
+ rack-test (~> 0.6.1)
22
+ sprockets (~> 2.2.1)
23
+ activemodel (3.2.9)
24
+ activesupport (= 3.2.9)
25
+ builder (~> 3.0.0)
26
+ activerecord (3.2.9)
27
+ activemodel (= 3.2.9)
28
+ activesupport (= 3.2.9)
29
+ arel (~> 3.0.2)
30
+ tzinfo (~> 0.3.29)
31
+ activeresource (3.2.9)
32
+ activemodel (= 3.2.9)
33
+ activesupport (= 3.2.9)
34
+ activesupport (3.2.9)
35
+ i18n (~> 0.6)
36
+ multi_json (~> 1.0)
37
+ addressable (2.3.2)
38
+ arel (3.0.2)
39
+ builder (3.0.4)
40
+ capybara (2.0.1)
41
+ mime-types (>= 1.16)
42
+ nokogiri (>= 1.3.3)
43
+ rack (>= 1.0.0)
44
+ rack-test (>= 0.5.4)
45
+ selenium-webdriver (~> 2.0)
46
+ xpath (~> 1.0.0)
47
+ childprocess (0.3.6)
48
+ ffi (~> 1.0, >= 1.0.6)
49
+ diff-lcs (1.1.3)
50
+ erubis (2.7.0)
51
+ ffi (1.2.0)
52
+ hike (1.2.1)
53
+ i18n (0.6.1)
54
+ journey (1.0.4)
55
+ json (1.7.5)
56
+ libwebsocket (0.1.7.1)
57
+ addressable
58
+ websocket
59
+ mail (2.4.4)
60
+ i18n (>= 0.4.0)
61
+ mime-types (~> 1.16)
62
+ treetop (~> 1.4.8)
63
+ mime-types (1.19)
64
+ multi_json (1.5.0)
65
+ nokogiri (1.5.6)
66
+ polyglot (0.3.3)
67
+ rack (1.4.1)
68
+ rack-cache (1.2)
69
+ rack (>= 0.4)
70
+ rack-ssl (1.3.2)
71
+ rack
72
+ rack-test (0.6.2)
73
+ rack (>= 1.0)
74
+ rails (3.2.9)
75
+ actionmailer (= 3.2.9)
76
+ actionpack (= 3.2.9)
77
+ activerecord (= 3.2.9)
78
+ activeresource (= 3.2.9)
79
+ activesupport (= 3.2.9)
80
+ bundler (~> 1.0)
81
+ railties (= 3.2.9)
82
+ railties (3.2.9)
83
+ actionpack (= 3.2.9)
84
+ activesupport (= 3.2.9)
85
+ rack-ssl (~> 1.3.2)
86
+ rake (>= 0.8.7)
87
+ rdoc (~> 3.4)
88
+ thor (>= 0.14.6, < 2.0)
89
+ rake (10.0.3)
90
+ rdoc (3.12)
91
+ json (~> 1.4)
92
+ rspec-core (2.12.2)
93
+ rspec-expectations (2.12.1)
94
+ diff-lcs (~> 1.1.3)
95
+ rspec-mocks (2.12.1)
96
+ rspec-rails (2.12.0)
97
+ actionpack (>= 3.0)
98
+ activesupport (>= 3.0)
99
+ railties (>= 3.0)
100
+ rspec-core (~> 2.12.0)
101
+ rspec-expectations (~> 2.12.0)
102
+ rspec-mocks (~> 2.12.0)
103
+ rubyzip (0.9.9)
104
+ selenium-webdriver (2.27.2)
105
+ childprocess (>= 0.2.5)
106
+ libwebsocket (~> 0.1.3)
107
+ multi_json (~> 1.0)
108
+ rubyzip
109
+ sprockets (2.2.2)
110
+ hike (~> 1.2)
111
+ multi_json (~> 1.0)
112
+ rack (~> 1.0)
113
+ tilt (~> 1.1, != 1.3.0)
114
+ thor (0.16.0)
115
+ tilt (1.3.3)
116
+ treetop (1.4.12)
117
+ polyglot
118
+ polyglot (>= 0.3.1)
119
+ tzinfo (0.3.35)
120
+ websocket (1.0.6)
121
+ xpath (1.0.0)
122
+ nokogiri (~> 1.3)
123
+
124
+ PLATFORMS
125
+ ruby
126
+
127
+ DEPENDENCIES
128
+ capybara (~> 2.0.1)
129
+ crystalmeta!
130
+ rspec-rails (~> 2.12)
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/Readme.markdown ADDED
@@ -0,0 +1,210 @@
1
+ **Crystalmeta helps you control meta tags through I18n and/or manually. It plays well with [OpenGraph](http://ogp.me/).**
2
+
3
+ It gives you 3 helpers:
4
+
5
+ **1.** `meta(options)` — available in both controller & views.
6
+
7
+ Accepts `Hash`.
8
+
9
+ Collects meta tags for future use:
10
+
11
+ ```ruby
12
+ # folded hashes
13
+ meta og: {title: "The Rock (1996)", type: "video.movie"}
14
+
15
+ # one level hash
16
+ meta "og:title" => "The Rock (1996)", "og:type" => "video.movie"
17
+ meta :"og:image" => "the-rock.png"
18
+ ```
19
+
20
+ **2.** `meta_tag(name)` — available in views
21
+
22
+ Accepts `String` or `Symbol`.
23
+
24
+ Returns value for a certain tag:
25
+
26
+ ```erb
27
+ <title><%= meta_tag 'og:title' %></title>
28
+ ```
29
+
30
+ **3.** `meta_tags(pattern = //)`
31
+
32
+ Returns all meta tags which names match the pattern. Under the hoods it uses the three-qual to pattern match (like in `Enumerable#grep`).
33
+
34
+ ```erb
35
+ <%= meta_tags %>
36
+ ```
37
+
38
+ displays in this case:
39
+
40
+ ```html
41
+ <meta property="og:url" content="http://www.imdb.com/title/tt0117500/" />
42
+ <meta property="og:image" content="/assets/the-rock.png" />
43
+ <meta property="og:title" content="The Rock (1996)" />
44
+ <meta property="og:type" content="video.movie" />
45
+ ```
46
+
47
+ Please note that:
48
+
49
+ * `image_path` is automatically used for `og:image` or `og:image:url` (`video_path` for `og:video` and `audio_path` for `og:audio` respectively), so you don't have to bother setting explicit paths;
50
+ * `og:url` is set to `controller.request.url`.
51
+
52
+ ## I18n translations
53
+
54
+ Crystalmeta tries to fetch translations and merge them in this order (consider an example for `pages#show` action):
55
+
56
+ * `meta._defaults` — application defaults (lowest-priority)
57
+ * `meta.pages._defaults` — controller defaults
58
+ * `meta.pages.show` — action
59
+
60
+ Tags set manually by the `meta` helper will be given the highest priority.
61
+
62
+ **Namespaced controllers**
63
+
64
+ Crystalmeta uses `controller_path` in order to get the translation scope, so if you have a namespaced controller, for instance `Library::BooksController`, then the `show` action will use: `meta.library.books._defaults` & `meta.library.books.show` respectively.
65
+
66
+ **Aliases**
67
+
68
+ Crystalmeta uses translations for the `edit` action for `update`:
69
+
70
+ * `meta._defaults`
71
+ * `meta.movies._defaults`
72
+ * `meta.movies.edit`
73
+ * `meta.movies.update`
74
+
75
+ And `new` for `create`:
76
+
77
+ * `meta._defaults`
78
+ * `meta.movies._defaults`
79
+ * `meta.movies.new`
80
+ * `meta.movies.create`
81
+
82
+ You can omit defining translations for `update` & `create` actions if they're the same.
83
+
84
+ ## Displaying only certain meta tags using a pattern
85
+
86
+ Usually I set window title with Crystalmeta separately (I call it `head` for example) and I don't want to display it in meta tags, so I can do:
87
+
88
+ ```erb
89
+ <%= meta_tags /:/ %>
90
+ ```
91
+
92
+ It will filter tag names using `/:/ === name`.
93
+
94
+ You may wish to use a `Proc` in some cases:
95
+
96
+ ```erb
97
+ <%= meta_tags Proc.new{|name| name != "head"} %>
98
+ ```
99
+
100
+ ## Interpolation
101
+
102
+ You can interpolate meta tags like this:
103
+
104
+ ```erb
105
+ <% meta({
106
+ "og:title" => "The Rock (1996)",
107
+ "og:site_name" => "IMDb",
108
+ "head" => "%{og:title} — %{og:site_name}"
109
+ }) %>
110
+ ```
111
+
112
+ `meta_tag :head` will return `"The Rock (1996) — IMDb"`.
113
+
114
+ Please use this feature with care to avoid [SystemStackError](http://ruby-doc.org/core-1.9.3/SystemStackError.html).
115
+
116
+ ## Caveats
117
+
118
+ **Merging tags**
119
+
120
+ When tags are merged (while fetching translations or collecting with `meta`) Crystalmeta doesn't do anything smart — it just merges hashes brutally. So if you defined a tag like that:
121
+
122
+ ```yml
123
+ en:
124
+ meta:
125
+ _defaults:
126
+ og:
127
+ title: IMDb
128
+ ```
129
+
130
+ and then you redefine it in different style:
131
+
132
+ ```ruby
133
+ meta 'og:title' => 'The Rock (1996)'
134
+ ```
135
+
136
+ then you'll just get 2 tags in the end:
137
+
138
+ ```erb
139
+ <%= meta_tags /og:title/ %>
140
+ ```
141
+
142
+ will display
143
+
144
+ ```html
145
+ <meta property="og:title" content="IMDb" />
146
+ <meta property="og:title" content="Newer IMDb" />
147
+ ```
148
+
149
+ So you'd better prefer one style.
150
+
151
+ **Date/Time**
152
+
153
+ Crystalmeta will convert Date/Time/Datetime objects to [iso8601](http://en.wikipedia.org/wiki/ISO_8601) automatically, so if you don't want it, pass them as strings.
154
+
155
+ **Arrays**
156
+
157
+ To maximize [OpenGraph](http://ogp.me/#array) compatibility, Crystalmeta handles Arrays in similar way:
158
+
159
+ ```erb
160
+ <% meta :og => {:image => %w{1.png 2.png}} %>
161
+ <%= meta_tags /og:image/ %>
162
+ ```
163
+
164
+ will display:
165
+
166
+ ```html
167
+ <meta property="og:image" content="/assets/1.png" />
168
+ <meta property="og:image" content="/assets/2.png" />
169
+ ```
170
+
171
+ **Multiple images with properties**
172
+
173
+ If you have multiple images with properties like this:
174
+
175
+ ```html
176
+ <meta property="og:image:url" content="/assets/rock.jpg" />
177
+ <meta property="og:image:width" content="300" />
178
+ <meta property="og:image:height" content="300" />
179
+ <meta property="og:image:url" content="/assets/rock2.jpg" />
180
+ <meta property="og:image:url" content="/assets/rock3.jpg" />
181
+ <meta property="og:image:height" content="1000" />
182
+ ```
183
+
184
+ then you'll have to set them in a special way:
185
+
186
+ ```erb
187
+ <% meta 'og:image' => [
188
+ {:url => 'rock.jpg', :width => 300, :height => 300},
189
+ {:url => 'rock2.jpg'},
190
+ {:url => 'rock3.jpg', :height => 1000},
191
+ ] %>
192
+ ```
193
+
194
+ or:
195
+
196
+ ```yml
197
+ en:
198
+ meta:
199
+ pages:
200
+ show:
201
+ og:image:
202
+ - url: rock.jpg
203
+ width: 300
204
+ height: 300
205
+ - url: rock2.jpg
206
+ - url: rock3.jpg
207
+ height: 1000
208
+ ```
209
+
210
+ It will sort keys so that the `url` property is on top.
@@ -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 'crystal/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "crystalmeta"
8
+ gem.version = Crystal::VERSION
9
+ gem.authors = ["Max Savchenko"]
10
+ gem.email = ["robotector@gmail.com"]
11
+ gem.description = %q{Crystal clean meta tags for Rails applications. Support I18n. Perfect for OpenGraph.}
12
+ gem.summary = %q{Crystalmeta helps you control meta tags through I18n and/or manually. It plays well with OpenGraph.}
13
+ gem.homepage = "http://github.com/macovsky/crystalmeta"
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("rails", ">= 3.0.0")
21
+ gem.add_development_dependency 'rspec-rails', '~> 2.12'
22
+ gem.add_development_dependency 'capybara', '~> 2.0.1'
23
+ end
@@ -0,0 +1,19 @@
1
+ module Crystal
2
+ module ControllerExtensions
3
+ def self.included(base)
4
+ base.before_filter :_set_meta
5
+ base.helper_method :meta
6
+ end
7
+
8
+ def meta(options = {})
9
+ options.present? && @_meta.store(options)
10
+ @_meta
11
+ end
12
+
13
+ protected
14
+
15
+ def _set_meta
16
+ @_meta = Meta.new(OptionsForController.new(self).options)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,52 @@
1
+ module Crystal
2
+ class HashWithStringifyKeys < Hash
3
+ def initialize(constructor = {})
4
+ if constructor.is_a?(Hash)
5
+ super()
6
+ update(constructor).stringify_keys!
7
+ else
8
+ super(constructor)
9
+ end
10
+ end
11
+
12
+ # https://github.com/intridea/hashie/blob/master/lib/hashie/extensions/key_conversion.rb
13
+
14
+ # Convert all keys in the hash to strings.
15
+ # @example
16
+ # test = {:abc => 'def'}
17
+ # test.stringify_keys!
18
+ # test # => {'abc' => 'def'}
19
+ def stringify_keys!
20
+ keys.each do |k|
21
+ stringify_keys_recursively!(self[k])
22
+ self[k.to_s] = self.delete(k)
23
+ end
24
+ self
25
+ end
26
+
27
+ # Return a new hash with all keys converted
28
+ # to strings.
29
+ def stringify_keys
30
+ dup.stringify_keys!
31
+ end
32
+
33
+ protected
34
+
35
+ # Stringify all keys recursively within nested
36
+ # hashes and arrays.
37
+ def stringify_keys_recursively!(object)
38
+ if self.class === object
39
+ object.stringify_keys!
40
+ elsif ::Array === object
41
+ object.each do |i|
42
+ stringify_keys_recursively!(i)
43
+ end
44
+ object
45
+ elsif object.respond_to?(:stringify_keys!)
46
+ object.stringify_keys!
47
+ else
48
+ object
49
+ end
50
+ end
51
+ end
52
+ end