DEV Community

Cover image for Rails Bitcoin Prodavnica - deo I
Linuxander
Linuxander

Posted on • Updated on

Rails Bitcoin Prodavnica - deo I

This is tutorial in Serbian language, creating simple shop with Rails 6 and Bitcoin, without JavaScript (or to not depends on JavaScript).

Prvi u seriji od 3 tutoriala u kojima cemo napraviti kompletnu prodavnicu, bez funkcija koje necemo koristiti, bez online wallet-a, privatnih kljuceva. Siguran i jednostavan nacin, koristeci BIP32 Master Public Key. Podrazumeva se osnovno znanje Ruby on Rails i MVC.

U prvom delu napravicemo novu aplikaciju, korisnike, proizvode i kopru. U drugom delu porudzbine i placanje, i u trecem delu izgled sajta, pagination, i priprema za production env.


Nova aplikacija


Kreiraj novu rails aplikaciju (istraziti rails new komandu, kao i --no-javascript i --api opcije).

rails new example-shop

Otvorite Gemfile i u njega dodajte sledece:

gem 'devise'
gem 'bulma'
Enter fullscreen mode Exit fullscreen mode

Ovo ce instalirati gemove za autentikaciju (devise) i za stil (bulma).
Nakon toga kucajte bundle install za instaliranje gemova.


Novi Korisnik


rails g devise:install
rails g devise user
rails g controller registrations
Enter fullscreen mode Exit fullscreen mode

Ovo ce integrisati devise u vasu aplikaciju, dodati model i tabelu :users,
i napraviti novi kontroler za registracije. Otvorite Registrations Controller:

class RegistrationsController < Devise::RegistrationsController

 private

 def sign_up_params
  params.require(:user).permit(:name, :address, :country, :email, :password, :password_confirmation)
 end

 def account_update_params
  params.require(:user).permit(:name, :address, :country, :email, :password, :password_confirmation, :current_password)
 end

end
Enter fullscreen mode Exit fullscreen mode

Registrations Controller proizilazi iz Devise::Registrations, pa u njemu menjamo params, da bi smo
dodali nova polja (:name, :address, :country). Nakon toga napravite novu migraciju:

rails g migration add_fields_to_users

class AddFieldsToUsers < ActiveRecord::Migration[6.0]

  def change
    add_column :users, :name,    :string, null: false, default: ""
    add_column :users, :country, :string, null: false, default: ""
    add_column :users, :address, :text
  end

end
Enter fullscreen mode Exit fullscreen mode

Za kraj izvrsite komandu rails db:migrate.

Otvoride model User i dodajte sledece:

class User < ApplicationRecord

  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable

  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  validates :name,    length: { maximum:  16, too_long: "%{count} chars is maximum for User name."}
  validates :country, length: { maximum:  20, too_long: "%{count} chars is maximum for User country."}
  validates :address, length: { maximum:  75, too_long: "%{count} chars is maximum for User address."}

  has_many :products

end
Enter fullscreen mode Exit fullscreen mode

Provera da li je neko polje validno moze da se definise u modelu ili direktno u DB (migrations).
Ako je polje obavezno za sve, definisemo null: false, ali nekad je polje obavezno za jednu grupu
korisnika, dok za drugu nije. Zato sve provere treba definisati u modelu.


Dodati Proizvode


Rails scaffold automatski napravi model, kontroler, kao i views.

rails g scaffold product title description:text photo price:decimal user_id:integer published:boolean

Otvorite ProductsController i promenite kod da izgleda ovako:

class ProductsController < ApplicationController


  before_action :authenticate_user!, except: [:show, :index]
  before_action :set_product,        only:   [:show, :update, :destroy, :edit]


  def index
    @products = Product.all
  end

  def show
  end

  def new
    @product = current_user.products.build
  end

  def edit
  end


  def create
    @product = current_user.products.build(product_params)
    if @product.save
      redirect_to @product, notice: "Product #{@product.title} has been created."
    else
      render :new
    end
  end


  def update
    if @product.update(product_params)
      redirect_to @product, notice: "Product #{@product.title} has been updated."
    else
      render :edit
    end
  end

  def destroy
    @product.published = false if @product.user_id == current_user.id
    redirect_to root_path, notice: 'Product was successfully destroyed.'
  end


  private

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

  def product_params
    params.require(:product).permit(:title, :price, :description, :photo, :user_id, :published)
  end

end
Enter fullscreen mode Exit fullscreen mode

Promenite model Product da izleda:

class Product < ApplicationRecord

  belongs_to :user

  validates :title, :price, :description, :photo, :user_id, :published, presence: true
  validates :price,         length: { maximum:    7 }
  validates :title,         length: { maximum:  140, too_long: "Title is too long! [%{count}] chars is maximum."}
  validates :description,   length: { maximum: 1000, too_long: "Description is too long! [%{count}] chars is maximum."}

end
Enter fullscreen mode Exit fullscreen mode

Na kraju migracije:

class CreateProducts < ActiveRecord::Migration[6.0]
  def change
    create_table :products do |t|
      t.string  :title,       null: false, default: ""
      t.decimal :price,       null: false, default: 0
      t.text    :description
      t.string  :photo
      t.boolean :published,   null:false, default: true

      t.timestamps
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Izvrsite rails db:migrate i kreirali ste Products za vasu aplikaciju!


Shopping Cart


Shopping Cart je nezaobilazni deo svake prodavnice. Za to su nam potrebni
Cart i CartItem. Ponovo koristimo scaffold, i pocinjemo sa Cart modelom.

rails g scaffold cart

Otvorite CartsController i neka izgleda ovako:

class CartsController < ApplicationController

  rescue_from   ActiveRecord::RecordNotFound, with: :invalid_cart

  before_action :set_cart, except: [:create, :new]


  def index
    @carts = list_cart_products
  end

  def new
    @cart = current_user.carts.build
  end

  def create
    @cart = current_user.carts.build(cart_params)
    if @cart.save
      redirect_to @cart
    else
      render :new
    end
  end

  def destroy
    if current_cart.id == session[:cart_id]
      @cart.destroy
      session[:cart_id] = session[:token] = nil
      set_cart and redirect_to root_url, notice: 'Your cart is empty!'
    end
  end


  private

  def list_cart_products
    CartItem.where(cart_id: session[:cart_id]).all
  end

  def invalid_cart
    logger.error "Attempt to access invalid cart with ID: #{params[:id]}"
    redirect_to root_path, notice: "Cart doesn't exist!"
  end

  def cart_params
    params.require(:cart).permit(:token)
  end

end
Enter fullscreen mode Exit fullscreen mode

Kako bi odrzali korpu i za korisnike koji nisu registrovani, koristimo :sessions.
Takodje koristimo i :token, koji ce kasnije sluziti za friendly-id gem kao rute,
i svaki model verifikuje token (cart, order, payment)

Sada otvorite Cart model i dodajte sledece:

class Cart < ApplicationRecord

  has_many  :cart_items, dependent: :destroy

  validates :token, presence: true


  def add(product)
    cart_item = cart_items.find_by(product_id: product.id)
    cart_item ? cart_item.quantity += 1 : cart_item = cart_items.build(product_id: product.id)
  end


  def total_price
    cart_items.to_a.sum { |item| item.total_price }
  end

end

Enter fullscreen mode Exit fullscreen mode

Otvorite concerns u folderu models i napravite shopping_cart.rb.
U njega dodajte sledece:

module ShoppingCart

  private

  def set_cart
    begin
      @cart = Cart.find(session[:cart_id])
    rescue    ActiveRecord::RecordNotFound
      @cart = Cart.create(token: generate_token)
      session[:token]   = @cart.token
      session[:cart_id] = @cart.id
    end
  end

  def generate_token
    SecureRandom.urlsafe_base64
  end

end
Enter fullscreen mode Exit fullscreen mode

Otvorite ApplicationController i dodajte sledece:

class ApplicationController < ActionController::Base

  protect_from_forgery with: :exception

  include ShoppingCart

  before_action :set_cart
  helper        :all

  def current_cart
    Cart.find(session[:cart_id])
  end

end
Enter fullscreen mode Exit fullscreen mode

Sada napravite CartItem, ponovo uz pomoc scaffold-a:

rails g scaffold cart_item

Otvorite CartItems Controller i dodajte sledece:

class CartItemsController < ApplicationController

  before_action :set_cart
  before_action :set_cart_item, except: [:index, :create]

  def index
    @cart_item = CartItem.find_by(cart_id: session[:cart_id])
  end

  def show
    @cart_item = CartItem.find(params[:id])
  end

  def new
    @cart_item = CartItem.new
  end

  def create

    @product              = Product.find(params[:product_id])
    @cart_item            = @cart.add(@product)
    @cart_item.product_id = @product.id

    if @cart_item.save
      redirect_to carts_path, notice: "#{@cart_item.product.title} added to cart for $#{@cart_item.total_price}"
    else
      render :new
    end
  end


  def destroy
    if @cart_item.cart_id == @cart.id
      @cart.destroy
      set_cart and redirect_to @cart, notice: "#{@cart_item.product.title} has been removed from cart!"
    end
  end


  private

  def set_cart_item
    @cart_item = CartItem.find_by(id: params[:id], cart_id: @cart.id)
  end

  def cart_item_params
    params.require(:cart_item).permit(:product_id, :cart_id, :quantity, :total_price)
  end

end
Enter fullscreen mode Exit fullscreen mode

Sada otvorite model CartItem i dodajte:

class CartItem < ApplicationRecord

  belongs_to :cart
  belongs_to :product

  def total_price
    product.price.to_i * quantity.to_i
  end

end
Enter fullscreen mode Exit fullscreen mode

I to je sve za prvi deo! Sada imamo potpuno funkcionalne korisnike, proizvode i korpu.
U drugom delu dodacemo porudzbine i placanje Bitcoinom, kao i izgled sajta koristeci bulmu ili bootstrap, po vasem izboru.

Ostavite wordpress i woo-commerce, korstite Ruby on Rails za online prodavnicu.


=> Procitajte Deo II

Top comments (0)