Skip to content

Instantly share code, notes, and snippets.

@pdswan
Created September 10, 2013 06:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pdswan/6505651 to your computer and use it in GitHub Desktop.
Save pdswan/6505651 to your computer and use it in GitHub Desktop.
Experimentation with making a wrapper for ActiveRecord::Relation that can be used like any other enumerable. Starting with select.
class ActiveRecordEnumerable
def initialize(relation)
@relation = relation
end
def all
relation.all
end
def select(&block)
scope = block.call(ScopeBuilder.new)
self.class.new(scope.call(relation))
end
private
attr_reader :relation
class ScopeBuilder
def method_missing(attr_name)
ConditionBuilder.new(attr_name)
end
end
class ConditionBuilder < Struct.new(:attr_name)
def ==(value)
Conditions::Equality.new(attr_name, value)
end
def in(value)
Conditions::In.new(attr_name, value)
end
def !=(value)
Conditions::Comparison.new(attr_name, value, :not_eq)
end
def <=(value)
Conditions::Comparison.new(attr_name, value, :lte)
end
def >=(value)
Conditions::Comparison.new(attr_name, value, :gte)
end
def <(value)
Conditions::Comparison.new(attr_name, value, :lt)
end
def >(value)
Conditions::Comparison.new(attr_name, value, :gt)
end
end
module Conditions
class Equality < Struct.new(:attr_name, :attr_value)
def call(relation)
relation.where(attr_name => attr_value)
end
end
class In < Equality; end
class Comparison < Struct.new(:attr_name, :attr_value, :comparison_method)
def call(relation)
relation.where(where_condition(relation))
end
private
def where_condition(relation)
relation.
arel_table[attr_name].
public_send(comparison_method, attr_value)
end
end
end
end
require 'pry'
require 'active_record'
describe ActiveRecordEnumerable do
describe "#all" do
let(:relation) { double("ActiveRecord::Relation") }
let(:all_records) { double("All records") }
let(:enumerable) { ActiveRecordEnumerable.new(relation) }
it "proxies through the relation" do
relation.stub(:all).and_return(all_records)
expect(enumerable.all).to eq(all_records)
end
end
end
describe ActiveRecordEnumerable do
describe "#select" do
let(:relation) { double("ActiveRecord::Relation", arel_table: relation_arel_table) }
let(:relation_arel_table) { double("ArelTable") }
let(:scoped_relation) { double("ActiveRecord::Relation scoped to name") }
let!(:enumerable) { ActiveRecordEnumerable.new(relation) }
let(:scoped_enumerable) { double("ActiveRecordEnumerable scoped to name") }
before do
ActiveRecordEnumerable.stub(:new).with(scoped_relation).and_return(scoped_enumerable)
end
describe "with ==" do
before do
relation.stub(:where).with(name: 'Billy Bob').and_return(scoped_relation)
end
it "should return a new relation scoped to the correct conditions" do
expect(enumerable.select { |foo| foo.name == 'Billy Bob' }).to eq(scoped_enumerable)
end
end
describe "in" do
let(:array) { double("Array") }
before do
relation.stub(:where).with(name: array).and_return(scoped_relation)
end
it "should return a new relation scoped to the correct conditions" do
expect(enumerable.select { |foo|
foo.name.in(array)
}).to eq(scoped_enumerable)
end
end
describe "comparison operators" do
let(:arel_attribute) { double("Arel::Attributes::Attribute") }
let(:date) { double("Date") }
let(:arel_node) { double("Arel::Node") }
before do
relation_arel_table.stub(:[]).with(:created_at).and_return(arel_attribute)
arel_attribute.stub(arel_comparison_method).with(date).and_return(arel_node)
relation.stub(:where).with(arel_node).and_return(scoped_relation)
end
describe "<=" do
let(:arel_comparison_method) { :lte }
it "should return a new relation scoped to the correct conditions" do
expect(enumerable.select { |foo| foo.created_at <= date }).to eq(scoped_enumerable)
end
end
describe ">=" do
let(:arel_comparison_method) { :gte }
it "should return a new relation scoped to the correct conditions" do
expect(enumerable.select { |foo| foo.created_at >= date }).to eq(scoped_enumerable)
end
end
describe "<" do
let(:arel_comparison_method) { :lt }
it "should return a new relation scoped to the correct conditions" do
expect(enumerable.select { |foo| foo.created_at < date }).to eq(scoped_enumerable)
end
end
describe ">" do
let(:arel_comparison_method) { :gt }
it "should return a new relation scoped to the correct conditions" do
expect(enumerable.select { |foo| foo.created_at > date }).to eq(scoped_enumerable)
end
end
describe "!=" do
let(:arel_comparison_method) { :not_eq }
it "should return a new relation scoped to the correct conditions" do
expect(enumerable.select { |foo| foo.created_at != date }).to eq(scoped_enumerable)
end
end
end
end
end
describe ActiveRecordEnumerable do
class CreateFoos < ActiveRecord::Migration
self.verbose = false
def up
create_table(:foos) do |t|
t.column :name, :string
t.column :created_at, :datetime
end
end
def down
drop_table(:foos)
end
end
class Foo < ActiveRecord::Base; end
context "integration" do
before do
ActiveRecord::Base.establish_connection(
adapter: 'mysql2',
host: 'localhost',
username: 'root',
password: '',
database: 'active_record_enumerable_test'
)
end
before do
CreateFoos.migrate(:up)
end
after do
CreateFoos.migrate(:down)
end
describe ".scoped" do
subject { Foo.scoped }
it "should respond to :arel_table" do
expect(Foo.scoped).to respond_to(:arel_table)
end
end
#describe "pry" do
# it "should pry" do
# binding.pry
# end
#end
end
end
source 'https://rubygems.org'
gem 'activerecord', '~> 3.2'
gem 'activerecord-mysql2-adapter'
group :test do
gem 'rspec'
gem 'pry'
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment