How use closures in Rails controller?

Viewed 188
class MyController < ApplicationController
  def getuser
    data = params[:data]
    first.call
  end

  first = lambda { user = User.first.name + data }
end

But in view I get a

NameError undefined local variable or method `first' for

Thank you for your help

1 Answers

The closure of a lambda or a proc is gathered when the lambda is created. As such, the body of the lambda has access to all local variables that were defined and visible at the point where the lambda was created. This does not include other variables defined only when calling the lambda.

Thus, you have to pass any data the lambda needs (and which was not already available in the closure when the lambda was created) into it with a parameter.

Finally, to be able to call the lambda you need to make it available in the scope where you want to use it. This can look like this:

class MyController < ApplicationController

  # define a local variable which will be available in the lambda's closure
  scope = ENV['scope']

  # Assign the lambda to a constant. That way, it can be resolved
  # by every method in the current class
  FIRST = lambda do |data|

    # The scope variable is available here when the lambda is called
    # since it is available in the closure from the lambda's creation.
    #
    # Since the data variable is not available during the lamda's
    # creation, we need to get it from a parameter from the caller.
    User.where(scope: scope).first.name + data
  end

  def getuser
    data = params[:data]
    FIRST.call(data)
  end
end

With that being said, a more common approach to this to use full methods on an object rather than passing lambdas around:

class MyController < ApplicationController
  def getuser
    data = params[:data]
    first(data)
  end

  private

  def first(data)
    User.first.name + data
  end
end

This avoids having to reason about the contents of the (mostly implicit and not very visible) closure in favour of a plain method call which has access to the instance variables and methods of the current object.

Especially if you are coming from Javascript (where it is common to pass around anonymous functions with a closure slightly similar to Ruby's lambdas), you should try to adapt to the Ruby-way and avoid such construct in favor of objects with explicit methods.

Related