Skip to content

Instantly share code, notes, and snippets.

@lloeki
Last active August 15, 2023 18:11
Show Gist options
  • Save lloeki/2bc0ece35b2ba42b681d to your computer and use it in GitHub Desktop.
Save lloeki/2bc0ece35b2ba42b681d to your computer and use it in GitHub Desktop.
Arel composition (without ActiveRecord)
# rebuttal of http://www.try-alf.org/blog/2013-10-21-relations-as-first-class-citizen
require 'arel'
require 'sqlite3'
require 'pry'
def suppliers
@suppliers ||= Arel::Table.new(:suppliers)
end
def cities
@cities ||= Arel::Table.new(:cities)
end
def suppliers_in(city)
suppliers.project(suppliers[:sid], suppliers[:name], suppliers[:city])
.where(suppliers[:city].eq city)
end
def with_country(operand)
operand.join(cities).project(cities[:country])
end
# with_country(suppliers_in('Paris')).to_sql
# => "SELECT `suppliers`.`sid`, `suppliers`.`name`, `suppliers`.`city`, `cities`.`country` FROM `suppliers` INNER JOIN `cities` WHERE `suppliers`.`city` = \"Paris\""
# minimal hack of a sqlite adapter
Column = Struct.new(:name, :default, :cast_type, :sql_type, :null)
module SchemaCache
def schema_cache
self
end
def table_exists?(name)
tables.include?(name)
end
def tables
@tables ||= execute(<<-SQL).flatten
SELECT name FROM sqlite_master
WHERE type = 'table' AND NOT name = 'sqlite_sequence'
SQL
end
def columns(table_name)
schema = execute(<<-SQL)
PRAGMA table_info(#{table_name})
SQL
@columns ||= schema.map do |_, name, type, not_null, default, _|
Column.new(name, default == 'null' ? nil : default, String, type, not_null == 0)
end
end
def columns_hash
@columns_hash ||= Hash.new do |h, table_name|
h[table_name] = Hash[columns(table_name).map { |col|
[col.name, col]
}]
end
end
end
module Quotes
def quote_table_name(name)
"`#{name}`"
end
def quote_column_name(name)
"`#{name}`"
end
def quote(value, column)
"\"#{value}\""
end
end
module Visitable
def visitor
@visitor ||= Arel::Visitors::SQLite.new(self)
end
end
$connection = SQLite3::Database.new(':memory:')
$connection.extend SchemaCache
$connection.extend Quotes
$connection.extend Visitable
$connection.execute(<<-SQL)
CREATE TABLE suppliers
(sid int, name varchar(255), status int, city varchar(255))
SQL
$connection.execute(<<-SQL)
CREATE TABLE cities
(city varchar(255), country varchar(255))
SQL
Arel::Table.engine = Class.new do
def connection
$connection
end
end.new
binding.pry
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment