Module: Injectable::ClassMethods

Defined in:
lib/injectable/class_methods.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.extended(base) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
# File 'lib/injectable/class_methods.rb', line 3

def self.extended(base)
  base.class_eval do
    simple_class_attribute :dependencies,
                           :call_arguments,
                           :initialize_arguments

    self.dependencies = DependenciesGraph.new(namespace: base)
    self.initialize_arguments = {}
    self.call_arguments = {}
  end
end

Instance Method Details

#argument(name, options = {}) ⇒ Object

Declare the arguments for #call and initialize the accessors This helps us clean up the code for memoization:

“‘ private

def player

# player_id exists in the context because we added it as an argument
@player ||= player_query.call(player_id)

end “‘

Every argument is required unless given an optional default value

Examples:

argument :player_id
  # => def call(player_id:)
  # =>   @player_id = player_id
  # => end

with default arguments

argument :team_id, default: 1
  # => def call(team_id: 1)
  # =>   @team_id = team_id
  # => end

Parameters:

  • name

    Name of the argument

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :default (Object)

    The default value of the argument



135
136
137
138
# File 'lib/injectable/class_methods.rb', line 135

def argument(name, options = {})
  call_arguments[name] = options
  attr_accessor name
end

#call(args = {}) ⇒ Object

Use the service with the params declared with ‘.argument’

Examples:

MyService.call(foo: ‘first_argument’, bar: ‘second_argument’)

Parameters:

  • args (Hash) (defaults to: {})

    parameters needed for the Service



59
60
61
# File 'lib/injectable/class_methods.rb', line 59

def call(args = {})
  new.call(args)
end

#dependency(name, options = {}) { ... } ⇒ Object

Declare dependencies for the service

Examples:

Using the same name as the service object

dependency :team_query
  # => @team_query = TeamQuery.new

Specifying a different class

dependency :player_query, class: UserQuery
  # => @player_query = UserQuery.new

With a block

dependency :active_players do
  ->(players) { players.select(&:active?) }
end
  # => @active_players = [lambda]

With more dependencies

dependency :counter
dependency :team_service
dependency :player_counter, depends_on: [:counter, :team_service]
  # => @counter = Counter.new
  # => @team_service = TeamService.new
  # => @player_counter = PlayerCounter.new(counter: @counter, team_service: @team_service)

Dependencies that don’t accept keyword arguments

dependency :counter
dependency :player_counter, depends_on: :counter do |counter:|
  PlayerCounter.new(counter)
end
  # => @counter = Counter.new
  # => @player_counter = PlayerCounter.new(@counter)

Parameters:

  • name (Symbol)

    the name of the service

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :class (Class)

    The class to use if it’s different from name

  • :depends_on (Symbol, Array<Symbol>)

    if the dependency has more dependencies

Yields:

  • explicitly declare the dependency

Returns:

  • (Object)

    the injected dependency



100
101
102
103
104
105
106
107
108
# File 'lib/injectable/class_methods.rb', line 100

def dependency(name, options = {}, &block)
  options[:block] = block if block_given?
  options[:depends_on] = Array(options.fetch(:depends_on, []))
  options[:name] = name
  dependencies.add(**options)
  define_method name do
    instance_variable_get("@#{name}") || dependencies_proxy.get(name)
  end
end

#find_required_arguments(hash) ⇒ Object



157
158
159
# File 'lib/injectable/class_methods.rb', line 157

def find_required_arguments(hash)
  hash.reject { |_arg, options| options.key?(:default) }.keys
end

#inherited(base) ⇒ Object



15
16
17
18
19
20
21
# File 'lib/injectable/class_methods.rb', line 15

def inherited(base)
  base.class_eval do
    self.dependencies = dependencies.with_namespace(base)
    self.initialize_arguments = initialize_arguments.dup
    self.call_arguments = call_arguments.dup
  end
end

#initialize_with(name, options = {}) ⇒ Object



140
141
142
143
# File 'lib/injectable/class_methods.rb', line 140

def initialize_with(name, options = {})
  initialize_arguments[name] = options
  attr_accessor name
end

#required_call_argumentsObject

Get the #call arguments declared with ‘.argument’ with no default



153
154
155
# File 'lib/injectable/class_methods.rb', line 153

def required_call_arguments
  find_required_arguments call_arguments
end

#required_initialize_argumentsObject

Get the #initialize arguments declared with ‘.initialize_with’ with no default



147
148
149
# File 'lib/injectable/class_methods.rb', line 147

def required_initialize_arguments
  find_required_arguments initialize_arguments
end

#simple_class_attribute(*attrs) ⇒ Object

Blatantly stolen from rails’ ActiveSupport. This is a simplified version of class_attribute



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/injectable/class_methods.rb', line 25

def simple_class_attribute(*attrs)
  attrs.each do |name|
    define_singleton_method(name) { nil }

    ivar = "@#{name}"

    # Define the instance reader immediately when this attribute is declared
    # so instances always respond to the reader even if the singleton
    # class value has not been set yet.
    if singleton_class?
      class_eval do
        define_method(name) do
          if instance_variable_defined? ivar
            instance_variable_get ivar
          else
            singleton_class.send name
          end
        end
      end
    end

    define_singleton_method("#{name}=") do |val|
      singleton_class.class_eval do
        define_method(name) { val }
      end

      val
    end
  end
end