better_auth 0.2.0 → 0.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/README.md +5 -3
- data/lib/better_auth/adapters/internal_adapter.rb +168 -18
- data/lib/better_auth/adapters/memory.rb +4 -1
- data/lib/better_auth/adapters/mongodb.rb +5 -365
- data/lib/better_auth/adapters/sql.rb +17 -1
- data/lib/better_auth/api.rb +1 -1
- data/lib/better_auth/context.rb +2 -1
- data/lib/better_auth/plugin.rb +14 -1
- data/lib/better_auth/plugins/oauth_protocol.rb +403 -57
- data/lib/better_auth/plugins/organization.rb +5 -0
- data/lib/better_auth/rate_limiter.rb +19 -2
- data/lib/better_auth/router.rb +14 -1
- data/lib/better_auth/routes/email_verification.rb +5 -2
- data/lib/better_auth/routes/password.rb +19 -0
- data/lib/better_auth/routes/session.rb +27 -4
- data/lib/better_auth/routes/sign_in.rb +1 -1
- data/lib/better_auth/routes/sign_up.rb +52 -1
- data/lib/better_auth/routes/social.rb +201 -22
- data/lib/better_auth/routes/user.rb +14 -2
- data/lib/better_auth/schema/sql.rb +11 -0
- data/lib/better_auth/schema.rb +16 -0
- data/lib/better_auth/social_providers/apple.rb +44 -8
- data/lib/better_auth/social_providers/atlassian.rb +32 -0
- data/lib/better_auth/social_providers/base.rb +262 -4
- data/lib/better_auth/social_providers/cognito.rb +32 -0
- data/lib/better_auth/social_providers/discord.rb +27 -5
- data/lib/better_auth/social_providers/dropbox.rb +33 -0
- data/lib/better_auth/social_providers/facebook.rb +35 -0
- data/lib/better_auth/social_providers/figma.rb +31 -0
- data/lib/better_auth/social_providers/github.rb +21 -6
- data/lib/better_auth/social_providers/gitlab.rb +16 -3
- data/lib/better_auth/social_providers/google.rb +38 -13
- data/lib/better_auth/social_providers/huggingface.rb +31 -0
- data/lib/better_auth/social_providers/kakao.rb +32 -0
- data/lib/better_auth/social_providers/kick.rb +32 -0
- data/lib/better_auth/social_providers/line.rb +33 -0
- data/lib/better_auth/social_providers/linear.rb +44 -0
- data/lib/better_auth/social_providers/linkedin.rb +30 -0
- data/lib/better_auth/social_providers/microsoft_entra_id.rb +79 -7
- data/lib/better_auth/social_providers/naver.rb +31 -0
- data/lib/better_auth/social_providers/notion.rb +33 -0
- data/lib/better_auth/social_providers/paybin.rb +31 -0
- data/lib/better_auth/social_providers/paypal.rb +36 -0
- data/lib/better_auth/social_providers/polar.rb +31 -0
- data/lib/better_auth/social_providers/railway.rb +49 -0
- data/lib/better_auth/social_providers/reddit.rb +32 -0
- data/lib/better_auth/social_providers/roblox.rb +31 -0
- data/lib/better_auth/social_providers/salesforce.rb +38 -0
- data/lib/better_auth/social_providers/slack.rb +30 -0
- data/lib/better_auth/social_providers/spotify.rb +31 -0
- data/lib/better_auth/social_providers/tiktok.rb +35 -0
- data/lib/better_auth/social_providers/twitch.rb +39 -0
- data/lib/better_auth/social_providers/twitter.rb +32 -0
- data/lib/better_auth/social_providers/vercel.rb +47 -0
- data/lib/better_auth/social_providers/vk.rb +34 -0
- data/lib/better_auth/social_providers/wechat.rb +104 -0
- data/lib/better_auth/social_providers/zoom.rb +31 -0
- data/lib/better_auth/social_providers.rb +29 -0
- data/lib/better_auth/version.rb +1 -1
- data/lib/better_auth.rb +0 -1
- metadata +30 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: df93cdae06059d52fa2c3d35c5b173aba9025d5ac46bda0b93a7a8abc5045f40
|
|
4
|
+
data.tar.gz: adec802dade2610da15329266c91dcd46aecb8642165c29e364ce7db2829d217
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f46ac8a5cf79a859a417e2cbe60f000c290d014975fb3ce910c49b6c1cd455b2123e436a42a323dd530b38096a4ebc18a1f206c97f0ef6624378c9eb051d37e3
|
|
7
|
+
data.tar.gz: '0469ddcdcad97a7b1b58e3ddc0ef8d54c7469a1e0411546367f829291ab5664fc0b4685d460d8d328fe6ef67543908060d33eb961a638121930154ca31a4cfaf'
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.0] - 2026-04-29
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Added upstream-parity social provider support, including provider-specific authorization, token, profile, refresh, and revocation behavior for the expanded provider set.
|
|
15
|
+
- Added OAuth/OIDC protocol hardening for authorization, callback, discovery, metadata, token, and userinfo flows.
|
|
16
|
+
- Added upstream v1.6.9 parity coverage for schema generation, adapter behavior, plugin hooks, session handling, and account/user route edge cases.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Extracted MongoDB adapter support behind the external `better_auth-mongo-adapter` shim while preserving compatibility for existing adapter configuration.
|
|
21
|
+
- Updated auth routes, router behavior, rate limiting, password and email-verification flows, and schema metadata to match upstream semantics more closely.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- Fixed social provider edge cases, magic-link expiration behavior, adapter value coercion, and callback/session handling across Rack integrations.
|
|
26
|
+
|
|
10
27
|
## [0.1.1] - 2026-03-22
|
|
11
28
|
|
|
12
29
|
### Fixed
|
data/README.md
CHANGED
|
@@ -90,7 +90,7 @@ Custom Better Auth-style password callbacks are still supported through `email_a
|
|
|
90
90
|
|
|
91
91
|
### Database Adapters
|
|
92
92
|
|
|
93
|
-
The core gem ships framework-agnostic adapters for memory, PostgreSQL, MySQL, SQLite,
|
|
93
|
+
The core gem ships framework-agnostic adapters for memory, PostgreSQL, MySQL, SQLite, and MSSQL. Driver gems are loaded only when their adapter is instantiated. MongoDB support lives in the external `better_auth-mongo-adapter` package so apps that do not use MongoDB do not install the Mongo driver.
|
|
94
94
|
|
|
95
95
|
```ruby
|
|
96
96
|
auth = BetterAuth.auth(
|
|
@@ -100,6 +100,8 @@ auth = BetterAuth.auth(
|
|
|
100
100
|
```
|
|
101
101
|
|
|
102
102
|
```ruby
|
|
103
|
+
require "better_auth/mongo_adapter"
|
|
104
|
+
|
|
103
105
|
auth = BetterAuth.auth(
|
|
104
106
|
secret: ENV.fetch("BETTER_AUTH_SECRET"),
|
|
105
107
|
database: BetterAuth::Adapters::MongoDB.new(
|
|
@@ -155,7 +157,7 @@ export const authClient = createAuthClient({
|
|
|
155
157
|
Add to your Gemfile:
|
|
156
158
|
|
|
157
159
|
```ruby
|
|
158
|
-
gem
|
|
160
|
+
gem "better_auth-rails"
|
|
159
161
|
```
|
|
160
162
|
|
|
161
163
|
Then in your ApplicationController:
|
|
@@ -170,7 +172,7 @@ Now you have access to `current_user` and authentication methods:
|
|
|
170
172
|
|
|
171
173
|
```ruby
|
|
172
174
|
class PostsController < ApplicationController
|
|
173
|
-
before_action :
|
|
175
|
+
before_action :require_authentication
|
|
174
176
|
|
|
175
177
|
def index
|
|
176
178
|
@posts = current_user.posts
|
|
@@ -74,28 +74,31 @@ module BetterAuth
|
|
|
74
74
|
"userId" => user_id,
|
|
75
75
|
"token" => token
|
|
76
76
|
}.merge(timestamps)
|
|
77
|
+
base["id"] = generated_id if secondary_storage
|
|
77
78
|
data = override_all ? base.merge(override) : override.merge(base)
|
|
78
79
|
|
|
79
80
|
custom = secondary_storage && lambda do |session_data|
|
|
80
81
|
actual_session = apply_schema_create("session", session_data)
|
|
81
82
|
store_session(actual_session)
|
|
83
|
+
adapter.create(model: "session", data: actual_session, force_allow_id: true) if options.session[:store_session_in_database]
|
|
82
84
|
actual_session
|
|
83
85
|
end
|
|
84
|
-
|
|
85
|
-
created = hooks.create(data, "session", custom: custom, context: context)
|
|
86
|
-
adapter.create(model: "session", data: data, force_allow_id: true) if secondary_storage && execute_main
|
|
87
|
-
created
|
|
86
|
+
hooks.create(data, "session", custom: custom, context: context)
|
|
88
87
|
end
|
|
89
88
|
|
|
90
89
|
def find_session(token)
|
|
91
90
|
if secondary_storage
|
|
92
91
|
data = parse_storage(secondary_storage.get(token))
|
|
93
|
-
|
|
92
|
+
unless data
|
|
93
|
+
return nil unless options.session[:store_session_in_database] && !options.session[:preserve_session_in_database]
|
|
94
|
+
end
|
|
94
95
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
if data
|
|
97
|
+
return {
|
|
98
|
+
session: normalize_session_dates(data["session"]),
|
|
99
|
+
user: normalize_user_dates(data["user"])
|
|
100
|
+
}
|
|
101
|
+
end
|
|
99
102
|
end
|
|
100
103
|
|
|
101
104
|
found = find_session_with_user(token)
|
|
@@ -113,7 +116,9 @@ module BetterAuth
|
|
|
113
116
|
data = stringify_keys(session)
|
|
114
117
|
if secondary_storage
|
|
115
118
|
return hooks.update(data, [{field: "token", value: token}], "session", custom: lambda { |actual_data|
|
|
116
|
-
update_stored_session(token, actual_data)
|
|
119
|
+
stored = update_stored_session(token, actual_data)
|
|
120
|
+
db = adapter.update(model: "session", where: [{field: "token", value: token}], update: actual_data) if options.session[:store_session_in_database]
|
|
121
|
+
db || stored
|
|
117
122
|
})
|
|
118
123
|
end
|
|
119
124
|
|
|
@@ -226,30 +231,93 @@ module BetterAuth
|
|
|
226
231
|
end
|
|
227
232
|
|
|
228
233
|
def create_verification_value(data)
|
|
229
|
-
|
|
234
|
+
payload = timestamps.merge(stringify_keys(data))
|
|
235
|
+
stored_identifier = processed_verification_identifier(payload.fetch("identifier"))
|
|
236
|
+
payload["identifier"] = stored_identifier
|
|
237
|
+
|
|
238
|
+
custom = secondary_storage && lambda do |verification_data|
|
|
239
|
+
actual = apply_schema_create("verification", verification_data)
|
|
240
|
+
actual["id"] ||= generated_id
|
|
241
|
+
store_verification(actual)
|
|
242
|
+
adapter.create(model: "verification", data: actual, force_allow_id: true) if verification_store_in_database?
|
|
243
|
+
actual
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
hooks.create(payload, "verification", custom: custom)
|
|
230
247
|
end
|
|
231
248
|
|
|
232
249
|
def find_verification_value(identifier)
|
|
250
|
+
stored_identifier = processed_verification_identifier(identifier)
|
|
251
|
+
storage_option = verification_storage_option(identifier)
|
|
252
|
+
if secondary_storage
|
|
253
|
+
cached = read_verification(stored_identifier)
|
|
254
|
+
cached ||= read_verification(identifier) if storage_option && storage_option.to_s != "plain"
|
|
255
|
+
return cached if cached
|
|
256
|
+
return nil unless verification_store_in_database?
|
|
257
|
+
end
|
|
258
|
+
|
|
233
259
|
values = adapter.find_many(
|
|
234
260
|
model: "verification",
|
|
235
|
-
where: [{field: "identifier", value:
|
|
261
|
+
where: [{field: "identifier", value: stored_identifier}],
|
|
236
262
|
sort_by: {field: "createdAt", direction: "desc"},
|
|
237
263
|
limit: 1
|
|
238
264
|
)
|
|
265
|
+
if values.empty? && storage_option && storage_option.to_s != "plain"
|
|
266
|
+
values = adapter.find_many(
|
|
267
|
+
model: "verification",
|
|
268
|
+
where: [{field: "identifier", value: identifier}],
|
|
269
|
+
sort_by: {field: "createdAt", direction: "desc"},
|
|
270
|
+
limit: 1
|
|
271
|
+
)
|
|
272
|
+
end
|
|
239
273
|
hooks.delete_many([{field: "expiresAt", value: Time.now, operator: "lt"}], "verification") unless options.verification[:disable_cleanup]
|
|
240
274
|
values.first
|
|
241
275
|
end
|
|
242
276
|
|
|
243
277
|
def delete_verification_value(id)
|
|
278
|
+
if secondary_storage
|
|
279
|
+
stored_identifier = secondary_storage.get(verification_id_key(id))
|
|
280
|
+
if stored_identifier
|
|
281
|
+
secondary_storage.delete(verification_key(stored_identifier))
|
|
282
|
+
secondary_storage.delete(verification_id_key(id))
|
|
283
|
+
return nil unless verification_store_in_database?
|
|
284
|
+
elsif !verification_store_in_database?
|
|
285
|
+
return nil
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
244
289
|
hooks.delete([{field: "id", value: id}], "verification")
|
|
245
290
|
end
|
|
246
291
|
|
|
247
292
|
def delete_verification_by_identifier(identifier)
|
|
248
|
-
|
|
293
|
+
stored_identifier = processed_verification_identifier(identifier)
|
|
294
|
+
if secondary_storage
|
|
295
|
+
cached = read_verification(stored_identifier)
|
|
296
|
+
secondary_storage.delete(verification_key(stored_identifier))
|
|
297
|
+
secondary_storage.delete(verification_id_key(cached["id"])) if cached && cached["id"]
|
|
298
|
+
return nil unless verification_store_in_database?
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
hooks.delete([{field: "identifier", value: stored_identifier}], "verification")
|
|
249
302
|
end
|
|
250
303
|
|
|
251
304
|
def update_verification_value(id, data)
|
|
252
|
-
|
|
305
|
+
update = stringify_keys(data)
|
|
306
|
+
if secondary_storage
|
|
307
|
+
stored_identifier = secondary_storage.get(verification_id_key(id))
|
|
308
|
+
if stored_identifier
|
|
309
|
+
cached = read_verification(stored_identifier)
|
|
310
|
+
if cached
|
|
311
|
+
updated = cached.merge(update)
|
|
312
|
+
store_verification(updated)
|
|
313
|
+
return updated unless verification_store_in_database?
|
|
314
|
+
end
|
|
315
|
+
elsif !verification_store_in_database?
|
|
316
|
+
return nil
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
hooks.update(update, [{field: "id", value: id}], "verification")
|
|
253
321
|
end
|
|
254
322
|
|
|
255
323
|
private
|
|
@@ -285,6 +353,14 @@ module BetterAuth
|
|
|
285
353
|
{"createdAt" => now, "updatedAt" => now}
|
|
286
354
|
end
|
|
287
355
|
|
|
356
|
+
def generated_id
|
|
357
|
+
generator = options.advanced.dig(:database, :generate_id)
|
|
358
|
+
return generator.call.to_s if generator.respond_to?(:call)
|
|
359
|
+
return SecureRandom.uuid if generator == "uuid"
|
|
360
|
+
|
|
361
|
+
SecureRandom.hex(16)
|
|
362
|
+
end
|
|
363
|
+
|
|
288
364
|
def stringify_keys(data)
|
|
289
365
|
data.each_with_object({}) do |(key, value), result|
|
|
290
366
|
result[Schema.storage_key(key)] = value
|
|
@@ -295,6 +371,8 @@ module BetterAuth
|
|
|
295
371
|
fields = Schema.auth_tables(options)[model]&.fetch(:fields)
|
|
296
372
|
fields ||= session_additional_fields if model == "session"
|
|
297
373
|
output = stringify_keys(data)
|
|
374
|
+
return output unless fields
|
|
375
|
+
|
|
298
376
|
fields.each do |field, attributes|
|
|
299
377
|
unless output.key?(field)
|
|
300
378
|
if attributes.key?(:default_value)
|
|
@@ -334,7 +412,8 @@ module BetterAuth
|
|
|
334
412
|
.push({"token" => session["token"], "expiresAt" => expires_ms})
|
|
335
413
|
.sort_by { |entry| entry["expiresAt"] }
|
|
336
414
|
write_active_sessions(session["userId"], entries)
|
|
337
|
-
|
|
415
|
+
ttl_seconds = ttl(expires_ms)
|
|
416
|
+
secondary_storage.set(session["token"], JSON.generate({session: session, user: user}), ttl_seconds) if ttl_seconds.positive?
|
|
338
417
|
end
|
|
339
418
|
|
|
340
419
|
def update_stored_session(token, data)
|
|
@@ -345,7 +424,12 @@ module BetterAuth
|
|
|
345
424
|
merged["expiresAt"] = normalize_time(merged["expiresAt"])
|
|
346
425
|
merged["createdAt"] = normalize_time(merged["createdAt"])
|
|
347
426
|
merged["updatedAt"] = normalize_time(merged["updatedAt"])
|
|
348
|
-
|
|
427
|
+
ttl_seconds = ttl(millis(merged["expiresAt"]))
|
|
428
|
+
if ttl_seconds.positive?
|
|
429
|
+
secondary_storage.set(token, JSON.generate({session: merged, user: parsed["user"]}), ttl_seconds)
|
|
430
|
+
else
|
|
431
|
+
secondary_storage.delete(token)
|
|
432
|
+
end
|
|
349
433
|
entries = active_session_entries(merged["userId"])
|
|
350
434
|
.reject { |entry| entry["token"] == token || entry["expiresAt"].to_i <= current_millis }
|
|
351
435
|
.push({"token" => token, "expiresAt" => millis(merged["expiresAt"])})
|
|
@@ -369,7 +453,7 @@ module BetterAuth
|
|
|
369
453
|
raw = secondary_storage.get(active_key(user_id))
|
|
370
454
|
Array(parse_storage(raw)).map do |entry|
|
|
371
455
|
entry.transform_keys(&:to_s)
|
|
372
|
-
end
|
|
456
|
+
end.uniq { |entry| entry["token"] }
|
|
373
457
|
end
|
|
374
458
|
|
|
375
459
|
def write_active_sessions(user_id, entries)
|
|
@@ -377,7 +461,12 @@ module BetterAuth
|
|
|
377
461
|
if future.empty?
|
|
378
462
|
secondary_storage.delete(active_key(user_id))
|
|
379
463
|
else
|
|
380
|
-
|
|
464
|
+
ttl_seconds = ttl(future.last["expiresAt"])
|
|
465
|
+
if ttl_seconds.positive?
|
|
466
|
+
secondary_storage.set(active_key(user_id), JSON.generate(future), ttl_seconds)
|
|
467
|
+
else
|
|
468
|
+
secondary_storage.delete(active_key(user_id))
|
|
469
|
+
end
|
|
381
470
|
end
|
|
382
471
|
end
|
|
383
472
|
|
|
@@ -415,6 +504,67 @@ module BetterAuth
|
|
|
415
504
|
)
|
|
416
505
|
end
|
|
417
506
|
|
|
507
|
+
def normalize_verification_dates(verification)
|
|
508
|
+
return nil unless verification
|
|
509
|
+
|
|
510
|
+
verification.transform_keys(&:to_s).merge(
|
|
511
|
+
"expiresAt" => normalize_time(verification["expiresAt"] || verification[:expiresAt]),
|
|
512
|
+
"createdAt" => normalize_time(verification["createdAt"] || verification[:createdAt]),
|
|
513
|
+
"updatedAt" => normalize_time(verification["updatedAt"] || verification[:updatedAt])
|
|
514
|
+
)
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
def store_verification(verification)
|
|
518
|
+
normalized = normalize_verification_dates(verification)
|
|
519
|
+
ttl_seconds = ttl(millis(normalized["expiresAt"]))
|
|
520
|
+
return normalized unless ttl_seconds.positive?
|
|
521
|
+
|
|
522
|
+
secondary_storage.set(verification_key(normalized["identifier"]), JSON.generate(normalized), ttl_seconds)
|
|
523
|
+
secondary_storage.set(verification_id_key(normalized["id"]), normalized["identifier"], ttl_seconds) if normalized["id"]
|
|
524
|
+
normalized
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
def read_verification(identifier)
|
|
528
|
+
normalize_verification_dates(parse_storage(secondary_storage.get(verification_key(identifier))))
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
def verification_key(identifier)
|
|
532
|
+
"verification:#{identifier}"
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
def verification_id_key(id)
|
|
536
|
+
"verification-id:#{id}"
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
def verification_store_in_database?
|
|
540
|
+
!!options.verification[:store_in_database]
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
def processed_verification_identifier(identifier)
|
|
544
|
+
option = verification_storage_option(identifier)
|
|
545
|
+
return identifier.to_s if option.nil? || option.to_s == "plain"
|
|
546
|
+
return Crypto.sha256(identifier.to_s, encoding: :base64url) if option.to_s == "hashed"
|
|
547
|
+
return option[:hash].call(identifier.to_s).to_s if option.is_a?(Hash) && option[:hash].respond_to?(:call)
|
|
548
|
+
return option["hash"].call(identifier.to_s).to_s if option.is_a?(Hash) && option["hash"].respond_to?(:call)
|
|
549
|
+
|
|
550
|
+
identifier.to_s
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
def verification_storage_option(identifier)
|
|
554
|
+
config = options.verification[:store_identifier]
|
|
555
|
+
return nil unless config
|
|
556
|
+
|
|
557
|
+
if config.is_a?(Hash) && (config.key?(:default) || config.key?("default"))
|
|
558
|
+
overrides = config[:overrides] || config["overrides"] || {}
|
|
559
|
+
overrides.each do |prefix, option|
|
|
560
|
+
return option if identifier.to_s.start_with?(prefix.to_s)
|
|
561
|
+
end
|
|
562
|
+
return config[:default] || config["default"]
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
config
|
|
566
|
+
end
|
|
567
|
+
|
|
418
568
|
def normalize_time(value)
|
|
419
569
|
return value if value.is_a?(Time)
|
|
420
570
|
return Time.at(value / 1000.0) if value.is_a?(Integer) && value > 10_000_000_000
|
|
@@ -225,7 +225,10 @@ module BetterAuth
|
|
|
225
225
|
end
|
|
226
226
|
|
|
227
227
|
def fetch_key(hash, key)
|
|
228
|
-
|
|
228
|
+
[key, key.to_s, Schema.storage_key(key), Schema.storage_key(key).to_sym].each do |candidate|
|
|
229
|
+
return hash[candidate] if hash.key?(candidate)
|
|
230
|
+
end
|
|
231
|
+
nil
|
|
229
232
|
end
|
|
230
233
|
end
|
|
231
234
|
end
|