Adding Subscribers to Products

In order to notify users that a product is back in stock, we need to keep track of these subscribers.

Let’s generate a model called Subscriber to store these email addresses and associate them with the respective product.

Terminal window
$ bin/rails generate model Subscriber product:belongs_to email

Then run the new migration:

Terminal window
$ bin/rails db:migrate

By including product:belongs_to above, we told Rails that subscribers and products have a one-to-many relationship, meaning a Subscriber “belongs to” a single Product instance.

A Product, however, can have many subscribers, so we then add has_many :subscribers, dependent: :destroy to our Product model to add the second part of this association between the two models. This tells Rails how to join queries between the two database tables.

class Product < ApplicationRecord
has_many :subscribers, dependent: :destroy
has_one_attached :featured_image
has_rich_text :description
validates :name, presence: true
validates :inventory_count, numericality: { greater_than_or_equal_to: 0 }
end

Now we need a controller to create these subscribers. Let’s create that in with the following code:

class SubscribersController < ApplicationController
allow_unauthenticated_access
before_action :set_product
def create
@product.subscribers.where(subscriber_params).first_or_create
redirect_to @product, notice: "You are now subscribed."
end
private
def set_product
@product = Product.find(params[:product_id])
end
def subscriber_params
params.expect(subscriber: [ :email ])
end
end

Our redirect sets a notice in the Rails flash. The flash is used for storing messages to display on the next page.

To display the flash message, let’s add the notice to inside the body:

<html>
<!-- ... -->
<body>
<div class="notice"><%= notice %></div>
<!-- ... -->
</body>
</html>

To subscribe users to a specific product, we’ll use a nested route so we know which product the subscriber belongs to. In change resources :products to the following:

resources :products do
resources :subscribers, only: [ :create ]
end

On the product show page, we can check if there is inventory and display the amount in stock. Otherwise, we can display an out of stock message with the subscribe form to get notified when it is back in stock.

Create a new partial at and add the following:

<% if product.inventory_count? %>
<p><%= product.inventory_count %> in stock</p>
<% else %>
<p>Out of stock</p>
<p>Email me when available.</p>
<%= form_with model: [product, Subscriber.new] do |form| %>
<%= form.email_field :email, placeholder: "you@example.com", required: true %>
<%= form.submit "Submit" %>
<% end %>
<% end %>

Then update to render this partial after the cache block.

<%= render "inventory", product: @product %>
Proudly built by Evil Martians based on the Rails Guides.
Files
Preparing Environment
  • Preparing Ruby runtime
  • Prepare development database