Skip to content

Commit

Permalink
add .includes for belongs_to and has_one
Browse files Browse the repository at this point in the history
  • Loading branch information
c910335 committed Apr 14, 2020
1 parent d8098df commit e961a57
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 4 deletions.
14 changes: 14 additions & 0 deletions spec/granite/associations/belongs_to_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,18 @@ describe "belongs_to" do

courier.service!.owner_id.should eq 123_321
end

it "works with includes that preloads parents" do
ids = Array(Int64).new(3) do
teacher = Teacher.create(name: "teacher for includes")
klass = Klass.new(name: "class for includes")
klass.teacher = teacher
klass.save
teacher.id!
end

Klass.includes(:teacher).where(name: "class for includes").order(:teacher_id).select.each_with_index do |klass, i|
klass.@teacher.not_nil!.id.should eq ids[i]
end
end
end
14 changes: 14 additions & 0 deletions spec/granite/associations/has_one_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,18 @@ describe "has_one" do

courier.issuer!.character_id.should eq 999
end

it "works with includes that preloads children" do
ids = Array(Int64).new(3) do
user = User.create(email: "user@for.includes")
profile = Profile.new(name: "profile for includes")
user.profile = profile
profile.save
profile.id!
end

User.where(email: "user@for.includes").order(:id).includes(:profile).select.each_with_index do |user, i|
user.@profile.not_nil!.id.should eq ids[i]
end
end
end
7 changes: 7 additions & 0 deletions spec/granite/query/builder_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ describe Granite::Query::Builder(Model) do
query.where_fields.should eq expected
end

it "stores includes" do
query = builder.includes(:children).includes(:parent)
expected = Set{:children, :parent}

query.includes.should eq expected
end

it "stores limit" do
query = builder.limit(7)
query.limit.should eq 7
Expand Down
48 changes: 47 additions & 1 deletion src/granite/associations.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,22 @@
module Granite::Associations
macro included
macro inherited
macro finished
\\{% if !@type.constant(:INCLUDERS.id) %}
disable_granite_docs? INCLUDERS = NamedTuple.new
\\{% end %}
end
end
end

macro __new_includer(name, includer)
{% if @type.constant(:INCLUDERS.id) %}
{% INCLUDERS[name.id] = includer %}
{% else %}
disable_granite_docs? INCLUDERS = { "{{name.id}}": {{includer}} }
{% end %}
end

macro belongs_to(model, **options)
{%
nilable = false
Expand Down Expand Up @@ -36,6 +54,16 @@ module Granite::Associations
@[YAML::Field(ignore: true)]
@{{name}} : {{type}}?

__new_includer(:{{name}}, ->(children : Array({{@type}})) {
parents = {} of Int64 | Nil => {{type}}
{{type}}.where({{primary_key}}: children.compact_map(&.{{foreign_key}})).select.each do |parent|
parents[parent.{{primary_key}}.try &.to_i64] = parent
end
children.each do |child|
child.__set_{{name}}(parents[child.{{foreign_key}}]?)
end
})

def {{nilable_method}} : {{type}}?
@{{name}} ||= {{type}}.find_by({{primary_key}}: {{foreign_key}})
end
Expand All @@ -53,6 +81,10 @@ module Granite::Associations
@{{name}} = nil
{{nilable_method}}
end

def __set_{{name}}(parent : {{type}}?) : {{type}}?
@{{name}} = parent
end
end

macro has_one(model, **options)
Expand Down Expand Up @@ -86,6 +118,16 @@ module Granite::Associations
@[YAML::Field(ignore: true)]
@{{name}} : {{type}}?

__new_includer(:{{name}}, ->(parents : Array({{@type}})) {
children = {} of Int64 | Nil => {{type}}
{{type}}.where({{foreign_key}}: parents.compact_map(&.{{primary_key}})).select.each do |child|
children[child.{{foreign_key}}.try &.to_i64] = child
end
parents.each do |parent|
parent.__set_{{name}}(children[parent.{{primary_key}}]?)
end
})

def {{nilable_method}} : {{type}}?
@{{name}} ||= {{type}}.find_by({{foreign_key}}: self.{{primary_key}})
end
Expand All @@ -95,14 +137,18 @@ module Granite::Associations
end

def {{name}}=(child : {{type}}) : {{type}}
child.{{foreign_key.id}} = self.{{primary_key.id}}
child.{{foreign_key}} = self.{{primary_key}}
@{{name}} = child
end

def reload_{{name}} : {{type}}?
@{{name}} = nil
{{nilable_method}}
end

def __set_{{name}}(child : {{type}}?) : {{type}}?
@{{name}} = child
end
end

macro has_many(model, **options)
Expand Down
2 changes: 1 addition & 1 deletion src/granite/query/assemblers/base.cr
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ module Granite::Query::Assembler
s << offset
end

Executor::List(Model).new sql, numbered_parameters
Executor::List(Model).new sql, numbered_parameters, @query.includes
end

def exists? : Executor::Value(Model, Bool)
Expand Down
7 changes: 7 additions & 0 deletions src/granite/query/builder.cr
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class Granite::Query::Builder(Model)
getter where_fields = [] of NamedTuple(join: Symbol, field: String, operator: Symbol, value: Granite::Columns::Type)
getter order_fields = [] of NamedTuple(field: String, direction: Sort)
getter group_fields = [] of NamedTuple(field: String)
getter includes = Set(Symbol).new
getter offset : Int64?
getter limit : Int64?

Expand Down Expand Up @@ -164,6 +165,12 @@ class Granite::Query::Builder(Model)
self
end

def includes(association)
@includes << association

self
end

def offset(num)
@offset = num.to_i64

Expand Down
2 changes: 1 addition & 1 deletion src/granite/query/builder_methods.cr
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ module Granite::Query::BuilderMethods
Builder(self).new(db_type)
end

delegate where, order, offset, limit, to: __builder
delegate where, includes, order, offset, limit, to: __builder
end
6 changes: 5 additions & 1 deletion src/granite/query/executors/list.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module Granite::Query::Executor
class List(Model)
include Shared

def initialize(@sql : String, @args = [] of Granite::Columns::Type)
def initialize(@sql : String, @args = [] of Granite::Columns::Type, @includes = Set(Symbol).new)
end

def run : Array(Model)
Expand All @@ -18,6 +18,10 @@ module Granite::Query::Executor
end
end

@includes.each do |i|
Model::INCLUDERS[i].call(results)
end

results
end

Expand Down

0 comments on commit e961a57

Please sign in to comment.