sosek sosek - 1 year ago 71
Ruby Question

Where to put given logic in rails?

I'm writing an application where user enters a date, and then the system fetches the historical weather data for that week (I assume that Wednesday is representative for the whole week) from an external API. For certain reasons, I don't want to do live calls for each date - I want to fetch it once and persist on-site.

In Spring, I'd put most of it into a service layer. Since I am new to Rails, I am not sure where to put certain logic, but here's my proposal:


def create
transform date entered by user to Wednesday of the same week.
Check if there is a already record for that date, if not, fetch the JSON from external API.
Parse JSON to Ruby object, save.
Return the weather data.


validate if the date is indeed Wednesday
validate if entered date is unique

Answer Source

Generally, I wouldn't put the logic in a create action. Even though you're creating something, the user of your site is really asking you to show the weather. The user should be oblivious to where you're bringing the info from and how you're caching it.

Option 1 - Use Rails Caching

One option is to use Rails caching in the show action. Right in that action you will do a blocking call to the API, and then Rails will store the return value in the cache store (e.g. Redis).

def show
  date = Date.parse params[:date]
  @info_to_show = Rails.cache.fetch(cache_key_for date) do


def cache_key_for(date)
  "weather-cache-#{date + (3 - date.wday)}"

Option 2: Non-blocking calls with ActiveJobs

Option 1 above will make accessing the data you already accumulated somewhat awkward (e.g. for statistics, graphs, etc). In addition, it blocks the server while you are waiting for a response from the API endpoint. If these are non-issues, you should consider option 1, as it's very simple. If you need more than that, below is a suggestion for storing the data you fetch in the DB.

I suggest a model to store the data and an async job that retrieves the data. Note you'll need to have ActiveJob set up for the WeatherFetcherJob.

# migration file
create_table :weather_logs do |t|
  t.datetime :date

  # You may want to use an enumerized string field `status` instead of a boolean so that you can record 'not_fetched', 'success', 'error'.
  t.boolean :fetch_completed, default: false
  t.text :error_message
  t.text :error_backtrace

  # Whatever info you're saving

add_index :weather_logs, :date

# app/models/weather_log.rb

class WeatherLog
  # Return a log record immediately (non-blocking).
  def self.find_for_week(date_str)
    date = Date.parse(date_str)
    wednesday_representative = date + (3 - date.wday)
    record = find_or_create_by(date: wednesday_representative)
    WeatherFetcherJob.perform_later(record) unless record.fetch_completed

# app/jobs/weather_fetcher_job.rb

class WeatherFetcherJob < ActiveJob::Base
  def perform(weather_log_record)
    # Fetch from API
    # Update the weather_log_record with the information
    # Update the weather_log_record's fetch_completed to true
    # If there is an error - store it in the error fields.

Then, in the controller you can rely on whether the API completed to decide what to display to the user. These are broad strokes, you'll have to adapt to your use case.

# app/controllers/weather_controller
def show
  @weather_log = WeatherLog.find_for_week(params[:date])
  @show_spinner = true unless @weather_log.fetch_completed

def poll
  @weather_log = WeatherLog.find(params[:id])
  render json: @weather_log.fetch_completed

# app/javascripts/

$(document).ready ->
  poll = -> 
    $.get($('#spinner-element').data('poll-url'), (fetch_in_progress) ->
      if fetch_in_progress
        setTimeout(poll, 2000)
        window.location = $('#spinner-element').data('redirect-to')
  $('#spinner-element').each -> poll()

# app/views/weather_controller.rb
<% if @show_spinner %>
    <%= content_tag :div, 'Loading...', id: 'spinner-element', data: { poll_url: poll_weather_path(@weather_log), redirect_to: weather_path(@weather_log) } %>
<% end %>
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download