Tutorijal Ruby on Rails - Projector App (tutorial korak-po-korak)

  • Začetnik teme Začetnik teme lnxdr
  • Datum pokretanja Datum pokretanja

lnxdr

Obećava
Poruka
68
Ovo je tutorial za pocetnike u Rails-u, sa nekim osnovnim poznavanjem kako funkcionise Ruby on Rails i uopste MVC (model-view-controller), REST. Molim vas da ne komentarisete osim u slucaju da moj metod nije u redu, i tom slucaju Vas molim da prilozite ispravan kod, ili tacno mesto gde je greska. Hvala!


Instaliranje RoR-a sam opisao u drugoj temi, tako da cu ovde preskociti. Preporucujem da radite sa Linuxa, i da koristite RVM za instaliranje Ruby jezika. Ako ste instalirali RVM i resetovali racunar, instalirali Ruby, Bundler i Rails, mozete poceti:


Napravite novu aplikaciju, i udjite u folder nakon kreiranja. U ovom slucaju nazvao sam je projector.


rails new projector && cd projector


Ovo ce napraviti novi direktorijum, i u njemu je sve sto nas zanima. Nazivi fajlova su prilicno jasni, barem ono sto nas zanima. U folderu app nalaze se folderi controllers, models, views izmedju ostalih. Kako bi najlakse razumeli kako Rails funkcionise, koristicemo scaffold funkciju. Scaffold ce napraviti CONTROLLER + MODEL + VIEW sa vec napisanim kodom za osnovne REST funkcije.


rails g scaffold project naziv:string opis:text ukupno:decimal do_sada:decimal aktivan:boolean


Model je taj koji komunicira sa bazom podataka, i opcije koristene uz komandu scaffold su iste koje bi dodali da ste pravili posebno model. Opcije nazivi tabela i njihov tip (naziv:string, opis:text). Model je trenutno prazan, na njega se vracamo kasnije.


Novi kontroler pravimo komandom rails g controller (rails g je isto sto i rails generate). Kao argumenti mogu se dodati funkcije koje zelimo (index, show, create...). Otvorite dobijeni kontroller pod nazivom projects_controller i pogledajte kod. U njemu su vec napravljene rute za sve, i svaka je komentarisana tako da mozete da razumete za cega sluzi. Da bi ste bolje razumeli, pogledajte na primeru koja se funkcija koristi kada korisnik otvori nas web-sajt.


INDEX - metod koji izlistava sve projekte (websajt.rs/projects)

SHOW - metod koji otvara odabrani projekat (websajt.rs/project/1)

DESTROY - brise odabrani projekat (websajt.rs/project/1/delete)

NEW - daje mogucnost da kreiramo novi projekat, npr web-forma (example.com/project/new)

CREATE - pravi novi projekat

EDIT - funkcija koja nam omogucava da izmenimo projekat (poziva UPDATE)

UPDATE - izmenjuje projekat


Vratimo se na za trenutak na pocetak, u folder controllers.Tu vidimo da se u folderu nalazi application_controller. Ako ste procitali osnove rails-a, znate da jedan kontroler moze da koristi drugi, ili da proizilazi iz njega. U ovom slucaju mi imamo ApplicationController, koji je glavni, i svi ostali ce proizilaziti iz njega (ProjectsController < ApplicationController). To znaci da one funkcije koje definisemo u ApplicationController-u su dostupne u svim kontrolerima koji proizilaze iz njega. Kontroleri su uvek u mnozini, dok se modeli pisu u jednini (projects_controller.rb i project.rb u folderu models).


Sada otvorite ProjectsController, i pogledajte funkciju index:


Ruby:
  def index

    @projects = Project.all

  end


# Svi projekti koji ce biti izlistani na strani = Svi projekti

# Project je u jednini, jer je to objekat(model) koji komunicira sa DB

# Ako bi zeleli samo zadnjih 10 projekata, uradili bi ovako:


  def index

    @projects = Project.last(10)

  end


Funkcija show je prazna, ali i dalje obavlja sav potreban posao. Samim tim sto je definisano "def show; end" rails zna da je u pitanju metod koji prikazuje proizvod. Medjutim ono sto takodje koristi metod show, definisano je u donjem delu ispod PRIVATE. To znaci da je metod privatan, i da nece biti koriscen direktno (u HTML na primer ne mozete koristiti PRIVATE metod). Taj metod se zove set_project, i on nije bez razloga privatan. On odredjuje sta ce korisniku biti prikazano.


Ruby:
# Na pocetku koda, before_action samo govori da se pre akcije izvrsi definisan metod.

# U ovom slucaju, pre akcije SHOW, izvrsi se metod SET_PROJECT


  before_action :set_project, only: [:show, :edit, :update, :destroy]


  ... def index

  ... def show

  ...


  private


# Pre nego sto se izvrsi metod show, pronadji Projekat pod zadatim ID

# Rails koristi polje ID za identifikaciju, kao i u URL


  def set_project

    @project = Project.find(params[:id])

  end


# Ako se doda jos nesto, obavice se nakon ovoga set_project

# Iako je SHOW metod gore prazan, set_project ce pronaci projekat,

# jer smo definisali BEFORE_ACTION


Napravite bazu podataka uz pomoc funkcije rails db:create. Standardna baza podataka u Rails-u je SQLite3, medjutim rad sa MySQL ili PostgreSQL se ne razlikuje, sve radimo isto (DB se definise u config-u). Kako bi Rails znao sta treba da uradi, koristimo migracije. Vratite se u glavni folder i idite u db/migrations projector > db > migrate. Ako ste izvrsili db:create, videcete bazu podataka u folderu DB, i zove se development.sqlite3. U rails-u su Development - Test - Production odvojeni u svakom pogledu, na svaki nacin, sto je jos jedna od prednsoti rails-a. Takodje imate i dokument seed.rb koji koristimo da popunimo bazu podataka sa materijalom za development, pa umesto da kucate sql funkcije, koristite Rails metod Project.create(naziv: PROJECTOR, opis: blablabla..). Mnogo jednostavnije od kucanja SQL-a, i za to je zasluzan ActiveRecord model. On komunicira sa bazom podataka, i videcete da glavni model (ApplicationRecord) proizilazi iz njega (ActiveRecord::Base), kao sto sam objasnio i za ProjectsController.


Kada udjete u folder migrate, primeticete naziv fajlova. Rails automatski pravi migracije, i na pocetku generise broj migracije pre naziva. Otvorite migraciju create_projects i pogledajte unutra. Ovo je kod koji rails koristi da napravi tabele u bazi podataka, u ovom slucaju SQLite3. Citajuci kod shvaticete sami sta se radi. Pravi nova tabela, oznacava se naziv i tip. Necu vas zamari sa dodatnim detaljima za sada.


Ruby:
# Polje :id ne morate da pravite, Rails ga pravi sam

# Vreme i datum takodje Rails sam dodaje, created_at kao i updated_at

# Za to je zaduzen t.datetime u sledecem kodu


class CreateProjects < ActiveRecord::Migration[6.0]

  def change

    create_table :projects do |t|

      t.string  :naziv

      t.text    :opis

      t.decimal :ukupno

      t.decimal :do_sada

      t.boolean :aktivan


      t.timestamps

    end

  end

end


Evo kako na kraju izgleda ProjectsController, najjednostavniji moguci. Ne koristi JSON kao sto je prvobitno definisao scaffold, nego samo HTML (radi lakseg razumevanja)


Ruby:
class ProjectsController < ApplicationController


# takodje moze da se koristi OSIM, umesto AKO

# before_action :set_project, except: [:index, :new, :create]


  before_action :set_project, only: [:show, :edit, :update, :destroy]



  # GET /projects


  def index

    @projects = Project.all

  end



  # GET /projects/1


  def show

  end



  # GET /projects/new


  def new

    @project = Project.new

  end



  # GET /projects/1/edit


  def edit

  end



  # POST /projects


  def create

    @project = Project.new(project_params)


    if @project.save

      redirect_to @project, notice: 'Project was successfully created.'

    else

      render :new

    end

  end



  # PATCH/PUT /projects/1


  def update

    if @project.update(project_params)

      redirect_to @project, notice: 'Project was successfully updated.'

    else

      render :edit

    end

  end



  # DELETE /projects/1 


  def destroy

    @project.destroy

    redirect_to projects_url, notice: 'Project was successfully destroyed.'

  end



  private



    # Use callbacks to share common setup or constraints between actions.


    def set_project

      @project = Project.find(params[:id])

    end



    # Rais proverava polja koja mu mi dozvolimo


    def project_params

      params.require(:project).permit(:naziv, :opis, :ukupno, :do_sada, :aktivan)

    end

end


Sada otvorite folder views unutar app, i pogledajte folder projects. Unutra imate fajlove sa imenima metoda koje koristimo u kontroleru. Fajlovi su .HTML.ERB (embedded ruby). Omogucava nam da koristimo prikacimo Ruby uz HTML. Tako kada otvorite index.html.erb, vidite da koristimo @projects (instance variable). Uvek pocinje sa @, i dostupna je izmedju metoda, kao i u views. Tako kada hocemo da prikazemo sve projekte, mi imamo @projects.each do |project|; project.naziv; project.opis .... Tabela UKUPNO oznacava koliko je ukupno novca potrebno da bi se ostvario projekat, a DO_SADA oznacava koliko je do sada skupljeno. Kasnije cemo ubaciti Bitcoin adrese za ove projekte, i dodati nova polja.


U sledecem tekstu objasnicu kako napraviti novog korisnika, autentikaciju i sve sto je potrebno za sajt. Koristicemo Devise Gem, jednostavan, testiran, i dobro se pokazao.
 
U glavnom direktorijumu nalazi se Gemfile, najbitniji deo nase aplikacije, i u njemu definisemo koje gemove cemo koristiti. Otvorite ga i pogledajte sta rails po default-u instalira. Mi cemo dodati gem Devise (koji je zaduzen za autentikaciju) i gem Bootstrap. Gemfile je takodje podeljen u grupe za development, test i production, a ukoliko gem ubacite van tih grupa, bice dostupan u sve tri. Evo kako moj Gemfile izgleda sada:

Ruby:
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby '2.6.4'

gem 'devise'
gem 'bootstrap'

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 6.0.3', '>= 6.0.3.4'
# Use sqlite3 as the database for Active Record
gem 'sqlite3', '~> 1.4'
# Use Puma as the app server
gem 'puma', '~> 4.1'
# Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker
gem 'webpacker', '~> 4.0'
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'

# Use Active Model has_secure_password
gem 'bcrypt', '~> 3.1.7'

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
end

group :development do
  # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
  gem 'web-console', '>= 3.3.0'
  gem 'listen', '~> 3.2'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end

group :test do
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara', '>= 2.15'
  gem 'selenium-webdriver'
  # Easy installation and use of web drivers to run system tests with browsers
  gem 'webdrivers'
end

Nakon toga izvrsite komandu bundle install, koja ce instalirati sve iz Gemfile-a. Tu komandu ponavljate kada god izmenite Gemfile.
Devise gem ima automatski generator, i sve sto treba je izvrsiti 2 komande. Prva komanda integrise Devise u nasu aplikaciju, dok druga automatski pravi model User, i sve sto je potrebno za autentikaciju korisnika na sajtu. Takodje napravi i migracije za DB, kao i views (.html.erb).

Ruby:
# Prva komanda koja integrise Devise u app:
rails g devise:install

# Druga komanda koja pravi User model
rails g devise user

Nakon ovoga ponovo izvrsite rails db:migrate kako bi smo napravili tabelu users. Kada smo to uradili, vreme je da se okrenemo Modelu. Rails radi na principu da je baza podataka samo skladiste, i da sva logika treba biti definisana u modelu. Druga stvar koja je bitna za model je kvalitet koda, i najbolji nacin za kvalitetnu aplikaciju je da kontroler bude sto jednostavniji, da sto vise bude definisano u modelu. Kontroler je taj koji govori aplikaciji sta treba da se uradi, dok model izvrsava zadatak i komunicira sa DB.

Asocijacije su veoma bitan faktor u rails-u, i omogucavaju nam na povezemo razlicite tabele. U ovom slucaju imamo model User, i model Project. Rails nam ovde ponovo olaksava posao, i skracuje nam vreme koje inace trosimo na pravljenje tabela i njihov Foreign_Key. Sve sto treba da uradimo je da napisemo da jedan model pripada drugom, i da jedan model poseduje drugi. Otvorite model user.rb, i u njega dodajte sledece:

Ruby:
class User < ApplicationRecord

# Ako zelite da korisnik mora da potvrdi email, dodajte :confirmable.
# Iz naziva je jasno sta koja opcija radi, i ona obavlja sav posao

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

  devise :database_authenticable, :registerable,
             :recoverable, :rememberable, :validatable

  has_many  :projects

Ovde smo dodali has_many, a u modelu Project dodacemo belongs_to. Otvorite Project.rb i dodajte belongs_to :user.
Nakon sto smo povezali korisnika i projekat, treba da se pozabavimo tabelama, odnosno migracijama. Obzirom da vec imamo napravljene tabele i za korisnika i za projekat, treba da dodamo kolonu user_id u tabelu projects. To cemo uraditi tako sto cemo napraviti novu migraciju, i u nju dodati tabelu user_id.

prvo izvrsite komandu:
Bash:
rails g migration add_user_id_to_projects
Nakon toga popunite napravljenu migraciju:
Ruby:
class AddUserIdToProjects < ActiveRecord::Migration[6.0]
  def change
    add_column :projects, :user_id, :integer
  end
end

Nakon toga ponovo izvrsavamo komandu rails db:migrate, i imamo zavrsen veci deo posla. Ostalo je jos prepraviti ProjectsController jer sada pripada korisniku. Obzirom da koristimo Devise gem, on nam omogucava da u kontrolerima imamo metod current_user, koji oznacava trenutnog korisnika. ProjectsController treba da izgleda ovako:

Ruby:
class ProjectsController < ApplicationController

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


# GET /projects

def index
   @projects = Project.where(aktivan: true) # napravili smo boolean tabelu aktivan
end


# GET /projects/1

def show
end


# GET /projects/new

def new
   @project = Project.new
end


# GET /projects/1/edit

def edit
   @projects = Project.where(user_id: current_user.id)
end


# POST /projects

def create

   @project = current_user.projects.build(project_params) # build je rails-ov metod, ne ruby

   if @project.save
     redirect_to @project, notice: 'Project was successfully created.'
   else
     render :new
   end

end


# PATCH/PUT /projects/1

def update

   if @project.update(project_params)
     redirect_to @project, notice: 'Project was successfully updated.'
   else
     render :edit
   end

end


# DELETE /projects/1

def destroy
   @project.destroy if @project.user_id = current_user.id # samo korisnik moze da obrise svoj projekat
   redirect_to projects_url, notice: 'Project was successfully destroyed.'
end


private


# Use callbacks to share common setup or constraints between actions.

def set_project
   @project = Project.find(params[:id])
end


# Rais proverava polja koja mu mi dozvolimo

def project_params
   params.require(:project).permit(:naziv, :opis, :ukupno, :do_sada, :aktivan, :user_id)
end

end

Imamo kompletan MVC za Projekat i User, ukljucujuci bazu podataka. Sada treba uraditi izgled sajta, i ubaciti u model opcije da proverava polja pre upisa u bazu podataka.
 
Ne znam zašto, ali zaista ne znam zašto nikada nisam voleo programske jezike koji imaju previše pisanja za nešto što je jednostavno.
Ruby:
params.require(:project).permit(:naziv, :opis, :ukupno, :do_sada, :aktivan)
Sve ovo require pa onda permit. Zar nisu mogli to prostije da pišu.
Moze, ali ovako mora da se napravi novi projekat, i samo ta polja se prihvataju. params.require(:project, fetch {} ) i prihvatao bi bilo koja polja, ovako su samo ova moguca. Ruby bas ima manje za pisanje od ostalih, ovo su validacije ako hoces siguran app, nije neophodno. Ali bi bilo dobro da ne nastavljamo diskusiju ovde.
 
Obzirom da zaista ne volim front-end, dodao sam gem 'bulma' umesto gem 'bootstrap', i prekopirao nekoliko linija iz nekog drugog projekta, cisto da postavim osnovu. Sutra cu dodati Bitcoin adrese, detalje o konfiguraciji, i postaviti demo na Heroku.

Ovako izgleda za sad: Izvorni Kod (GitHub)

Ako imate instaliran ruby jezik na linuxu, mozete probati sajt lokalno:

Bash:
# skinuti kod sa github-a

git clone https://www.github.com/cybersecrs/projector
cd projector && bundle install

# pokrenite server (puma):

rails s
 
U prethodnom delu napravljen je sajt koji koristi sqlite kao bazu podataka. Heroku je ne podrzava, i za demo je potrebna postgresql. Pobrinite se da je imate uspesno instaliranu, kao i node-js. Onda mozete napraviti novu aplikaciju i postaviti demo-sajt.

Napravi novu aplikaciju sa PostgreSQL DB, i udji u folder

Kod:
  rails new projector-demo -d=postgresql
  cd projector


Instaliraj gemove Devise, Bulma i Simple_Form

Kod:
  echo "gem 'devise'"      >> Gemfile
  echo "gem 'bulma'"       >> Gemfile
  echo "gem 'simple_form'" >> Gemfile
  bundle install


Integrisi simple_form i devise, i napravi scaffold Projects (prekopirajte kod za model i controller)

Kod:
  rails g simple_form:install
  rails g devise:install
  rails g devise user
  rails g controller registrations
  rails g scaffold project naziv opis:text ukupno:decimal do_sada:decimal aktivan:boolean user_id:integer
  sudo rm -r app/views/registrations/


Izmeni config/routes.rb file

Config/routes.rb odredjuje koja ruta pokrece koji metod u kom kontroleru. Scaffold i Devise su napravili rute automatski, ali posto smo izmenili RegistrationsController, dodajemo i njega:

Kod:
Rails.application.routes.draw do

# 'resources' oznacava sve metode definisane u Controller-u (index, show, create...)
#
# Svaka od njih moze da se definise posebno (GET i POST):
# get '/projects/:id', to: 'projects#show'

  resources  :projects
  devise_for :users, controllers: { registrations: 'registrations' }

  root 'projects#index'

end


Izmeni Devise i Projects migracije, definisi asocijacije

Ovog puta direktno u 'create_devise' migraciju dodajemo polje :nadimak, i definisemo ga kao jedinstveno polje:

Kod:
    .............
    .............

      ## Lockable
      # t.integer  :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
      # t.string   :unlock_token # Only if unlock strategy is :email or :both
      # t.datetime :locked_at

# DODAJEMO NADIMAK DIREKTNO U MIGRACIJU CREATE_DEVISE

      t.string  :nadimak, null: false, default: ""

      t.timestamps
    end


    add_index :users,   :nadimak              unique: true # DEFINISEMO NADIMAK KAO JEDINSTVEN

    add_index :users,   :email                unique: true
    add_index :users,   :reset_password_token unique: true
    # add_index :users, :confirmation_token   unique: true
    # add_index :users, :unlock_token         unique: true
  end
end


U projektu smo :user_id dodali dok smo kreirali scaffold, a sada definisemo polja kako zelimo da izgledaju

Kod:
class CreateProjects < ActiveRecord::Migration[6.0]
  def change
    create_table :projects do |t|
      t.string    :naziv,        null: false, default: ""
      t.text       :opis
      t.decimal :ukupno,    default: 0
      t.decimal :do_sada,  default: 0
      t.boolean :aktivan,    null: false, default: true
      t.integer :user_id,      foreign_key: true

      t.timestamps
    end

    add_index :projects, :naziv, unique: true
  end
end


Kreiramo tabele izvrsavajuci komande:

Kod:
rails db:setup
rails db:migrate


Postaviti demo aplikaciju na Heroku

Registrujte se na https://www.heroku.com
Instalirajte heroku-cli: sudo snap install --classic heroku
Ako nemate snap:curl https://cli-assets.heroku.com/install-ubuntu.sh | sh
(instalacija pomocu SH skripte ne vrsi automatski update)

Kod:
#Nakon registracije i instalacije heroku-cli:

heroku login
heroku apps:create projector

# Spremite GIT i postavite ga

git add .
git commit -m 'app initialized'
git push heroku master

# Napravite tabele u DB

heroku run rails db:migrate

Proverite log, i otvorite aplikaciju u browser-u

Kod:
heroku logs # prihvata opciju --tail da izlistava logove

heroku open


Front-end zaista ne volim da radim, ako neko hoce neka se igra, ja imam servere za prvih 6 meseci. Ako ne, pricekace jos malo.

GitHub Source (Sqlite3): https://www.github.com/cybersecrs/projector
GitHub Source (Heroku/PgSQL): https://www.github.com/cybersecrs/projector-heroku
 

Back
Top