DEV Community

Fidel Leal
Fidel Leal

Posted on

Using Sinatra Filters

Sinatra filters are special methods invoked for each individual request and evaluated in the same context as the routes. They provide an efficient way to organize logic and make variables available across multiple routes. There are two types of filters, before and after, named for when they are executed in relation to a particular request.

The before filter's use cases include authentication, logging, and the declaration of instance variables that should be available to all or a several routes within the same request-response cycle. This is an important distinction! Variables declared inside a before filter will be initialized before each request and made available to all the routes that follow in that particular cycle. This behavior is different from regular instance variables declared in the body of the application, which can retain their value across different requests.

Here is a hypothetical example, showcasing the use of before in a version of the Umbrella app that implements user authentication.

before do 
    # Redirect to login route if user not authenticated
    unless session[:user_id] 
        redirect to('/login') 
    end  

    # Log the request 
    logger.info "Request: #{request.request_method} #{request.path} Params: #{params.inspect}"

    # Pre-process some data from the request to make it available to the routes
    if params[:data] 
        @user_coordinates = get_coordinates(params.fetch("user_location")) 
    end
end

# Helper method to retrieve user's location coordinates from Google Maps' API
def  get_coordinates(user_location) 
    # Make a request to Google Map's API and process response to get the specific coordinates
    user_coordinates
end
Enter fullscreen mode Exit fullscreen mode

The after filter has access to a similar context as before. However, as its name implies, it is invoked after the route block is finished. (Although I have not had the opportunity to use this filter within my applications, I am including the example below for documentation purposes.) Here is another hypothetical example. The filter is used to ensure that the response's Content-Type header is appropriately set to application/json before sending it back to the client.

get '/route' do 
    body({ message:  'This is an endpoint!' }.to_json) 
end 

get '/another_route' do 
    body({ data:  'This is another endpoint!' }.to_json) 
end 

after do 
    response['Content-Type'] = 'application/json'  
end
Enter fullscreen mode Exit fullscreen mode

Lastly, a couple interesting aspects about filters:

1) Multiple before and after filters can be defined in the same application. They can be placed before or after the routes they affect, but the order in which they are defined determines the order in which they will be executed.

2) Sinatra filters (like routes) can optionally accept patterns and conditions. In those cases, filters will be evaluated only when the request's path matches the specified pattern. Here is an example from Sinatra's documentation:

# Any request whose path begins with '/protected/' will require authentication
before '/protected/*' do
    authenticate!
end
Enter fullscreen mode Exit fullscreen mode

Top comments (0)