Skip to content

Instantly share code, notes, and snippets.

@caleon
Created August 29, 2011 12:16
Show Gist options
  • Save caleon/1178278 to your computer and use it in GitHub Desktop.
Save caleon/1178278 to your computer and use it in GitHub Desktop.
Some of my favorite core extensions for Ruby and Rails used on practically all of my projects these days
# This file meant for globally available, utility methods.
###############################
## Ruby-level Extensions ##
###############################
class Object
def as(&block)
yield self
end
# Logic utilities
def or_else(&block)
self || yield(self)
end
def and_also(&block)
self && yield(self)
end
# Convenience methods for a more idiomatic code than [ "users", "purchases" ].include?(controller_name) so that
# the same can now be written as controller_name.is_included_in?('users', 'purchases')
def is_included_in?(*others)
others.flatten_splat.include?(self)
end
def is_excluded_from?(*others)
others.flatten_splat.exclude?(self)
end
# Similar to is_a? or kind_of? but with an array of possible classes. Returns the matching class or a nil.
def is_one_kind_of?(*klasses)
klasses.flatten_splat.detect {|klass| klass === self }
end
end
module Enumerable
def map_select(value_for_skip = nil)
self.inject([]) do |acc, item|
(value = yield(item)) == value_for_skip ? acc : acc << value
end
end
def map_detect(value_for_no_matching = nil)
self.each { |el| if result = yield(el) then return result end }
value_for_no_matching
end
end
class Set
def not_subset?(set)
set.is_a?(Set) or raise ArgumentError, "value must be a set"
return true if set.size > size
any? { |o| !set.include?(o) }
end
end
class Array
# Compares two arrays to see if the elements are same but simply rearranged.
def rearranges?(*other_ary)
Set.new(self) == Set.new(other_ary.flatten_splat)
end
def subset?(other_ary)
other_ary.is_a?(Array) or raise ArgumentError, "Other array in argument must be an Array"
Set.new(self).subset?(Set.new(other_ary))
end
def is_included_in?(other_ary)
other_ary.is_a?(Array) or raise ArgumentError, "Other array in argument must be an Array"
super || subset?(other_ary)
end
# Careful with this. This means 4 out of 5 elements can be the same but if not all of this self Array object are a part of other_ary,
# this returns true, as in "Yes, depite having 4 out of 5 in other_ary, I am excluded from the other_ary."
def is_excluded_from?(other_ary)
other_ary.is_a?(Array) or raise ArgumentError, "Other array in argument must be an Array"
super || not_subset?(other_ary)
end
alias_method :include_one?, :include?
def include_all?(*other_ary)
other_ary.flatten_splat!
include_one(other_ary) || superset?(other_ary)
end #alias_method :include?, :include_all?
#############################
## Quick selectors/filters ##
#############################
def select_kind_of(klass); select { |el| el.kind_of?(klass) }; end
def reject_kind_of(klass); reject { |el| el.kind_of?(klass) }; end
def select_kind_of!(klass); select! { |el| el.kind_of?(klass) }; end
def reject_kind_of!(klass); reject! { |el| el.kind_of?(klass) }; end
# flatten_splat! is used for dealing conveniently with the common pattern where a method's arguments has a splat (*) operator,
# but the developer wants to provide the option of the method accepting either a list or an array for the argument. Typically
# dealt with in the following manner:
#
# BEFORE: NOW:
# def do_something(*args) def do_something(*args)
# args = args.shift if args.one? && args.first.is_a?(Array) args.flatten_splat!
# end end
def flatten_splat(with_bang=false)
flatten_splat_needed? ? with_bang ? flatten! : flatten : self
end
def flatten_splat!
flatten_splat(true)
end
def flatten_splat_needed?
self.size == 1 and first.is_a?(Array)
end
private :flatten_splat_needed?
######################################
## Array#extract_options extensions ##
######################################
def extract_options
options_extractable? ? last : {}
end
def extract_options!
options_extractable? ? pop : {}
end
def options_extractable?
last.is_a?(Hash) && last.extractable_options?
end
def extract_options_with_merge(update_hash={})
extract_options_without_merge.merge(update_hash || {})
end; alias_method_chain :extract_options, :merge
def extract_options_with_merge!(update_hash={})
extract_options_without_merge!.merge(update_hash || {})
end; alias_method_chain :extract_options!, :merge
def merge_options(update_hash={})
endex, base_hash = options_extractable? ? [ -2, last ] : [ -1, {} ]
Array[ *self[0..endex], base_hash.merge(update_hash || {}) ]
end; alias_method :merge_opts, :merge_options
def merge_options!(update_hash={})
push(extract_options!(update_hash))
end; alias_method :merge_opts!, :merge_options!
def arguments_and_options(update_hash={})
merge_options(update_hash).args_and_opts!
end
def arguments_and_options!(update_hash={})
[ extract_options_with_merge!(update_hash), self ].rotate
end
[ :args_and_opts!, :args_and_opts_with_merge!, :arguments_and_options_with_merge! ].each do |meth|
alias_method meth[0..-2].intern, :arguments_and_options
alias_method meth, :arguments_and_options!
end
# duplicated logic from argumentation
def constrained_by_archetype_and_method(archetype, method_name)
# all_proc = lambda { |type, m| type != :rest }
limit = [ archetype.method(method_name).parameters.if?(".all? { |type, m| type != :rest }", :else => (1.0/0)).count,
archetype.method(method_name).arity.abs ].max
take(limit.unless?(:finite?) { self.size })
rescue
self
end
private
def method_missing(method_sym, *arguments, &block)
return super unless method_sym =~ /^#{%w(arg(?:ument)?s opt(?:ion)?s).zip(Array.new(2, REGEX[:with_method_name].source)).map(&:join).join('_and_')}!$/
args_and_opts.tap do |argopts|
[ $1, $2 ].each_with_index do |meth, i|
next unless meth.significant?
params = (that_meth = (obj = argopts[-i]).method(meth.intern)).parameters
argopts[-i] = (meth && obj.if?([ :respond_to?, meth.intern ]).send(*(i == 0 ? meth.intern : [ meth.intern, *arguments.constrained_by_archetype_and_method(obj, meth) ])))
end
end # end args_and_opts.tap
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment