crystalmeta 0.9.0
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 +6 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +130 -0
- data/Rakefile +1 -0
- data/Readme.markdown +210 -0
- data/crystalmeta.gemspec +23 -0
- data/lib/crystal/controller_extensions.rb +19 -0
- data/lib/crystal/hash_with_stringify_keys.rb +52 -0
- data/lib/crystal/interpolates_tags.rb +19 -0
- data/lib/crystal/meta.rb +29 -0
- data/lib/crystal/options_for_controller.rb +52 -0
- data/lib/crystal/railtie.rb +17 -0
- data/lib/crystal/tag.rb +26 -0
- data/lib/crystal/tags.rb +52 -0
- data/lib/crystal/version.rb +3 -0
- data/lib/crystal/view_helpers.rb +13 -0
- data/lib/crystalmeta.rb +24 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +12 -0
- data/spec/dummy/app/assets/stylesheets/application.css +11 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/controllers/movies_controller.rb +5 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.gitkeep +0 -0
- data/spec/dummy/app/models/.gitkeep +0 -0
- data/spec/dummy/app/views/layouts/application.html.erb +11 -0
- data/spec/dummy/app/views/movies/index.html.erb +1 -0
- data/spec/dummy/app/views/movies/show.html.erb +0 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +64 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +67 -0
- data/spec/dummy/config/environments/test.rb +37 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +18 -0
- data/spec/dummy/config/routes.rb +3 -0
- data/spec/dummy/lib/assets/.gitkeep +0 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/features/meta_tags_spec.rb +52 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/support/tag.rb +3 -0
- data/spec/unit/hash_with_stringify_keys_spec.rb +31 -0
- data/spec/unit/meta_spec.rb +41 -0
- data/spec/unit/tag_spec.rb +31 -0
- data/spec/unit/tags_spec.rb +132 -0
- metadata +185 -0
data/.gitignore
ADDED
data/.rspec
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
--color
|
data/Gemfile
ADDED
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.
|
data/crystalmeta.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 '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
|