Skip to content

Instantly share code, notes, and snippets.

@kml
Last active August 29, 2015 14:25
Show Gist options
  • Save kml/ce164e1e0c1bde2ab96d to your computer and use it in GitHub Desktop.
Save kml/ce164e1e0c1bde2ab96d to your computer and use it in GitHub Desktop.
Mongoid::Extensions::StrictDocument
# encoding: utf-8
# https://gist.github.com/kml/ce164e1e0c1bde2ab96d
require "mongoid"
module Mongoid
module Extensions
module DefaultScopeOnlyId
def self.included(base)
base.class_eval do
default_scope ->{ only(:id) }
end
end
end
module ScopeAllFields
def self.included(base)
base.class_eval do
scope :all_fields, ->{ only(attribute_names) }
end
end
end
module DefaultScopeAllFields
def self.included(base)
base.class_eval do
default_scope ->{ all_fields }
end
end
end
module ForbidQueryWithoutSelection
def self.included(base)
base.class_eval do
after_initialize do
selection = Mongoid::Threaded.selection(criteria_instance_id)
unless selection
raise "Query without selection is not allowed"
end
end
end
end
end
module ForbidUnknownFieldsInSelection
def self.included(base)
base.class_eval do
after_initialize do
selection = Mongoid::Threaded.selection(criteria_instance_id)
if selection
selection.each_key do |field|
unless self.class.fields[field]
raise NameError, "Undefined field `#{field}' selected for class `#{self.class}'"
end
end
end
end
end
end
end
module ForbidNotSelectedAccessors
def self.included(base)
base.class_eval do
after_initialize do
selection = Mongoid::Threaded.selection(criteria_instance_id)
if selection
self.class.fields.each_key do |field|
next if field == "_id"
unless selection[field]
forbid_not_selected_accessor(field)
end
end
end
end
end
end
private
def forbid_not_selected_accessor(name)
forbid_not_selected_reader(name)
forbid_not_selected_writer(name)
end
def forbid_not_selected_reader(name)
class_eval <<-READER
def #{name}
raise NameError, "Accessing reader for not selected field `#{name}' in class `#{self.class}'"
end
READER
end
def forbid_not_selected_writer(name)
class_eval <<-WRITER
def #{name}=(value)
raise NameError, "Accessing writer for not selected field `#{name}' in class `#{self.class}'"
end
WRITER
end
end
module ForbidDynamicAccessors
def define_dynamic_reader(name)
forbid_dynamic_accessor(name)
super(name)
end
def define_dynamic_writer(name)
forbid_dynamic_accessor(name)
super(name)
end
def forbid_dynamic_accessor(name)
raise NameError, "Undefined field `#{name}' for class `#{self.class}'. Dynamic readers are not allowed"
end
end
module StrictDocument
def self.included(base)
base.send(:include, DefaultScopeOnlyId)
base.send(:include, ScopeAllFields)
#base.send(:include, ForbidQueryWithoutSelection)
base.send(:include, ForbidUnknownFieldsInSelection)
base.send(:include, ForbidNotSelectedAccessors)
base.send(:include, ForbidDynamicAccessors)
end
end
end
end
=begin
class Test1
include Mongoid::Document
include Mongoid::Extensions::StrictDocument
field :name
field :age
end
Test1.delete_all
Test1.create(name: "john", age: 1)
Test1.first
# ok but only id field is selected
Test1.all_fields.first
# all fields selected (id, name, age)
Test1.only([]).first
# uses default_scope (only id field is selected)
Test1.only(:name).first
# id and name selected
Test1.only(:name, :test).first
# NameError: Undefined field `test' selected for class `Test1'
t = Test1.only(:name).first
t.age
# NameError: Accessing reader for not selected field `age' in class `Test1'
t.age = 1
# NameError: Accessing writer for not selected field `age' in class `Test1'
Test1.collection.insert({name: "x", address: "u"})
t = Test1.find_by(name: "x")
t.address
# NoMethodError: undefined method `address' for #<Test1 _id: 55aff77c0b67ad2f10f4d81a, name: nil, age: nil>
=end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment