Skip to content

Commit

Permalink
Allow has_many associations to be scoped and limited
Browse files Browse the repository at this point in the history
  • Loading branch information
hasghari committed Oct 26, 2021
1 parent 574d018 commit 09fd8d2
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 18 deletions.
1 change: 1 addition & 0 deletions lib/table_saw/dependency_graph.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
require 'table_saw/dependency_graph/add_directive'
require 'table_saw/dependency_graph/belongs_to_directives'
require 'table_saw/dependency_graph/build'
require 'table_saw/dependency_graph/build_has_many_query'
require 'table_saw/dependency_graph/dump_table'
require 'table_saw/dependency_graph/has_many_directives'
2 changes: 1 addition & 1 deletion lib/table_saw/dependency_graph/add_directive.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class AddDirective
attr_reader :table_name, :partial, :has_many
attr_accessor :ids

def initialize(table_name, ids: [], partial: true, has_many: [])
def initialize(table_name, ids: [], partial: true, has_many: {})
@table_name = table_name
@ids = ids
@partial = partial
Expand Down
55 changes: 55 additions & 0 deletions lib/table_saw/dependency_graph/build_has_many_query.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# frozen_string_literal: true

module TableSaw
module DependencyGraph
class BuildHasManyQuery
QUERY = <<~SQL.squish
select %{primary_key} from %{table} where %{clause} and %{polymorphic}
SQL

attr_reader :manifest, :directive, :foreign_key

def initialize(manifest, directive, foreign_key)
@manifest = manifest
@directive = directive
@foreign_key = foreign_key
end

def call
build_base_query
.then { |query| append_scope(query) }
.then { |query| append_limit(query) }
end

private

# rubocop:disable Metrics/AbcSize
def build_base_query
format(QUERY, primary_key: TableSaw.schema_cache.primary_keys(foreign_key.from_table),
table: foreign_key.from_table,
clause: TableSaw::Queries::SerializeSqlInClause.new(foreign_key.from_table,
foreign_key.column.primary_key,
directive.ids).call,
polymorphic: foreign_key.type_condition)
end
# rubocop:enable Metrics/AbcSize

def append_scope(query)
return query unless has_many&.scope

[query, has_many.scope].join(' and ')
end

def append_limit(query)
return query unless has_many&.limit

[query, "limit #{has_many.limit}"].join(' ')
end

def has_many
directive.has_many[foreign_key.from_table] ||
manifest.has_many.fetch(directive.table_name, {})[foreign_key.from_table]
end
end
end
end
17 changes: 4 additions & 13 deletions lib/table_saw/dependency_graph/has_many_directives.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@
module TableSaw
module DependencyGraph
class HasManyDirectives
QUERY = <<~SQL
select %{primary_key} from %{table} where %{clause} and %{polymorphic}
SQL

attr_reader :manifest, :directive

def initialize(manifest, directive)
Expand Down Expand Up @@ -34,25 +30,20 @@ def associations
def valid_associations
associations.select do |fk|
next false if directive.partial? && TableSaw.schema_cache.primary_keys(fk.from_table).nil?
next true if directive.has_many.include?(fk.from_table)
next true if directive.has_many.key?(fk.from_table)

manifest.has_many.fetch(directive.table_name, []).include?(fk.from_table)
manifest.has_many.fetch(directive.table_name, {}).key?(fk.from_table)
end
end
# rubocop:enable Metrics/AbcSize

def query_result(foreign_key)
return [] unless directive.selectable?

TableSaw::Connection.exec(
format(QUERY, primary_key: TableSaw.schema_cache.primary_keys(foreign_key.from_table),
table: foreign_key.from_table,
clause: TableSaw::Queries::SerializeSqlInClause.new(foreign_key.from_table,
foreign_key.column.primary_key,
directive.ids).call,
polymorphic: foreign_key.type_condition)
TableSaw::DependencyGraph::BuildHasManyQuery.new(manifest, directive, foreign_key).call
)
end
# rubocop:enable Metrics/AbcSize
end
end
end
35 changes: 33 additions & 2 deletions lib/table_saw/manifest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,35 @@

module TableSaw
class Manifest
class HasManyEntry
def self.build(config)
config.each_with_object({}) do |(table, options), memo|
case table
when String
memo[table] = new(table, options)
when Hash
table, options = table.first
memo[table] = new(table, options)
end
end
end

attr_reader :table, :options

def initialize(table, options)
@table = table
@options = options || {}
end

def scope
options['scope']
end

def limit
options['limit']
end
end

class Table
attr_reader :variables, :config

Expand All @@ -30,7 +59,7 @@ def partial?
end

def has_many
config.fetch('has_many', [])
config.fetch('has_many', {}).then { |config| HasManyEntry.build(config) }
end
end

Expand Down Expand Up @@ -58,7 +87,9 @@ def tables
end

def has_many
@has_many ||= config.fetch('has_many', {})
@has_many ||= config.fetch('has_many', {}).transform_values do |value|
HasManyEntry.build(value)
end
end

def foreign_keys
Expand Down
38 changes: 36 additions & 2 deletions spec/table_saw/dependency_graph/build_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,8 @@
before do
Author.create!(id: 1, name: 'Dan Brown')
Book.create!(id: 1, author_id: 1, name: 'Angels & Demons')
Chapter.create!(id: 1, book_id: 1)
Chapter.create!(id: 2, book_id: 1)
Chapter.create!(id: 1, book_id: 1, title: 'Chapter 1')
Chapter.create!(id: 2, book_id: 1, title: 'Chapter 2')
end

context 'with full table' do
Expand Down Expand Up @@ -186,6 +186,40 @@
it 'fetches associated has_many' do
expect(graph.call['chapters'].ids).to eq Set.new([1, 2])
end

context 'with has_many scope' do
let(:manifest) do
TableSaw::Manifest.new(
'tables' => [
{ 'table' => 'books', 'query' => 'select id from books where id = 1' }
],
'has_many' => {
'books' => [{ 'chapters' => { 'scope' => "title = 'Chapter 1'" } }]
}
)
end

it 'fetches associated has_many' do
expect(graph.call['chapters'].ids).to eq Set.new([1])
end
end

context 'with has_many limit' do
let(:manifest) do
TableSaw::Manifest.new(
'tables' => [
{ 'table' => 'books', 'query' => 'select id from books where id = 1' }
],
'has_many' => {
'books' => [{ 'chapters' => { 'limit' => 1 } }]
}
)
end

it 'fetches associated has_many' do
expect(graph.call['chapters'].ids).to(satisfy { |v| Set.new([1, 2]).superset?(v) })
end
end
end

context 'when scoped to partial' do
Expand Down

0 comments on commit 09fd8d2

Please sign in to comment.