dchun / contestapp

sample shopify app based on tutorial

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

####Chapter 2 - Setting Up run commands

mkdir contestapp
cd contestapp
rvm --ruby-version use 2.1.2@contestapp --create

gem install rails -v 4.0.3 --no-ri --no-rdoc
rails new .
bundle install

rails generate controller Dashboard index

add to config/routes.rb

root 'dashboard#index'

add to Gemfile

gem "execjs"
gem "twitter-bootstrap-rails"
gem "bootstrap-sass"

run commands

bundle install
rails generate bootstrap:install sass
rails g bootstrap:layout application fluid -f

run commands

git init
git remote add origin path_to_remote_repository
git add --all
git commit -am "Initial commit"
git push -u origin master

add to Gemfile

ruby '2.1.2'
group :production do
  gem "rails_12factor"
  gem "pg"
end

modify Gemfile

group :development, :test do
  gem "sqlite3"
end

run commands

bundle install

git add --all
git commit -am "Required Heroku gems"
git push

heroku create contestapp
git push heroku master
heroku run rake db:migrate

heroku open

####Chapter 3 - Building A Private App

#####Section 1 run command

git checkout -b ch03_01_gem_updates

add to Gemfile inside development and test group

# Helpful gems
gem "better_errors" # improves error handling
gem "binding_of_caller" # used by better errors
# Testing frameworks
gem 'rspec-rails' # testing framework
gem "factory_girl_rails" # use factories, not fixtures
gem "capybara" # simulate browser activity
gem "fakeweb"
# Automated testing
gem 'guard' # automated execution of test suite upon change
gem "guard-rspec" # guard integration with rspec
# Only install the rb-fsevent gem if on Max OSX
gem 'rb-fsevent' # used for Growl notifications

run commands

bundle install

rails generate rspec:install

guard init rspec

git add --all
git commit -am "Added gems for testing"
git checkout master
git merge ch03_01_gem_updates
git push

#####Section 2 make a private shopify app and run commands

git checkout -b ch03_02_shopify_credentials

rails g scaffold Account shopify_account_url:string shopify_api_key:string shopify_password:string shopify_shared_secret:string

bundle exec rake db:migrate
bundle exec rake db:migrate RAILS_ENV=test

rails g bootstrap:themed Accounts -f

bundle exec guard

modify app/views/layouts/application.html.erb

<a class="brand" href="/">Contestapp</a>
<div class="container-fluid nav-collapse">
	<ul class="nav">
		<li><%= link_to "Accounts", accounts_path%></li>
	</ul>
</div><!--/.nav-collapse -->

add to app/models/account.rb

validates_presence_of :shopify_account_url
validates_presence_of :shopify_api_key
validates_presence_of :shopify_password
validates_presence_of :shopify_shared_secret

run commands

git add --all
git commit -am "Account model and related files"
git checkout master
git merge ch03_02_shopify_credentials
git push

#####Section 3 run command

git checkout -b ch03_03_shopify_connection

add to Gemfile

gem 'shopify_api'

run command

bundle install

create file app/services/shopify_integration.rb

class ShopifyIntegration

  attr_accessor :api_key, :shared_secret, :url, :password

  def initialize(params)
    # Ensure that all the parameters are passed in
    %w{api_key shared_secret url password}.each do |field|
      raise ArgumentError.new("params[:#{field}] is required") if params[field.to_sym].blank?

      # If present, then set as an instance variable
      instance_variable_set("@#{field}", params[field.to_sym])
    end
  end

  # Uses the provided credentials to create an active Shopify session
  def connect

    # Initialize the gem
    ShopifyAPI::Session.setup({api_key: @api_key, secret: @shared_secret})

    # Instantiate the session
    session = ShopifyAPI::Session.new(@url, @password)

    # Activate the Session so that requests can be made
    return ShopifyAPI::Base.activate_session(session)

  end

  def import_orders

    # Local variables
    created = failed = 0
    page = 1


    # Get the first page of orders
    shopify_orders = ShopifyAPI::Order.find(:all, params: {limit: 50, page: page})

    # Keep going while we have more orders to process
    while shopify_orders.size > 0

      shopify_orders.each do |shopify_order|

        # See if we've already imported the order
        order = Order.find_by_shopify_order_id(shopify_order.id)

        unless order.present?

          # If not already imported, create a new order
          order = Order.new(number: shopify_order.name,
                            email: shopify_order.email,
                            first_name: shopify_order.billing_address.first_name,
                            last_name: shopify_order.billing_address.last_name,
                            shopify_order_id: shopify_order.id,
                            order_date: shopify_order.created_at,
                            total: shopify_order.total_price,
                            financial_status: shopify_order.financial_status
                            )

          # Iterate through the line_items
          shopify_order.line_items.each do |line_item|
            variant = Variant.find_by_shopify_variant_id(line_item.variant_id)
            if variant.present?
              order.order_items.build(variant_id: variant.id,
                                      shopify_product_id: line_item.product_id,
                                      shopify_variant_id: line_item.id,
                                      quantity:  line_item.quantity,
                                      unit_price: line_item.price)
            end
          end

          if order.save
            created += 1
          else
            failed += 1
          end
        end

      end

      # Grab the next page of products
      page += 1
      shopify_orders = ShopifyAPI::Order.find(:all, params: {limit: 50, page: page})


    end

    # Once we are done, return the results
    return {created: created,  failed: failed}
  end

  def import_products

    # Local variables
    created = failed = updated = 0
    page = 1

    # Grab the first page of products
    shopify_products = ShopifyAPI::Product.find(:all, params: {limit: 100, page: page})

    # Keep looping until no more products are returned
    while shopify_products.size > 0

      shopify_products.each do |shopify_product|

        # See if the product exists
        product = Product.find_by_shopify_product_id(shopify_product.id)

        # If so, attempt to update it
        if product.present?
          unless product.update_attributes(last_shopify_sync: DateTime.now, name: shopify_product.title)
            failed += 1
            next
          end
        else

          # Otherwise, create it
          product = Product.new(last_shopify_sync: DateTime.now, name: shopify_product.title, shopify_product_id: shopify_product.id)
          unless product.save
            failed += 1
            next
          end
        end

        # Iterate through the variants
        shopify_product.variants.each do |shopify_variant|

          # See if the variant exists
          variant = Variant.find_by_shopify_variant_id(shopify_variant.id)
          if variant.present?
            # If so, update it
            if variant.update_attributes(sku: shopify_variant.sku, barcode: shopify_variant.barcode, option1: shopify_variant.option1, option2: shopify_variant.option2, option3: shopify_variant.option3, product_id: product.id, shopify_variant_id: shopify_variant.id, price: shopify_variant.price, last_shopify_sync: DateTime.now)
              updated += 1
            else
              failed += 1
            end
          else
            # Otherwise create it
            if Variant.create(sku: shopify_variant.sku, barcode: shopify_variant.barcode, option1: shopify_variant.option1, option2: shopify_variant.option2, option3: shopify_variant.option3, product_id: product.id, shopify_variant_id: shopify_variant.id, price: shopify_variant.price, last_shopify_sync: DateTime.now)
              created += 1
            else
              failed += 1
            end
          end
        end

      end

      # Grab the next page of products
      page += 1
      shopify_products = ShopifyAPI::Product.find(:all, params: {limit: 100, page: page})


    end

    # Return the results once no more products are left
    return {created: created, updated: updated, failed: failed}

  end

end

add to config/routes.rb

  resources :accounts do
    member do
      get 'test_connection'
    end
  end

modify app/controllers/accounts_controller.rb

  before_action :set_account, only: [:show, :edit, :update, :destroy, :test_connection]

add to app/controllers/accounts_controller.rb

  # GET /accounts/1/test_connection
  # GET /accounts/1/test_connection.json
  def test_connection
    # Connect to Shopify using our class
    ShopifyIntegration.new(api_key: @account.shopify_api_key,
                           shared_secret: @account.shopify_shared_secret,
                           url: @account.shopify_account_url,
                           password: @account.shopify_password).connect()
    begin
      # The gem will throw an exception if unable to retrieve Shop information
      shop = ShopifyAPI::Shop.current
    rescue => ex
      @message = ex.message
    end

    if shop.present?
      respond_to do |format|
        # Report the good news
        format.html { redirect_to @account, notice: "Successfully Connected to #{shop.name}" }
        format.json { render json: "Successfully Connected to #{shop.name}" }
      end
    else
      respond_to do |format|
        # Return the message from the exception
        format.html { redirect_to @account, alert: "Unable to Connect: #{@message}" }
        format.json { render json: "Unable to Connect: #{@message}", status: :unprocessable_entity }
      end
    end
  end

add to app/views/account/show.html.erb

  <%= link_to "Test Connection", test_connection_account_path(@account),
              :class => 'btn' %>

run commands

git add --all
git commit -am "Shopify connection and related UI"
git checkout master
git merge ch03_03_shopify_connection
git push

#####Section 4 run command

git checkout -b ch03_04_product_import

run commands

rails g scaffold Product name:string shopify_product_id:integer last_shopify_sync:datetime

rails g scaffold Variant product_id:integer:index shopify_variant_id:integer option1:string option2:string option3:string sku:string barcode:string price:float last_shopify_sync:datetime

bundle exec rake db:migrate
       
bundle exec rake db:migrate RAILS_ENV=test

rails g bootstrap:themed Products -f

rails g bootstrap:themed Variants -f

add to app/views/layouts/application.html.erb

<ul class="nav">
	<li><%= link_to "Products", products_path  %></li>
	<li><%= link_to "Accounts", accounts_path  %></li>
</ul>

modify app/models/product.rb

  has_many :variants

modify app/models/variant.rb

  belongs_to :product

modify config/routes.rb

resources :products do
  resources :variants
end

add to app/controllers/variants_controller.rb

  before_action :set_product  
  def set_product
    @product = Product.find(params[:product_id])
  end

modify app/controllers/variants_controller.rb

  def set_variant
    @variant = @product.variants.find(params[:id])
  end

modify config/routes/rb

resources :products do
  collection do
    get 'import'
  end
  resources :variants
end

add to app/controllers/products_controller.rb

# GET /products/import
# GET /products/import.json
  def import
  # For now we'll use the first Account in the database
    account = Account.first
  # Instantiate the ShopifyIntegration class
    shopify_integration = ShopifyIntegration.new(
    api_key: account.shopify_api_key,
    shared_secret: account.shopify_shared_secret,
    url: account.shopify_account_url,
    password: account.shopify_password)
    respond_to do |format|
      shopify_integration.connect
      result = shopify_integration.import_products
      format.html { redirect_to ({action: :index}), notice: "#{result[:created].to_i} created, #{result[:updated]} updated, #{result[:failed]} failed." }
    end 
  end

add to app/views/products/index.html.erb

<p><a href="<%= import_products_path %>">Import Products</a></p>

run commands

git add --all
git commit -am "Shopify Product Import"
git checkout master
git merge ch03_04_product_import
git push

#####Section 5 run command

git checkout -b ch03_05_order_import

run commands

rails g scaffold Order number:string email:string first_name:string last_name:string shopify_order_id:integer order_date:datetime total:float line_item_count:integer financial_status:string
       
rails g model OrderItem order_id:integer:index variant_id:integer:index shopify_product_id:integer:integer shopify_variant_id:integer:index unit_price:float quantity:integer

bundle exec rake db:migrate
       
bundle exec rake db:migrate RAILS_ENV=test

rails g bootstrap:themed Orders -f

add to app/models/order.rb

  has_many :order_items

add to app/models/order_item.rb

  belongs_to :order

add to app/views/layouts/application.html.erb

<li><%= link_to "Orders", orders_path %></li>

run commands

git add --all
git commit -am "Shopify connection and related UI"
git checkout master
git merge ch03_05_order_import
git push

#####Section 6 run command

git checkout -b ch03_06_ui_cleanup

modify app/views/layouts/application.html.erb

<div class="row-fluid">
     <% if content_for?(:sidebar) %>
     <div class="span3">
       <div class="well sidebar-nav">
         <%= yield(:sidebar) %>
        </div>
      </div>
      <div class="span9">
        <%= bootstrap_flash %>
           <%= yield %>
      </div>
      <% else %>
        <div class="span12">
           <%= bootstrap_flash %>
              <%= yield %>
           </div>
      <% end %>
</div>

add to app/views/dashboard/index.html.erb

<%- content_for :sidebar do-%>
	<h4>Welcome!</h4>
	<p>This is the Dashboard for your account.  You'll be able to
	view past contest results, as well as create new ones.</p>
<%- end -%>

run commands

git add --all
git commit -am "UI cleanup"
git checkout master
git merge ch03_06_ui_cleanup
git push

#####Section 7 run command

git checkout -b ch03_07_winner_selection

create and add to app/services/contest_results.rb

class ContestResults
  def initialize(array)
    raise ArgumentError.new("array is required") if array.blank?
    @array = array
  end

  #Picks <count> winners
  def results(count=1)
    if count.to_i < 2
      @array.sample
    else
      @array.sample(count)
    end 
  end
end

run commands

git add --all
git commit -am "Random winner selection"
git checkout master
git merge ch03_07_winner_selection
git push

#####Section 8 run command

git checkout -b ch03_08_contests

modify app/views/dashboard/index.html.erb

<%- content_for :sidebar do -%>
  <h4>New Contest</h4>
  <p>Use the form below to run a new Contest. Fields marked
   with * are required.</p>
  <form>
    <div class="control-group">
      <label for="name">Contest Name *</label>
      <div class="controls">
        <input type="text" class="text_field" id="name"
        class="name" required/>
      </div>
    </div>
    <div class="control-group">
      <label for="name">Type *</label>
      <div class="controls">
        <select id="product" name="product" required>
          <option>Any Product</option>
          <optgroup label="Specific Product">
            <option>Product A</option>
            <option>Product B</option>
          </optgroup>
        </select>
      </div>
    </div>
    <div class="control-group">
      <label for="name">Order Date Range (optional)</label>
      <div class="controls">
        <input type="text" class="text_field" id="from-
        date" name="from-date"/> to
        <input type="text" class="text_field" id="to-date"
        name="to-date"/>
      </div>
    </div>
    <div class="control-group">
      <label for="name">Max # (optional)</label>
      <div class="controls">
        <input type="text" class="text_field" id="limit"
        name="limit"/>
      </div>
    </div>
    <div class="form-actions">
      <input type="submit" value="Submit" class="btn"/>
      <%= link_to t('.cancel', :default =>
        t("helpers.links.cancel")),
        dashboard_index_path, :class => 'btn' %>
    </div>
  </form>
<%- end -%>

<h1>Contests</h1>
<table class="table table-striped">
  <thead> 
    <tr>
      <th>Name</th>
      <th>Criteria</th>
      <th>Winner</th>
      <th>Date</th>
    </tr>
  </thead>
  <tbody>
   <tr>
     <td>Customer Appreciation Giveaway</td>
     <td>Purchase made within last 30 days</td>
     <td>Order #1010, John Doe</td>
     <td>October 22, 2013</td>
   </tr>
 </tbody>
</table>

add to config/routes.rb

get "dashboard/index"

run commands

rails g model Contest name:string product_id:integer start_date:datetime end_date:datetime max_results:integer order_id:integer:index

bundle exec rake db:migrate

bundle exec rake db:migrate RAILS_ENV=test

add to app/models/contest.rb

  belongs_to :order
  validates_presence_of :name
  def criteria
    results = []
    results << "Product Name: #{product_name}" if
    product_name.present?
    results << "Start Date: #{I18n.l start_date.to_date,
    format: :short}" if start_date.present?
    results << "End Date: #{I18n.l end_date.to_date,
    format: :short}" if end_date.present?
    results << "Max #: #{max_results}" if
    max_results.present?
    return results.join(', ')
  end

add to app/models/order.rb

  # This method constructs the query based on
  # the passed-in parameters
  def self.candidate_list(params={})
    params[:order] ||= "order_date asc"
    orders = order(params[:order]).includes(:order_items)
    if params[:limit].present?
      orders = orders.limit(params[:limit].to_i)
    end
    if params[:product_id].present?
      orders = orders.where("order_items.shopify_product_id" =>
        params[:product_id].to_i)
    end
    if params[:start_date].present?
      orders = orders.where(["orders.order_date >= ?",
        params[:start_date]])
    end
    if params[:end_date].present?
      orders = orders.where(["orders.order_date <= ?",
        params[:end_date]])
    end
    
    # .pluck returns an array containing the specified field
    return orders.pluck(:id).uniq
  end

modify app/controllers/dashboard_controller.rb

  def index
    # Load past results in reverse order
    @contests = Contest.order("created_at desc")
         
    # Instantiate a new Contest so the form loads properly
    @contest = Contest.new

    # Load the Products we want to use for Contests
    @products = Product.all.order(:name)
  end

  # This method creates a Contest and returns
  # the winner(s) in the notice message
  def create_contest
    @contest = Contest.new(contest_params)
  
    # Store the name of the product for easier readability
    @contest.product_name = Product.find_by_shopify_product_id(contest_params[:product_id]).try(:name) if contest_params[:product_id].present?
    respond_to do |format|
      if @contest.save
        # Pick a winner
        candidates = Order.candidate_list(params)
        contest_results = ContestResults.new(candidates)
           
        # Save the winner
        @contest.update_attribute(:order_id, contest_results.results)
        format.html { redirect_to root_path, notice: "Contest Winner: <a href='#{order_path(@contest.order)}'>#{@contest.order.email}</a>" }
        format.json { render action: 'show', status: :created, location: @contest }
      else
        format.html { redirect_to root_path, alert: "Unable to create a Contest" }
        format.json { render json: @contest.errors, status: :unprocessable_entity }
      end 
    end
  end

private
  def contest_params
    params.require(:contest).permit(:name, :product_id, :start_date, :end_date, :max_results, :order_id)
  end

add to config/routes.rb

post "create_contest" => 'dashboard#create_contest'

replace app/views/dashboard/index.html.erb

<%- model_class = Contest -%>
<%- content_for :sidebar do -%>
<h4>New Contest</h4>
<p>Use the form below to run a new Contest. Fields marked with * are required.</p>
  <%= form_for @contest, :url => create_contest_path do |f| %>
    <div class="control-group">
       <%= f.label :name, "Contest Name *"  %>
      <div class="controls">
        <%= f.text_field :name, :class => 'text_field', :required => true %>
      </div>
    </div>
    <div class="control-group">
      <%= f.label :product_id, "Type *" %>
      <div class="controls">
        <select id="contest_product_id" name="contest[product_id]" required="required">
          <option>Any Product</option>
          <optgroup label="Specific Product">
            <%- @products.each do |product| -%>
              <option value="<%= product.shopify_product_id %>"><%= product.name %></option>
            <%- end -%>
          </optgroup>
        </select>
      </div>
    </div>
    <div class="control-group">
      <%= f.label :start_date, "Order Date Range (optional)" %>
      <div class="controls">
        <%= f.text_field :start_date, :class => 'text_field' %>
        <%= f.text_field :end_date, :class => 'text_field' %>
      </div>
    </div>
    <div class="control-group">
      <%= f.label :max_results, "Max # (optional)" %>
      <div class="controls">
        <%= f.text_field :max_results, :class => 'text_field' %>
      </div>
    </div>
    <div class="form-actions">
      <input type="submit" value="Submit" class="btn"/>
      <%= link_to t('.cancel', :default => t("helpers.links.cancel")),
      root_path, :class => 'btn' %>
    </div>
    <!-- End form -->
  <%- end -%>
  <!-- End sidebar -->
<%- end -%>
<h1>Contests</h1>
<table class="table table-striped">
  <thead>
    <tr>
      <th>Name</th>
      <th>Criteria</th>
      <th>Winner</th>
      <th>Date</th>
    </tr>
  </thead>
  <tbody>
    <% @contests.each do |contest| %>
    <tr>
      <td><%= contest.name %></td>
      <td><%= contest.criteria %></td>
      <% if contest.order.present? %>
      <td>Order <%= link_to contest.order.number, order_path(contest.order) %>, <%= contest.order.first_name %> <%= contest.order.last_name %> (<%= mail_to contest.order.email, contest.order.email %>)</td>
      <% else %>
        <td>Error picking winner</td>
      <% end %>
      <td><%=l contest.created_at, format: :long %></td>
    </tr>
    <% end %>
  </tbody>
</table>

run commands

git add --all
git commit -am "Contest Functionality"
git checkout master
git merge ch03_08_contests
git push

####Chapter 4 - Going Public

#####Section 1

run command

git checkout -b ch04_01_account_rework

rails g migration AccountRework

add to db/migrate/[timestamp]_account_rework.rb

    # These columns are not unique but global
    remove_column :accounts, :shopify_shared_secret
    remove_column :accounts, :shopify_api_key
    # We need to store more information about the shop
    add_column :accounts, :shopify_shop_id, :integer
    add_column :accounts, :shopify_shop_name, :string
    add_column :accounts, :shop_owner, :string
    add_column :accounts, :email, :string
    # Index commonly searched for fields
    add_index :accounts, :shopify_account_url
    add_index :accounts, :email

run commands

bundle exec rake db:migrate

bundle exec rake db:migrate RAILS_ENV=test

add to config/environments/development.rb

  SHOPIFY_API_KEY = "your_shopify_api_key"
  SHOPIFY_SHARED_SECRET = "your_shopify_shared_secret"
  DOMAIN = "http://localhost:3000"

add to config/environments/productions.rb

  SHOPIFY_API_KEY = ENV["SHOPIFY_API_KEY"]
  SHOPIFY_SHARED_SECRET = ENV["SHOPIFY_SHARED_SECRET"]
  DOMAIN = ENV["DOMAIN"]

add to .gitignore

/config/environments/development.rb

run commands

heroku config:set SHOPIFY_API_KEY=your_shopify_api_key

heroku config:set SHOPIFY_SHARED_SECRET=your_shopify_shared_secret

heroku config:set DOMAIN=your_heroku_domain

modify app/services/shopify_integration.rb

  attr_accessor :url, :password
  
  def initialize(params)
    # Ensure that all the parameters are passed in %w{url password}.each do |field|
    raise ArgumentError.new("params[:#{field}] is required") 
      if params[field.to_sym].blank?
      # If present, then set as an instance variable
      instance_variable_set("@#{field}", params[field.to_sym])
    end
  end
     
  # Uses the provided credentials to connect to Shopify
  def connect
    # Initialize the gem
    ShopifyAPI::Session.setup({
      api_key: SHOPIFY_API_KEY,
      secret: SHOPIFY_SHARED_SECRET
    })
    
    # Instantiate the session
    session = ShopifyAPI::Session.new(@url, @password)
  
    # Activate the Session so that requests can be made
    return ShopifyAPI::Base.activate_session(session)
  end

modify app/controllers.dashboard_controller.rb

  # GET /
  def index

    # Instantiate a new Contest so the form loads properly
    @contest = Contest.new

    # Load past results in reverse order
    @contests = current_account.contests.order("created_at desc")

    # Load the Products we want to use for Contests
    @products = current_account.products.order(:name)

  end

  # This method creates a Contest and returns the winner(s) in the notice message
  def create_contest
    @contest = current_account.contests.new(contest_params)

    respond_to do |format|
      if @contest.save

        # Pick a winner
        candidates = current_account.orders.candidate_list(params)
        contest_results = ContestResults.new(candidates)
        @contest.update_attribute(:order_id,contest_results.results)


        format.html { redirect_to root_path, notice: "Contest Winner: <a href='#{order_path(@contest.order)}'>#{@contest.order.email}</a>" }
        format.json { render action: 'show', status: :created, location: @order }

      else
        format.html { rredirect_to root_path, alert: "Unable to create a Contest" }
        format.json { render json: @contest.errors, status: :unprocessable_entity }
      end
    end

  end

  # GET /accounts/1/test_connection
  # GET /accounts/1/test_connection.json
  def test_connection
    # Connect to Shopify using our class
    ShopifyIntegration.new(url: current_account.shopify_account_url,
                           password: current_account.shopify_password,
                           account_id: current_account.id).connect()
    begin
      # The gem will throw an exception if unable to retrieve Shop information
      shop = ShopifyAPI::Shop.current
    rescue => ex
      @message = ex.message
    end

    if shop.present?
      respond_to do |format|
        # Report the good news
        format.html { redirect_to dashboard_index_path, notice: "Successfully Connected to #{shop.name}" }
        format.json { render json: "Successfully Connected to #{shop.name}" }
      end
    else
      respond_to do |format|
        # Return the message from the exception
        format.html { redirect_to dashboard_index_path, alert: "Unable to Connect: #{@message}" }
        format.json { render json: "Unable to Connect: #{@message}", status: :unprocessable_entity }
      end
    end
  end

  private

  # Never trust parameters from the scary internet, only allow the white list through.
  def contest_params
    params.require(:contest).permit(:name, :product_id, :start_date, :end_date, :max_results, :order_id)
  end

remove app/views/accounts/*

modify app/views/layouts/application.html.erb

 <ul class="nav">
   <li><%= link_to "Products", products_path  %></li>
   <li><%= link_to "Orders", orders_path %></li>
 </ul>

About

sample shopify app based on tutorial


Languages

Language:Ruby 96.1%Language:CSS 2.3%Language:CoffeeScript 1.1%Language:JavaScript 0.6%