Skip to content

Instantly share code, notes, and snippets.

@eliotsykes
Last active December 15, 2015 10:24
Show Gist options
  • Save eliotsykes/59b5595581132c000e73 to your computer and use it in GitHub Desktop.
Save eliotsykes/59b5595581132c000e73 to your computer and use it in GitHub Desktop.
Rails BaseScopes and subclasses for tidying up excessive SQL scopes in models
+++ b/app/scopes/base_scopes.rb
+class BaseScopes < SimpleDelegator
+
+ def initialize
+ super self.class.relation
+ end
+
+ def self.method_missing(method_name, *args)
+ method = self.new.method(method_name)
+ method.to_proc
+ end
+
+ def self.relation
+ without_scopes_suffix = self.name.sub(/Scopes\z/, '')
+ without_scopes_suffix.constantize
+ end
+
+end
+++ b/app/scopes/event_scopes.rb
+class EventScopes < BaseScopes
+
+ def all_scope
+ joins("LEFT JOIN courses on events.course_id = courses.id").
+ joins("LEFT JOIN (
+ SELECT participants.event_id, participants.email
+ FROM (
+ SELECT event_id, MIN(id) AS first_instructor_id
+ FROM participants
+ WHERE type = 'Instructor'
+ GROUP BY event_id
+ ) AS event_id_to_first_instructor_id
+ JOIN participants
+ ON event_id_to_first_instructor_id.event_id = participants.event_id
+ AND event_id_to_first_instructor_id.first_instructor_id = participants.id
+ ) AS instructors
+ ON instructors.event_id = events.id").
+ select("events.*,
+ courses.name as course_name,
+ events.name as event_name,
+ instructors.email as first_instructor_email,
+ events.updated_at as event_updated_at
+ ")
+ end
+
+ def foo
+ where("start_at > ?", Time.zone.now)
+ end
+
+ def bar
+ where("end_at < ?", Time.zone.now)
+ end
+
+end
@eliotsykes
Copy link
Author

Then in model:

class Event < ActiveRecord::Base
  ...
  scope :foo, EventScopes.foo
  scope :bar, EventScopes.bar
  scope :search, EventScopes.all_scope
  ...
end

@eliotsykes
Copy link
Author

For background, read this article: http://craftingruby.com/posts/2015/06/29/query-objects-through-scopes.html

Solution derived from the above article plus an aim to reduce the boilerplate necessary in the Event and EventScopes class, such as avoiding needing to remember to use @relation in EventScopes and avoid adding an include or extend to Event. This solution minimizes boilerplate whilst still retaining the discoverability of preserving scope :foo, ... declarations in the model class.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment