23. Ruby on Rails

23. Ruby on Rails

23. Ruby on Rails

18 януари 2017

Днес

Rails

Що е то?

Защо Rails?

Кой ползва Rails?

Принципи

Възможности

aka "фийчъри"

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

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

Версии

Инсталация

Архитектура

Компоненти

Структура на едно приложение

rails new quizzes: 38 directories, 40 files

quizzes
├── app
│   ├── assets
│   ├── controllers
│   ├── helpers
│   ├── mailers
│   ├── models
│   └── views
├── bin
├── config
│   ├── environments
│   ├── initializers
│   └── locales
├── db
├── lib
│   └── tasks
├── log
├── public
├── test
│   ├── controllers
│   ├── fixtures
│   ├── integration
│   ├── mailers
│   └── models
├── tmp
└── vendor

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

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

Пример с ERB

<div id="profile">
  <% if user_signed_in? %>
    Здравейте, <%= current_user.email %>.
    <%= link_to 'Изход', destroy_user_session_path, method: :delete %>
  <% else %>
    <%= link_to 'Вход', new_user_session_path %> |
    <%= link_to 'Регистрация', new_user_registration_path %>
  <% end %>
</div>

Пример с Haml

#profile
  %p
    - if user_signed_in?
      Здравейте, #{h current_user.email}.
      = link_to 'Изход', destroy_user_session_path, method: :delete
    - else
      = link_to 'Вход', new_user_session_path
      |
      = link_to 'Регистрация', new_user_registration_path

Пример със Slim

#profile
  p
    - if user_signed_in?
      ' Здравейте, #{current_user.email}.
      == link_to 'Изход', destroy_user_session_path, method: :delete
    - else
      == link_to 'Вход', new_user_session_path
      '  |
      == link_to 'Регистрация', new_user_registration_path

Пример със Slim (2)

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
  def self.admins
    where(admin: true)
  end
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

david = User.find_by_name_and_age('David', 33)
# или:
david = User.find_by(name: 'David', age: 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, '2016-01-04 19:58:13', '2016-...')

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

class Venue < ActiveRecord::Base
  has_many :events
end

class Event < ActiveRecord::Base
  def self.past
    # Use ARel to construct a more complex query
    where arel_table[:end_date].lt(Date.current)
  end
end

ActiveRecord query API

Venue.joins(:events).merge(Event.past)

# SELECT `venues`.* FROM `venues`
#   INNER JOIN `events` ON `events`.`venue_id` = `venues`.`id`
# WHERE (`events`.`end_date` < '2016-01-04')

ActiveModel валидации

ActiveModel валидации

class Product < ActiveRecord::Base
  validates :description, presence: true
  validates :price, presence: true, numericality: {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
  # Called when the user requests /projects/123 via HTTP GET
  def show
    @project = Project.find(params[:id]) # params[:id] == "123"
  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

Babel

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

Миграции

Миграции и 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 задачи

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

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

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

Книги

Въпроси