Class: Api::ReviewsIo::ReviewsFetcher
- Inherits:
-
Object
- Object
- Api::ReviewsIo::ReviewsFetcher
- 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
-
#cache ⇒ Object
readonly
Returns the value of attribute cache.
-
#client ⇒ Object
readonly
Returns the value of attribute client.
-
#logger ⇒ Object
readonly
Returns the value of attribute logger.
Instance Method Summary collapse
-
#build_cache_key(sku: nil, tag: nil, page: 1, per_page: 5, order: 'date_desc') ⇒ String
Build cache key for reviews lookup.
-
#fetch(sku:, page: 1, per_page: 5, order: 'date_desc', skip_cache: false) ⇒ Hash
Fetch reviews for a SKU with pagination (cached).
-
#fetch_by_tag(tag:, page: 1, per_page: 5, order: 'date_desc', skip_cache: false) ⇒ Hash
Fetch reviews by tag with pagination (cached).
-
#initialize(client: nil, logger: Rails.logger, cache: Rails.cache) ⇒ ReviewsFetcher
constructor
A new instance of ReviewsFetcher.
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
#cache ⇒ Object (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 |
#client ⇒ Object (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 |
#logger ⇒ Object (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
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)
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.}") EMPTY_RESULT.merge(item_sku: sku) rescue Client::Error => e logger.error("Reviews.io fetch failed for SKU #{sku}: #{e.}") # 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.}") 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)
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.}") EMPTY_RESULT rescue Client::Error => e logger.error("Reviews.io fetch failed for tag #{tag}: #{e.}") # 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.}") EMPTY_RESULT end end |