tina4ruby 3.11.32 → 3.11.35
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/lib/tina4/database.rb +4 -31
- data/lib/tina4/dev_admin.rb +1 -463
- data/lib/tina4/drivers/firebird_driver.rb +71 -13
- data/lib/tina4/frond.rb +0 -62
- data/lib/tina4/mcp.rb +0 -190
- data/lib/tina4/orm.rb +66 -220
- data/lib/tina4/public/js/tina4-dev-admin.js +238 -1086
- data/lib/tina4/public/js/tina4-dev-admin.min.js +209 -1142
- data/lib/tina4/rack_app.rb +1 -46
- data/lib/tina4/response.rb +0 -3
- data/lib/tina4/shutdown.rb +0 -10
- data/lib/tina4/version.rb +1 -1
- data/lib/tina4.rb +0 -14
- metadata +2 -6
- data/lib/tina4/background.rb +0 -81
- data/lib/tina4/docs.rb +0 -636
- data/lib/tina4/plan.rb +0 -471
- data/lib/tina4/project_index.rb +0 -366
data/lib/tina4/orm.rb
CHANGED
|
@@ -4,7 +4,7 @@ require "json"
|
|
|
4
4
|
module Tina4
|
|
5
5
|
# Convert a snake_case name to camelCase.
|
|
6
6
|
def self.snake_to_camel(name)
|
|
7
|
-
parts = name.to_s.
|
|
7
|
+
parts = name.to_s.split("_")
|
|
8
8
|
parts[0] + parts[1..].map(&:capitalize).join
|
|
9
9
|
end
|
|
10
10
|
|
|
@@ -18,7 +18,7 @@ module Tina4
|
|
|
18
18
|
|
|
19
19
|
class << self
|
|
20
20
|
def db
|
|
21
|
-
@db || Tina4.database
|
|
21
|
+
@db || Tina4.database
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
# Per-model database binding
|
|
@@ -54,32 +54,20 @@ module Tina4
|
|
|
54
54
|
|
|
55
55
|
# Auto-map flag (no-op in Ruby since snake_case is native)
|
|
56
56
|
def auto_map
|
|
57
|
-
@auto_map
|
|
57
|
+
@auto_map || false
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
def auto_map=(val)
|
|
61
61
|
@auto_map = val
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
-
# Auto-CRUD flag: when set to true, registers this model for CRUD route generation
|
|
65
|
-
def auto_crud
|
|
66
|
-
@auto_crud || false
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def auto_crud=(val)
|
|
70
|
-
@auto_crud = val
|
|
71
|
-
if val
|
|
72
|
-
Tina4::AutoCrud.register(self) if defined?(Tina4::AutoCrud)
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
|
|
76
64
|
# Relationship definitions
|
|
77
65
|
def relationship_definitions
|
|
78
66
|
@relationship_definitions ||= {}
|
|
79
67
|
end
|
|
80
68
|
|
|
81
69
|
# has_one :profile, class_name: "Profile", foreign_key: "user_id"
|
|
82
|
-
def has_one(name, class_name: nil, foreign_key: nil)
|
|
70
|
+
def has_one(name, class_name: nil, foreign_key: nil)
|
|
83
71
|
relationship_definitions[name] = {
|
|
84
72
|
type: :has_one,
|
|
85
73
|
class_name: class_name || name.to_s.split("_").map(&:capitalize).join,
|
|
@@ -92,7 +80,7 @@ module Tina4
|
|
|
92
80
|
end
|
|
93
81
|
|
|
94
82
|
# has_many :posts, class_name: "Post", foreign_key: "user_id"
|
|
95
|
-
def has_many(name, class_name: nil, foreign_key: nil)
|
|
83
|
+
def has_many(name, class_name: nil, foreign_key: nil)
|
|
96
84
|
relationship_definitions[name] = {
|
|
97
85
|
type: :has_many,
|
|
98
86
|
class_name: class_name || name.to_s.sub(/s$/, "").split("_").map(&:capitalize).join,
|
|
@@ -105,7 +93,7 @@ module Tina4
|
|
|
105
93
|
end
|
|
106
94
|
|
|
107
95
|
# belongs_to :user, class_name: "User", foreign_key: "user_id"
|
|
108
|
-
def belongs_to(name, class_name: nil, foreign_key: nil)
|
|
96
|
+
def belongs_to(name, class_name: nil, foreign_key: nil)
|
|
109
97
|
relationship_definitions[name] = {
|
|
110
98
|
type: :belongs_to,
|
|
111
99
|
class_name: class_name || name.to_s.split("_").map(&:capitalize).join,
|
|
@@ -123,46 +111,31 @@ module Tina4
|
|
|
123
111
|
# results = User.query.where("active = ?", [1]).order_by("name").get
|
|
124
112
|
#
|
|
125
113
|
# @return [Tina4::QueryBuilder]
|
|
126
|
-
def query
|
|
127
|
-
QueryBuilder.
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
filter = filter.merge(extra_filter) unless extra_filter.empty?
|
|
145
|
-
conditions = []
|
|
146
|
-
params = []
|
|
147
|
-
|
|
148
|
-
filter.each do |key, value|
|
|
149
|
-
col = field_mapping[key.to_s] || key
|
|
150
|
-
conditions << "#{col} = ?"
|
|
151
|
-
params << value
|
|
114
|
+
def query
|
|
115
|
+
QueryBuilder.from(table_name, db: db)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def find(id_or_filter = nil, filter = nil, **kwargs)
|
|
119
|
+
include_list = kwargs.delete(:include)
|
|
120
|
+
|
|
121
|
+
# find(id) — find by primary key
|
|
122
|
+
# find(filter_hash) — find by criteria
|
|
123
|
+
# find(name: "Alice") — keyword args as filter hash
|
|
124
|
+
result = if id_or_filter.is_a?(Hash)
|
|
125
|
+
find_by_filter(id_or_filter)
|
|
126
|
+
elsif filter.is_a?(Hash)
|
|
127
|
+
find_by_filter(filter)
|
|
128
|
+
elsif !kwargs.empty?
|
|
129
|
+
find_by_filter(kwargs)
|
|
130
|
+
else
|
|
131
|
+
find_by_id(id_or_filter)
|
|
152
132
|
end
|
|
153
133
|
|
|
154
|
-
if
|
|
155
|
-
|
|
134
|
+
if include_list && result
|
|
135
|
+
instances = result.is_a?(Array) ? result : [result]
|
|
136
|
+
eager_load(instances, include_list)
|
|
156
137
|
end
|
|
157
|
-
|
|
158
|
-
sql = "SELECT * FROM #{table_name}"
|
|
159
|
-
sql += " WHERE #{conditions.join(' AND ')}" unless conditions.empty?
|
|
160
|
-
sql += " ORDER BY #{order_by}" if order_by
|
|
161
|
-
|
|
162
|
-
results = db.fetch(sql, params, limit: limit, offset: offset)
|
|
163
|
-
instances = results.map { |row| from_hash(row) }
|
|
164
|
-
eager_load(instances, include) if include
|
|
165
|
-
instances
|
|
138
|
+
result
|
|
166
139
|
end
|
|
167
140
|
|
|
168
141
|
# Eager load relationships for a collection of instances (prevents N+1).
|
|
@@ -243,20 +216,20 @@ module Tina4
|
|
|
243
216
|
end
|
|
244
217
|
end
|
|
245
218
|
|
|
246
|
-
def where(conditions, params = [],
|
|
219
|
+
def where(conditions, params = [], include: nil)
|
|
247
220
|
sql = "SELECT * FROM #{table_name}"
|
|
248
221
|
if soft_delete
|
|
249
222
|
sql += " WHERE (#{soft_delete_field} IS NULL OR #{soft_delete_field} = 0) AND (#{conditions})"
|
|
250
223
|
else
|
|
251
224
|
sql += " WHERE #{conditions}"
|
|
252
225
|
end
|
|
253
|
-
results = db.fetch(sql, params
|
|
226
|
+
results = db.fetch(sql, params)
|
|
254
227
|
instances = results.map { |row| from_hash(row) }
|
|
255
228
|
eager_load(instances, include) if include
|
|
256
229
|
instances
|
|
257
230
|
end
|
|
258
231
|
|
|
259
|
-
def all(limit: nil, offset: nil, order_by: nil, include: nil)
|
|
232
|
+
def all(limit: nil, offset: nil, order_by: nil, include: nil)
|
|
260
233
|
sql = "SELECT * FROM #{table_name}"
|
|
261
234
|
if soft_delete
|
|
262
235
|
sql += " WHERE #{soft_delete_field} IS NULL OR #{soft_delete_field} = 0"
|
|
@@ -268,19 +241,19 @@ module Tina4
|
|
|
268
241
|
instances
|
|
269
242
|
end
|
|
270
243
|
|
|
271
|
-
def select(sql, params = [], limit: nil, offset: nil, include: nil)
|
|
244
|
+
def select(sql, params = [], limit: nil, offset: nil, include: nil)
|
|
272
245
|
results = db.fetch(sql, params, limit: limit, offset: offset)
|
|
273
246
|
instances = results.map { |row| from_hash(row) }
|
|
274
247
|
eager_load(instances, include) if include
|
|
275
248
|
instances
|
|
276
249
|
end
|
|
277
250
|
|
|
278
|
-
def select_one(sql, params = [], include: nil)
|
|
251
|
+
def select_one(sql, params = [], include: nil)
|
|
279
252
|
results = select(sql, params, limit: 1, include: include)
|
|
280
253
|
results.first
|
|
281
254
|
end
|
|
282
255
|
|
|
283
|
-
def count(conditions = nil, params = [])
|
|
256
|
+
def count(conditions = nil, params = [])
|
|
284
257
|
sql = "SELECT COUNT(*) as cnt FROM #{table_name}"
|
|
285
258
|
where_parts = []
|
|
286
259
|
if soft_delete
|
|
@@ -292,48 +265,25 @@ module Tina4
|
|
|
292
265
|
result[:cnt].to_i
|
|
293
266
|
end
|
|
294
267
|
|
|
295
|
-
def create(attributes = {})
|
|
268
|
+
def create(attributes = {})
|
|
296
269
|
instance = new(attributes)
|
|
297
270
|
instance.save
|
|
298
271
|
instance
|
|
299
272
|
end
|
|
300
273
|
|
|
301
|
-
def find_or_fail(id)
|
|
274
|
+
def find_or_fail(id)
|
|
302
275
|
result = find(id)
|
|
303
276
|
raise "#{name} with #{primary_key_field || :id}=#{id} not found" if result.nil?
|
|
304
277
|
result
|
|
305
278
|
end
|
|
306
279
|
|
|
307
|
-
|
|
308
|
-
def exists(pk_value) # -> bool
|
|
309
|
-
find(pk_value) != nil
|
|
310
|
-
end
|
|
311
|
-
|
|
312
|
-
# SQL query with in-memory result caching.
|
|
313
|
-
# Results are cached by (class, sql, params, limit, offset) for +ttl+ seconds.
|
|
314
|
-
def cached(sql, params = [], ttl: 60, limit: 20, offset: 0, include: nil) # -> list[Self]
|
|
315
|
-
@_query_cache ||= Tina4::QueryCache.new(default_ttl: ttl, max_size: 500)
|
|
316
|
-
cache_key = Tina4::QueryCache.query_key("#{name}:#{sql}", params + [limit, offset])
|
|
317
|
-
hit = @_query_cache.get(cache_key)
|
|
318
|
-
return hit unless hit.nil?
|
|
319
|
-
|
|
320
|
-
results = select(sql, params, limit: limit, offset: offset, include: include)
|
|
321
|
-
@_query_cache.set(cache_key, results, ttl: ttl, tags: [name])
|
|
322
|
-
results
|
|
323
|
-
end
|
|
324
|
-
|
|
325
|
-
# Clear all cached query results for this model.
|
|
326
|
-
def clear_cache # -> nil
|
|
327
|
-
@_query_cache&.clear_tag(name)
|
|
328
|
-
end
|
|
329
|
-
|
|
330
|
-
def with_trashed(conditions = "1=1", params = [], limit: 20, offset: 0) # -> list[Self]
|
|
280
|
+
def with_trashed(conditions = "1=1", params = [], limit: 20, offset: 0)
|
|
331
281
|
sql = "SELECT * FROM #{table_name} WHERE #{conditions}"
|
|
332
282
|
results = db.fetch(sql, params, limit: limit, offset: offset)
|
|
333
283
|
results.map { |row| from_hash(row) }
|
|
334
284
|
end
|
|
335
285
|
|
|
336
|
-
def create_table
|
|
286
|
+
def create_table
|
|
337
287
|
return true if db.table_exists?(table_name)
|
|
338
288
|
|
|
339
289
|
type_map = {
|
|
@@ -374,7 +324,7 @@ module Tina4
|
|
|
374
324
|
true
|
|
375
325
|
end
|
|
376
326
|
|
|
377
|
-
def scope(name, filter_sql, params = [])
|
|
327
|
+
def scope(name, filter_sql, params = [])
|
|
378
328
|
define_singleton_method(name) do |limit: 20, offset: 0|
|
|
379
329
|
where(filter_sql, params)
|
|
380
330
|
end
|
|
@@ -393,42 +343,15 @@ module Tina4
|
|
|
393
343
|
instance
|
|
394
344
|
end
|
|
395
345
|
|
|
396
|
-
|
|
397
|
-
|
|
346
|
+
private
|
|
347
|
+
|
|
348
|
+
def find_by_id(id)
|
|
398
349
|
pk = primary_key_field || :id
|
|
399
350
|
sql = "SELECT * FROM #{table_name} WHERE #{pk} = ?"
|
|
400
351
|
if soft_delete
|
|
401
352
|
sql += " AND (#{soft_delete_field} IS NULL OR #{soft_delete_field} = 0)"
|
|
402
353
|
end
|
|
403
|
-
select_one(sql, [id]
|
|
404
|
-
end
|
|
405
|
-
|
|
406
|
-
# Clear the relationship cache on all loaded instances (class-level helper).
|
|
407
|
-
# Useful after bulk operations when you want to force relationship re-loads.
|
|
408
|
-
def clear_rel_cache # -> nil
|
|
409
|
-
@_rel_cache = {}
|
|
410
|
-
nil
|
|
411
|
-
end
|
|
412
|
-
|
|
413
|
-
# Return the database connection used by this model.
|
|
414
|
-
def get_db # -> Database
|
|
415
|
-
db
|
|
416
|
-
end
|
|
417
|
-
|
|
418
|
-
# Map a Ruby property name to its database column name using field_mapping.
|
|
419
|
-
# Returns the column name as a symbol.
|
|
420
|
-
def get_db_column(property) # -> Symbol
|
|
421
|
-
col = field_mapping[property.to_s] || property
|
|
422
|
-
col.to_sym
|
|
423
|
-
end
|
|
424
|
-
|
|
425
|
-
private
|
|
426
|
-
|
|
427
|
-
def auto_discover_db
|
|
428
|
-
url = ENV["DATABASE_URL"]
|
|
429
|
-
return nil unless url
|
|
430
|
-
Tina4.database = Tina4::Database.new(url, username: ENV.fetch("DATABASE_USERNAME", ""), password: ENV.fetch("DATABASE_PASSWORD", ""))
|
|
431
|
-
Tina4.database
|
|
354
|
+
select_one(sql, [id])
|
|
432
355
|
end
|
|
433
356
|
|
|
434
357
|
def find_by_filter(filter)
|
|
@@ -458,7 +381,7 @@ module Tina4
|
|
|
458
381
|
end
|
|
459
382
|
end
|
|
460
383
|
|
|
461
|
-
def save
|
|
384
|
+
def save
|
|
462
385
|
@errors = []
|
|
463
386
|
@relationship_cache = {} # Clear relationship cache on save
|
|
464
387
|
validate_fields
|
|
@@ -490,7 +413,7 @@ module Tina4
|
|
|
490
413
|
false
|
|
491
414
|
end
|
|
492
415
|
|
|
493
|
-
def delete
|
|
416
|
+
def delete
|
|
494
417
|
pk = self.class.primary_key_field || :id
|
|
495
418
|
pk_value = __send__(pk)
|
|
496
419
|
return false unless pk_value
|
|
@@ -510,7 +433,7 @@ module Tina4
|
|
|
510
433
|
true
|
|
511
434
|
end
|
|
512
435
|
|
|
513
|
-
def force_delete
|
|
436
|
+
def force_delete
|
|
514
437
|
pk = self.class.primary_key_field || :id
|
|
515
438
|
pk_value = __send__(pk)
|
|
516
439
|
raise "Cannot delete: no primary key value" unless pk_value
|
|
@@ -522,7 +445,7 @@ module Tina4
|
|
|
522
445
|
true
|
|
523
446
|
end
|
|
524
447
|
|
|
525
|
-
def restore
|
|
448
|
+
def restore
|
|
526
449
|
raise "Model does not support soft delete" unless self.class.soft_delete
|
|
527
450
|
|
|
528
451
|
pk = self.class.primary_key_field || :id
|
|
@@ -540,7 +463,7 @@ module Tina4
|
|
|
540
463
|
true
|
|
541
464
|
end
|
|
542
465
|
|
|
543
|
-
def validate
|
|
466
|
+
def validate
|
|
544
467
|
errors = []
|
|
545
468
|
self.class.field_definitions.each do |name, opts|
|
|
546
469
|
value = __send__(name)
|
|
@@ -551,36 +474,19 @@ module Tina4
|
|
|
551
474
|
errors
|
|
552
475
|
end
|
|
553
476
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
# orm.id = 1; orm.load — uses PK already set
|
|
560
|
-
# orm.load("id = ?", [1]) — filter with params
|
|
561
|
-
# orm.load("id = 1") — filter string
|
|
562
|
-
#
|
|
563
|
-
# Returns true if a record was found, false otherwise.
|
|
564
|
-
def load(filter = nil, params = [], include: nil) # -> bool
|
|
565
|
-
@relationship_cache = {}
|
|
566
|
-
table = self.class.table_name
|
|
567
|
-
|
|
568
|
-
if filter.nil?
|
|
569
|
-
pk = self.class.primary_key
|
|
570
|
-
pk_col = self.class.field_mapping[pk.to_s] || pk
|
|
571
|
-
pk_value = __send__(pk)
|
|
572
|
-
return false if pk_value.nil?
|
|
573
|
-
sql = "SELECT * FROM #{table} WHERE #{pk_col} = ?"
|
|
574
|
-
params = [pk_value]
|
|
575
|
-
else
|
|
576
|
-
sql = "SELECT * FROM #{table} WHERE #{filter}"
|
|
577
|
-
end
|
|
477
|
+
def load(id = nil)
|
|
478
|
+
pk = self.class.primary_key_field || :id
|
|
479
|
+
id ||= __send__(pk)
|
|
480
|
+
return false unless id
|
|
481
|
+
@relationship_cache = {} # Clear relationship cache on reload
|
|
578
482
|
|
|
579
|
-
result = self.class.
|
|
483
|
+
result = self.class.db.fetch_one(
|
|
484
|
+
"SELECT * FROM #{self.class.table_name} WHERE #{pk} = ?", [id]
|
|
485
|
+
)
|
|
580
486
|
return false unless result
|
|
581
487
|
|
|
582
488
|
mapping_reverse = self.class.field_mapping.invert
|
|
583
|
-
result.
|
|
489
|
+
result.each do |key, value|
|
|
584
490
|
attr_name = mapping_reverse[key.to_s] || key
|
|
585
491
|
setter = "#{attr_name}="
|
|
586
492
|
__send__(setter, value) if respond_to?(setter)
|
|
@@ -599,8 +505,7 @@ module Tina4
|
|
|
599
505
|
|
|
600
506
|
# Convert to hash using Ruby attribute names.
|
|
601
507
|
# Optionally include relationships via the include keyword.
|
|
602
|
-
|
|
603
|
-
def to_h(include: nil, case: 'snake') # -> dict
|
|
508
|
+
def to_h(include: nil)
|
|
604
509
|
hash = {}
|
|
605
510
|
self.class.field_definitions.each_key do |name|
|
|
606
511
|
hash[name] = __send__(name)
|
|
@@ -622,49 +527,27 @@ module Tina4
|
|
|
622
527
|
if related.nil?
|
|
623
528
|
hash[rel_name] = nil
|
|
624
529
|
elsif related.is_a?(Array)
|
|
625
|
-
hash[rel_name] = related.map { |r| r.to_h(include: nested.empty? ? nil : nested
|
|
530
|
+
hash[rel_name] = related.map { |r| r.to_h(include: nested.empty? ? nil : nested) }
|
|
626
531
|
else
|
|
627
|
-
hash[rel_name] = related.to_h(include: nested.empty? ? nil : nested
|
|
532
|
+
hash[rel_name] = related.to_h(include: nested.empty? ? nil : nested)
|
|
628
533
|
end
|
|
629
534
|
end
|
|
630
535
|
end
|
|
631
536
|
|
|
632
|
-
case_mode = binding.local_variable_get(:case)
|
|
633
|
-
if case_mode == 'camel'
|
|
634
|
-
camel_hash = {}
|
|
635
|
-
hash.each do |key, value|
|
|
636
|
-
camel_key = Tina4.snake_to_camel(key.to_s).to_sym
|
|
637
|
-
camel_hash[camel_key] = value
|
|
638
|
-
end
|
|
639
|
-
return camel_hash
|
|
640
|
-
end
|
|
641
|
-
|
|
642
537
|
hash
|
|
643
538
|
end
|
|
644
539
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
540
|
+
alias to_hash to_h
|
|
541
|
+
alias to_dict to_h
|
|
542
|
+
alias to_object to_h
|
|
648
543
|
|
|
649
|
-
def
|
|
650
|
-
to_h(include: include, case: binding.local_variable_get(:case))
|
|
651
|
-
end
|
|
652
|
-
|
|
653
|
-
def to_assoc(include: nil, case: 'snake')
|
|
654
|
-
to_h(include: include, case: binding.local_variable_get(:case))
|
|
655
|
-
end
|
|
656
|
-
|
|
657
|
-
def to_object(include: nil, case: 'snake')
|
|
658
|
-
to_h(include: include, case: binding.local_variable_get(:case))
|
|
659
|
-
end
|
|
660
|
-
|
|
661
|
-
def to_array # -> list
|
|
544
|
+
def to_array
|
|
662
545
|
to_h.values
|
|
663
546
|
end
|
|
664
547
|
|
|
665
548
|
alias to_list to_array
|
|
666
549
|
|
|
667
|
-
def to_json(include: nil, **_args)
|
|
550
|
+
def to_json(include: nil, **_args)
|
|
668
551
|
JSON.generate(to_h(include: include))
|
|
669
552
|
end
|
|
670
553
|
|
|
@@ -747,44 +630,7 @@ module Tina4
|
|
|
747
630
|
fk_value = __send__(fk.to_sym) if respond_to?(fk.to_sym)
|
|
748
631
|
return nil unless fk_value
|
|
749
632
|
|
|
750
|
-
@relationship_cache[name] = klass.
|
|
751
|
-
end
|
|
752
|
-
|
|
753
|
-
public
|
|
754
|
-
|
|
755
|
-
# ── Imperative relationship methods (ad-hoc, like Python/PHP/Node) ──
|
|
756
|
-
|
|
757
|
-
def has_one(related_class, foreign_key: nil)
|
|
758
|
-
pk = self.class.primary_key_field || :id
|
|
759
|
-
pk_value = __send__(pk)
|
|
760
|
-
return nil unless pk_value
|
|
761
|
-
|
|
762
|
-
fk = foreign_key || "#{self.class.name.split('::').last.downcase}_id"
|
|
763
|
-
result = related_class.db.fetch_one(
|
|
764
|
-
"SELECT * FROM #{related_class.table_name} WHERE #{fk} = ?", [pk_value]
|
|
765
|
-
)
|
|
766
|
-
result ? related_class.from_hash(result) : nil
|
|
767
|
-
end
|
|
768
|
-
|
|
769
|
-
def has_many(related_class, foreign_key: nil, limit: 100, offset: 0)
|
|
770
|
-
pk = self.class.primary_key_field || :id
|
|
771
|
-
pk_value = __send__(pk)
|
|
772
|
-
return [] unless pk_value
|
|
773
|
-
|
|
774
|
-
fk = foreign_key || "#{self.class.name.split('::').last.downcase}_id"
|
|
775
|
-
results = related_class.db.fetch(
|
|
776
|
-
"SELECT * FROM #{related_class.table_name} WHERE #{fk} = ?",
|
|
777
|
-
[pk_value], limit: limit, offset: offset
|
|
778
|
-
)
|
|
779
|
-
results.map { |row| related_class.from_hash(row) }
|
|
780
|
-
end
|
|
781
|
-
|
|
782
|
-
def belongs_to(related_class, foreign_key: nil)
|
|
783
|
-
fk = foreign_key || "#{related_class.name.split('::').last.downcase}_id"
|
|
784
|
-
fk_value = respond_to?(fk.to_sym) ? __send__(fk.to_sym) : nil
|
|
785
|
-
return nil unless fk_value
|
|
786
|
-
|
|
787
|
-
related_class.find_by_id(fk_value)
|
|
633
|
+
@relationship_cache[name] = klass.find(fk_value)
|
|
788
634
|
end
|
|
789
635
|
end
|
|
790
636
|
end
|