Module: Interjectable::ClassMethods

Defined in:
lib/interjectable.rb,
lib/interjectable/rspec.rb

Constant Summary collapse

BLANK =
Object.new

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.test_inject(rspec_example_group, target, dependency, value, &setter) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/interjectable/rspec.rb', line 80

def self.test_inject(rspec_example_group, target, dependency, value, &setter)
  unless value || setter
    raise ArgumentError, "missing value or setter for #{target}'s #{dependency.inspect}"
  end

  unless rspec_example_group < RSpec::Core::ExampleGroup
    raise "#test_inject can only be called from an RSpec ExampleGroup (e.g.: it, before, after)"
  end

  injector =
    if target.singleton_methods(false).include?(dependency) # inject_static(dependency) on this class
      InjectStatic.new(target, dependency)
    elsif target.singleton_methods.include?(dependency) # inject_static(dependency) on a superclass of this class
      SuperclassInjectStatic.new(target, dependency)
    elsif target.instance_methods(false).include?(dependency) # inject(dependency) on this class
      Inject.new(target, dependency)
    elsif target.instance_methods.include?(dependency) # inject(dependency) on a superclass of this class
      SuperclassInject.new(target, dependency)
    else
      raise ArgumentError, "tried to override a non-existent dependency: #{dependency.inspect}"
    end

  injector.override(value, &setter)

  scope = rspec_example_group.currently_executing_a_context_hook? ? :context : :each

  key = [target, dependency, scope]
  # If we already have a restore after(:each) hook for this class +
  # dependency + scope, don't add another. To check if we already have an
  # after(:each) hook, we look at all previous after(:each) hooks we've
  # registered and see if we are currently in a subclass (i.e. we are
  # nested within) of any of them.
  #
  # We don't need to guard against multiple after(:context / :all) hooks
  # for the same #test_inject call since those before hooks only run once,
  # and therefore only setup a single after hook.
  return if scope == :each && RESTORE_HOOKS[key].any? { |group| rspec_example_group <= group }

  RESTORE_HOOKS[key] << rspec_example_group

  rspec_example_group.after(scope) do
    injector.restore
  end
end

Instance Method Details

#inject(dependency, &default_block) ⇒ Object

Defines a helper methods on instances that memoize values per-instance.

Calling a second time is an error. Use ‘#test_inject` for overriding in RSpec tests. You need to `require “interjectable/rspec”` to use `#test_inject`. See the README.md.

Similar to writing

attr_writer :dependency

def dependency
  @dependency ||= instance_eval(&default_block)
end


54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/interjectable.rb', line 54

def inject(dependency, &default_block)
  if instance_methods(false).include?(dependency)
    raise MethodAlreadyDefined, "#{dependency} is already defined"
  end

  attr_writer dependency

  define_method(dependency) do
    ivar_name = :"@#{dependency}"
    if instance_variable_defined?(ivar_name)
      instance_variable_get(ivar_name)
    else
      instance_variable_set(ivar_name, instance_eval(&default_block))
    end
  end

  @injected_methods ||= []
  @injected_methods += [dependency, :"#{dependency}="]
end

#inject_static(dependency, &default_block) ⇒ Object

Defines helper methods on instances that memoize values per-class. (shared across all instances of a class, including instances of subclasses).

Calling a second time is an error. Use ‘#test_inject` for overriding in RSpec tests. You need to `require “interjectable/rspec”` to use `#test_inject`. See the README.md.

Similar to writing

cattr_writer :dependency

def dependency
  @@dependency ||= instance_eval(&default_block)
end


89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/interjectable.rb', line 89

def inject_static(dependency, &default_block)
  if instance_methods(false).include?(dependency) || methods(false).include?(dependency)
    raise MethodAlreadyDefined, "#{dependency} is already defined"
  end

  injecting_class = self

  cvar_name = :"@@#{dependency}"
  setter = :"#{dependency}="

  define_method(setter) do |value|
    injecting_class.send(setter, value)
  end

  define_singleton_method(setter) do |value|
    injecting_class.class_variable_set(cvar_name, value)
  end

  define_method(dependency) do
    injecting_class.send(dependency)
  end

  define_singleton_method(dependency) do
    if class_variable_defined?(cvar_name)
      injecting_class.class_variable_get(cvar_name)
    else
      injecting_class.class_variable_set(cvar_name, instance_eval(&default_block))
    end
  end

  @static_injected_methods ||= []
  @static_injected_methods += [dependency, :"#{dependency}="]
end

#injected_methods(include_super = true) ⇒ Array<Symbol>

Returns:

  • (Array<Symbol>)


124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/interjectable.rb', line 124

def injected_methods(include_super = true)
  injected = @static_injected_methods.to_a

  if include_super
    super_injected = ancestors.flat_map do |klass|
      klass.instance_variable_get(:@static_injected_methods).to_a
    end

    [
      :injected_methods,
      *super_injected,
      *injected,
    ].uniq
  else
    [:injected_methods, *injected]
  end
end

#test_inject(dependency, &setter) ⇒ Object



71
72
73
74
75
76
77
78
# File 'lib/interjectable/rspec.rb', line 71

def test_inject(dependency, &setter)
  unless setter
    raise ArgumentError, "missing setter #{dependency.inspect}, correct usage: #test_inject(#{dependency.inspect}) { FakeDependency.new }"
  end
  rspec_example_group = setter.binding.receiver.class

  ClassMethods.test_inject(rspec_example_group, self, dependency, BLANK, &setter)
end