Skip to content

Instantly share code, notes, and snippets.

@rummelonp
Created September 12, 2013 06:49
Show Gist options
  • Save rummelonp/6533760 to your computer and use it in GitHub Desktop.
Save rummelonp/6533760 to your computer and use it in GitHub Desktop.
# -*- coding: utf-8 -*-
module ValidationContext
extend ActiveSupport::Concern
included do
before_validation :reflect_context_to_associations
end
# @return [Symbol]
attr_reader :context
# Set context
#
# @param context [Symbol]
# @return [void]
def context=(context)
@context = context
reflect_context_to_associations
end
# Reflect context to all loaded associations
#
# @return [void]
def reflect_context_to_associations
self.class.reflect_on_all_associations.each do |reflection|
association = self.association(reflection.name)
Array.wrap(association.target).each do |record|
if record.respond_to?(:context=)
record.context = context
end
end
end
end
# Evaluate block in given context
#
# @param context
# @yield [object]
# @yieldparam [Object] object
# @return Last evaluated value
# @example
# unless post.with_context(:publish) { |post| post.update_attributes(post_params) }
# render :edit
# end
def with_context(context, &block)
current_context, self.context = self.context, context
yield self
ensure
self.context = current_context
end
module ClassMethods
# Configuration of validation that run in the given context
#
# @overload with_context(*contexts, &block)
# @param contexts [Array<Symbol>]
# @yield [context]
# @yieldparam [ValidationContext::OptionMerger] context
# @overload with_context(*contexts, options, &block)
# @param contexts [Array<Symbol>]
# @param options [Hash]
# @option options [Boolean] :default (true) Run when not given context
# @yield [context]
# @yieldparam [ValidationContext::OptionMerger] context
# @return [void]
# @example
# class Post < ActiveRecord::Base
# validates_presence_of :title
#
# with_context :publish do |context|
# context.validates_presence_of :content
# context.validates_presence_of :published_at
# end
# end
def with_context(*contexts, &block)
options = contexts.last.is_a?(Hash) ? contexts.last.dup : {}
options[:if] = Array.wrap(options[:if])
if options.fetch(:default, true)
options[:if] << ->(o) { o.context.nil? || o.context.in?(contexts) }
else
options[:if] << ->(o) { o.context.in?(contexts) }
end
options.delete(:default)
yield ValidationContext::OptionMerger.new(self, options)
end
end
class OptionMerger
instance_methods.each do |method_name|
undef_method(method_name) if method_name !~ /^(__|instance_eval|class|object_id)/
end
def initialize(context, options)
@context, @options = context, options
end
private
def method_missing(method_name, *args, &block)
if args.last.is_a?(Proc)
proc = args.pop
args << ->(*args) { @options.deep_merge(proc.call(*args)) }
elsif args.last.respond_to?(:to_hash)
options = args.pop
if @options[:if]
options = options.dup
options[:if] = Array.wrap(options[:if])
options[:if].unshift(*@options[:if])
end
args << options
else
args << @options
end
@context.__send__(method_name, *args, &block)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment