diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8a73ade --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +*.gem +*.rbc +.bundle +.config +.yardoc +Gemfile.lock +InstalledFiles +_yardoc +coverage +doc/ +lib/bundler/man +pkg +rdoc +spec/reports +test/tmp +test/version_tmp +tmp +*.bundle +*.so +*.o +*.a +*.swp +*.swo +mkmf.log diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c059350 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,14 @@ +language: ruby +rvm: + - '1.9.3' + - '2.0.0' + - '2.1.5' + - '2.2.1' + +addons: + postgresql: '9.3' + +env: + - BUILDER=travis +before_script: + - psql -c 'create database travis_ci_test;' -U postgres diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4239464 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +# SqlQuery change log + +## 0.0.1 / Unreleased + +* [Added] + +* [Deprecated] + +* [Removed] + +* [Fixed] + diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..fab945d --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +# Specify your gem's dependencies in sql_query.gemspec +gemspec diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..6195e38 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (c) 2014 Szymon FrÄ…cczak + +MIT License + +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..55b72f0 --- /dev/null +++ b/README.md @@ -0,0 +1,83 @@ +[![Gem Version](https://badge.fury.io/rb/sql_query.svg)](http://badge.fury.io/rb/sql_query) +[![Dependency Status](https://gemnasium.com/sufleR/sql_query.svg)](https://gemnasium.com/sufleR/sql_query) +[![Code Climate](https://codeclimate.com/github/sufleR/sql_query/badges/gpa.svg)](https://codeclimate.com/github/sufleR/sql_query) +[![Test Coverage](https://codeclimate.com/github/sufleR/sql_query/badges/coverage.svg)](https://codeclimate.com/github/sufleR/sql_query) +[![Build Status](https://travis-ci.org/sufleR/sql_query.svg?branch=master)](https://travis-ci.org/sufleR/sql_query) + +# SqlQuery + +Ruby gem to load SQL queries from `.sql.erb` templates using ERB. + +It makes working with pure SQL easier with syntax highlighting. +Let's you clean your Ruby code from SQL strings. + +## Installation + +Add this line to your application's Gemfile: + + gem 'sql_query' + +And then execute: + + $ bundle + +Or install it yourself as: + + $ gem install sql_query + +## Usage + +Create SQL query in file in `app/sql_queries` directory + +```sql +# app/sql_queries/get_player_by_email.sql.erb +SELECT * +FROM players +WHERE email = <%= quote @email %> +``` + +You can use SQL like this: + +```shell +> query = SqlQuery.new(sql_name: :get_player_by_email, email: 'e@mail.dev') + +> query.execute + (0.6ms) SELECT * FROM players WHERE email = 'e@mail.dev' +=> [] + +> query.explain +=> EXPLAIN for: +SELECT * +FROM players +WHERE email = 'e@mail.dev' + + QUERY PLAN +---------------------------------------------------------- + Seq Scan on players (cost=0.00..2.14 rows=1 width=5061) + Filter: ((email)::text = 'e@mail.dev'::text) +(2 rows) + +> query.sql +=> "SELECT *\nFROM players\nWHERE email = 'e@mail.dev'\n" + +> query.pretty_sql +=> SELECT * +FROM players +WHERE email = 'e@mail.dev' +``` + +### Methods + +- **execute** - executes query and returns result data. + +- **explain** - runs explain for SQL from template +- **sql** - returns SQL string +- **pretty_sql** - return SQL string prettier to read in console + +## Contributing + +1. Fork it ( https://github.com/[my-github-username]/sql_query/fork ) +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create a new Pull Request diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..809eb56 --- /dev/null +++ b/Rakefile @@ -0,0 +1,2 @@ +require "bundler/gem_tasks" + diff --git a/lib/sql_query.rb b/lib/sql_query.rb new file mode 100644 index 0000000..d6f01f2 --- /dev/null +++ b/lib/sql_query.rb @@ -0,0 +1,79 @@ +require 'erb' + +class SqlQuery + attr_reader :connection + + def initialize(options = {}) + prepare_variables(options) + @connection = ActiveRecord::Base.connection + end + + def explain + msg = "EXPLAIN for: \n#{ sql }\n" + msg += connection.explain(sql) + pretty(msg) + end + + def execute + connection.execute(prepared_for_logs).entries + end + + def sql + @sql ||= ERB.new(File.read(path)).result(binding) + end + + def pretty_sql + pretty(sql.dup) + end + + def quote(value) + connection.quote(value) + end + + def prepared_for_logs + sql.gsub(/(\n|\s)+/,' ') + end + + def self.config=(value) + @config = value + end + + def self.config + @config ||= Config.new + end + + def self.configure + yield(config) + end + + class Config + attr_accessor :path + + def initialize + @path = '/app/sql_queries' + end + end + + private + + def pretty(value) + # override inspect to be more human readable from console + # code copy from ActiveRecord + # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/explain.rb#L30 + def value.inspect; self; end + value + end + + def prepare_variables(options) + options.each do |k, v| + instance_variable_set("@#{k}", v) + end + end + + def path + root = defined?(Rails) ? Rails.root.to_s : Dir.pwd + tmp_path = "#{ root }#{self.class.config.path}" + tmp_path += "#{ @sql_path }/#{ @sql_name }.sql.erb" + tmp_path + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..b19fef1 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,42 @@ +if ENV['BUILDER'] == 'travis' + require "codeclimate-test-reporter" + CodeClimate::TestReporter.start +else + require 'simplecov' + SimpleCov.start do + add_filter '/spec/' + end +end + +require 'active_record' +require 'sql_query' +require 'pry' + +SqlQuery.configure do |config| + config.path = '/spec/sql_queries' +end + +RSpec.configure do |config| + config.expect_with :rspec do |expectations| + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + config.mock_with :rspec do |mocks| + mocks.verify_partial_doubles = true + end + + connection = if ENV['BUILDER'] == 'travis' + "postgres://postgres@localhost/travis_ci_test" + else + "postgres://sqlquery:sqlquery@localhost/sqlquery" + end + + ActiveRecord::Base.establish_connection(connection) + + ActiveRecord::Base.connection.execute( + "CREATE TABLE IF NOT EXISTS players (email text);" + ) + + config.order = :random + Kernel.srand config.seed +end diff --git a/spec/sql_queries/get_player_by_email.sql.erb b/spec/sql_queries/get_player_by_email.sql.erb new file mode 100644 index 0000000..e7b8e8f --- /dev/null +++ b/spec/sql_queries/get_player_by_email.sql.erb @@ -0,0 +1,3 @@ +SELECT * +FROM players +WHERE email = <%= quote @email %> diff --git a/spec/sql_query_spec.rb b/spec/sql_query_spec.rb new file mode 100644 index 0000000..bb4bdd5 --- /dev/null +++ b/spec/sql_query_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' + +describe SqlQuery do + + let(:base_options) { { sql_name: :get_player_by_email, email: 'e@mail.dev' } } + let(:options) { base_options } + let(:query) { described_class.new(options) } + + describe '#sql' do + it 'returns query string' do + expect(query.sql).to eq "SELECT *\nFROM players\nWHERE email = 'e@mail.dev'\n" + end + end + + describe '#pretty_sql' do + it 'returns query string' do + expect(query.pretty_sql).to eq "SELECT *\nFROM players\nWHERE email = 'e@mail.dev'\n" + end + end + + describe '#explain' do + let(:explain) { query.explain } + it 'returns explain string' do + expect(explain).to include 'EXPLAIN for:' + expect(explain).to include "FROM players" + expect(explain).to include "WHERE email = 'e@mail.dev'" + expect(explain).to include 'QUERY PLAN' + expect(explain).to include 'Seq Scan on players' + end + end + + describe '#execute' do + before do + ActiveRecord::Base.connection.execute( + "INSERT INTO players (email) VALUES ('e@mail.dev')" + ) + end + after do + ActiveRecord::Base.connection.execute( + "DELETE FROM players" + ) + end + + it 'returns data from database' do + expect(query.execute).to eq [{ 'email' => 'e@mail.dev'}] + end + end +end diff --git a/sql_query.gemspec b/sql_query.gemspec new file mode 100644 index 0000000..e817a6c --- /dev/null +++ b/sql_query.gemspec @@ -0,0 +1,33 @@ +# coding: utf-8 +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) + +Gem::Specification.new do |spec| + spec.name = "sql_query" + spec.version = "0.0.1" + spec.authors = ["sufleR"] + spec.email = ["szymon.fracczak@netguru.co"] + spec.summary = %q{Ruby gem to load and execute SQL queries from `.sql.erb` templates} + spec.description = %q{ + It makes working with pure SQL easier with syntax highlighting. + Let's you clean your Ruby code from SQL strings. + } + spec.homepage = "" + spec.license = "MIT" + + spec.files = `git ls-files -z`.split("\x0") + spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } + spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) + spec.require_paths = ["lib"] + + spec.required_ruby_version = ">= 1.9.3" + + spec.add_dependency "activerecord", ">= 4" + spec.add_development_dependency "bundler", "~> 1.7" + spec.add_development_dependency "rake", "~> 10" + spec.add_development_dependency "rspec", "~> 3.2" + spec.add_development_dependency "pg", "~> 0.18" + spec.add_development_dependency "pry", "~> 0.10" + spec.add_development_dependency "with_model", "~> 1.2" + spec.add_development_dependency "codeclimate-test-reporter", "~> 0.4" +end