-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from GeorgeKaraszi/feature/either_anyof
Add .either_join and .either_order querying methods
- Loading branch information
Showing
10 changed files
with
182 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
# frozen_string_literal: true | ||
|
||
require "ar_outer_joins" | ||
|
||
module ActiveRecordExtended | ||
module QueryMethods | ||
module Either | ||
XOR_FIELD_SQL = "(CASE WHEN %<t1>s.%<c1>s IS NULL THEN %<t2>s.%<c2>s ELSE %<t1>s.%<c1>s END) " | ||
XOR_FIELD_KEYS = %i[t1 c1 t2 c2].freeze | ||
|
||
def either_join(initial_association, fallback_association) | ||
associations = [initial_association, fallback_association] | ||
association_options = xor_field_options_for_associations(associations) | ||
condition__query = xor_field_sql(association_options) + "= #{table_name}.#{primary_key}" | ||
outer_joins(associations).where(Arel.sql(condition__query)) | ||
end | ||
|
||
def either_order(direction, **associations_and_columns) | ||
reflected_columns = map_columns_to_tables(associations_and_columns) | ||
conditional_query = xor_field_sql(reflected_columns) + sort_order_sql(direction) | ||
outer_joins(associations_and_columns.keys).order(Arel.sql(conditional_query)) | ||
end | ||
|
||
private | ||
|
||
def xor_field_sql(options) | ||
XOR_FIELD_SQL % Hash[xor_field_options(options)] | ||
end | ||
|
||
def sort_order_sql(dir) | ||
%w[asc desc].include?(dir.to_s) ? dir.to_s : "asc" | ||
end | ||
|
||
def xor_field_options(options) | ||
str_args = options.flatten.take(XOR_FIELD_KEYS.size).map(&:to_s) | ||
Hash[XOR_FIELD_KEYS.zip(str_args)] | ||
end | ||
|
||
def map_columns_to_tables(associations_and_columns) | ||
if associations_and_columns.respond_to?(:transform_keys) | ||
associations_and_columns.transform_keys { |assc| reflect_on_association(assc).table_name } | ||
else | ||
associations_and_columns.each_with_object({}) do |(assc, value), key_table| | ||
reflect_table = reflect_on_association(assc).table_name | ||
key_table[reflect_table] = value | ||
end | ||
end | ||
end | ||
|
||
def xor_field_options_for_associations(associations) | ||
associations.each_with_object({}) do |association_name, options| | ||
reflection = reflect_on_association(association_name) | ||
options[reflection.table_name] = reflection.foreign_key | ||
end | ||
end | ||
end | ||
end | ||
end | ||
|
||
ActiveRecord::Base.extend(ActiveRecordExtended::QueryMethods::Either) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# frozen_string_literal: true | ||
|
||
require "spec_helper" | ||
|
||
RSpec.describe "Active Record Either Methods" do | ||
let!(:one) { Person.create! } | ||
let!(:two) { Person.create! } | ||
let!(:three) { Person.create! } | ||
let!(:profile_l) { ProfileL.create!(person_id: one.id, likes: 100) } | ||
let!(:profile_r) { ProfileR.create!(person_id: two.id, dislikes: 50) } | ||
|
||
describe ".either_join/2" do | ||
it "Should only only return records that belong to profile L or profile R" do | ||
query = Person.either_join(:profile_l, :profile_r) | ||
expect(query).to include(one, two) | ||
expect(query).to_not include(three) | ||
end | ||
end | ||
|
||
describe ".either_order/2" do | ||
it "Should not exclude anyone who does not have a relationship" do | ||
query = Person.either_order(:asc, profile_l: :likes, profile_r: :dislikes) | ||
expect(query).to include(one, two, three) | ||
end | ||
|
||
it "Should order people based on their likes and dislikes in ascended order" do | ||
query = Person.either_order(:asc, profile_l: :likes, profile_r: :dislikes).where(id: [one.id, two.id]) | ||
expect(query).to match_array([two, one]) | ||
end | ||
|
||
it "Should order people based on their likes and dislikes in descending order" do | ||
query = Person.either_order(:desc, profile_l: :likes, profile_r: :dislikes).where(id: [one.id, two.id]) | ||
expect(query).to match_array([one, two]) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
# frozen_string_literal: true | ||
|
||
require "spec_helper" | ||
|
||
RSpec.describe "Either Methods SQL Queries" do | ||
let(:contains_array_regex) { /\"people\"\.\"tag_ids\" @> '\{1,2\}'/ } | ||
let(:profile_l_outer_join) { /LEFT OUTER JOIN \"profile_ls\" ON \"profile_ls\".\"person_id\" = \"people\".\"id\"/ } | ||
let(:profile_r_outer_join) { /LEFT OUTER JOIN \"profile_rs\" ON \"profile_rs\".\"person_id\" = \"people\".\"id\"/ } | ||
let(:where_join_case) do | ||
"WHERE ((CASE WHEN profile_ls.person_id IS NULL"\ | ||
" THEN profile_rs.person_id"\ | ||
" ELSE profile_ls.person_id END) "\ | ||
"= people.id)" | ||
end | ||
|
||
let(:order_case) do | ||
"ORDER BY "\ | ||
"(CASE WHEN profile_ls.likes IS NULL"\ | ||
" THEN profile_rs.dislikes"\ | ||
" ELSE profile_ls.likes END)" | ||
end | ||
|
||
describe ".either_join/2" do | ||
it "Should contain outer joins on the provided relationships" do | ||
query = Person.either_join(:profile_l, :profile_r).to_sql | ||
expect(query).to match_regex(profile_l_outer_join) | ||
expect(query).to match_regex(profile_r_outer_join) | ||
end | ||
|
||
it "Should contain a case statement that will conditionally alternative between tables" do | ||
query = Person.either_join(:profile_l, :profile_r).to_sql | ||
expect(query).to include(where_join_case) | ||
end | ||
end | ||
|
||
describe ".either_order/2" do | ||
let(:ascended_order) { Person.either_order(:asc, profile_l: :likes, profile_r: :dislikes).to_sql } | ||
let(:descended_order) { Person.either_order(:desc, profile_l: :likes, profile_r: :dislikes).to_sql } | ||
|
||
it "Should contain outer joins on the provided relationships" do | ||
expect(ascended_order).to match_regex(profile_l_outer_join) | ||
expect(ascended_order).to match_regex(profile_r_outer_join) | ||
expect(descended_order).to match_regex(profile_l_outer_join) | ||
expect(descended_order).to match_regex(profile_r_outer_join) | ||
end | ||
|
||
it "Should contain a relational ordering case statement for a relations column" do | ||
expect(ascended_order).to include(order_case) | ||
expect(ascended_order).to end_with("asc") | ||
|
||
expect(descended_order).to include(order_case) | ||
expect(descended_order).to end_with("desc") | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters