Created
September 12, 2013 06:49
-
-
Save rummelonp/6533760 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
# -*- 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