Skip to content

Commit

Permalink
Adding new base_query_class arg for associations. Fixes #85
Browse files Browse the repository at this point in the history
  • Loading branch information
jwoertink committed Feb 24, 2024
1 parent f1bfffc commit f105794
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 15 deletions.
15 changes: 15 additions & 0 deletions db/migrations/20240224173636_create_transactions.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class CreateTransactions::V20240224173636 < Avram::Migrator::Migration::V1
def migrate
create table_for(Transaction) do
primary_key id : Int64
add_timestamps
add type : Int32
add soft_deleted_at : Time?
add_belongs_to user : User, on_delete: :cascade
end
end

def rollback
drop table_for(Transaction)
end
end
40 changes: 40 additions & 0 deletions spec/avram/preloading/preloading_has_many_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -270,4 +270,44 @@ describe "Preloading has_many associations" do
end
end
end

describe "override base_query_class", focus: true do
it "uses the custom query class to ignore soft_deleted records" do
user = UserFactory.create
good_txn = TransactionFactory.create(&.user(user))
deleted_txn = TransactionFactory.create(&.user(user).soft_deleted_at(1.day.ago))

u = UserQuery.new.preload_transactions.first
u.transactions_count.should eq(1)
ids = u.transactions.map(&.id)
ids.should contain(good_txn.id)
ids.should_not contain(deleted_txn.id)
end

it "has an escape hatch" do
user = UserFactory.create
good_txn = TransactionFactory.create(&.user(user))
deleted_txn = TransactionFactory.create(&.user(user).soft_deleted_at(1.day.ago))

u = UserQuery.new.preload_transactions(Transaction::BaseQuery.new).first
u.transactions_count.should eq(2)

Check failure on line 293 in spec/avram/preloading/preloading_has_many_spec.cr

View workflow job for this annotation

GitHub Actions / specs (shard.yml, 12, 1.6.2, false)

got: 1

Check failure on line 293 in spec/avram/preloading/preloading_has_many_spec.cr

View workflow job for this annotation

GitHub Actions / specs (shard.yml, 12, latest, false)

got: 1

Check failure on line 293 in spec/avram/preloading/preloading_has_many_spec.cr

View workflow job for this annotation

GitHub Actions / specs (shard.yml, 13, 1.6.2, false)

got: 1

Check failure on line 293 in spec/avram/preloading/preloading_has_many_spec.cr

View workflow job for this annotation

GitHub Actions / specs (shard.yml, 13, latest, false)

got: 1

Check failure on line 293 in spec/avram/preloading/preloading_has_many_spec.cr

View workflow job for this annotation

GitHub Actions / specs (shard.yml, 14, 1.6.2, false)

got: 1

Check failure on line 293 in spec/avram/preloading/preloading_has_many_spec.cr

View workflow job for this annotation

GitHub Actions / specs (shard.yml, 14, latest, false)

got: 1

Check failure on line 293 in spec/avram/preloading/preloading_has_many_spec.cr

View workflow job for this annotation

GitHub Actions / specs (shard.edge.yml, latest, 14, true)

got: 1

Check failure on line 293 in spec/avram/preloading/preloading_has_many_spec.cr

View workflow job for this annotation

GitHub Actions / specs (shard.yml, nightly, 14, true)

got: 1
ids = u.transactions.map(&.id)
ids.should contain(good_txn.id)
ids.should contain(deleted_txn.id)
end

it "extends the custom base query class" do
user = UserFactory.create
non_special_txn = TransactionFactory.create(&.user(user))
deleted_txn = TransactionFactory.create(&.user(user).soft_deleted_at(1.day.ago))
special_txn = TransactionFactory.create(&.user(user).type(Transaction::Type::Special))

u = UserQuery.new.preload_transactions(&.special).first
u.transactions_count.should eq(1)

Check failure on line 306 in spec/avram/preloading/preloading_has_many_spec.cr

View workflow job for this annotation

GitHub Actions / specs (shard.yml, 12, 1.6.2, false)

got: 2

Check failure on line 306 in spec/avram/preloading/preloading_has_many_spec.cr

View workflow job for this annotation

GitHub Actions / specs (shard.yml, 12, latest, false)

got: 2

Check failure on line 306 in spec/avram/preloading/preloading_has_many_spec.cr

View workflow job for this annotation

GitHub Actions / specs (shard.yml, 13, 1.6.2, false)

got: 2

Check failure on line 306 in spec/avram/preloading/preloading_has_many_spec.cr

View workflow job for this annotation

GitHub Actions / specs (shard.yml, 13, latest, false)

got: 2

Check failure on line 306 in spec/avram/preloading/preloading_has_many_spec.cr

View workflow job for this annotation

GitHub Actions / specs (shard.yml, 14, 1.6.2, false)

got: 2

Check failure on line 306 in spec/avram/preloading/preloading_has_many_spec.cr

View workflow job for this annotation

GitHub Actions / specs (shard.yml, 14, latest, false)

got: 2

Check failure on line 306 in spec/avram/preloading/preloading_has_many_spec.cr

View workflow job for this annotation

GitHub Actions / specs (shard.edge.yml, latest, 14, true)

got: 2

Check failure on line 306 in spec/avram/preloading/preloading_has_many_spec.cr

View workflow job for this annotation

GitHub Actions / specs (shard.yml, nightly, 14, true)

got: 2
ids = u.transactions.map(&.id)
ids.should_not contain(non_special_txn.id)
ids.should_not contain(deleted_txn.id)
ids.should contain(special_txn.id)
end
end
end
13 changes: 13 additions & 0 deletions spec/support/factories/transaction_factory.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class TransactionFactory < BaseFactory
def initialize
before_save do
if operation.user_id.value.nil?
user(UserFactory.create)
end
end
end

def user(u : User)
user_id(u.id)
end
end
26 changes: 26 additions & 0 deletions spec/support/models/transaction.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
class Transaction < BaseModel
include Avram::SoftDelete::Model

enum Type
Unknown
Special
end

table do
column type : Transaction::Type = Transaction::Type::Unknown
column soft_deleted_at : Time?
belongs_to user : User
end
end

class TransactionQuery < Transaction::BaseQuery
include Avram::SoftDelete::Query

def initialize
defaults &.only_kept
end

def special
type(Transaction::Type::Special)
end
end
1 change: 1 addition & 0 deletions spec/support/models/user.cr
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class User < BaseModel
column average_score : Float64?
column available_for_hire : Bool?
has_one sign_in_credential : SignInCredential?
has_many transactions : Transaction, base_query_class: TransactionQuery
end
end

Expand Down
30 changes: 15 additions & 15 deletions src/avram/associations/has_many.cr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module Avram::Associations::HasMany
macro has_many(type_declaration, through = nil, foreign_key = nil)
macro has_many(type_declaration, through = nil, foreign_key = nil, base_query_class = nil)
{% if !through.is_a?(NilLiteral) && (!through.is_a?(ArrayLiteral) || through.any? { |item| !item.is_a?(SymbolLiteral) }) %}
{% through.raise <<-ERROR
'through' on #{@type.name} must be given an Array(Symbol). Instead, got: #{through}
Expand Down Expand Up @@ -31,6 +31,8 @@ module Avram::Associations::HasMany
{% end %}

{% foreign_key = foreign_key.id %}
{% model = type_declaration.type %}
{% query_class = base_query_class || "#{model}::BaseQuery".id %}

association \
assoc_name: :{{ assoc_name }},
Expand All @@ -39,20 +41,18 @@ module Avram::Associations::HasMany
through: {{ through }},
relationship_type: :has_many

{% model = type_declaration.type %}

define_has_many_lazy_loading({{ assoc_name }}, {{ model }}, {{ foreign_key }}, {{ through }})
define_has_many_base_query({{ @type }}, {{ assoc_name }}, {{ model }}, {{ foreign_key }}, {{ through }})
define_has_many_lazy_loading({{ assoc_name }}, {{ model }}, {{ foreign_key }}, {{ through }}, {{ query_class }})
define_has_many_base_query({{ @type }}, {{ assoc_name }}, {{ model }}, {{ foreign_key }}, {{ through }}, {{ query_class }})
end

private macro define_has_many_base_query(class_type, assoc_name, model, foreign_key, through)
private macro define_has_many_base_query(class_type, assoc_name, model, foreign_key, through, query_class)
class BaseQuery
def self.preload_{{ assoc_name }}(record : {{ class_type }}, force : Bool = false) : {{ class_type }}
preload_{{ assoc_name }}(record: record, preload_query: {{ model }}::BaseQuery.new, force: force)
preload_{{ assoc_name }}(record: record, preload_query: {{ query_class }}.new, force: force)
end

def self.preload_{{ assoc_name }}(record : {{ class_type }}, force : Bool = false) : {{ class_type }}
modified_query = yield {{ model }}::BaseQuery.new
modified_query = yield {{ query_class }}.new
preload_{{ assoc_name }}(record: record, preload_query: modified_query, force: force)
end

Expand All @@ -73,11 +73,11 @@ module Avram::Associations::HasMany
{% end %}

def self.preload_{{ assoc_name }}(records : Enumerable({{ class_type }}), force : Bool = false) : Array({{ class_type }})
preload_{{ assoc_name }}(records: records, preload_query: {{ model }}::BaseQuery.new, force: force)
preload_{{ assoc_name }}(records: records, preload_query: {{ query_class }}.new, force: force)
end

def self.preload_{{ assoc_name }}(records : Enumerable({{ class_type }}), force : Bool = false) : Array({{ class_type }})
modified_query = yield {{ model }}::BaseQuery.new
modified_query = yield {{ query_class }}.new
preload_{{ assoc_name }}(records: records, preload_query: modified_query, force: force)
end

Expand Down Expand Up @@ -123,11 +123,11 @@ module Avram::Associations::HasMany
{% end %}

def preload_{{ assoc_name }} : self
preload_{{ assoc_name }}({{ model }}::BaseQuery.new)
preload_{{ assoc_name }}({{ query_class }}.new)
end

def preload_{{ assoc_name }} : self
modified_query = yield {{ model }}::BaseQuery.new
modified_query = yield {{ query_class }}.new
preload_{{ assoc_name }}(modified_query)
end

Expand Down Expand Up @@ -169,7 +169,7 @@ module Avram::Associations::HasMany
end
end

private macro define_has_many_lazy_loading(assoc_name, model, foreign_key, through)
private macro define_has_many_lazy_loading(assoc_name, model, foreign_key, through, query_class)
@[DB::Field(ignore: true)]
@_preloaded_{{ assoc_name }} : Array({{ model }})?
@[DB::Field(ignore: true)]
Expand All @@ -196,7 +196,7 @@ module Avram::Associations::HasMany
.map(&.{{ through[1].id }}_count)
.sum
{% else %}
{{ model }}::BaseQuery
{{ query_class }}
.new
.{{ foreign_key }}(id)
.select_count
Expand All @@ -218,7 +218,7 @@ module Avram::Associations::HasMany
assoc_results.is_a?(Array) ? assoc_results : [assoc_results]
end.compact
{% else %}
{{ model }}::BaseQuery
{{ query_class }}
.new
.{{ foreign_key }}(id)
.results
Expand Down

0 comments on commit f105794

Please sign in to comment.