צעדים ראשונים עם Ruby ו Rails
ביום חמישי בבוקר אעביר וובינר של שעה על הספריה Ruby On Rails: ספריה לפיתוח יישומי Web מלאים. בשעה אני מתכנן לכתוב אתכם Web Application מלא בו משתמשים יוכלו לפרסם לינקים לדברים שאהבו ומשתמשים אחרים יוכלו לעשות "לייק" על הלינקים.
רוצים לבוא? זה קישור להרשמה:
1. התקנת הסביבה על Windows
נתחיל עם התקנת Ruby ו Rails על מכונת חלונות. הצעד הראשון הוא להוריד את Ruby Installer מהקישור:
לאחר הורדה והתקנה נכנס ל cmd ונפעיל את הפקודות הבאות כדי להתקין ריילס:
gem install rails
gem install bundler
ויש לנו ריילס! אגב לא יזיק להתקין סביבת פיתוח. אפשר להשתמש בכל Text Editor לצורך פיתוח היישום אבל אני אבחר את Ruby Mine שהיא סביבה קצת יותר רצינית עם השלמות אוטומטיות וצבעים וכל הדברים. אפשר להוריד לחודש ניסיון מהאתר שלהם בקישור:
https://www.jetbrains.com/ruby/download/
2. יצירת פרויקט ראשון
זה כל מה שהיה צריך כדי להתקין ריילס. עכשיו ניצור תיקיה חדשה ובה הפרויקט הראשון שלנו. בשורת הפקודה נרשום:
mkdir demoproject
cd demoproject
rails new webinar-demo
עכשיו אפשר להיכנס ל Ruby Mine ולפתוח את תיקיית הפרויקט החדש שיצרנו כדי להתחיל לעבוד. כדאי גם להשאיר את שורת הפקודה פתוחה כי תכף נצטרך אותה שוב.
3. יצירת Scaffold עבור "לינק"
משתמשים במערכת שלנו יוכלו לפרסם לינקים. בינתיים עוד אין לנו משתמשים ולכן עד שיהיו נתחיל עם טבלא של לינקים ונפרסם את הלינקים בעצמנו עבור כולם. כתבו את הפקודה הבאה מתוך שורת הפקודה:
rails g scaffold link name:string header:string likes:integer
בתגובה המערכת תייצר מסכים ליצירת לינקים חדשים, לעריכת לינקים, למחיקת לינקים וכמובן להצגת כל רשימת הלינקים במערכת. מוזמנים להיכנס לקבצים לראות מה יש בכל אחד מהם.
4. הצגת דף בית עם המוצרים הפופולריים ביותר
ניצור מספר "לינקים" דרך מסכי הניהול ונעבור ליצור דף בית חדש שיציג את 10 הלינקים הפופולריים ביותר במערכת. בשביל זה נצטרך ליצור Controller חדש עבור דף הבית ואיתו View ו Layout לדף זה. הפעילו את הפקודה:
rails g controller home homepage
היכנסו לקובץ app/controllers/home_controller.rb
ועדכנו את תוכנו כך שיכיל את הקוד הבא:
class HomeController < ApplicationController
def homepage
@links = Link.all.order(likes: :desc).limit(10)
end
end
תפקיד ה Controller בריילס לקרוא בסיס הנתונים את המידע שמעניין אותנו כדי שיהיה קל להציג אותו. כל מידע שנשמר במשתנה שמתחיל בסימן @
יהיה זמין לשכבת התצוגה לצורך הצגה.
נעבור לקובץ app/views/home/homepage.html.erb
ונכתוב בו את התוכן הבא:
<h1>Linker Links</h1>
<ul class="links-list">
<% @links.each do |link| %>
<li>
<%= link_to link.header, link.href, target: '_blank' %> [<%= link.likes %>]
<button class="btn-like">Like</button>
<button class="btn-unlike">Unlike</button>
</li>
<% end %>
</ul>
קוד זה יגרום להצגת כל הקישורים בתור רשימה. ואפשר גם להוסיף עיצוב - נעבור לקובץ app/assets/stylesheets/home.scss
ונוכל להוסיף שם קוד scss שישפיע על עיצוב העמוד:
// Place all the styles related to the links controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
.links-list {
list-style: none;
padding: 0;
li {
margin: 30px 10px;
font-size: 18px;
}
}
body {
background: beige;
}
5. הוספת משתמשים
הספריה Devise של ריילס מטפלת בכל נושא המשתמשים במערכת. היא מגיעה עם Defaults טובים ומאפשרת התאמה אישית של כל המסכים וכל ההתנהגות. כדי להתקין את הספריה נלך לפי המדריך שכאן:
https://github.com/plataformatec/devise
בסיום ההתקנה קיבלנו טבלת משתמשים בבסיס הנתונים, מסכי התחברות וכניסה ואפשרות להשתמש בנתוני המשתמש הנוכחי במערכת שלנו.
6. עדכון מסכי הלינקים כך שכל משתמש יוכל לערוך רק את הלינקים שלו
בשביל שיהיה מעניין בואו נעדכן את מסכי עריכת הלינקים שקיבלנו כך שכל משתמש יוכל לערוך רק את הלינקים שלו. בשביל זה נצטרך לחבר בין לינק למשתמש באמצעות הוספת שדה user_id
לטבלת הלינקים.
הפקודה:
rails g migration add_userid_to_links
תיצור לכם קובץ חדש שנקרא migration. תפקידו של קובץ זה לתאר את השינוי המיוחל בבסיס הנתונים. נכתוב בקובץ את התוכן הבא:
class AddUseridToLink < ActiveRecord::Migration[5.2]
def change
add_reference :links, :user
end
end
נריץ את ה Migration עם הפקודה:
rails db:migrate
ואפשר לראות שבסיס הנתונים עודכן ועכשיו לטבלא links נוספה עמודה user_id
.
נמשיך לעדכון המודל. המודל הוא המקום בו ריילס שומר את כל ה Business logic של האפליקציה ואת כל הקשרים בין הטבלאות. שינוי המודל יהפוך את הקוד שמשתמש בנתונים של לינקים ומשתמשים לקל יותר, ובאופן כללי המודל הוא המקום בו נרצה ליצור את רוב הלוגיקה החדשה של התוכנית שלנו.
פתחו את הקובץ app/models/link
ורשמו בו את התוכן הבא:
class Link < ApplicationRecord
belongs_to :user
end
שורה זו תעזור לנו להגיע מ Link למשתמש שמתאים לו. עכשיו נעבור למודל המשתמש ונוסיף שם את השורה:
has_many :links
עכשיו אפשר להמשיך לעדכן את הקובץ app/controllers/links_controller.rb
כך שכל פעם שנוצר לינק הוא יווצר עם עמודת ה user_id
הנכונה. זה התוכן החדש של הקובץ:
class LinksController < ApplicationController
before_action :set_link, only: [:show, :edit, :update, :destroy]
# GET /links
# GET /links.json
def index
@links = current_user.links
end
# GET /links/1
# GET /links/1.json
def show
end
# GET /links/new
def new
@link = Link.new(user: current_user)
end
# GET /links/1/edit
def edit
end
# POST /links
# POST /links.json
def create
@link = Link.new(link_params)
respond_to do |format|
if @link.save
format.html { redirect_to @link, notice: 'Link was successfully created.' }
format.json { render :show, status: :created, location: @link }
else
format.html { render :new }
format.json { render json: @link.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /links/1
# PATCH/PUT /links/1.json
def update
respond_to do |format|
if @link.update(link_params)
format.html { redirect_to @link, notice: 'Link was successfully updated.' }
format.json { render :show, status: :ok, location: @link }
else
format.html { render :edit }
format.json { render json: @link.errors, status: :unprocessable_entity }
end
end
end
# DELETE /links/1
# DELETE /links/1.json
def destroy
@link.destroy
respond_to do |format|
format.html { redirect_to links_url, notice: 'Link was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_link
@link = current_user.links.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def link_params
params.require(:link).permit(:header, :href, :likes).merge(user: current_user)
end
end
7. מתן אפשרות לכל משתמש לעשות Like יחיד לקישור
משתמשים במערכת שלנו יכולים לעדכן רק את הקישורים של עצמם אבל הם יכולים גם לעשות לייק לקישורים של אחרים. נרצה שכל משתמש יוכל לעשות לייק רק פעם אחת לכל קישור ולכן נוסיף טבלא חדשה של לייקים המקשרת בין משתמשים לקישורים.
גם יצירת טבלא מתבצעת באמצעות Migration ולכן נכתוב את הפקודה:
rails g model like user:reference link:reference
עדכנו את קובץ ה Migration שנוצר והוסיפו לו אינדקס:
class CreateLikes < ActiveRecord::Migration[5.2]
def change
create_table :likes do |t|
t.references :user
t.references :link
t.index [:user_id, :link_id], unique: true
t.timestamps
end
remove_column :links, :likes, :integer
end
end
ולאחר מכן הפעילו באמצעות:
rails db:migrate
עכשיו אפשר לעבור לקונטרולר ולהוסיף נתיבים כדי לאפשר למשתמש לעשות Like או Unlike לקישור. הכנסו לקובץ app/controllers/home_controller.rb
וכתבו בו את הקוד הבא:
class HomeController < ApplicationController
def homepage
@links = Link.all.order(likes: :desc).limit(10)
end
def like
link = Link.find(params[:link_id])
Like.create!(user: current_user, link: link)
render :homepage
end
def unlike
link = Link.find(params[:link_id])
like = Like.find_by(user: current_user, link: link)
like.destroy
render :homepage
end
end
בשביל שמשתמשים גם יוכלו לגשת לנתיבים אלו נרצה להוסיף עבורם routes. הקובץ config/routes.rb
של ריילס כולל רשימה של כל הנתיבים או כל נקודות הכניסה ליישום שלנו. עדכנו את הקובץ עם הקוד הבא:
Rails.application.routes.draw do
devise_for :users
get 'home/homepage'
resources :links
post 'like', to: 'home#like', via: :post
post 'unlike', to: 'home#unlike', via: :post
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
הוספת הנתיבים מאפשרת לנו לגשת לפעולות שהוגדרו בקונטרולר דרך לחיצה על הכפתורים.
עכשיו נעבור לעדכן את התבנית כדי שהכפתורים יגרמו להפעלת הנתיבים. עדכנו את הקובץ app/views_home/homepage.html.erb
עם התוכן הבא:
<h1>Linker Links</h1>
<ul class="links-list">
<% @links.each do |link| %>
<li>
<%= link_to link.header, link.href, target: '_blank' %> [<%= link.likes_count %>]
<%= link_to 'Like', like_path(link_id: link.id), method: 'POST' %>
<%= link_to 'Unlike', unlike_path(link_id: link.id), method: 'POST' %>
</li>
<% end %>
</ul>
המעבר מספירת הלייקים בתור עמודה בטבלת links לספירת לייקים בטבלא נפרדת כולל גם בעיה: חישוב מספר הלייקים הוא עכשיו יותר מורכב מפשוט לקרוא את השדה likes. נכנס למודל Link ונכתוב בו את הקוד הבא שיעזור לנו בחישוב:
class Link < ApplicationRecord
belongs_to :user
has_many :likes, dependent: :destroy
scope :with_likes_count, -> { Link.left_joins(:likes).select('COUNT(likes.id) as likes_count, links.header, links.href, links.id').group(:id) }
end
הקוד מוסיף מתודה סטטית חדשה לאוביקט Link המחזירה תוצאת Join בין כמה טבלאות. ה join הזה הוא שנותן לנו את ספירת הלייקים לכל שורה.
נחזור לקובץ app/controllers/home_controller.rb
ונעדכן אותו כך שיכיל התיחסות למנגנון הספירה החדש:
class HomeController < ApplicationController
before_action :prepare_links, only: [:homepage, :like, :unlike]
def homepage
end
def like
link = Link.find(params[:link_id])
Like.create!(user: current_user, link: link)
render :homepage
end
def unlike
link = Link.find(params[:link_id])
like = Like.find_by(user: current_user, link: link)
like.destroy
render :homepage
end
def prepare_links
@links = Link.with_likes_count.order('likes_count desc').limit(10)
end
end
עכשיו אפשר להריץ ולראות את הרשימה וגם לעשות לייק כדי להעלות קישור למקום גבוה יותר.
8. הוספת תמונה לקישור
לריילס יש תמיכה מובנית בקבצים מצורפים והוא מנהל אותם באמצעות שתי הטבלאות active_storage_attachments
ו active_storage_blobs
. כדי להתחיל להשתמש בטבלאות אלו נריץ:
rails active_storage:install
rails db:migrate
עכשיו אפשר להוסיף גם תמונות לקישורים שלנו! ניכנס למודל Link ונעדכן את הקוד:
# app/models/link.rb
class Link < ApplicationRecord
belongs_to :user
has_many :likes, dependent: :destroy
scope :with_likes_count, -> { Link.left_joins(:likes).select('COUNT(likes.id) as likes_count, links.header, links.href, links.id').group(:id) }
has_one_attached :thumbnail
end
נמשיך לטופס בקובץ app/views/links/_form.html.erb
ונעדכן את הקוד:
<%= form_with(model: link, local: true) do |form| %>
<% if link.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(link.errors.count, "error") %> prohibited this link from being saved:</h2>
<ul>
<% link.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= form.label :header %>
<%= form.text_field :header %>
</div>
<div class="field">
<%= form.label :href %>
<%= form.text_field :href %>
</div>
<div class="field">
<%= form.label :thumbmail %>
<%= form.file_field :thumbnail %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
ונסיים עם הקובץ app/controllers/links_controller.rb
ונעדכן שם את הפונקציה link_params
:
def link_params
params.require(:link).permit(:header, :href, :thumbnail).merge(user: current_user)
end
לסיום נלך ל homepage.html.erb ונוסיף את התמונה שלנו:
<h1>Linker Links</h1>
<ul class="links-list">
<% @links.each do |link| %>
<li>
<% if link.thumbnail.attached? %>
<%= image_tag link.thumbnail, class: 'thumbnail', width: 80 %>
<% end %>
<%= link_to link.header, link.href, target: '_blank' %> [<%= link.likes_count %>]
<%= link_to 'Like', like_path(link_id: link.id), method: 'POST' %>
<%= link_to 'Unlike', unlike_path(link_id: link.id), method: 'POST' %>
</li>
<% end %>
</ul>
ויש לנו תוספת של תמונות ללינקים.
לסיכום - בלא הרבה מאמץ ובלי הרבה קוד הצלחנו לבנות מערכת שכוללת אחסון קבצים, ניהול משתמשים וכמובן שומרת מידע בבסיס הנתונים והקוד יצא מאוד קריא ונקי. אין ספק שריילס מגיעה כבר עם הסוללות בפנים וכל מה שאנחנו צריכים לעשות זה למלא את התוכן שרלוונטי למערכת שלנו. יחד עם זאת כמו שבטח התרשמתם יש לריילס המון יכולות ויכול לקחת זמן ללמוד את הכל.
כל הקוד שכתבנו זמין בגיטהאב בקישור: