Class: Api::ReviewsIo::ReviewsFetcher

Inherits:
Object
  • Object
show all
Defined in:
app/services/api/reviews_io/reviews_fetcher.rb

Overview

Fetches product review data from Reviews.io API with pagination support.
Returns data in a format compatible with legacy Review.get_review_data_for.
Uses Rails.cache for 30-minute caching to avoid repeated external API calls.

Constant Summary collapse

CACHE_NAMESPACE =
'reviews_io:product_reviews'
CACHE_TTL =

Cache full reviews for 30 minutes (shorter than ratings since content changes more)

30.minutes
EMPTY_RESULT =
{
  reviews: [],
  star_avg: nil,
  num: 0,
  name: nil,
  item_sku: nil,
  updated_at: Time.current
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(client: nil, logger: Rails.logger, cache: Rails.cache) ⇒ ReviewsFetcher

Returns a new instance of ReviewsFetcher.



23
24
25
26
27
# File 'app/services/api/reviews_io/reviews_fetcher.rb', line 23

def initialize(client: nil, logger: Rails.logger, cache: Rails.cache)
  @client = client
  @logger = logger
  @cache = cache
end

Instance Attribute Details

#cacheObject (readonly)

Returns the value of attribute cache.



21
22
23
# File 'app/services/api/reviews_io/reviews_fetcher.rb', line 21

def cache
  @cache
end

#clientObject (readonly)

Returns the value of attribute client.



21
22
23
# File 'app/services/api/reviews_io/reviews_fetcher.rb', line 21

def client
  @client
end

#loggerObject (readonly)

Returns the value of attribute logger.



21
22
23
# File 'app/services/api/reviews_io/reviews_fetcher.rb', line 21

def logger
  @logger
end

Instance Method Details

#build_cache_key(sku: nil, tag: nil, page: 1, per_page: 5, order: 'date_desc') ⇒ String

Build cache key for reviews lookup

Returns:

  • (String)

    Cache key



159
160
161
162
# File 'app/services/api/reviews_io/reviews_fetcher.rb', line 159

def build_cache_key(sku: nil, tag: nil, page: 1, per_page: 5, order: 'date_desc')
  identifier = sku ? "sku:#{sku.to_s.upcase}" : "tag:#{tag}"
  "#{CACHE_NAMESPACE}:#{identifier}:p#{page}:n#{per_page}:#{order}"
end

#fetch(sku:, page: 1, per_page: 5, order: 'date_desc', skip_cache: false) ⇒ Hash

Fetch reviews for a SKU with pagination (cached)

Parameters:

  • sku (String)

    Product SKU

  • page (Integer) (defaults to: 1)

    Page number (default: 1)

  • per_page (Integer) (defaults to: 5)

    Results per page (default: 5)

  • order (String) (defaults to: 'date_desc')

    Sort order (default: 'date_desc')

  • skip_cache (Boolean) (defaults to: false)

    Force refresh from API (default: false)

Returns:

  • (Hash)

    Hash containing reviews, ratings, and metadata



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'app/services/api/reviews_io/reviews_fetcher.rb', line 48

def fetch(sku:, page: 1, per_page: 5, order: 'date_desc', skip_cache: false)
  return EMPTY_RESULT.merge(item_sku: sku) if sku.blank?

  cache_key = build_cache_key(sku:, page:, per_page:, order:)

  # Check cache first unless skipping
  unless skip_cache
    cached = cache.read(cache_key)
    if cached
      logger.debug { "Reviews.io cache HIT for #{sku} (page=#{page})" }
      return cached.merge(updated_at: Time.current)
    end
  end

  logger.debug { "Reviews.io cache MISS for #{sku} (page=#{page}), fetching from API" }

  # Ensure client is initialized
  client_instance = ensure_client
  return EMPTY_RESULT.merge(item_sku: sku) unless client_instance

  begin
    # Fetch reviews from Reviews.io
    response = client_instance.fetch_reviews_for_sku(
      sku,
      page: page,
      per_page: per_page,
      order: order
    )

    # Transform response to match expected format
    result = transform_response(response, sku)

    # Cache the result (without updated_at which should be fresh on read)
    cache_data = result.except(:updated_at)
    cache.write(cache_key, cache_data, expires_in: CACHE_TTL)

    result
  rescue Client::MissingCredentialsError => e
    logger.warn("Reviews.io credentials missing: #{e.message}")
    EMPTY_RESULT.merge(item_sku: sku)
  rescue Client::Error => e
    logger.error("Reviews.io fetch failed for SKU #{sku}: #{e.message}")
    # Return cached value if available on API error
    cached = cache.read(cache_key)
    cached&.merge(updated_at: Time.current) || EMPTY_RESULT.merge(item_sku: sku)
  rescue StandardError => e
    logger.error("Unexpected error fetching Reviews.io reviews for SKU #{sku}: #{e.class} - #{e.message}")
    EMPTY_RESULT.merge(item_sku: sku)
  end
end

#fetch_by_tag(tag:, page: 1, per_page: 5, order: 'date_desc', skip_cache: false) ⇒ Hash

Fetch reviews by tag with pagination (cached)

Parameters:

  • tag (String)

    Review tag (e.g., 'for-homepage')

  • page (Integer) (defaults to: 1)

    Page number (default: 1)

  • per_page (Integer) (defaults to: 5)

    Results per page (default: 5)

  • order (String) (defaults to: 'date_desc')

    Sort order (default: 'date_desc')

  • skip_cache (Boolean) (defaults to: false)

    Force refresh from API (default: false)

Returns:

  • (Hash)

    Hash containing reviews, ratings, and metadata



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'app/services/api/reviews_io/reviews_fetcher.rb', line 106

def fetch_by_tag(tag:, page: 1, per_page: 5, order: 'date_desc', skip_cache: false)
  return EMPTY_RESULT if tag.blank?

  cache_key = build_cache_key(tag:, page:, per_page:, order:)

  # Check cache first unless skipping
  unless skip_cache
    cached = cache.read(cache_key)
    if cached
      logger.debug { "Reviews.io tag cache HIT for #{tag} (page=#{page})" }
      return cached.merge(updated_at: Time.current)
    end
  end

  logger.debug { "Reviews.io tag cache MISS for #{tag} (page=#{page}), fetching from API" }

  # Ensure client is initialized
  client_instance = ensure_client
  return EMPTY_RESULT unless client_instance

  begin
    # Fetch reviews from Reviews.io by tag
    response = client_instance.fetch_reviews_by_tag(
      tag,
      page: page,
      per_page: per_page,
      order: order
    )

    # Transform response to match expected format (no SKU for tag-based queries)
    result = transform_response(response, nil)

    # Cache the result
    cache_data = result.except(:updated_at)
    cache.write(cache_key, cache_data, expires_in: CACHE_TTL)

    result
  rescue Client::MissingCredentialsError => e
    logger.warn("Reviews.io credentials missing: #{e.message}")
    EMPTY_RESULT
  rescue Client::Error => e
    logger.error("Reviews.io fetch failed for tag #{tag}: #{e.message}")
    # Return cached value if available on API error
    cached = cache.read(cache_key)
    cached&.merge(updated_at: Time.current) || EMPTY_RESULT
  rescue StandardError => e
    logger.error("Unexpected error fetching Reviews.io reviews for tag #{tag}: #{e.class} - #{e.message}")
    EMPTY_RESULT
  end
end