Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add initial support for Query Objects
A new main component of Rectify is the Query Object. It allows a database query to be encapsulated into a class. This allows logic that is specific to the query as well as the query itself to be removed from the ActiveRecord model as another strategy to reducing their size. Query Objects need to be derived from `Rectify::Query` and must implement the `query` method that returns either an `ActiveRecord::Relation` or an array (via `ActiveRecord::Quering#find_by_sql`) This commit also adds a simple way to stub Query Objects with the `stub_query` RSpec helper. See the readme for full details of how to use Query Objects in a Rails app.
- Loading branch information
Showing
26 changed files
with
860 additions
and
37 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
*.gem | ||
*.sqlite3 |
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,50 @@ | ||
# Stolen from https://gist.github.com/schickling/6762581 | ||
# Thank you <3 | ||
require "yaml" | ||
require "active_record" | ||
|
||
namespace :db do | ||
db_config = YAML.load(File.open("spec/config/database.yml")) | ||
|
||
desc "Migrate the database" | ||
task :migrate do | ||
ActiveRecord::Base.establish_connection(db_config) | ||
ActiveRecord::Migrator.migrate("spec/db/migrate") | ||
Rake::Task["db:schema"].invoke | ||
puts "Database migrated." | ||
end | ||
|
||
desc "Create a db/schema.rb file that is portable against any supported DB" | ||
task :schema do | ||
ActiveRecord::Base.establish_connection(db_config) | ||
require "active_record/schema_dumper" | ||
filename = "spec/db/schema.rb" | ||
File.open(filename, "w:utf-8") do |file| | ||
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file) | ||
end | ||
end | ||
end | ||
|
||
namespace :g do | ||
desc "Generate migration" | ||
task :migration do | ||
name = ARGV[1] || fail("Specify name: rake g:migration your_migration") | ||
timestamp = Time.now.strftime("%Y%m%d%H%M%S") | ||
folder = "../spec/db/migrate" | ||
path = File.expand_path("#{folder}/#{timestamp}_#{name}.rb", __FILE__) | ||
|
||
migration_class = name.split("_").map(&:capitalize).join | ||
|
||
File.open(path, "w") do |file| | ||
file.write <<-EOF.strip_heredoc | ||
class #{migration_class} < ActiveRecord::Migration | ||
def change | ||
end | ||
end | ||
EOF | ||
end | ||
|
||
puts "Migration #{path} created" | ||
abort # needed stop other tasks | ||
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,11 @@ | ||
module Rectify | ||
class UnableToComposeQueries < StandardError | ||
def initialize(query, other) | ||
super( | ||
"Unable to composite queries #{query.class.name} and " \ | ||
"#{other.class.name}. You cannot compose queries where #query " \ | ||
"returns an ActiveRecord::Relation in one and an array in the other." | ||
) | ||
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
module Rectify | ||
module SqlQuery | ||
def query | ||
model.find_by_sql([sql, params]) | ||
end | ||
end | ||
|
||
class Query | ||
def initialize(scope = ActiveRecord::NullRelation) | ||
@scope = scope | ||
end | ||
|
||
def query | ||
@scope | ||
end | ||
|
||
def |(other) | ||
if relation? && other.relation? | ||
Rectify::Query.new(cached_query.merge(other.cached_query)) | ||
elsif eager? && other.eager? | ||
Rectify::Query.new(cached_query | other.cached_query) | ||
else | ||
fail UnableToComposeQueries.new(self, other) | ||
end | ||
end | ||
|
||
def count | ||
cached_query.count | ||
end | ||
|
||
def first | ||
cached_query.first | ||
end | ||
|
||
def each(&block) | ||
cached_query.each(&block) | ||
end | ||
|
||
def exists? | ||
return cached_query.exists? if relation? | ||
|
||
cached_query.present? | ||
end | ||
|
||
def none? | ||
!exists? | ||
end | ||
|
||
def to_a | ||
cached_query.to_a | ||
end | ||
|
||
def relation? | ||
cached_query.is_a?(ActiveRecord::Relation) | ||
end | ||
|
||
def eager? | ||
cached_query.is_a?(Array) | ||
end | ||
|
||
def cached_query | ||
@cached_query ||= query | ||
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
require "rectify/rspec/stub_query" | ||
require "rectify/rspec/helpers" | ||
require "rectify/rspec/matchers" |
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,10 @@ | ||
module Rectify | ||
module RSpec | ||
module Helpers | ||
def stub_query(query_class, options = {}) | ||
results = options.fetch(:results, []) | ||
allow(query_class).to receive(:new) { StubQuery.new(results) } | ||
end | ||
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
require "rspec/expectations" | ||
|
||
module Rectify | ||
module DatabaseReporting | ||
SQL_TO_IGNORE = / | ||
pg_table| | ||
pg_attribute| | ||
pg_namespace| | ||
current_database| | ||
information_schema| | ||
^TRUNCATE TABLE| | ||
^ALTER TABLE| | ||
^BEGIN| | ||
^COMMIT| | ||
^ROLLBACK| | ||
^RELEASE| | ||
^SAVEPOINT| | ||
^SHOW| | ||
^PRAGMA | ||
/xi | ||
end | ||
end | ||
|
||
RSpec::Matchers.define :make_database_queries_of do |expected| | ||
supports_block_expectations | ||
|
||
queries = [] | ||
|
||
match do |proc| | ||
ActiveSupport::Notifications | ||
.subscribe("sql.active_record") do |_, _, _, _, query| | ||
sql = query[:sql] | ||
|
||
unless Rectify::DatabaseReporting::SQL_TO_IGNORE.match(sql) | ||
queries << sql | ||
end | ||
end | ||
|
||
proc.call | ||
|
||
queries.size == expected | ||
end | ||
|
||
failure_message do |_| | ||
all_queries = queries.join("\n") | ||
|
||
"expected the number of queries to be #{expected} " \ | ||
"but there were #{queries.size}.\n\n" \ | ||
"Here are the queries that were made:\n\n#{all_queries}" | ||
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
module Rectify | ||
module RSpec | ||
class StubQuery < Query | ||
def initialize(results) | ||
@results = Array(results) | ||
end | ||
|
||
def query | ||
@results | ||
end | ||
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
module Rectify | ||
VERSION = "0.2.0" | ||
VERSION = "0.3.0" | ||
end |
Oops, something went wrong.