15. Ruby on Rails, част 1

15. Ruby on Rails, част 1

15. Ruby on Rails, част 1

10 декември 2012

Днес

Rails

Що е то?

Защо Rails?

Кой ползва Rails?

Принципи

Възможности

aka "фийчъри"

Възможности (2)

Кратка история

Версии

Инсталация

Архитектура

Компоненти

Генериране на изгледи

в HTML или друг формат

Пример с ERB

<div id="profile">
  <div class="left column">
    <div id="date"><%= print_date %></div>
    <div id="address"><%= current_user.address %></div>
  </div>
  <div class="right column">
    <div id="email"><%= current_user.email %></div>
    <div id="bio"><%= current_user.bio %></div>
  </div>
</div>

Пример с Haml

#profile
.left.column
  #date= print_date
  #address= current_user.address
.right.column
  #email= current_user.email
  #bio= current_user.bio

Пример със Slim

doctype html
html
  head
    title Slim Core Example
    meta name="keywords" content="template language"

  body
    h1 Markup examples

    div id="content" class="example1"
      p Nest by indentation

ORM

или как си говорим с бази данни

ActiveRecord

class User < ActiveRecord::Base
  # Много неща идват наготово по конвенция
  # Таблицата в случая е users (по конвенция)
end

ActiveRecord

class User < ActiveRecord::Base
  scope :admins, where(admin: true)
end

admin = User.admins.where(email: 'root@foo.bar').first

# Ще генерира следната заявка към MySQL база:
#
# SELECT `users`.* FROM `users` WHERE `users`.`admin` = 1
#   AND `users`.`email` = 'root@foo.bar' LIMIT 1

ActiveRecord

admin.email # 'root@foo.bar'
admin.name  # 'Charlie Sheen'
admin.name = "Charlie's Angels"

admin.save

# UPDATE `users` SET
#   `users`.`name` = 'Charlie\'s Angels',
#   `users`.`updated_at` = '2012-12-10 19:48:03'
# WHERE `users`.`id` = 42

ActiveRecord

dynamic finders

Няма да ги има в Rails 4!

david = User.find_by_name_and_age('David', 33)

# SELECT `users`.* FROM `users` WHERE
#   `users`.`name` = 'David' AND `users`.`age` = 33
# LIMIT 1

ActiveRecord

query DSL & chaining

lastest_signups = User.where(confirmed: true)
                      .order(:created_at).last(5)

# SELECT `users`.* FROM `users` WHERE `users`.`confirmed` = 1
# ORDER BY created_at DESC LIMIT 5

ActiveRecord асоциации

class Book < ActiveRecord::Base
end

class Publisher < ActiveRecord::Base
  has_many :books
end

vintage = Publisher.find(42)
vintage.books

# SELECT `publishers`.* FROM `publishers` WHERE `publishers`.`id` = 42 LIMIT 1
# SELECT `books`.* FROM `books` WHERE `books`.`publisher_id` = 42

ActiveRecord асоциации (2)

class Book < ActiveRecord::Base
end

class Publisher < ActiveRecord::Base
  has_many :books
end

vintage = Publisher.find(42)
vintage.books.create name: 'Rails Recipes'

# INSERT INTO `books` (`name`, `publisher_id`, `created_at`, `updated_at`)
# VALUES ('Rails Recipes', 42, '2012-12-10 19:58:13', '2012-...')

ActiveRecord асоциации (3)

class Author < ActiveRecord::Base; end
class Book < ActiveRecord::Base
  belongs_to :author
end

class Publisher < ActiveRecord::Base
  has_many :books
  has_many :authors, through: :books
end

Publisher.find(42).authors

# SELECT `authors`.* FROM `authors` INNER JOIN
#   `books` ON `books`.`author_id` = `authors`.`id`
# WHERE `books`.`publisher_id` = 42

ActiveRecord query API

ActiveRecord query API

module Institute
  class Workshop < ActiveRecord::Base
    has_many :events
  end

  class Event < ActiveRecord::Base
    def self.past
      # Използваме ARel да конструираме по-сложни условия
      where arel_table[:end_date].lt(Date.current)
    end
  end
end

ActiveRecord query API

Institute::Workshop.joins(:events).merge(Institute::Event.past)

# SELECT `institute_workshops`.* FROM `institute_workshops`
#   INNER JOIN `institute_events` ON
#   `institute_events`.`workshop_id` = `institute_workshops`.`id`
# WHERE (`institute_events`.`end_date` < '2012-12-10')

ActiveModel валидации

ActiveModel валидации

class Product < ActiveRecord::Base
  validates_presence_of :price, :description
  validates_numericality_of :price, greater_than_or_equal_to: 0
end

# Не запазва нищо в базата следствие на
# неуспешна валидация.
product = Product.create

product.errors.full_messages
# ["Price can't be blank", "Description can't be blank",
#  "Price is not a number", "Price must be greater than or equal to 0"]

ActiveModel валидации

как изглеждат грешките в браузър

NoSQL

Контролери

Контролери

class ProjectsController < ApplicationController
  def show
    @project = Project.find(params[:id])
  end
end

Контролери

филтри

class ProjectsController < ApplicationController
  def show
    @project = Project.find(params[:id])
  end

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

Контролери

филтри

class ProjectsController < ApplicationController
  before_filter :load_project

  def show() end
  def edit() end

  private

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

Контролери

конвенции за RESTful ресурси

Контролери

create/update конвенции

class ProjectsController < ApplicationController
  before_filter :load_project

  def update
    if @project.update_attributes(params[:project])
      # Препраща към ProjectsController#show()
      redirect_to @project, notice: 'Промените са съхранени успешно.'
    else
      render action: :edit
    end
  end
end

Контролери

create/update конвенции

Маршрути

Маршрути

Todo::Application.routes.draw do
  resources :projects
end

Маршрути

Rails.application.routes.draw do
  resources :activations, constraints: {id: /.+/}
  resources :vouchers, only: [:index, :new, :create]
  resources :announcements, except: [:show, :destroy]
  resources :quizzes

  resource :profile
  resource :dashboard, only: :show

  resources :tasks, except: :destroy do
    get :guide, on: :collection
    resources :solutions
    resource :check, controller: :task_checks, only: :create
  end

  mount Sidekiq::Web => '/queue'
  root to: 'home#index'
end

DSL на маршрутите

Маршрути и Rack

Маршрути

обратната връзка

Генериране на URL-и посредством "route helpers"

new_project_path()                  # "/projects/new"
project_path(project)               # "/projects/123"
new_project_document_path(project)  # "/projects/123/documents/new"

Application Server

Application Server

Обкръжения

environments

Development

Production

Test

Asset Pipeline

Asset Pipeline

Asset Pipeline

# Source
<header>
  <%= image_tag 'logo.png', class: 'logo' %>
</header>

# В development
<header>
  <img src="/assets/logo.png" alt="Logo" class="logo" />
</header>

# В production
<header>
  <img src="/assets/logo-9692fa42c3.png" alt="Logo" class="logo" />
</header>

Asset Pipeline

ползи

SASS

CSS препроцесор

SASS

CoffeeScript

JavaScript препроцесор

CoffeeScript

JavaScript препроцесор

CoffeeScript

Миграции

Миграции и deployment

Миграции

class CreateTodos < ActiveRecord::Migration
  def up
    create_table :todos do |t|
      t.text :text, null: false
      t.boolean :done, null: false, default: false
      t.integer :user_id, null: false
      t.timestamps # created_at, updated_at
    end
    add_index :todos, :user_id
  end

  def down
    remove_index :todos, :user_id
    drop_table :todos
  end
end

Миграции

auto-rollback (Rails 3+)

class CreateTodos < ActiveRecord::Migration
  def change
    create_table :todos do |t|
      t.text :text, null: false
      t.boolean :done, null: false, default: false
      t.integer :user_id, null: false
      t.timestamps # created_at, updated_at
    end
    add_index :todos, :user_id
  end
end

Тестове

Тестове

Други аспекти

Допълнителни компоненти

Допълнителни компоненти

управление

Генератори

Rake задачи

Интернационализация

Върхът на айсберга

Документация и ресурси

Книги

Въпроси