27 Nisan 2020 Pazartesi

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


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

Ana sayfa güncellemesi


Hali hazırda ana sayfada birkaç rastgele gönderi görebiliyoruz. Tüm kolleksiyonlardan birkaç gönderi gösterebilmek için ana sayfayı değiştirelim.

_main_content.html.erb dosyasının içeriğini şöyle değiştirelim:

app/views/pages/index/_main_content.html.erb
<div id="main-content" class="col-sm-9">
    <h3 class="page-name"><%= link_to "Hobi Arkadaşı", hobby_posts_path %></h3>
    <div class="row">
        <%= render @hobby_posts %>
        <%= render no_posts_partial_path(@hobby_posts) %>
    </div><!-- row -->

    <h3 class="page-name"><%= link_to "Çalışma Arkadaşı", study_posts_path %></h3>
    <div class="row">
        <%= render @study_posts %>
        <%= render no_posts_partial_path(@study_posts) %>
    </div><!-- row -->

    <h3 class="page-name"><%= link_to "Takım Arkadaşı", team_posts_path %></h3>
    <div class="row">
        <%= render @team_posts %>
        <%= render no_posts_partial_path(@team_posts) %>
    </div><!-- row -->

</div><!-- ana içerik -->




Her kolleksiyondan gönderilerin olduğu bölümler oluşturduk.

Oluşum değişkenlerini PagesController in index aksiyonunda tanımlayalım. Aksiyonu şöyle yapalım:

app/controllers/pages_controller.rb
    def index
        @hobby_posts = Post.by_branch("hobby").limit( 8 )
        @study_posts = Post.by_branch("study").limit( 8 )
        @team_posts = Post.by_branch("team").limit( 8 )
    end

Daha önce no_posts_partial_path yardımcı metodunu tanımlamıştık. Fakat üzerinde biraz değişiklik yapmamız gerekiyor. Şu anda sadece kolleksiyon sayfalarına göre çalışıyor. Metoda bir posts parametresi ekleyelim:

app/helpers/posts_helper.rb
    def no_posts_partial_path(posts)
        posts.empty? ? "posts/shared/no_posts" : "shared/empty_partial"
    end


Burada posts parametresini tanımladık, oluşum değişkenini basit değişken ile değiştirdik. Parça dosyayı daha mantıklı bir yere taşıyoruz çünkü şu anda sadece kolleksiyonlarda kullanılmıyor.

posts/branch/_no_posts.html.erb
dosyasını

posts/shared/_no_posts.html.erb
adresine taşıyalım.

Daha önce no_posts_partial_path metodunu kullandığımız _branch.html.erb dosyasında metoda parametre olarak @posts oluşum değişkenini verelim.

Bazı stil değişiklikleri için default.scss dosyasına şunları ekleyelim:

app/assets/stylesheets/base/default.scss
.container {
    padding: 0;
}

.row {
    margin: 0;
}

Ayrıca home_page.scss içine şunları ekleyelim:

app/assets/stylesheets/partials/home_page.scss
.page-name {
    margin: 15px 0 15px 0;
    text-align: center;
    background-color: white !important;
    font-weight: bold;
    a {
        color: black;
    }
    a:hover {
        text-decoration: underline;
    }
}

Artık ana sayfamız şuna benziyor:





Servis nesneleri


Daha önce belirttiğimiz gibi eğer kontrolör içine lojik koyarsanız kolayca ortalık karışabilir ve testler çok sıkıntılı olabilir. Bu yüzden lojiği kontrolör dışında bir yerlere almak faydalı olacaktır. Bu amaçla servis nesneleri (servisler) kullanabiliriz.

PostsController içinde şöyle bir get_posts metodumuz vardı:
    def get_posts
        branch = params[:action]
        search = params[:search]
        category = params[:category]

        if category.blank? && search.blank?
            posts = Post.by_branch(branch).all
        elsif category.blank? && search.present?
            posts = Post.by_branch(branch).search(search)
        elsif category.present? && search.blank? 
            posts = Post.by_category(branch, category)
        elsif category.present? && search.present?
            posts = Post.by_category(branch, category).search(search)
        else
        end
    end

Bu metodda çok fazla servislere aktarılacak lojik var. Servisler Ruby sınıfları gibidir. Tanımlanmış bir metodu çağırırız ve dönen değeri kullanırız. Ruby’de sınıfın üreteci olan initialize metodu parametreler ile çağırarak sınıf üretiriz. Daha sonra tüm lojiği işleyecek bir metod tanımlarız. Uygulamalı yaparak kod içinde nasıl göründüğüne bakalım.

app klasörü altında services adında bir alt klasör oluşturalım.

app/services
Klasör içinde yeni bir posts_for_branch_service.rb dosyası tanımlayarak içine şu kodu yazalım:

app/services/posts_for_branch_service.rb
class PostsForBranchService
    def initialize(params)
        @search = params[:search]
        @category = params[:category]
        @branch = params[:branch]
    end

    # isteğe uygun gönderileri getir
    def call
        if @category.blank? && @search.blank?
            posts = Post.by_branch(@branch).all
        elsif @category.blank? && @search.present?
            posts = Post.by_branch(@branch).search(@search)
        elsif @category.present? && @search.blank? 
            posts = Post.by_category(@branch, @category)
        elsif @category.present? && @search.present?
            posts = Post.by_category(@branch, @category).search(@search)
        else
        end
    end

end

Görüldüğü üzere initialize metodunda parametreleri alıyor ve başka metodda kullanılacağı için oluşum değişkeni olarak kaydediyoruz. Sonra call metodu içinde daha öncesinde kontrolörde yazdığımız get_posts metodunun lojiğini aynen işliyoruz. Şimdi get_posts metoduna geri dönelim ve içeriğini şöyle değiştirelim:

app/controllers/posts_controller.rb
...
    def get_posts
        PostsForBranchService.new({
            search: params[:search],
            category: params[:category],
            branch: params[:action]
        }).call
    end
...

Peki Rails nereden bilecek bu sınıfı? Default olarak Rails app klasörü altındaki tüm sınıf tanımlarını başlangıçta yükler. Server’ı tekrar başlatmamız yeterli olacaktır.

Şimdi aslında baya baya yeni bir bölüme geçeceğiz ama ne yapalım ben de öğrendikçe yazıyorum. İsterseniz şimdiye kadar yaptıklarınızı yedekleyin yada Git kullanıyorsanız yeni bir dal açın.



Yeni gönderi oluşturmak


Şimdiye kadarki gönderiler seed ile üretilmiş yapay gönderilerdi. Kullanıcının yeni gönderiler girmesi için gereken arabirimi hazırlayalım artık.

Yeni bir gönderi için posts_controller.rb dosyasında new ve create aksiyonlarına ihtiyacımız var. Bunları tanımlayalım (tabi ki private bölümü dışında):

app/controllers/posts_controller.rb
...

    def new
        @branch = params[:branch]
        @categories = Category.where(branch: @branch)
        @post = Post.new
    end

    def create
        @post = Post.new(post_params)
        if @post.save
            redirect_to post_path(@post)
        else
            redirect_to root_path
        end
    end
...

new aksiyonu içinde formda kullanılmak üzere yeni gönderi oluşturmak amaçlı oluşum değişkenlerini belirliyoruz. @categories oluşum değişkeninde o kolleksiyona ait kategorilerin listesi bulunuyor. @post oluşum değişkeni ise formda yeni bir gönderi oluşturmak için bir yeni Post nesnesi bulunuyor.

create aksiyonu ise form doldurulup gönderilince yeni gönderiyi post_params metodu ile oluşturarak kaydını yapıyor. Şimdi post_params metodunu private bölümüne ekleyelim:

app/controllers/posts_controller.rb
...
private
...
    def post_params
        params.require(:post).
                permit(:content, :title, :category_id).
                merge(user_id: current_user.id)
    end
...

permit metodu sayesinde sadece belirtilen parametreler form gönderiminden alınır, gerisine izin verilmez.

Ayrıca PostsController en üst satıra şunu ekleyelim:

app/controllers/posts_controller.rb
class PostsController < ApplicationController
    before_action :redirect_if_not_signed_in, only: [:new]
...

before_action Rails filtrelerinden biridir. Burada kullanıcı girişi yapmamış ziyaretçilerin yeni gönderi oluşturmasına engel oluyoruz. Buradaki satırın anlamı new aksiyonuna gitmeden önce redirect_if_not_signed_in metodu çağırılacak. Tahmin edersiniz ki bu metod uygulamada başka yerlerde de gerekecektir mutlaka. O yüzden metodu application_controller.rb içine ekleyelim:

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  def redirect_if_not_signed_in
    redirect_to root_path if !user_signed_in?
  end

  def redirect_if_signed_in
    redirect_to root_path if user_signed_in?
  end
end

Giriş yapmamış kullanıcılar ana sayfaya yönlendirilecek. Ayrıca nasıl olsa lazım olur diyerek giriş yapmış kullanıcıları ana sayfaya yönlendiren bir metod daha ekledik.

Sıra geldi görsel kalıp dosyamıza. app/views/posts klasöründe new.html.erb kalıp dosyasını tanımlayıp içine şunları yazalım:

app/views/posts/new.html.erb
<div class="container new-post">
    <div class="row">
        <div class="col-sm-6 col-sm-offset-3">
            <h1>Yeni gönderi oluşturun</h1>
            <%= render "posts/new/post_form" %>
        </div>
    </div>
</div>

Parça görsel için new klasörünü ve içine _post_form.html.erb dosyasını tanımlayarak içine şunları yazalım:

app/views/posts/new/_post_form.html.erb
<%= bootstrap_form_for(@post) do |f| %>
    <%= f.text_field :title,
                    label: "Başlık",
                    maxlength: 100,
                    placeholder: "Başlık",
                    class: "form-control",
                    required: true,
                    minlength: 5 %>
    <%= f.hidden_field :branch, :value => @branch %>
    <%= f.text_area :content, label: "İçerik",
                    rows: 6,
                    required: true,
                    minlength: 20,
                    maxlength: 1000,
                    placeholder: "Neler aradığınızı yazın. 
                    Örn. özel ilgiler, uzmanlık seviyesi vs.",
                    class: "form-control" %>
    <%= f.collection_select :category_id, @categories, 
                    :id, :name, class: "form-control",
                    label: "Kategori" %>
    <%= f.submit "Gönderi oluşturun", class: "form-control" %>
<% end %>

Bu kodda yeni olan tek şey collection_select bize bir dropdown list tanımlıyor. Kısaca şöyle, category_id parametresine @categories oluşum değişkenine koyduğumuz kolleksiyona ait kategorilerden birini id sini kullanıcı seçimime göre koyuyor.

Burada kullanılan label: satırlarını ben Türkçe etiketler olması için ekledim. Bu parametreyi koymazsanız da etiketler gelir, ancak model tanımında verilen sütun isimleri olarak gelir.

Şimdi new görsel kalıbı için biraz stil tanımlayalım. app/assets/stylesheets/partials/posts klasöründe new.scss dosyası tanımlayalım ve içine şunları yazalım:

app/assets/stylesheets/partials/posts/new.scss
.new-post {
    height: calc(100vh - 50px);
    background-color: white;
    h1 {
        text-align: center;
        margin: 25px 0;
    }
    input, text-area, select {
        width: 100%;
    }
}

Yeni gönderi oluşturma formu şuna benzedi:


Son olarak tüm alanların doğru doldurulduğundan emin olmak için bazı doğrulamalar yapalım. Post modeli içine şunları ekleyelim:

app/models/post.rb
...
    validates :title, 
                presence: true, 
                length: { minimum: 5, maximum: 255 }
    validates :content, 
                presence: true,
                length: { minimum: 20, maximum: 1000 }
    validates :category, presence: true
...

Bu bölümde anlatılacaklar da bu kadar. Gelecek bölümde yeni bir özellik ile uygulamamızı genişleteceğiz. Kullanıcılara anında mesajlaşma özelliği sağlayacağız. Şimdilik kalın sağlıcakla..



Sırada şunlar var:






Hiç yorum yok:

Yorum Gönder