Project

sql_runner

0.0
Low commit activity in last 3 years
A long-lived project that still receives updates
SQLRunner allows you to load your queries out of SQL files, without using ORMs.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

Runtime

 Project Readme

SQLRunner

ruby-tests Gem Gem

SQLRunner allows you to load your queries out of SQL files, without using ORMs. Available for PostgreSQL and MySQL.

Installation

Add this line to your application's Gemfile:

gem "sql_runner"

And then execute:

$ bundle

Or install it yourself as:

$ gem install sql_runner

Usage

Run raw queries:

SQLRunner.connect("postgresql:///test?application_name=myapp")

SQLRunner.execute("SELECT 1")
#=> <PG:Result:0x007fdf8d3475f0>

SQLRunner.execute("SELECT 1").values
#=> [["1"]]

SQLRunner.execute("SELECT :number::integer", number: 1).values
#=> [["1"]]

Run SQL files:

SQLRunner.root_dir = "#{__dir__}/sql"

class GetMembers < SQLRunner::Query
  # by default will use root_dir/get_members.{sql,psql}
end

Specify custom SQL:

class GetMembers < SQLRunner::Query
  query "SELECT * FROM members ORDER BY created_at"
end

Specify custom connection:

class GetMembers < SQLRunner::Query
  connect "postgresql:///another_database"
end

Specify other options:

class GetMembers < SQLRunner::Query
  query_name "users" #=> will load root_dir/users.{psql,sql}
  root_dir "/some/path"
end

You can use this with ActiveRecord as well. To make it work, all you need to do is establishing the connection using activerecord:///:

require "active_record"

# You probably won't need this if you're using Rails.
ActiveRecord::Base.establish_connection("postgresql:///database")

# Set the adapter to be based on ActiveRecord.
SQLRunner.connect "activerecord:///"

SQLRunner.execute "SELECT 1"
#=> <PG:Result:0x008adf4d5495b0>

Plugins

Load just one record

class FindUserByEmail < SQLRunner::Query
  plugin :one
  query "SELECT * FROM users WHERE email = :email LIMIT 1"
end

FindUserByEmail.call(email: "john@example.com")
#=> {"id" => 1, "email" => "john@example.com"}

FindUserByEmail.call(email: "invalid")
#=> nil

FindUserByEmail.call!(email: "invalid")
#=> raise exception SQLRunner::RecordNotFound

Use a model

require "virtus"

class UserModel
  include Virtus.model

  attribute :id, String
  attribute :email, String
  attribute :name, String
end

class FindUserByEmail < SQLRunner::Query
  plugin :one
  plugin model: UserModel
  query "SELECT * FROM users WHERE email = :email LIMIT 1"
end

FindUserByEmail.call(email: "john@example.com")
#=> <UserModel:0x007fdf8c2c1280>

Avoid calling .to_a in collections

class FindUsers < SQLRunner::Query
  plugin :many
  query "SELECT * FROM users"
end

FindUsers.call
#=> [{"id" => "1", "email" => "john@example.com"}]

Adding new plugins

First you have to create a class/module that implements the .activate(target, options) class method. The following example overrides the call(**bind_vars) method by using Module.prepend.

module ReverseRecords
  def self.activate(target, options)
    target.singleton_class.prepend self
  end

  def call(**bind_vars)
    super(**bind_vars).to_a.reverse
  end
end

Register the plugin.

SQLRunner::Query.register_plugin :reverse, ReverseRecords

class Users < SQLRunner::Query
  query "SELECT * FROM users ORDER BY created_at ASC"
  plugin :reverse
end

Users.call

If your plugin can receive options, you can call it as plugin reverse: options, where options can be anything (e.g. Hash, Array, Object, etc).

Benchmarks

You won't gain too much performance by using this gem. The idea is making SQL easier to read by extracting complex stuff to their own files. These are the results against ActiveRecord using different wrapping libraries like virtus and dry-types.

Loading just one record:

  sql_runner - find one (raw)      :     5518.6 i/s
  sql_runner - find one (dry-types):     5015.4 i/s - same-ish: difference falls within error
  sql_runner - find one (virtus)   :     4746.2 i/s - 1.16x slower
activerecord - find one            :     3468.5 i/s - 1.59x slower

Loading several records:

  sql_runner - find many (raw)      :     6808.2 i/s
  sql_runner - find many (dry-types):     5251.4 i/s - same-ish: difference falls within error
  sql_runner - find many (virtus)   :     4145.6 i/s - 1.64x slower
activerecord - find many            :     2731.5 i/s - 2.49x slower

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/fnando/sql_runner. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

License

The gem is available as open source under the terms of the MIT License.