From 77c44a0b2d5cb9ca06b954a1275d6511ee075479 Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Mon, 27 Feb 2017 19:18:03 +0100 Subject: [PATCH] Going public --- Gemfile | 7 ++ Gemfile.lock | 41 +++++++++ LICENSE | 19 +++++ README.md | 37 ++++++++ lib/rebel.rb | 3 + lib/rebel/sql.rb | 213 +++++++++++++++++++++++++++++++++++++++++++++++ rebel.gemspec | 11 +++ 7 files changed, 331 insertions(+) create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 LICENSE create mode 100644 README.md create mode 100644 lib/rebel.rb create mode 100644 lib/rebel/sql.rb create mode 100644 rebel.gemspec diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..4c64125 --- /dev/null +++ b/Gemfile @@ -0,0 +1,7 @@ +source 'https://rubygems.org' + +gemspec + +gem 'rubocop' +gem 'minitest' +gem 'pry' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..4a23efa --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,41 @@ +PATH + remote: . + specs: + rebel (0.1.0) + +GEM + remote: https://rubygems.org/ + specs: + ast (2.3.0) + coderay (1.1.1) + method_source (0.8.2) + minitest (5.10.1) + parser (2.4.0.0) + ast (~> 2.2) + powerpack (0.1.1) + pry (0.10.4) + coderay (~> 1.1.0) + method_source (~> 0.8.1) + slop (~> 3.4) + rainbow (2.2.1) + rubocop (0.47.1) + parser (>= 2.3.3.1, < 3.0) + powerpack (~> 0.1) + rainbow (>= 1.99.1, < 3.0) + ruby-progressbar (~> 1.7) + unicode-display_width (~> 1.0, >= 1.0.1) + ruby-progressbar (1.8.1) + slop (3.6.0) + unicode-display_width (1.1.3) + +PLATFORMS + ruby + +DEPENDENCIES + minitest + pry + rebel! + rubocop + +BUNDLED WITH + 1.14.5 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..682c24b --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2016 Loic Nageleisen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1107aed --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# Rebel - Ruby-flavored SQL + +SQL-flavored Ruby. + +``` +You've been fighting yet another abstraction... +Aren't you fed up with object-relation magic? +But wait, here comes a humongous migration. +Is ActiveRecord making you sick? +To hell with that monstrous ARel expression! +Tell the truth, you were just wishing +That it was as simple as a here-string. +But could it keep some Ruby notation +Instead of silly interpolations? +Stop the menace, time for a revolution! + +Rebel! + +No magic +No bullshit +No layers +No wrappers +No smarty-pants +No weird stance +No sexy +No AST +No lazy loading +No crazy mapping +No pretense +No nonsense + +What you write is what you get: readable and obvious +What you write is what you meant: tasty and delicious + +Wait, it doesn't execute!? +Just use your fave client gem, isn't that cute? +``` diff --git a/lib/rebel.rb b/lib/rebel.rb new file mode 100644 index 0000000..32b7e45 --- /dev/null +++ b/lib/rebel.rb @@ -0,0 +1,3 @@ +module Rebel; end + +require 'rebel/sql' diff --git a/lib/rebel/sql.rb b/lib/rebel/sql.rb new file mode 100644 index 0000000..3d8830c --- /dev/null +++ b/lib/rebel/sql.rb @@ -0,0 +1,213 @@ +module Rebel::SQL + attr_reader :conn + + def exec(query) + conn.exec(query) + end + + def create_table(table_name, desc) + exec(SQL.create_table(table_name, desc)) + end + + def select(*fields, from: nil, where: nil, inner: nil, left: nil, right: nil) + exec(SQL.select(*fields, + from: from, + where: where, + inner: inner, + left: left, + right: right)) + end + + def insert_into(table_name, *rows) + exec(SQL.insert_into(table_name, *rows)) + end + + def update(table_name, set: nil, where: nil, inner: nil, left: nil, right: nil) + exec(SQL.update(table_name, set: set, where: where, inner: inner, left: left, right: right)) + end + + def delete_from(table_name, where: nil, inner: nil, left: nil, right: nil) + exec(SQL.delete_from(table_name, where: where, inner: inner, left: left, right: right)) + end + + def truncate(table_name) + exec(SQL.truncate(table_name)) + end + + def count(*n) + SQL.count(*n) + end + + def join(table, on: nil) + SQL.join(table, on: on) + end + + def outer_join(table, on: nil) + SQL.outer_join(table, on: on) + end + + class Raw < String + def as(n) + Raw.new(self + " AS #{SQL.name(n)}") + end + + def as?(n) + n ? as(n) : self + end + + def on(clause) + Raw.new(self + " ON #{SQL.and_clause(clause)}") + end + + def on?(clause) + clause ? on(clause) : self + end + end + + class << self + def raw(str) + Raw.new(str) + end + + def create_table(table_name, desc) + <<-SQL + CREATE TABLE #{SQL.name(table_name)} ( + #{SQL.list(desc.map { |k, v| "#{SQL.name(k)} #{v}" })} + ); + SQL + end + + def select(*fields, from: nil, where: nil, inner: nil, left: nil, right: nil) + <<-SQL + SELECT #{names(*fields)} FROM #{name(from)} + #{SQL.inner?(inner)} + #{SQL.left?(left)} + #{SQL.right?(right)} + #{SQL.where?(where)}; + SQL + end + + def insert_into(table_name, *rows) + <<-SQL + INSERT INTO #{SQL.name(table_name)} (#{SQL.names(*rows.first.keys)}) + VALUES #{SQL.list(rows.map { |r| "(#{SQL.values(*r.values)})" })}; + SQL + end + + def update(table_name, set: nil, where: nil, inner: nil, left: nil, right: nil) + fail ArgumentError if set.nil? + + <<-SQL + UPDATE #{SQL.name(table_name)} + SET #{SQL.assign_clause(set)} + #{SQL.inner?(inner)} + #{SQL.left?(left)} + #{SQL.right?(right)} + #{SQL.where?(where)}; + SQL + end + + def delete_from(table_name, where: nil, inner: nil, left: nil, right: nil) + <<-SQL + DELETE FROM #{SQL.name(table_name)} + #{SQL.inner?(inner)} + #{SQL.left?(left)} + #{SQL.right?(right)} + #{SQL.where?(where)}; + SQL + end + + def truncate(table_name) + <<-SQL + TRUNCATE #{SQL.name(table_name)}; + SQL + end + + ## Functions + + def count(*n) + raw("COUNT(#{names(*n)})") + end + + def join(table, on: nil) + raw("JOIN #{name(table)}").on?(on) + end + + def outer_join(table, on: nil) + raw("OUTER JOIN #{name(table)}").on?(on) + end + + ## Support + + def name(name) + return name if name.is_a?(Raw) + return raw('*') if name == '*' + + name.to_s.split('.').map { |e| "\"#{e}\"" }.join('.') + end + + def names(*names) + list(names.map { |k| name(k) }) + end + + def list(*items) + items.join(', ') + end + + def value(v) + case v + when Raw then v + when String then raw "'#{v.tr("'", "''")}'" + when Fixnum then raw v.to_s + when nil then raw 'NULL' + else fail NotImplementedError, v.inspect + end + end + + def values(*values) + list(values.map { |v| value(v) }) + end + + def name_or_value(item) + item.is_a?(Symbol) ? name(item) : value(item) + end + + def equal(l, r) + "#{name_or_value(l)} = #{name_or_value(r)}" + end + + def assign_clause(clause) + list(clause.map { |k, v| equal(k, v) }) + end + + def and_clause(clause) + return clause if clause.is_a?(Raw) || clause.is_a?(String) + + clause.map do |e| + case e + when Array then "#{name(e[0])} = #{name_or_value(e[1])}" + when Raw, String then e + else fail NotImplementedError, e.class + end + end.join(' AND ') + end + + def where?(clause) + return "WHERE #{clause}" if clause.is_a?(Raw) || clause.is_a?(String) + + (clause && clause.any?) ? "WHERE #{SQL.and_clause(clause)}" : nil + end + + def inner?(join) + join ? "INNER #{join}" : nil + end + + def left?(join) + join ? "LEFT #{join}" : nil + end + + def right?(join) + join ? "RIGHT #{join}" : nil + end + end +end diff --git a/rebel.gemspec b/rebel.gemspec new file mode 100644 index 0000000..61fa0e2 --- /dev/null +++ b/rebel.gemspec @@ -0,0 +1,11 @@ +Gem::Specification.new do |s| + s.name = 'rebel' + s.version = '0.1.0' + s.licenses = ['MIT'] + s.summary = "Fight against the Object tyranny" + s.description = "Write SQL queries in Ruby, or is it the other way around?" + s.authors = ["Loic Nageleisen"] + s.email = 'loic.nageleisen@gmail.com' + s.files = ["lib/**/*.rb"] + s.homepage = 'https://github.com/lloeki/rebel.git' +end