initializable.rb 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. require 'tsort'
  2. module Rails
  3. module Initializable
  4. def self.included(base)
  5. base.extend ClassMethods
  6. end
  7. class Initializer
  8. attr_reader :name, :block
  9. def initialize(name, context, options, &block)
  10. options[:group] ||= :default
  11. @name, @context, @options, @block = name, context, options, block
  12. end
  13. def before
  14. @options[:before]
  15. end
  16. def after
  17. @options[:after]
  18. end
  19. def belongs_to?(group)
  20. @options[:group] == group || @options[:group] == :all
  21. end
  22. def run(*args)
  23. @context.instance_exec(*args, &block)
  24. end
  25. def bind(context)
  26. return self if @context
  27. Initializer.new(@name, context, @options, &block)
  28. end
  29. end
  30. class Collection < Array
  31. include TSort
  32. alias :tsort_each_node :each
  33. def tsort_each_child(initializer, &block)
  34. select { |i| i.before == initializer.name || i.name == initializer.after }.each(&block)
  35. end
  36. def +(other)
  37. Collection.new(to_a + other.to_a)
  38. end
  39. end
  40. def run_initializers(group=:default, *args)
  41. return if instance_variable_defined?(:@ran)
  42. initializers.tsort.each do |initializer|
  43. initializer.run(*args) if initializer.belongs_to?(group)
  44. end
  45. @ran = true
  46. end
  47. def initializers
  48. @initializers ||= self.class.initializers_for(self)
  49. end
  50. module ClassMethods
  51. def initializers
  52. @initializers ||= Collection.new
  53. end
  54. def initializers_chain
  55. initializers = Collection.new
  56. ancestors.reverse_each do |klass|
  57. next unless klass.respond_to?(:initializers)
  58. initializers = initializers + klass.initializers
  59. end
  60. initializers
  61. end
  62. def initializers_for(binding)
  63. Collection.new(initializers_chain.map { |i| i.bind(binding) })
  64. end
  65. def initializer(name, opts = {}, &blk)
  66. raise ArgumentError, "A block must be passed when defining an initializer" unless blk
  67. opts[:after] ||= initializers.last.name unless initializers.empty? || initializers.find { |i| i.name == opts[:before] }
  68. initializers << Initializer.new(name, nil, opts, &blk)
  69. end
  70. end
  71. end
  72. end