19 Nisan 2020 Pazar

ORTA SEVİYE BİR RAİLS TUTORIAL - 2

Önceki bölümü okumadan buraya geldiyseniz hiç kasmayın geri dönün

Gönderiler


Artık blog özelliğini eklemeye başlayabiliriz. Uygulamamızın amacı aynı fikirde olan insanları bir araya getirmek olduğundan, bu mesajların yazarlarının tanımlanması gerekiyor. Bunun için kullanıcı yetkilendirme sistemi gerekiyor (authentication).

Kullanıcı Yetkilendirmesi


Kullanıcı yetkilendirme sistemi kurmak için Devise gem kullanacağız. Kendi kendimize de yetkilendirme sistemi kurabiliriz ama bu çok vakit ve kod alacaktır. Bu kolay bir yol ve Rails topluluğu tarafından çok kullanılıyor.


İlk adım Gemfile içine eklemek.

gem "devise"
sonra aktif hale getirmek için şu komutları girelim.

bundle
rails g devise:install
Şimdi devise jeneratörü kullanarak bir User modeli oluşturalım.

rails g devise User
Yeni modelimizi veritabanına ekleyelim.

rails db:migrate
Bu kadarıyla yetkilendirme sistemi hazır. Devise metodları ile yeni kullanıcı ekleyebilir, kullanıcıları düzenleyebiliriz. Şu anda uygulamada tanımlı linklerin listesine bakarsak bir hayli link eklendiğini göreceğiz.

rails routes


Şimdi server’ı çalıştırıp bu linklerden birkaçını deneyelim. http://localhost:3000/users/sign_in adresine gidelim ve kullanıcı girişi sayfasını görelim.



Eğer http://localhost:3000/users/sign_up sayfasına gidersek de bir yeni kullanıcı kaydı sayfası görürüz. Fakat views klasörüne gittiğimizde Devise kontrolorüne ait görsellerin bulunduğu düzenleyebileceğimiz bir klasör yok. Devise dökümanlarını incelediğimizde görselleri modifiye edebilmek için ilk önce jeneratör ile devise görsellerini oluşturmamız gerekiyor.

Konsolda şu komutu çalıştıralım:

rails g devise:views
Şimdi views klasörüne baktığımızda Devise görsellerinin toplandığı klasörü görürüz. Şimdi giriş ve kayıt sayfalarını görmek istediğimiz gibi düzenleyebiliriz. Kullanıcı girişi sayfası ile başlayacağız, çünkü kayıt sayfasında User modeline ilave bilgi ekleyeceğiz.


Login sayfası


app/views/devise/sessions/new.html.erb dosyasını açalım. Sadece bir form genel olarak dikkatimizi çeker. Rails’in form_for metodu ile bu form üretilmiştir. Form stilini Bootstrap stiline göre değiştireceğiz. Dosyanın içeriğini şu hale getirelim:

app/views/devise/sessions/new.html.erb
<%= bootstrap_form_for(resource, 
                      as: resource_name, 
                      url: session_path(resource_name)) do |f| %>

  <%= f.email_field :email, 
                    autofocus: true,
                    class: "form-control",  
                    placeholder: "email" %>

  <%= f.password_field :password, 
                        label: "Şifre",
                        autocomplete: "off",
                        class: "form-control",
                        placeholder: "password" %>

  <% if devise_mapping.rememberable? %>
    <%= f.check_box :remember_me, label: "Beni hatırla" %>
  <% end %>

  <%= f.submit "Giriş yap", class: "form-control login-button" %>

<% end %>


Eğer http://localhost:3000/users/sign_in adresine gidersek bir metod bulunamadı hatası alırız.

undefined method 'bootstrap_form_for

Bootstrap formlarını kullanmak için Rails’e bootstrap_form gem eklemeliyiz. Gemfile açın ve şunu ekleyin:

gem "bootstrap_form"
Sonra da tabiki konsolda

bundle
Şu anda login sayfası şuna benziyor:


Formun da Bootstrap içinde olması için etrafını stillerle çevirmeliyiz.

app/views/devise/sessions/new.html.erb
<div class="container">
  <div class="row">
    <div class="col-sm-6 col-sm-offset-3">
      <h2 class="text-center">Log in</h2>

      <!-- önceki içeriği buraya kopyalayın -->

    </div>
  </div>
</div>

Formun genişliği 12 lik grid alanının 6 kolonluk kısmı, başına da 3 kolon ofset verilmiş. Cep telefonu gibi küçük ekranlarda form tüm genişliği kaplayacaktır.

Biraz route düzenlemesi yapalım. Kullanıcı giriş sayfasına users/sign_in adresi yerine /login adresiyle girsek daha derli toplu olacak. İlk önce kullanıcı girişi yapmak için kullanılan aksiyonun nerede olduğunu bilmeliyiz. Devise kontrolörleri gem’in kendisi içinde saklanmıştır. Devise dökümanlarına göre devise_scope metodu ile route değiştirmesi yapabiliriz.

config/routes.rb
Rails.application.routes.draw do
  devise_for :users
  root to: "pages#index"

  devise_scope :user do
    get "login", to: "devise/sessions#new"
  end
end

Artık http://localhost:3000/login adresine giderek kullanıcı giriş sayfasını açabiliriz. Şimdilik login sayfasını böyle bırakalım ve yeni kullanıcı kaydı sayfasına geçelim.



Signup sayfası


Eğer http://localhost:3000/users/sign_up sayfasına gidersek default yeni kullanıcı sayfasını görürüz. Daha önce dediğimiz gibi yeni kayıt sayfasında ilave yapacaktık. Standart devise bilgilerin yanında kullanıcı ismini de kaydetmek istiyoruz. users tablosuna :name sütunu ekleyeceğiz.

Yeni bir migration dosyası oluşturarak tabloda değişikliğe gidebiliriz ancak daha herhangi bir kayıt girmedik bu yüzden tabloyu üretmek için üretilmiş olan migrasyon dosyasını değiştirip, sonra da tabloyu silip tekrar üretebiliriz.

db/migrate klasörüne gidelim ve içinde şu anda yegane dosya olan *ÜretimTarihi*_devise_create_users.rb gibi ismi olan dosyayı açalım. change metodu içindeki create_table bloğunun içine en başa t.string :name, null: false, default: "" satırını ekleyelim. Şimdi tabloyu tekrar üretmek için konsolda:

rails db:drop:_unsafe
rails db:migrate
Tabloya yeni sütunu ekledik. Şimdi bunu kontrolöre eklemeli. Devise kontrolörlerinde değişiklik yapmak için devise jeneratörü kullanarak kontrolörleri üretebiliriz ya da sadece değiştirmek istediğimiz kontrolör ve metodlara ait dosyalar yapabiliriz. Biz 2. yoldan gideceğiz. app/controllers klasörüne gidelim ve registrations_controller.rb adında yeni bir dosya ekleyip içine şu kodu yazalım:

app/controllers/registrations_cotroller.rb
class RegistrationsController < Devise::RegistrationsController

    private

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

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

end

Bu kod :name parametresini de içereren yeni metodları orjinalde üretilmiş olan sign_up_params ve account_update_params metodlarının üzerine yazar.

Şimdi bu kontrolörü routes.rb dosyasında tanımlamamız gerekiyor.

devise_for :users
satırını şöyle değiştirelim:

devise_for :users, :controllers => {:registrations => "registrations"}
Artık new.html.erb dosyasında :name alanını ekleyebiliriz.

app/views/devise/registrations/new.html.erb
<%= bootstrap_form_for(resource, 
    as: resource_name, 
    url: registration_path(resource_name)) do |f| %>

  <%= f.text_field :name,
      placeholder: "username (herkesin göreceği isim)",
      class: "form-control",
      label: "İsim" %>

  <%= f.text_field :email, 
      placeholder: "email",
      class: "form-control" %>

  <%= f.password_field :password, 
      placeholder: "password",
      class: "form-control",
      label: "Şifre" %>

  <%= f.password_field :password_confirmation, 
      placeholder: "password doğrulama",
      class: "form-control",
      label: "Şifre (tekrar)" %>

  <%= f.submit "Kayıt ol", class: "btn sign-up-btn" %>

<% end %>

<%= render "devise/shared/links" %>

Şimdi bu formu da login formunda olduğu gibi Bootstrap grid sistem içine alalım.

app/views/devise/registrations/new.html.erb
<div class="container" id="sign-up-form">
  <div class="row">
    <h1>Aynı amaçlı insanlarla görüşün</h1>
    <h3>Birlikte amaçlar belirleyin, çalışın, başarıya ulaşın</h3>
    <div class="col-sm-4 col-sm-offset-4">
      <h3>Kayıt olun <small> bedava!</small></h3>

      <!-- önceki içeriği buraya kopyalayın -->

    </div>
  </div>
</div>

Aynı login sayfasında olduğu gibi user/sign_up yerine bu syfaya /signup adresi yazarak girebilmek için config/routes.rb dosyasına ilave yapalım.

config/routes.rb
Rails.application.routes.draw do
    devise_for :users, :controllers => {:registrations => "registrations"}  
    root to: "pages#index"

  devise_scope :user do
    get "login", to: "devise/sessions#new"
    get "signup", to: "devise/registrations#new"
  end
end

Devam etmeden biraz stil düzenlemesi yapalım. app/assets/stylesheets/partials klasörüne signup.scss isimli bir dosya ekleyelim ve içine şu kodu yazalım:

app/assets/stylesheets/partials/signup.scss
#sign-up-form {
    margin-top: 100px;
    h1 {
        font-size: 36px !important;
        font-size: 3.6rem !important;
    }
    text-align: center;
    padding-bottom: 20px;
}

application.scss dosyası içine @import "partials/layout/*" satırının hemen üzerine tüm partials klasörü içini de dahil edelim.

app/assets/stylesheets/application.scss
...

// Parça dosyalar
@import "partials/*";
@import "partials/layout/*";

Tüm sitenin görüntüsünü etkileyecek birkaç stil daha ekleyelim. app/assets/stylesheets/base klasörü içine default.scss adında bir dosya ekleyelim ve içine şu kodu yazalım:

app/assets/stylesheets/base/default.scss
* {
    box-sizing: border-box;
}

html {
    font-size: 62.5%;
}

body {
    background: $backgroundColor;
    font-size: 14px;
    font-size: 1.4rem;
}

h1 {
    font-size: 24px;
    font-size: 2.4rem;
}

i {
    width: 26px;
}

ul {
    list-style-type: none;
}

a:hover, a:active, a:link, a:visited {
    text-decoration: none;
}

.control-label {
    display: none;
}


Burada $backgroundColor adında bir değişken kullandık ama daha önce tanımlamadık. Değişkenleri koymak için yazdığımız variables.scss dosyasına şu satırı ekleyelim.

$backgroundColor: #f0f0f0;
Şimdi default.scss dosyasını da application.scss içinde import edelim.

app/assets/stylesheets/application.scss
...

// Değişkenler
@import "base/variables";

// Default stiller
@import "base/default";

...



Gezinti bar düzenleme


Artık 3 sayfamız var, ana sayfa, login ve signup sayfaları. Bunlar arasında geçiş yapabilmek için _navigation.html.erb parça görseline linkler ekleyelim.

Önce kısa bir plan. Bootstrap grid yapısını gezinti bara uygularken .container bloğu içindeki yapı şuna benzeyecek:

<div class="row">

    <!-- Her zaman görünen elemanlar -->
    <div class="col-sm-7">
    ...
    </div>

    <!-- Küçük ekranlarda otomatik gizlenecek elemanlar -->
    <div class="col-sm-5">
    ...
    </div>

</div>

Böyle devam edersek çarşı karışacak. Şu yukarıdaki iki bölümü ayrı dosyalara alalım ki hakimiyeti kaybetmeyelim. app/views/layouts klasörü altında navigation adında yeni bir klasör oluşturalım ve içinde _header.html.erb dosyasını oluşturarak _navigation.html.erb içindeki tüm .navbar-header bölümünü bu dosya içine taşılayalım. Yine aynı yere _collapsible_elements.html.erb dosyası üretip içine tüm .navbar-collapse bölümünü taşıyalım.

En son _navigation.html.erb dosyasını şu hale geitrelim:

app/views/layouts/_navigation.html.erb
<nav class="navbar navbar-default navbar-fixed-top">
  <div class="container">
    <div class="row">

      <!-- Her zaman görünen elemanlar -->
      <div class="col-sm-7">
        <%= render "layouts/navigation/header" %>
      </div>

      <!-- Küçük ekranlarda otomatik gizlenecek elemanlar -->
      <div class="col-sm-5">
        <%= render "layouts/navigation/collapsible_elements" %>
      </div>
      
    </div>
  </div><!-- /.container -->
</nav>

Şimdi http://localhost:3000/ adresine gidersek bir değişiklik olmadı ama sadece kodumuzu biraz temizledik ve ileride yapacağımız ilavelere hazırladık.

Linklerimizi eklemek için _collapsible_elements.html.erb dosyasını şu hale getirelim:

app/views/layouts/navigation/_collapsible_elements.html.erb
<div class="collapse navbar-collapse navbar-right" 
        id="bs-example-navbar-collapse-1">
  <ul class="nav navbar-nav">
    <% if user_signed_in? %>
        <li class="dropdown pc-menu">
            <a id="user-settings" class="dropdown-toggle" 
                                  data-toggle="dropdown" href="#">
                <span id="user-name"><%= current_user.name %></span>
                <span class="caret"></span>
            </a>
            <ul class="dropdown-menu" role="menu">
                <li><%= link_to "Profili düzenle", 
                                 edit_user_registration_path %></li>
                <li><%= link_to "Çıkış yap", 
                         destroy_user_session_path, method: :delete %></li>
            </ul>
        </li>

        <li class="mobile-menu">
            <%= link_to "Profili düzenle", edit_user_registration_path %>
        </li>
        <li class="mobile-menu">
            <%= link_to "Çıkış yap", destroy_user_session_path, 
                                   method: :delete %>
        </li>
    <% else # kullanıcı giriş yapmamış %>
        <li><%= link_to "Login", login_path %></li>
        <li><%= link_to "Signup", signup_path %></li>
    <% end %>
  </ul>

</div><!-- /.navbar-collapse -->

Burada aynı şeylerden bazen iki kopya konmuş birine pc-menu diğerine mobile-menu CSS sınıfı verilmiş. Bunun amacı farklı ekran boyutlarındaki görünümü değiştirmek. Şimdi bu sınıflara ait stiller ekleyelim. app/assets/stylesheets klasörü altında responsive adında bir klasör ekleyelim ve içine desktop.scss ve mobile.scss adında iki stil dosyası ekleyelim.

app/assets/stylesheets/respnsive/desktop.scss
@media screen and (min-width: 767px) {
    .mobile-menu {
        display: none !important;
    }
}


app/assets/stylesheets/respnsive/mobile.scss
@media screen and (max-width: 767px) {
    .pc-menu {
        display: none !important;
    }
}


Şimdi bu stilleri application.scss içinde import edelim.

...

// Ekrana göre görünüm seçimleri
@import "responsive/*";


app/assets/stylesheets/partials/layout/navigation.scss dosyasında nav elemanı bloğunun içine şunu ekleyelim:

.col-sm-5, .col-sm-7 {
    padding: 0;
}

Dosyanın en altına da şunları ekleyelim:

.pc-menu {
    margin-right: 10px;
}

.mobile-menu {
    i {
        color: white;
    }
    ul {
        padding: 0px;
    }
    a {
        display: block;
        padding: 10px 0px 10px 25px !important;
    }
    a:hover {
        background: white !important;
        color: black !important;
        i {
            color: black;
        }
    }
}

.icon-bar {
    background-color: white !important;
}

.active a {
    background: $navbarColor !important;
    border-bottom: solid 5px white;
}

.dropdown-toggle, .dropdown-menu {
    background: $navbarColor !important;
    border: none;
}

.dropdown-menu a:hover {
    color: black !important;
    background: white !important;
}


Şu anda uygulamamızın giriş yapılmamış görüntüsü şöyle olacak:


Giriş yapılmış hali de şuna benzeyecek:


Küçük ekranlarda da şöyle olacak:





Yardımcılar


_collapsible_elements.html.erb dosyasında bulunan bir kısım yönetim programını görsel dosyası içinden çıkaracağız. app klasörüne bakarsak içinde bir de helpers adında klasör var. Görsel dosyasında bulunan lojik kısmı bu klasör içine koyacağız.

İlk yardımcımızı oluşturalım. helpers klasörüne gidip navigation_helper.rb adında bir dosya ekleyelim.

app/helpers/navigation_helper.rb
Helper dosyaları içinde yardımcılar modüller olarak tanımlanır. Şu modülü tanımlayarak başlayalım:

app/helpers/navigation_helper.rb
module NavigationHelper
end


Default olarak Rails tüm görsellere ait yardımcı dosyalarını yükler ve her yerde kullanılabilir. Fakat bu farklı yardımcı dosyaları içinde bulunabilecek benzer isimli metodlarda çakışmaya sebep olabilir. İlk önce bu default davranışı engelleyelim. Bunun için config/application.rb içinde Application sınıf tanımı içine şu satırı ekleyelim.

config.action_controller.include_all_helpers = false
Artık yardımcılar sadece ilgili kontrolörün görsellerinde kullanılabilir. Yani PagesController varsa pages_helper.rb tüm görseller içinden pages klasörü içindeki görsellerde kullanılabilir.

E biz yaptık NavigationHelper ama bir NavigationController yok. O zaman bu yardımcıya hiç bir yerden erişilemeyecek. Erişilebilir yapmak için NavigationHelper modülünü ApplicationHelper modülü içine include metodu ile enjekte ederiz.

app/helpers/application_helper.rb
module ApplicationHelper
    include NavigationHelper
end

Artık NavigationHelper modülü içindeki metodlara tüm uygulama genelinde erişilebilir.

Dönelim _collapsible_elements.html.erb dosyasına. Dosyayı editörde açalım. if else bloğu içindeki iki bölümü parça dosyalara alacağız. Dosyanın bulunduğu navigation klasörü altında collapsible_elements adında bir alt klasör tanımlayalım.

app/views/layouts/navigation/collapsible_elements
Klasör içine iki yeni dosya ekleyelim, _signed_in_links.html.erb (giriş yapmış kullanıcıların linkleri için) ve _non_signed_in_links.html.erb (giriş yapılmadan önceki linkler için). Şimdi _collapsible_elements.html.erb dosyası içinden ilgili kısımları kesip bu dosyalara taşıyalım. Dosyalar şu hale gelecek:

app/views/layouts/navigation/collapsible_elements/_signed_in_links.html.erb
<li class="dropdown pc-menu">
    <a id="user-settings" class="dropdown-toggle" 
             data-toggle="dropdown" href="#">
        <span id="user-name"><%= current_user.name %></span>
        <span class="caret"></span>
    </a>
    <ul class="dropdown-menu" role="menu">
        <li><%= link_to "Profili düzenle", 
            edit_user_registration_path %></li>
        <li><%= link_to "Çıkış yap", 
            destroy_user_session_path, method: :delete %></li>
    </ul>
</li>

<li class="mobile-menu">
    <%= link_to "Profili düzenle", edit_user_registration_path %>
</li>
<li class="mobile-menu">
    <%= link_to "Çıkış yap", 
        destroy_user_session_path, method: :delete %>
</li>

app/views/layouts/navigation/collapsible_elements/_non_signed_in_links.html.erb
<li><%= link_to "Login", login_path %></li>
<li><%= link_to "Signup", signup_path %></li>

Şimdi _collapsible_elements.html.erb dosyası içinde bulunan if else bloğu yerine collapsible_links_partial_path yardımcı metodunu render metoduyla çağıralım.

app/views/layouts/navigation/_collapsible_elements.html.erb
<div class="collapse navbar-collapse navbar-right" id="bs-example-navbar-collapse-1">
  <ul class="nav navbar-nav">
    <%= render collapsible_links_partial_path %>
  </ul>

</div><!-- /.navbar-collapse -->

Sıra geldi NavigationHelper içine metodumuzu eklemeye:

app/helpers/navigation_helper.rb
module NavigationHelper

    def collapsible_links_partial_path
        if user_signed_in?
            "layouts/navigation/collapsible_elements/signed_in_links"
        else
            "layouts/navigation/collapsible_elements/non_signed_in_links"
        end
    end
    
end

Böylece ilk yardımcımızı tanımlamış olduk. Bundan sonra mümkün olduğu kadar bu yolla görsellerin içinden lojiği ayıracağız.

Geldik bir bölümün daha sonuna. Bu uygulama için yapacağımız daha çok şey var. Bir süre sonra makyajlara hız vereceğiz. Sonraki bölümde görüşmek üzere kalın sağlıcakla..

Sırada şunlar var:


Hiç yorum yok:

Yorum Gönder