Class: Showroom::Product

Inherits:
Resource show all
Extended by:
Core::Countable
Defined in:
lib/showroom/models/product.rb

Overview

Represents a Shopify product with associations, convenience methods, and class-level query methods that delegate to client.

Examples:

Fetching products

products = Showroom::Product.where(product_type: 'Road Bike')
product  = Showroom::Product.find('lorem-road-bike')

Constant Summary

Constants included from Core::Countable

Core::Countable::MAX_COUNT, Core::Countable::MAX_PAGE, Core::Countable::MAX_PER_PAGE

Instance Attribute Summary

Attributes inherited from Resource

#attrs, #client

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Core::Countable

calculate_count

Methods inherited from Resource

#==, #[], has_many, has_one, #initialize, #inspect, main_attr_keys, main_attrs, #method_missing, #respond_to_missing?, #to_h

Constructor Details

This class inherits a constructor from Showroom::Resource

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class Showroom::Resource

Class Method Details

.all(max_pages: nil, force_all_without_limit: false, **params) ⇒ Enumerator<Product>

Returns an Enumerator that iterates over products across multiple pages.

You must pass either max_pages: (an explicit ceiling) or set force_all_without_limit: true to acknowledge that the number of requests is unbounded. The latter emits a warning.

Parameters:

  • max_pages (Integer, nil) (defaults to: nil)

    maximum number of pages to fetch

  • force_all_without_limit (Boolean) (defaults to: false)

    when true, fetch all pages up to pagination_depth without a hard cap (emits a warning)

  • params (Hash)

    additional query parameters

Returns:

Raises:

  • (ArgumentError)

    when neither max_pages: nor force_all_without_limit: true is given



58
59
60
61
62
63
64
65
# File 'lib/showroom/models/product.rb', line 58

def all(max_pages: nil, force_all_without_limit: false, **params)
  Enumerator.new do |yielder|
    each_page(max_pages: max_pages, force_all_without_limit: force_all_without_limit,
              **params) do |page_products, _page|
      page_products.each { |p| yielder << p }
    end
  end
end

.each_page(max_pages: nil, force_all_without_limit: false, limit: Showroom.per_page, **params) {|products, page| ... } ⇒ void

This method returns an undefined value.

Iterates through pages of products, yielding each page.

You must pass either max_pages: or force_all_without_limit: true. Without an explicit ceiling, unbounded pagination can issue dozens of HTTP requests silently. When force_all_without_limit: true is given, a warning is emitted and iteration proceeds up to pagination_depth.

Parameters:

  • max_pages (Integer, nil) (defaults to: nil)

    maximum number of pages to fetch

  • force_all_without_limit (Boolean) (defaults to: false)

    bypass the requirement at your own risk; emits a Kernel.warn and uses pagination_depth as the cap

  • limit (Integer) (defaults to: Showroom.per_page)

    results per page (defaults to Showroom.per_page)

  • params (Hash)

    additional query parameters

Yields:

  • (products, page)

    the products for this page and the 1-based page number

Yield Parameters:

  • products (Array<Product>)
  • page (Integer)

Raises:

  • (ArgumentError)

    when neither max_pages: nor force_all_without_limit: true is given



85
86
87
88
89
90
91
92
# File 'lib/showroom/models/product.rb', line 85

def each_page(max_pages: nil, force_all_without_limit: false, limit: Showroom.per_page, **params, &blk)
  validate_pagination_args!(max_pages, force_all_without_limit)
  effective_depth = max_pages || Showroom.client.pagination_depth
  Showroom.client.paginate('/products.json', 'products', params.merge(limit: limit),
                           max_pages: effective_depth) do |items, page|
    blk.call(items.map { |h| new(h) }, page)
  end
end

.find(handle) ⇒ Product

Fetches a single product by handle.

Parameters:

  • handle (String)

    the product handle

Returns:

Raises:



39
40
41
42
43
# File 'lib/showroom/models/product.rb', line 39

def find(handle)
  Showroom.client.get("/products/#{handle}.json")
          .fetch('product') { raise Showroom::NotFound, handle }
          .then { |h| new(h) }
end

.index_keyObject



21
# File 'lib/showroom/models/product.rb', line 21

def index_key  = 'products'

.index_pathObject



20
# File 'lib/showroom/models/product.rb', line 20

def index_path = '/products.json'

.where(limit: Showroom.per_page, **params) ⇒ Array<Product>

Fetches products matching the given query parameters.

Parameters:

  • params (Hash)

    Shopify query parameters (e.g. product_type:, vendor:)

Returns:



28
29
30
31
32
# File 'lib/showroom/models/product.rb', line 28

def where(limit: Showroom.per_page, **params)
  Showroom.client.get('/products.json', params.merge(limit: limit))
          .fetch('products', [])
          .map { |h| new(h) }
end

Instance Method Details

#available?Boolean

Returns true when at least one variant is available for purchase.

Returns:

  • (Boolean)


131
132
133
# File 'lib/showroom/models/product.rb', line 131

def available?
  variants.any?(&:available?)
end

Returns the first image, or nil if there are no images.

Returns:



138
139
140
# File 'lib/showroom/models/product.rb', line 138

def featured_image
  images.first
end

#main_imageProductImage?

Returns the image whose position field equals 1, or nil if none match.

Unlike #featured_image (which returns images.first regardless of position), this explicitly matches on the position attribute.

Returns:



168
169
170
171
172
# File 'lib/showroom/models/product.rb', line 168

def main_image
  images.find do |img|
    img['position'] == 1
  end
end

#priceString?

Returns the lowest variant price as a String.

Returns:

  • (String, nil)


113
114
115
# File 'lib/showroom/models/product.rb', line 113

def price
  variants.min_by { |v| v['price'].to_f }&.then { |v| v['price'] }
end

#price_rangeString?

Returns a price range string (“min–max”) or just the price if all variants share the same price.

Returns:

  • (String, nil)


121
122
123
124
125
126
# File 'lib/showroom/models/product.rb', line 121

def price_range
  prices = variants.map { |v| v['price'] }.uniq
  return nil if prices.empty?

  prices.length == 1 ? prices.first : "#{prices.min} - #{prices.max}"
end

#pricesArray<String>

Returns unique prices across all variants as an Array of Strings.

Unlike #price (lowest single price) or #price_range (formatted string), this returns the raw deduplicated list useful for custom rendering.

Returns:

  • (Array<String>)


156
157
158
159
160
# File 'lib/showroom/models/product.rb', line 156

def prices
  variants.map do |variant|
    variant['price']
  end.uniq
end

#urlString

Returns the canonical storefront URL for this product.

Returns:

  • (String)


145
146
147
148
# File 'lib/showroom/models/product.rb', line 145

def url
  conn = client || Showroom.client
  "#{conn.base_url}/products/#{handle}"
end