better_auth-mongo-adapter 0.6.2 → 0.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c3abe1d68b1d2865e2f74cefbcd53ee67fd1f2384b20a20db6532d7fe3fc4c96
4
- data.tar.gz: 6e8b7e4e3ad10076e7cc2ad9f8b9982be4d355e64dcac381c6a7c6624e2ed369
3
+ metadata.gz: 86afd33544ccb8e9fff5ccfa4e055cd31007086648c7985f1e9b64f40176f68e
4
+ data.tar.gz: 953a82389a1bbc913d702396fe0c4a66abcf79f042d4a5fb6bcd36eecaf233b3
5
5
  SHA512:
6
- metadata.gz: 369ce690cbe825954bd2e716d32ec6dac751d18e766a4656e2d904e834e85f4fbab26a0b77dc939d7c6e52648aba18866adcf01cde4e9935a60583ca7ebd6a52
7
- data.tar.gz: 32e903a2de49c6b6718c280e7d185dbc4d6d43a22349cd960b3e219a4b6154efd402e6e3a1cc5493460d12dd9062083de005bf9a5ee4a00aabd67ef7535513fe
6
+ metadata.gz: c67b4588a64b8fbb648ee5ec5fe029a67a6cd56c8c7ef35a48aacad33abecdff31f91eef0c2d110295c622cd877cbdb783a6583337c6bad0ce77985c2bdae34b
7
+ data.tar.gz: 86ab56e50db549d6ad11e28b06282376581651ecbdc540f15c5e2c114fab2a55ae8c21c9a7b11178826b78d9edce7a69875459c8be77a096dafec831405e6998
data/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - Deprecated this package in favor of `better_auth-mongodb`.
6
+ - Kept `require "better_auth/mongo_adapter"` as a compatibility entrypoint.
7
+
8
+ ## 0.7.0 - 2026-05-05
9
+
10
+ - Added explicit `ensure_indexes!` setup helper for Mongo indexes derived from Better Auth schema metadata.
11
+ - Updated MongoDB setup docs to use the lambda adapter form, clearer standalone/replica-set transaction guidance, and production index guidance.
12
+ - Consolidated Mongo fake test support and strengthened transaction rollback coverage for staged mutations.
13
+ - Apply `advanced[:database][:default_find_many_limit]` to uncapped `find_many` calls and one-to-many Mongo `$lookup` joins, defaulting to 100 when omitted.
14
+ - Match upstream Mongo where-clause semantics for mixed connectors by bucketing multi-clause filters into `$and` and `$or` arrays instead of left-fold nesting.
15
+ - Allow scalar values for `in` and `not_in` filters as an intentional Ruby adapter-family adaptation.
16
+
5
17
  ## 0.1.1 - 2026-04-30
6
18
 
7
19
  - Fixed inferred limited joins so explicit relation and limit configuration is preserved.
data/README.md CHANGED
@@ -1,6 +1,20 @@
1
1
  # better_auth-mongo-adapter
2
2
 
3
- MongoDB database adapter package for Better Auth Ruby.
3
+ Deprecated compatibility package for Better Auth Ruby MongoDB support.
4
+
5
+ New applications should use `better_auth-mongodb`:
6
+
7
+ ```ruby
8
+ gem "better_auth-mongodb"
9
+ ```
10
+
11
+ ```ruby
12
+ require "better_auth/mongodb"
13
+ ```
14
+
15
+ This package remains available so existing applications that install
16
+ `better_auth-mongo-adapter` and require `better_auth/mongo_adapter` continue to
17
+ load the same adapter.
4
18
 
5
19
  ## Installation
6
20
 
@@ -18,16 +32,85 @@ mongo_client = Mongo::Client.new(ENV.fetch("BETTER_AUTH_MONGODB_URL"))
18
32
 
19
33
  auth = BetterAuth.auth(
20
34
  secret: ENV.fetch("BETTER_AUTH_SECRET"),
21
- database: BetterAuth::Adapters::MongoDB.new(
22
- database: mongo_client.database,
23
- client: mongo_client,
24
- transaction: false
25
- )
35
+ database: ->(options) {
36
+ BetterAuth::Adapters::MongoDB.new(
37
+ options,
38
+ database: mongo_client.database,
39
+ client: mongo_client,
40
+ transaction: false
41
+ )
42
+ }
26
43
  )
27
44
  ```
28
45
 
46
+ The lambda form lets Better Auth pass the final configuration into the adapter,
47
+ including plugins, custom schemas, and advanced database options.
48
+
29
49
  ## Notes
30
50
 
31
51
  This package depends on the official `mongo` gem. Keeping MongoDB support outside `better_auth` avoids installing MongoDB client dependencies for applications that only use SQL, Rails, Hanami, or in-memory storage.
32
52
 
33
53
  The adapter stores Better Auth models in singular MongoDB collections by default, maps logical `id` values to Mongo `_id`, converts ObjectId-compatible ids through the Mongo driver, and supports the shared Better Auth database adapter contract.
54
+
55
+ Transactions are deployment-dependent. MongoDB multi-document transactions may
56
+ be unavailable on standalone servers and usually require a replica set plus
57
+ compatible driver/session settings. The setup example uses `transaction: false`;
58
+ enable transactions only when the MongoDB deployment supports them.
59
+
60
+ When using a replica set, remove `transaction: false` or pass
61
+ `transaction: true`. When using standalone local MongoDB, keep
62
+ `transaction: false`.
63
+
64
+ ## Indexes
65
+
66
+ MongoDB does not run SQL-style migrations. The adapter can create recommended
67
+ indexes from Better Auth schema metadata, but this is an explicit setup step:
68
+
69
+ ```ruby
70
+ adapter = BetterAuth::Adapters::MongoDB.new(
71
+ options,
72
+ database: mongo_client.database,
73
+ client: mongo_client,
74
+ transaction: false
75
+ )
76
+
77
+ adapter.ensure_indexes!
78
+ ```
79
+
80
+ `ensure_indexes!` creates indexes for schema fields marked `unique: true` or
81
+ `index: true`, including plugin schemas and custom model or field names. It
82
+ skips Mongo `_id` because MongoDB creates that index automatically. The method
83
+ returns a summary of requested indexes so deployment scripts can log what was
84
+ applied.
85
+
86
+ ## Limits
87
+
88
+ By default, `find_many` calls without an explicit `limit:` are capped at 100 records. Configure the default with Better Auth's advanced database option:
89
+
90
+ ```ruby
91
+ auth = BetterAuth.auth(
92
+ secret: ENV.fetch("BETTER_AUTH_SECRET"),
93
+ advanced: {
94
+ database: {
95
+ default_find_many_limit: 250
96
+ }
97
+ },
98
+ database: ->(options) {
99
+ BetterAuth::Adapters::MongoDB.new(
100
+ options,
101
+ database: mongo_client.database,
102
+ client: mongo_client,
103
+ transaction: false
104
+ )
105
+ }
106
+ )
107
+ ```
108
+
109
+ The same default applies to one-to-many join lookups when the join config does not set `limit:`. Passing an explicit `limit:` to `find_many` or to the join config overrides the default.
110
+
111
+ One-to-one joins ignore one-to-many limits. They are returned as a single object or `nil`.
112
+
113
+ Ruby's adapters accept scalar values for `in` and `not_in` filters and coerce
114
+ them to a one-element list. This is an intentional Ruby adapter-family behavior;
115
+ upstream's TypeScript adapter factory is stricter before the Mongo adapter sees
116
+ the query.
@@ -2,6 +2,6 @@
2
2
 
3
3
  module BetterAuth
4
4
  module MongoAdapter
5
- VERSION = "0.6.2"
5
+ VERSION = "0.8.0"
6
6
  end
7
7
  end
@@ -1,641 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "better_auth"
4
- require "mongo"
5
- require "securerandom"
6
- require "time"
3
+ warn "The better_auth-mongo-adapter gem is deprecated; use better_auth-mongodb and require \"better_auth/mongodb\" instead.", uplevel: 1
7
4
 
8
- module BetterAuth
9
- module Adapters
10
- class MongoDB < Base
11
- class MongoAdapterError < Error
12
- attr_reader :code
13
-
14
- def initialize(code, message)
15
- @code = code
16
- super(message)
17
- end
18
- end
19
-
20
- attr_reader :database, :client, :use_plural
21
-
22
- def initialize(options = nil, database:, client: nil, transaction: nil, use_plural: false, session: nil)
23
- require "mongo" unless database
24
-
25
- super(options || Configuration.new(secret: Configuration::DEFAULT_SECRET, database: :memory))
26
- @database = database
27
- @client = client
28
- @transaction_enabled = transaction.nil? ? !client.nil? : !!transaction
29
- @use_plural = !!use_plural
30
- @session = session
31
- end
32
-
33
- def create(model:, data:, force_allow_id: false)
34
- model = model.to_s
35
- record = transform_input(model, data, "create", force_allow_id)
36
- document = to_document(model, record)
37
- collection_for(model).insert_one(document, session_options)
38
- from_document(model, document)
39
- end
40
-
41
- def find_one(model:, where: [], select: nil, join: nil)
42
- find_many(model: model, where: where, select: select, join: join, limit: 1).first
43
- end
44
-
45
- def find_many(model:, where: [], sort_by: nil, limit: nil, offset: nil, select: nil, join: nil)
46
- model = model.to_s
47
- pipeline = [{"$match" => mongo_filter(model, where || [])}]
48
- pipeline.concat(join_stages(model, join)) if join
49
- pipeline << {"$project" => projection_for(model, select, join)} if select && !select.empty?
50
- pipeline << {"$sort" => {sort_field(model, sort_by) => sort_direction(sort_by)}} if sort_by
51
- pipeline << {"$skip" => offset.to_i} if offset
52
- pipeline << {"$limit" => limit.to_i} if limit
53
-
54
- collection_for(model)
55
- .aggregate(pipeline, session_options)
56
- .to_a
57
- .map { |document| from_document(model, stringify_document(document), join: join) }
58
- end
59
-
60
- def update(model:, where:, update:)
61
- model = model.to_s
62
- data = transform_input(model, update, "update", true)
63
- document = to_document(model, data)
64
- document.delete("_id")
65
- result = collection_for(model).find_one_and_update(
66
- mongo_filter(model, where || []),
67
- {"$set" => document},
68
- session_options.merge(return_document: :after)
69
- )
70
- result = unwrap_update_result(result)
71
- result ? from_document(model, stringify_document(result)) : nil
72
- end
73
-
74
- def update_many(model:, where:, update:)
75
- model = model.to_s
76
- data = transform_input(model, update, "update", true)
77
- document = to_document(model, data)
78
- document.delete("_id")
79
- result = collection_for(model).update_many(
80
- mongo_filter(model, where || []),
81
- {"$set" => document},
82
- session_options
83
- )
84
- result.respond_to?(:modified_count) ? result.modified_count : result.to_i
85
- end
86
-
87
- def delete(model:, where:)
88
- collection_for(model.to_s).delete_one(mongo_filter(model.to_s, where || []), session_options)
89
- nil
90
- end
91
-
92
- def delete_many(model:, where:)
93
- result = collection_for(model.to_s).delete_many(mongo_filter(model.to_s, where || []), session_options)
94
- result.respond_to?(:deleted_count) ? result.deleted_count : result.to_i
95
- end
96
-
97
- def count(model:, where: nil)
98
- pipeline = [
99
- {"$match" => mongo_filter(model.to_s, where || [])},
100
- {"$count" => "total"}
101
- ]
102
- row = collection_for(model.to_s).aggregate(pipeline, session_options).to_a.first
103
- return 0 unless row
104
-
105
- (row["total"] || row[:total] || 0).to_i
106
- end
107
-
108
- def transaction
109
- return yield self unless client && @transaction_enabled && client.respond_to?(:start_session)
110
-
111
- session = client.start_session
112
- begin
113
- session.start_transaction
114
- adapter = self.class.new(options, database: database, client: client, transaction: @transaction_enabled, use_plural: use_plural, session: session)
115
- result = yield adapter
116
- session.commit_transaction
117
- result
118
- rescue
119
- session.abort_transaction
120
- raise
121
- ensure
122
- session.end_session
123
- end
124
- end
125
-
126
- private
127
-
128
- def transform_input(model, data, action, force_allow_id)
129
- fields = fields_for(model)
130
- input = stringify_keys(data)
131
- output = {}
132
-
133
- fields.each do |field, attributes|
134
- next if field == "id" && input.key?(field) && !force_allow_id
135
-
136
- value_provided = input.key?(field)
137
- value = input[field]
138
- if value_provided && attributes[:input] == false && value && !force_allow_id
139
- raise APIError.new("BAD_REQUEST", message: "#{field} is not allowed to be set")
140
- end
141
-
142
- if !value_provided && action == "create" && attributes.key?(:default_value)
143
- value = resolve_default(attributes[:default_value])
144
- value_provided = true
145
- elsif !value_provided && action == "update" && attributes[:on_update]
146
- value = resolve_default(attributes[:on_update])
147
- value_provided = true
148
- end
149
-
150
- if !value_provided && action == "create" && attributes[:required]
151
- raise APIError.new("BAD_REQUEST", message: "#{field} is required") unless field == "id"
152
- end
153
- output[field] = coerce_value(value, attributes) if value_provided
154
- end
155
-
156
- output["id"] = generated_id if action == "create" && !output.key?("id")
157
- output
158
- end
159
-
160
- def mongo_filter(model, where)
161
- clauses = Array(where)
162
- return {} if clauses.empty?
163
-
164
- clauses.each_with_index.reduce(nil) do |filter, (clause, index)|
165
- condition = condition_for(model, clause)
166
- next condition if index.zero?
167
-
168
- if fetch_key(clause, :connector).to_s.upcase == "OR"
169
- {"$or" => [filter, condition]}
170
- else
171
- {"$and" => [filter, condition]}
172
- end
173
- end
174
- end
175
-
176
- def condition_for(model, clause)
177
- operator = (fetch_key(clause, :operator) || "eq").to_s.downcase
178
- value = fetch_key(clause, :value)
179
- if operator == "in" && !value.is_a?(Array)
180
- raise MongoAdapterError.new("UNSUPPORTED_OPERATOR", "Value must be an array")
181
- end
182
-
183
- field = resolve_field(model, fetch_key(clause, :field))
184
- attributes = fields_for(model).fetch(field)
185
- key = (field == "id") ? "_id" : storage_field(model, field)
186
- mode = (fetch_key(clause, :mode) || "sensitive").to_s
187
- id_field = id_field?(field, attributes)
188
- insensitive = !id_field && mode == "insensitive" && insensitive_value?(value)
189
- value = coerce_where_value(value, attributes)
190
-
191
- case operator
192
- when "eq"
193
- (insensitive && value.is_a?(String)) ? regex_condition(key, value, :eq, insensitive: true) : {key => store_value(field, value, attributes, strict_id: true)}
194
- when "in"
195
- (insensitive && value.is_a?(Array)) ? insensitive_in_condition(key, value) : {key => {"$in" => Array(value).map { |entry| store_value(field, entry, attributes, strict_id: true) }}}
196
- when "not_in"
197
- (insensitive && value.is_a?(Array)) ? insensitive_not_in_condition(key, value) : {key => {"$nin" => Array(value).map { |entry| store_value(field, entry, attributes, strict_id: true) }}}
198
- when "ne"
199
- (insensitive && value.is_a?(String)) ? {key => {"$not" => regex_for(value, :eq, insensitive: true)}} : {key => {"$ne" => store_value(field, value, attributes, strict_id: true)}}
200
- when "gt", "gte", "lt", "lte"
201
- {key => {"$#{operator}" => store_value(field, value, attributes, strict_id: true)}}
202
- when "contains", "starts_with", "ends_with"
203
- regex_condition(key, value.to_s, operator.to_sym, insensitive: insensitive)
204
- else
205
- raise MongoAdapterError.new("UNSUPPORTED_OPERATOR", "Unsupported operator: #{operator}")
206
- end
207
- end
208
-
209
- def insensitive_value?(value)
210
- value.is_a?(String) || (value.is_a?(Array) && value.all? { |entry| entry.is_a?(String) })
211
- end
212
-
213
- def insensitive_in_condition(key, values)
214
- return {"$expr" => {"$eq" => [1, 0]}} if values.empty?
215
-
216
- {"$or" => values.map { |value| regex_condition(key, value, :eq, insensitive: true) }}
217
- end
218
-
219
- def insensitive_not_in_condition(key, values)
220
- return {} if values.empty?
221
-
222
- {"$nor" => values.map { |value| regex_condition(key, value, :eq, insensitive: true) }}
223
- end
224
-
225
- def regex_condition(key, value, operator, insensitive:)
226
- {key => regex_for(value, operator, insensitive: insensitive)}
227
- end
228
-
229
- def regex_for(value, operator, insensitive:)
230
- escaped = Regexp.escape(value.to_s[0, 256])
231
- pattern = case operator.to_s
232
- when "eq" then "\\A#{escaped}\\z"
233
- when "starts_with" then "\\A#{escaped}"
234
- when "ends_with" then "#{escaped}\\z"
235
- else escaped
236
- end
237
- Regexp.new(pattern, insensitive ? Regexp::IGNORECASE : nil)
238
- end
239
-
240
- def join_stages(model, join)
241
- normalized_join(model, join).flat_map do |join_model, config|
242
- local_field = storage_field_for_join(model, config.fetch(:from))
243
- foreign_field = storage_field_for_join(join_model, config.fetch(:to))
244
- relation = config[:relation]
245
- limit = config[:limit]
246
- unique = relation == "one-to-one" || config[:unique]
247
- should_limit = !unique && limit && limit.to_i.positive?
248
-
249
- lookup = if should_limit
250
- {
251
- "$lookup" => {
252
- "from" => collection_name(join_model),
253
- "let" => {"localFieldValue" => "$#{local_field}"},
254
- "pipeline" => [
255
- {"$match" => {"$expr" => {"$eq" => ["$#{foreign_field}", "$$localFieldValue"]}}},
256
- {"$limit" => limit.to_i}
257
- ],
258
- "as" => join_model
259
- }
260
- }
261
- else
262
- {
263
- "$lookup" => {
264
- "from" => collection_name(join_model),
265
- "localField" => local_field,
266
- "foreignField" => foreign_field,
267
- "as" => join_model
268
- }
269
- }
270
- end
271
-
272
- unique ? [lookup, {"$unwind" => {"path" => "$#{join_model}", "preserveNullAndEmptyArrays" => true}}] : [lookup]
273
- end
274
- end
275
-
276
- def normalized_join(model, join)
277
- join.each_with_object({}) do |(join_model, config), result|
278
- join_model = join_model.to_s
279
- result[join_model] = normalize_join_config(model, join_model, config)
280
- end
281
- end
282
-
283
- def normalize_join_config(model, join_model, config)
284
- if config.is_a?(Hash) && (config.key?(:on) || config.key?("on"))
285
- on = config[:on] || config["on"]
286
- relation = config[:relation] || config["relation"]
287
- limit = config[:limit] || config["limit"]
288
- from = fetch_key(on, :from)
289
- to = fetch_key(on, :to)
290
- return {from: Schema.storage_key(from), to: Schema.storage_key(to), relation: relation, limit: limit, unique: unique_join_field?(join_model, to)}
291
- end
292
-
293
- inferred = inferred_join_config(model, join_model)
294
- if config.is_a?(Hash)
295
- limit = config[:limit] || config["limit"]
296
- relation = config[:relation] || config["relation"]
297
- inferred = inferred.merge(limit: limit) if limit
298
- inferred = inferred.merge(relation: relation) if relation
299
- end
300
- inferred
301
- end
302
-
303
- def inferred_join_config(model, join_model)
304
- base_model = default_model_name(model)
305
- target_model = default_model_name(join_model)
306
- foreign_keys = fields_for(target_model).select do |_field, attributes|
307
- reference_model_matches?(attributes, base_model)
308
- end
309
- forward_join = true
310
-
311
- if foreign_keys.empty?
312
- foreign_keys = fields_for(base_model).select do |_field, attributes|
313
- reference_model_matches?(attributes, target_model)
314
- end
315
- forward_join = false
316
- end
317
-
318
- if foreign_keys.empty?
319
- raise Error, "No foreign key found for model #{join_model} and base model #{model} while performing join operation."
320
- end
321
- if foreign_keys.length > 1
322
- raise Error, "Multiple foreign keys found for model #{join_model} and base model #{model} while performing join operation. Only one foreign key is supported."
323
- end
324
-
325
- foreign_key, attributes = foreign_keys.first
326
- reference = attributes.fetch(:references)
327
- if forward_join
328
- unique = attributes[:unique] == true
329
- {from: reference.fetch(:field).to_s, to: foreign_key, relation: unique ? "one-to-one" : "one-to-many", unique: unique}
330
- else
331
- {from: foreign_key, to: reference.fetch(:field).to_s, relation: "one-to-one", unique: true}
332
- end
333
- end
334
-
335
- def reference_model_matches?(attributes, model)
336
- reference = attributes[:references]
337
- return false unless reference
338
-
339
- default_model_name(reference[:model] || reference["model"]) == model
340
- end
341
-
342
- def unique_join_field?(model, field)
343
- field = resolve_field(model, field)
344
- field == "id" || fields_for(model).dig(field, :unique) == true
345
- end
346
-
347
- def storage_field_for_join(model, field)
348
- field = resolve_field(model, field)
349
- (field == "id") ? "_id" : storage_field(model, field)
350
- end
351
-
352
- def projection_for(model, select, join)
353
- selected_fields = Array(select).map { |field| storage_field_for_join(model, field) }
354
- Array(select).each_with_object({}) do |field, projection|
355
- projection[storage_field_for_join(model, field)] = 1
356
- end.tap do |projection|
357
- projection["_id"] = 0 unless selected_fields.include?("_id")
358
- normalized_join(model, join).each_key { |join_model| projection[join_model] = 1 } if join
359
- end
360
- end
361
-
362
- def sort_field(model, sort_by)
363
- field = resolve_field(model, fetch_key(sort_by, :field))
364
- storage_field_for_join(model, field)
365
- end
366
-
367
- def sort_direction(sort_by)
368
- (fetch_key(sort_by, :direction).to_s == "desc") ? -1 : 1
369
- end
370
-
371
- def collection_for(model)
372
- database.collection(collection_name(model))
373
- end
374
-
375
- def collection_name(model)
376
- model = default_model_name(model)
377
- configured = configured_model_name(model)
378
- return "#{configured}s" if configured && use_plural
379
- return configured if configured
380
- return schema_for(model).fetch(:model_name) if use_plural
381
-
382
- model.to_s
383
- end
384
-
385
- def to_document(model, record)
386
- fields_for(model).each_with_object({}) do |(field, attributes), document|
387
- next unless record.key?(field)
5
+ require "better_auth/mongodb"
388
6
 
389
- key = (field == "id") ? "_id" : storage_field(model, field)
390
- document[key] = store_value(field, record[field], attributes)
391
- end
392
- end
393
-
394
- def from_document(model, document, join: nil)
395
- fields = fields_for(model)
396
- record = fields.each_with_object({}) do |(field, attributes), output|
397
- key = (field == "id") ? "_id" : storage_field(model, field)
398
- output[field] = output_value(field, fetch_document(document, key), attributes) if document_key?(document, key)
399
- end
400
-
401
- if join
402
- normalized_join(model, join).each do |join_model, config|
403
- next unless document_key?(document, join_model)
404
-
405
- joined_value = fetch_document(document, join_model)
406
- record[join_model] = if joined_value.is_a?(Array)
407
- joined_value.map { |entry| from_document(join_model, stringify_document(entry)) }
408
- elsif joined_value
409
- from_document(join_model, stringify_document(joined_value))
410
- elsif config[:relation] == "one-to-one"
411
- nil
412
- else
413
- []
414
- end
415
- end
416
- end
417
-
418
- record
419
- end
420
-
421
- def stringify_document(document)
422
- document.each_with_object({}) { |(key, value), result| result[key.to_s] = value }
423
- end
424
-
425
- def unwrap_update_result(result)
426
- return result unless result.is_a?(Hash)
427
- return result if document_key?(result, "_id")
428
-
429
- if result.key?("value") && (result.key?("ok") || result.key?("lastErrorObject"))
430
- return result["value"]
431
- end
432
- if result.key?(:value) && (result.key?(:ok) || result.key?(:last_error_object))
433
- return result[:value]
434
- end
435
-
436
- result
437
- end
438
-
439
- def store_value(field, value, attributes, strict_id: false)
440
- return nil if value.nil?
441
- return Array(value).map { |entry| store_value(field, entry, attributes, strict_id: strict_id) } if value.is_a?(Array)
442
-
443
- if id_field?(field, attributes)
444
- return value if custom_id_generator?
445
- return bson_id(value, strict: strict_id)
446
- end
447
-
448
- input_value(value, attributes)
449
- end
450
-
451
- def output_value(field, value, attributes)
452
- return nil if value.nil?
453
- if id_field?(field, attributes)
454
- return value.to_uuid if bson_uuid?(value)
455
- return value.to_s if value.is_a?(BSON::ObjectId)
456
- return value.map { |entry| output_value(field, entry, attributes) } if value.is_a?(Array)
457
- return value
458
- end
459
-
460
- output_scalar_value(value, attributes)
461
- end
462
-
463
- def id_field?(field, attributes)
464
- field.to_s == "id" || attributes.dig(:references, :field) == "id"
465
- end
466
-
467
- def bson_id(value, strict:)
468
- if use_uuid_ids?
469
- return value if bson_uuid?(value)
470
- return BSON::Binary.from_uuid(value.to_s) if value.is_a?(String)
471
- raise MongoAdapterError.new("INVALID_ID", "Invalid id value") if strict
472
-
473
- return value
474
- end
475
-
476
- return value if value.is_a?(BSON::ObjectId)
477
- return BSON::ObjectId.from_string(value.to_s) if value.is_a?(String)
478
- raise MongoAdapterError.new("INVALID_ID", "Invalid id value") if strict
479
-
480
- value
481
- rescue BSON::Error::InvalidObjectId, ArgumentError
482
- value
483
- end
484
-
485
- def bson_uuid?(value)
486
- defined?(BSON::Binary) && value.is_a?(BSON::Binary) && value.respond_to?(:to_uuid) && value.type == :uuid
487
- end
488
-
489
- def generated_id
490
- generator = options.advanced.dig(:database, :generate_id)
491
- return generator.call if generator.respond_to?(:call)
492
- return SecureRandom.uuid if use_uuid_ids?
493
- return BSON::ObjectId.new.to_s if defined?(BSON::ObjectId)
494
-
495
- SecureRandom.hex(12)
496
- end
497
-
498
- def use_uuid_ids?
499
- options.advanced.dig(:database, :generate_id) == "uuid"
500
- end
501
-
502
- def custom_id_generator?
503
- options.advanced.dig(:database, :generate_id).respond_to?(:call)
504
- end
505
-
506
- def resolve_default(default)
507
- default.respond_to?(:call) ? default.call : default
508
- end
509
-
510
- def coerce_value(value, attributes)
511
- return value if value.nil?
512
- return Time.parse(value) if attributes[:type] == "date" && value.is_a?(String)
513
-
514
- value
515
- end
516
-
517
- def input_value(value, attributes)
518
- value = coerce_value(value, attributes)
519
- return JSON.generate(value) if attributes[:type] == "json" && (value.is_a?(Hash) || value.is_a?(Array))
520
-
521
- value
522
- end
523
-
524
- def output_scalar_value(value, attributes)
525
- return JSON.parse(value) if attributes[:type] == "json" && value.is_a?(String)
526
-
527
- coerce_value(value, attributes)
528
- rescue JSON::ParserError
529
- value
530
- end
531
-
532
- def coerce_where_value(value, attributes)
533
- return value.map { |entry| coerce_where_value(entry, attributes) } if value.is_a?(Array)
534
- return value == "true" if attributes[:type] == "boolean" && value.is_a?(String)
535
- if attributes[:type] == "number" && value.is_a?(String) && !value.strip.empty?
536
- parsed = Float(value)
537
- return parsed.to_i if parsed.to_i == parsed
538
-
539
- return parsed
540
- end
541
- return JSON.generate(value) if attributes[:type] == "json" && (value.is_a?(Hash) || value.is_a?(Array))
542
-
543
- value
544
- rescue ArgumentError
545
- value
546
- end
547
-
548
- def session_options
549
- @session ? {session: @session} : {}
550
- end
551
-
552
- def document_key?(document, key)
553
- document.key?(key) || document.key?(key.to_sym)
554
- end
555
-
556
- def fetch_document(document, key)
557
- return document[key] if document.key?(key)
558
-
559
- document[key.to_sym]
560
- end
561
-
562
- def stringify_keys(data)
563
- data.each_with_object({}) do |(key, value), result|
564
- result[Schema.storage_key(key)] = value
565
- end
566
- end
567
-
568
- def fetch_key(hash, key)
569
- [key, key.to_s, Schema.storage_key(key), Schema.storage_key(key).to_sym].each do |candidate|
570
- return hash[candidate] if hash.key?(candidate)
571
- end
572
- nil
573
- end
574
-
575
- def schema_for(model)
576
- Schema.auth_tables(options).fetch(default_model_name(model))
577
- end
578
-
579
- def fields_for(model)
580
- schema_for(model).fetch(:fields).merge("id" => {type: "string", required: true})
581
- end
582
-
583
- def default_model_name(model)
584
- model = model.to_s
585
- tables = Schema.auth_tables(options)
586
- return model if tables.key?(model)
587
-
588
- pluraless = model.end_with?("s") ? model[0...-1] : nil
589
- return pluraless if pluraless && tables.key?(pluraless)
590
-
591
- matched = tables.find { |_key, table| table[:model_name].to_s == model }
592
- return matched.first if matched
593
-
594
- raise Error, "Model \"#{model}\" not found in schema"
595
- end
596
-
597
- def configured_model_name(model)
598
- configured = configured_model_option(model, :model_name)
599
- return configured.to_s if configured
600
-
601
- return nil if core_model?(model)
602
-
603
- table_model_name = schema_for(model).fetch(:model_name).to_s
604
- (table_model_name == physical_name(model)) ? nil : table_model_name
605
- end
606
-
607
- def configured_model_option(model, key)
608
- data = options.respond_to?(model.to_sym) ? options.public_send(model.to_sym) : nil
609
- data[key] || data[key.to_s] if data.respond_to?(:[])
610
- end
611
-
612
- def core_model?(model)
613
- ["user", "session", "account", "verification", "rateLimit"].include?(model.to_s)
614
- end
615
-
616
- def resolve_field(model, field)
617
- field = Schema.storage_key(field)
618
- return "id" if field == "id" || field == "_id"
619
-
620
- fields = fields_for(model)
621
- return field if fields.key?(field)
622
-
623
- matched = fields.find { |_key, attributes| attributes[:field_name].to_s == field.to_s }
624
- return matched.first if matched
625
-
626
- raise Error, "Field #{field} not found in model #{model}"
627
- end
628
-
629
- def storage_field(model, field)
630
- fields_for(model).fetch(field.to_s).fetch(:field_name, physical_name(field))
631
- end
632
-
633
- def physical_name(value)
634
- value.to_s
635
- .gsub(/([a-z\d])([A-Z])/, "\\1_\\2")
636
- .tr("-", "_")
637
- .downcase
638
- end
639
- end
640
- end
7
+ module BetterAuth
8
+ MongoAdapter = MongoDB unless const_defined?(:MongoAdapter, false)
641
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: better_auth-mongo-adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sebastian Sala
@@ -10,73 +10,19 @@ cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
- name: better_auth
13
+ name: better_auth-mongodb
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
- - - "~>"
17
- - !ruby/object:Gem::Version
18
- version: '0.1'
19
- type: :runtime
20
- prerelease: false
21
- version_requirements: !ruby/object:Gem::Requirement
22
- requirements:
23
- - - "~>"
24
- - !ruby/object:Gem::Version
25
- version: '0.1'
26
- - !ruby/object:Gem::Dependency
27
- name: bigdecimal
28
- requirement: !ruby/object:Gem::Requirement
29
- requirements:
30
- - - ">="
31
- - !ruby/object:Gem::Version
32
- version: '3.1'
33
- - - "<"
16
+ - - '='
34
17
  - !ruby/object:Gem::Version
35
- version: '5.0'
18
+ version: 0.8.0
36
19
  type: :runtime
37
20
  prerelease: false
38
21
  version_requirements: !ruby/object:Gem::Requirement
39
22
  requirements:
40
- - - ">="
41
- - !ruby/object:Gem::Version
42
- version: '3.1'
43
- - - "<"
44
- - !ruby/object:Gem::Version
45
- version: '5.0'
46
- - !ruby/object:Gem::Dependency
47
- name: logger
48
- requirement: !ruby/object:Gem::Requirement
49
- requirements:
50
- - - ">="
51
- - !ruby/object:Gem::Version
52
- version: '1.6'
53
- - - "<"
54
- - !ruby/object:Gem::Version
55
- version: '2.0'
56
- type: :runtime
57
- prerelease: false
58
- version_requirements: !ruby/object:Gem::Requirement
59
- requirements:
60
- - - ">="
61
- - !ruby/object:Gem::Version
62
- version: '1.6'
63
- - - "<"
64
- - !ruby/object:Gem::Version
65
- version: '2.0'
66
- - !ruby/object:Gem::Dependency
67
- name: mongo
68
- requirement: !ruby/object:Gem::Requirement
69
- requirements:
70
- - - "~>"
71
- - !ruby/object:Gem::Version
72
- version: '2.21'
73
- type: :runtime
74
- prerelease: false
75
- version_requirements: !ruby/object:Gem::Requirement
76
- requirements:
77
- - - "~>"
23
+ - - '='
78
24
  - !ruby/object:Gem::Version
79
- version: '2.21'
25
+ version: 0.8.0
80
26
  - !ruby/object:Gem::Dependency
81
27
  name: bundler
82
28
  requirement: !ruby/object:Gem::Requirement
@@ -147,8 +93,8 @@ dependencies:
147
93
  - - "~>"
148
94
  - !ruby/object:Gem::Version
149
95
  version: '0.22'
150
- description: Adds a MongoDB database adapter for Better Auth Ruby without requiring
151
- MongoDB dependencies in the core gem.
96
+ description: Deprecated compatibility package for Better Auth Ruby MongoDB support.
97
+ Use the better_auth-mongodb gem and require "better_auth/mongodb" instead.
152
98
  email:
153
99
  - sebastian.sala.tech@gmail.com
154
100
  executables: []