Skip to content
This repository was archived by the owner on May 1, 2020. It is now read-only.

BrVer/rails_presentation

Repository files navigation

RAILS GUIDE

highlighted with http://tinker.kotaweaver.com/blog/?p=152

1. Введение

environment:

ruby > 1.9.3
RubyGems

установка

$ gem install rails
$ rails --version

ещё раз о структуре:

Создание приложения Blog

$ rails new blog

структура (самые важные части) проекта:

app/ Контроллеры, модели, вьюхи, хелперы, рассыльщики и ресурсы вашего приложения.
script/ Скрипты для старта/деплоя/ещё чего нибудь (В rails 4 переименован в bin)
config/ Конфигурации маршрутов, БД и т.д.
db/ Содержит текущую схему вашей базы данных, а также миграции базы данных.
Gemfile, Gemfile.lock Эти файлы позволяют указать, какие зависимости от гемов нужны для вашего приложения на Rails. Эти файлы используются гемом Bundler.
lib/ Внешние модули для вашего приложения.
log/ Файлы логов приложения.
public/ Единственная папка, которая доступна извне как есть. Содержит статичные файлы и скомпилированные ресурсы.
Rakefile Содержит набор команд, которые могут быть запущены в командной строке. Вместо изменения Rakefile, вы можете добавить свои собственные задачи, добавив файлы в lib/tasks.
test/ Юнит-тесты, фикстуры и прочий аппарат тестирования.
tmp/ Временные файлы (такие как файлы кэша, pid и сессии)
vendor/ Место для кода внешних разработчиков. В типичном приложении на Rails, включает внешние гемы.

Запуск локального веб-сервера

$ bin/rails server

Это запустит WEBrick, веб-сервер, распространяющийся с Ruby по умолчанию, посмотреть можно по http://localhost:3000

создание контроллера и экшена

$ bin/rails generate controller welcome index

Rails создаст несколько файлов и маршрут.

create  app/controllers/welcome_controller.rb
 route  get 'welcome/index'
invoke  erb
create    app/views/welcome
create    app/views/welcome/index.html.erb
invoke  helper
create    app/helpers/welcome_helper.rb
invoke    test_unit
create      test/helpers/welcome_helper_test.rb
invoke  assets
invoke    coffee
create      app/assets/javascripts/welcome.js.coffee
invoke    scss
create      app/assets/stylesheets/welcome.css.scss

2. Модели

2.1 Введение в модели

Active Record это фреймворк ORM

Active Record предоставляет несколько механизмов, наиболее важными из которых является способности для:

  • Представления моделей и их данных.
  • Представления связей между этими моделями.
  • Представления иерархий наследования с помощью связанных моделей.
  • Валидации моделей до того, как они будут сохранены в базу данных.
  • Выполнения операций с базой данных в объектно-ориентированном стиле.

Соглашения по именованию:

  • Таблица базы данных - прописывается во множественной форме, разделение слов через знак подчеркивания (например, book_clubs).
  • Класс модели - прописывается в единственной форме с первой прописной буквой в каждом слове (например, BookClub).

Соглашения по схемам:

  • Внешние ключи - singularized_table_name_id (например, item_id, order_id)
  • Первичные ключи - id как первичный ключ таблицы.

Создание моделей Active Record:

class Product < ActiveRecord::Base
end

2.2 Миграции

создание миграции:

$ bin/rails generate migration AddPartNumberToProducts part_number:string:index price:decimal

Это создат следующую миграцию:

class AddPartNumberToProducts < ActiveRecord::Migration
  def change
    add_column :products, :part_number, :string
    add_column :products, :price, :decimal
    add_index :products, :part_number
  end
end

точно так же можно и удалять поля

$ bin/rails generate migration RemovePartNumberFromProducts part_number:string

или создавать таблицы

$ bin/rails generate migration CreateProducts name:string part_number:string

можно сразу создавать и модели и миграции к ним:

 $ bin/rails generate model Product name:string description:text    #создаст и модель и миграцию

список доступных в миграции комманд (некоторые из этих команд доступны только в Rails 4):

create_table
create_join_table
drop_table
drop_join_table
change_table (удаление полей, переименование, создание новых, индексы)
rename_table

add_column
change_column
change_column_null
change_column_default
rename_column

add_index
rename_index
remove_index

add_foreign_key
remove_foreign_key

add_reference
remove_reference

add_timestamps
remove_timestamps

как rails определяет что делать при накате/откате миграций:

  • change почти всегда сам знает как накатить и откатить (а то что не знает - в Rails 4 можно описать в reversible.up/reversible.down)
  • можно написать up/down методы

запуск миграций:

$ bin/rake db:migrate [VERSION=20080906120000]  # на последнюю [определённую] миграцию
$ bin/rake db:rollback [STEP=3]                 # откатиться назад одну [несколько] последних миграций, 
                                                # можно сделать предыдущей командой

генерация базы:

$ rake db:setup

2.3 Валидации Active Record

Валидации используются, чтобы быть уверенными, что только правильные данные сохраняются в вашу базу данных

class Person < ActiveRecord::Base
  validates :name, presence: true
end
 
Person.create(name: "John Doe").valid? # => true
p = Person.create(name: nil).valid? # => false
p.errors.messages # => {name:["can't be blank"]} #эти ошибки потом можно отображать во вьюхе
p.errors[:name].any? # => true

Следующие методы вызывают валидацию, и сохраняют объект в базу данных только если он валиден:

  • create
  • create!
  • save
  • save!
  • update
  • update!

Версии с восклицательным знаком вызывают исключение, если запись недействительна. Невосклицательные версии save и update просто возвращают false, create возвращает объект.

основные валидационные хэлперы:

validates_associated валидирует связанные объекты
confirmation введите подтверждение (для паролей и т.д)
inclusion/exclusion проверяют что значение входит/не входит в список
format проверяет на соответствие регэкспу
numericality ожидает только числовые значения
length проверяет на длину
precence/absence(последнее только в Rails 4) проверяют наличие/отсутствие значения
uniquiness проверяет на уникальность значения в БД
validetes_with валидирует с помощью кастомного класса-валидатора (да, можно написать свой класс-валидатор)

! Можно переопределить условия, при которых будет выполняться валидация с помощью :on, :if/:unless

2.4 Колбэки Active Record

class User < ActiveRecord::Base
  after_create do |user|
    puts "You have a user with name #{user.name} and age #{user.age}!"
  end
 
end

Cписок всех доступных колбэков Active Record:

  • before_validation
  • after_validation
  • before_save
  • around_save
  • after_save
  • before_create
  • around_create
  • after_create
  • before_update
  • around_update
  • after_update
  • before_destroy
  • around_destroy
  • after_destroy
  • after_commit/after_rollback

!можно писать классы колбэков, чтобы потом юзать в разных моделях

!колбэки бывают условными (:if/:unless)

2.4 Связи Active Record

определяем отношения:

class Customer < ActiveRecord::Base
  has_many :orders, dependent: :destroy
end
 
class Order < ActiveRecord::Base
  belongs_to :customer
end

и потом можно очень удобно этим пользоваться:

customer.orders                                         # вернёт список заказов определённого кастомера
order = customer.create_order(order_name = 'blah blah') # создаст новый заказ для данного кастомера

типы связей:

belongs_to

has_one

has_many

has_many :through

has_one :through

has_and_belongs_to_many

полиморфные связи

в данном примере модель изображения принадлежит или модели работника, или модели продукта

интерфейс запросов Active Record

куча методов для полноценного общения с БД через ОРМ:

  • find
  • take
  • first
  • last
  • find_by
  • where
  • order
  • select
  • limit
  • group
  • joins
  • includes

и т.д.

кэширование запросов

customer = Customer.find(10)
customer.orders                 # получаем заказы из базы данных
.....
customer.orders.empty?          # используем кэшированную копию заказов
customer.orders(true).empty?    # отказываемся от кэшированной копии заказов
                                # и снова обращаемся к базе данных

нетерпеливая загрузка

2 запроса вместо 11:

clients = Client.includes(:address).limit(10)
 
clients.each do |client|
  puts client.address.postcode
end

скоупы

class Article < ActiveRecord::Base
  default_scope                     -> { where(deleted_at: nil) }
  scope :published,                 -> { where(published: true) }
  scope :published_and_commented,   -> { published.where("comments_count > 0") }
end

Article.all         # => articles
Article.published   # => [published articles where deleted_at is nil]

3. Вьюхи и рендеринг

Convention over Configuration в действии:

контроллер:

class BooksController < ApplicationController
end

роутинг:

resources :books

в таком случае Rails автоматически отрендерит app/views/books/index.html.erb при переходе на адрес /books

широкий функционал:

можно явно переопределить вьюху, которую надо отрендерить с помощью render 'view_name' можно сделать редирект с помощью redirect_to

существует куча методов для генерации ответа от сервера в любом виде (json, xml, js, raw, HEAD с пустым телом и т.д.), с определением кода ответа, location, content-type

отрендеренные вьюхи вставляются в макеты :

/app/views/layouts/books.html.erb :

<html>
  <head>
  <%= yield :head %>
  </head>
  <body>
  <%= yield %>
  </body>
</html>

app/views/books/index.html.erb :

<% content_for :head do %>
  <title>A simple page</title>
<% end %>
 
<p>Hello, Rails!</p>

результат :

<html>
  <head>
    <title>A simple page</title>
  </head>
  <body>
    <p>Hello, Rails!</p>
  </body>
</html>

также во вьюхи можно вставлять партиалы, чтобы ещё лучше всё структурировать:

<%= render "shared/ad_banner" %>
<h1>Products</h1>
<p>Here are a few of our fine products:</p>
...
<%= render "shared/footer" %>

в партиалы можно передаватьлокальные переменные

также очень удобно использовать партиалы для рендеринга коллекций:

index.html.erb

<h1>Products</h1>
<%= render partial: "product", collection: @products %>

_product.html.erb

<p>Product Name: <%= product.name %></p>

хэлперы ресурсных тэгов:

  • auto_discovery_link_tag - встраивает RSS или ATOM
  • javascript_include_tag - встраивает js из app/assets/javascripts
  • stylesheet_link_tag - встривает css из app/assets/stylesheets
  • image_tag - встраивает картинки из public/img
  • video_tag - встраивает видео из public/videos
  • audio_tag - встраивает видео из public/audios

хэлперы форм

    <%= form_tag("/search", method: "get") do %>
      <%= label_tag(:q, "Search for:") %>
      <%= text_field_tag(:q) %>
      <%= check_box_tag(:pet_dog) %>
      <%= radio_button_tag(:age, "child") %>
      <%= text_area_tag(:message, "Hi, nice site", size: "24x6") %>
      <%= password_field_tag(:password) %>
      <%= hidden_field_tag(:parent_id, "5") %>
      <%= search_field(:user, :name) %>
      <%= telephone_field(:user, :phone) %>
      <%= date_field(:user, :born_on) %>
      <%= datetime_field(:user, :meeting_time) %>
      <%= datetime_local_field(:user, :graduation_day) %>
      <%= month_field(:user, :birthday_month) %>
      <%= week_field(:user, :birthday_week) %>
      <%= url_field(:user, :homepage) %>
      <%= email_field(:user, :address) %>
      <%= color_field(:user, :favorite_color) %>
      <%= time_field(:task, :started_at) %>
      <%= number_field(:product, :price, in: 1.0..20.0, step: 0.5) %>
      <%= range_field(:product, :discount, in: 1..100) %>
      <%= submit_tag("Search") %>
    <% end %>

привязка форм к объектам ActiveRecord
легка и непринуждённа с использованием form_for

articles_controller.rb:

def new
  @article = Article.new
end

articles/new.html.erb:

<%= form_for @article, url: {action: "create"}, html: {class: "nifty_form"} do |f| %>
  <%= f.text_field :title %>
  <%= f.text_area :body, size: "60x12" %>
  <%= f.file_field :picture %>
  <%= f.submit "Create" %>
<% end %>

формы и связанные объекты
если у вас есть связанные объекты (например пользователь имеет несколько адресов) - можно создавать вложенные формы:

<%= form_for @person do |person_form| %>
  <%= person_form.text_field :name %>
  <% @person.addresses.each do |address| %>
    <%= person_form.fields_for address, index: address.id do |address_form|%>
      <%= address_form.text_field :city %>
    <% end %>
  <% end %>
<% end %>

защита от CSRF
для форм, созданных с помощью хэлперов, добавляется автоматически.
для созданных вручную форм есть form_authenticity_token

<input type="hidden"
       value="67250ab105eb5ad10851c00a5621854a23af5489"
       name="authenticity_token"/>

4. Контроллеры

class ClientsController < ApplicationController
  def new           # пустой экшен просто отрендерит new.html.erb
  end
end

4.1 параметры запроса

получает, не делая разницы между GET и POST

class ClientsController < ApplicationController

  def create
    @client = Client.new(params[:client])
    if @client.save
      redirect_to @client
    else
      render "new"
    end
  end
end

Rails также отлично конвертит json -> params

params также содержит controller_name, action_name, и другие параметры из роутинга, например:

get '/clients/:status' => 'clients#index', foo: 'bar' # /clients/active will hit clients#index with params = {status:'active', foo:'bar'}

4.1 Сессия

Есть сессия для каждого пользователя, в которой можно хранить небольшие порции данных, которые будут сохранены между запросами. Сессия доступна только в контроллере и во вьюхе.

  • ActionDispatch::Session::CookieStore - Хранит все на клиенте.
  • ActionDispatch::Session::CacheStore - Хранит данные в кэше Rails.
  • ActionDispatch::Session::ActiveRecordStore - Хранит данные в базе данных с использованием Active Record. (требует гем activerecord-session_store).
  • ActionDispatch::Session::MemCacheStore - Хранит данные в кластере memcached (эта реализация - наследие старых версий, вместо нее рассмотрите использование CacheStore).

доступ к сессии:

session[:some_key] = some_value

Flash Cпециальная часть сессии, которая очищается с каждым запросом.

доступ:

flash[:notice] = "You have successfully logged out."

либо как часть редиректа:

redirect_to root_url, notice: "You have successfully logged out."
redirect_to root_url, alert: "You're stuck here!"
redirect_to root_url, flash: { referral_code: 1234 }

отображение flash во вьюхе:

<% if flash[:just_signed_up] %>
  <p class="welcome">Welcome to our site!</p>
<% end %>

можно продлять жизнь flash с помощью flash.keep / flash.keep(:some_key)

4.2 Куки

cookies[:commenter_name] = 'Boris'

есть возможность создавать подписанные и зашифрованные куки

4.3 Объекты Request и Response

request:

host Имя хоста, используемого для этого запроса.
domain(n=2) Первые n сегментов имени хоста, начиная справа (домен верхнего уровня).
format Тип содержимого, запрошенного с клиента.
method Метод HTTP, использованного для запроса.
get?, post?, patch?, put?, delete?, head? true, если метод HTTP - это GET/POST/PATCH/PUT/DELETE/HEAD.
headers Хэш, содержащий заголовки, связанные с запросом.
port Номер порта (число), использованного для запроса.
protocol Строка, содержащая использованный протокол плюс "://", например "http://".
query_string Часть URL со строкой запроса, т.е. все после "?".
remote_ip Адрес IP клиента.
url Полный URL, использованный для запроса.
path_parameters параметры, распозннные роутингом
query_parameters параметры, пришедшие в строке запроса
request_parameters параметры, посланные в теле запроса

response:

body Cтрока данных, которая будет возвращена клиенту. Часто это HTML.
status Код статуса HTTP для отклика, например 200 для успешного отклика или 404 для ненайденного файла.
location URL, по которому клиент будет перенаправлен, если указан.
content_type Тип содержимого отклика.
charset Кодировка, используемая для отклика. По умолчанию это "utf-8".
headers Заголовки, используемые для отклика.

4.4 Фильтры

Фильтры - это методы, которые запускаются до, после или "вокруг" экшна контроллера. Фильтры наследуются.

  • before_action
  • around_action (позволяет например обернуть экшен в транзакцию, в теле фильтра для вызова экшена используется yield)
  • after_action

типичный пример:

class ApplicationController < ActionController::Base
  before_action :require_login
 
  private
 
  def require_login
    unless logged_in?
      flash[:error] = "You must be logged in to access this section"
      redirect_to new_login_url # сообщает о переходе на страницу авторизации
    end
  end
end

можно задавать для каких именно экшенов будет работать фильтр с помощью :only:

around_action :wrap_in_transaction, only: :show

можно отменять использование фильтров в унаследованных контроллерах (для определённых экшенов или вцелом):

class LoginsController < ApplicationController
  skip_before_action :require_login, only: [:new, :create]
end

фильтры можно описывать как в виде функций, так и в виде классов

4.5 Аутентификация

встроенные механизмы:

basic auth

class AdminsController < ApplicationController
  http_basic_authenticate_with name: "humbaba", password: "5baa61e4"
end

digest auth

class AdminsController < ApplicationController
  USERS = { "lifo" => "world" }
 
  before_action :authenticate
 
  private
 
    def authenticate
      authenticate_or_request_with_http_digest do |username|
        USERS[username]
      end
    end
end

4.6 Потоки и загрузка файлов

2 метода: send_file и send_data. send_file посылает уже готовый файл с диска, send_data - сгенерированный

def download_pdf
    client = Client.find(params[:id])
    send_data generate_pdf(client),
              filename: "#{client.name}.pdf",
              type: "application/pdf"

4.7 Обработка ошибок

по дэфолту, 404 и 500 ошибки приводят к тому, что отдаются public/404.html и public/500.html соответственно но это можно переопределить в routes.rb : (Rails 4)

match '/404', via: :all, to: 'errors#not_found'
match '/422', via: :all, to: 'errors#unprocessable_entity'
match '/500', via: :all, to: 'errors#server_error'

ошибки также можно перехватывать при помощи resсue_from:

class ApplicationController < ActionController::Base
  rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
 
  private
    def record_not_found
      render plain: "404 Not Found", status: 404
    end
end

4.8 Навязывание HTTPS

class DinnerController
  force_ssl only: :cheeseburger
  # или
  force_ssl except: :cheeseburger
end

5. Роутинг

описывается в config/routes.rb:

пример роута:

get '/patients/:id', to: 'patients#show', as: 'patient'

просмотр всех роутов:
команда rake routes выдаёт примерно такую таблицу:

    users GET    /users(.:format)          users#index
          POST   /users(.:format)          users#create
 new_user GET    /users/new(.:format)      users#new
edit_user GET    /users/:id/edit(.:format) users#edit

генерация правильных урлов из кода:

get '/patients/:id', to: 'patients#show', as: 'patient'

далее можно сделать вот так:

PatientsController.rb

def some_action
  @patient = Patient.find(17)
end

app/views/patients/some_action.html.erb

<%= link_to 'Patient Record', patient_path(@patient) %>         #  преобразуется в /patients/17

5.1 Ресурсный роутинг

resources :photos

данная строка создаёт следующие маршруты:

Метод HTTP Путь Контроллер#Экшн Использование
GET /photos photos#index отображает список всех фото
GET /photos/new photos#new возвращает форму HTML для создания нового фото
POST /photos photos#create создает новое фото
GET /photos/:id photos#show отображает определенное фото
GET /photos/:id/edit photos#edit возвращает форму HTML для редактирования фото
PATCH/PUT /photos/:id photos#update обновляет определенное фото
DELETE /photos/:id photos#destroy удаляет определенное фото

Создание ресурсного маршрута также сделает доступными множество хелперов в контроллере . В случае с resources :photos:

  • photos_path возвращает /photos
  • new_photo_path возвращает /photos/new
  • edit_photo_path(:id) возвращает /photos/:id/edit (например, edit_photo_path(10) возвращает /photos/10/edit)
  • photo_path(:id) возвращает /photos/:id (например, photo_path(10) возвращает /photos/10)

иногда требуется определить ресурс, доступный только в 1 экземпляре, такой роутинг пишется так:

resource :geocoder

данный код создаст 6 ресурсных маршрутов и 3 хелпера (по аналогии с resouces)

Пространства имён

можно поместить некоторые контроллеры в отдельную папку, например /app/controllers/admin

в роутинге после этого можно сгруппировать их в пространство имён:

namespace :admin do
  resources :articles, :comments
end

это создаст соответствующие урлы, включающие нэймспэйс (GET /admin/articles например)

также можно задать кастомный базовый урл для ресурса:

resources :posts, path: '/admin/articles'

или переопределить контроллер:

resources :photos, :controller => "images"

или название именованных хелперов

resources :photos, :as => "images"

или название некоторых урлов, ведуших к экшенам (название самих экшенов не меняется)

resources :photos, :path_names => { :new => 'make', :edit => 'change' }

или вообще явно задать, какие маршруты ресурса нужно создавать, а какие - нет:

resources :photos, :only => [:index, :show]
resources :photos, :except => :destroy

Вложенные ресурсы

предположим, имеются две таких модели:

class Magazine < ActiveRecord::Base
  has_many :ads
end

class Ad < ActiveRecord::Base
  belongs_to :magazine
end

тогда можно сделать вложенный роутинг:

resources :magazines do
  resources :ads
end

что создаст урлы вот такого типа:

GET	/magazines/:magazine_id/ads	ads#index

можно тем не менее делать часть экшенов вложенного ресурса вложенными, а часть - невложенными :

resources :articles do
  resources :comments, only: [:index, :new, :create]
end
resources :comments, only: [:show, :edit, :update, :destroy]
# в данном примере экшены коллекции - вложенные, а экшены отдельных элементов , требующие id - нет

Концерты маршрутов (Rails 4)

Позволяют объявлять обычные маршруты, которые затем могут быть повторно использованы внутри других ресурсов и маршрутов.

Чтобы определить концерн:

concern :commentable do
  resources :comments
end

concern :image_attachable do
  resources :images, only: :index
end

и потом используются концерны вот так:

resources :messages, concerns: :commentable
resources :articles, concerns: [:commentable, :image_attachable]

добавление маршрутов

добавление маршрутов к элементам:

resources :photos do
  get 'preview', on: :member
end

добавление маршрутов к коллекции:

resources :photos do
  get 'search', on: :collection
end

5.2 Нересурсный роутинг

типичный урл

get ':controller(/:action(/:id))'

вообще, параметров можно передавать сколько угодно:

get ':controller/:action/:id/with_user/:user_id'

последяя строчка обработает урл /photos/show/1/with_user/2 и выдаст params:{ controller: 'photos', action: 'show', id: '1', user_id: '2' }

можно задавать дэфолтные значения:

get 'photos/:id', to: 'photos#show', defaults: { format: 'jpg' }

именование маршрутов

get 'exit', to: 'sessions#destroy', as: :logout         # создаст хелперы logout_path и logout_url

перенаправление маршрутов

get '/stories', to: redirect('/articles')

Всякие разные дополнительные штуки

6 ActiveSupport

Active Support – это компонент Ruby on Rails, отвечающий за предоставление расширений для языка Ruby, утилит и множества других вещей. Он предлагает более ценные функции на уровне языка.

например, у хэшей появляется метод to_xml:

require 'active_support/core_ext'

{"foo" => 1, "bar" => 2}.to_xml
# =>
# <?xml version="1.0" encoding="UTF-8"?>
# <hash>
#   <foo type="integer">1</foo>
#   <bar type="integer">2</bar>
# </hash>

7 API интернационализации

Гем I18n , поставляемый с RoR, представляет простой и расширяемый фреймворк для перевода вашего приложения на отдельный другой язык, иной чем английский, или для предоставления поддержки многоязычности в вашем приложении.

в самом простом рассмотрении работает он так: сначала нужно добавить переводы фраз:

# config/locales/en.yml
en:
  hello_world: Hello world!
  hello_flash: Hello flash!

# config/locales/pirate.yml
pirate:
  hello_world: Ahoy World
  hello_flash: Ahoy Flash

затем добавить код для определения локали из запроса, например вот такой (самый простой метод, хоть и не самый красивый):

before_filter :set_locale
 
def set_locale
  I18n.locale = params[:locale] || I18n.default_locale
end

затем можно использовать гем так:

# app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
    flash[:notice] = t(:hello_flash)
  end
end
 
# app/views/home/index.html.erb
<h1><%=t :hello_world %></h1>
<p><%= flash[:notice] %></p>

8 Action Mailer

Action Mailer позволяет отправлять электронные письма из вашего приложения посредством создание рассыльщиков, наследуемых от ActionMailer::Base, и находящихся в app/mailers. Эти рассыльщики имеют связанные вьюхи, которые находятся в app/views.

Работает очень просто:

# app/mailers/user_mailer.rb

class UserMailer < ActionMailer::Base
  default :from => "notifications@example.com"
 
  def welcome_email(user)
    @user = user
    @url  = "http://example.com/login"
    mail(:to => user.email, :subject => "Welcome to My Awesome Site")
  end
 
end
<!--app/views/user_mailer/welcome_email.html.erb-->
    
<!DOCTYPE html>
<html>
  <head>
    <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
  </head>
  <body>
    <h1>Welcome to example.com, <%= @user.name %></h1>
    <p>
      You have successfully signed up to example.com,
      your username is: <%= @user.login %>.<br/>
    </p>
    <p>
      To login to the site, just follow this link: <%= @url %>.
    </p>
    <p>Thanks for joining and have a great day!</p>
  </body>
</html>

(также можно добавить app/views/user_mailer/welcome_email.text.erb для поддержки текстовых версий)

ну и дальше письма отправляются очень просто:

UserMailer.welcome_email(@user).deliver

9 Отладка

  • debug @post # вернёт <pre> + @post.to_yaml
  • @post.to_yaml
  • inspect # печатает строкой

logger

Rails.logger = Logger.new(STDOUT)

уровни лога:

:debug, :info, :warn, :error, :fatal, :unknown

задание уровня:

config.log_level = :warn # В любом инициализаторе среды, или
ActiveRecord::Base.logger.level = 0 # в любое время

использование:

logger.info "Processing the request..."

gem debugger

$ gem install debugger
$ rails server --debugger



Available commands:
backtrace  delete   enable  help    next  quit     show    trace
break      disable  eval    info    p     reload   source  undisplay
catch      display  exit    irb     pp    restart  step    up
condition  down     finish  list    ps    save     thread  var
continue   edit     frame   method  putl  set      tmate   where

gem byebug (для Rails 4)

10 Command line

rails new создаёт новый проект rails

$ rails new commandsapp

rails server запускает сервер

$ rails server -e production -p 4000

rails generate генерирует что угодно

$ rails generate controller Greetings hello
$ rails generate model HighScore game:string score:integer
$ rails generate scaffold HighScore game:string score:integer

rails console запускает рельсовую консоль

$ rails console staging # запустит консоль в среде staging

rails dbconsole коннектит к базе данных исходя из настроек rails приложения

rails runner запускает без интерактивности

$ rails runner -e staging "Model.long_running_method"

rails destroy противоположность rails generate

rake

Rake означает Ruby Make, отдельная утилита Ruby, заменяющая утилиту Unix "make", и использующая файлы "Rakefile" и .rake для построения списка задач

$ rake --tasks
rake about                              # List versions of all Rails frameworks and the environment
rake assets:clean[keep]                 # Remove old compiled assets
rake assets:clobber                     # Remove compiled assets
rake assets:environment                 # Load asset compile environment
rake assets:precompile                  # Compile all the assets named in config.assets.precompile
rake cache_digests:dependencies         # Lookup first-level dependencies for TEMPLATE (like messages/show or comments/_comment.html)
rake cache_digests:nested_dependencies  # Lookup nested dependencies for TEMPLATE (like messages/show or comments/_comment.html)
rake db:create                          # Creates the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:create:all to create all databases in the config)
rake db:drop                            # Drops the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:drop:all to drop all databases in the config)
rake db:fixtures:load                   # Load fixtures into the current environment's database
rake db:migrate                         # Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)
rake db:migrate:status                  # Display status of migrations
rake db:rollback                        # Rolls the schema back to the previous version (specify steps w/ STEP=n)
rake db:schema:cache:clear              # Clear a db/schema_cache.dump file
rake db:schema:cache:dump               # Create a db/schema_cache.dump file
rake db:schema:dump                     # Create a db/schema.rb file that is portable against any DB supported by AR
rake db:schema:load                     # Load a schema.rb file into the database
rake db:seed                            # Load the seed data from db/seeds.rb
rake db:setup                           # Create the database, load the schema, and initialize with the seed data (use db:reset to also drop the database first)
rake db:structure:dump                  # Dump the database structure to db/structure.sql
rake db:version                         # Retrieves the current schema version number
rake doc:app                            # Generate docs for the app -- also available doc:rails, doc:guides (options: TEMPLATE=/rdoc-template.rb, TITLE="Custom Title")
rake log:clear                          # Truncates all *.log files in log/ to zero bytes (specify which logs with LOGS=test,development)
rake middleware                         # Prints out your Rack middleware stack
rake notes                              # Enumerate all annotations (use notes:optimize, :fixme, :todo for focus)
rake notes:custom                       # Enumerate a custom annotation, specify with ANNOTATION=CUSTOM
rake rails:template                     # Applies the template supplied by LOCATION=(/path/to/template) or URL
rake rails:update                       # Update configs and some other initially generated files (or use just update:configs or update:bin)
rake routes                             # Print out all defined routes in match order, with names
rake secret                             # Generate a cryptographically secure secret key (this is typically used to generate a secret for cookie sessions)
rake stats                              # Report code statistics (KLOCs, etc) from the application
rake test                               # Runs test:units, test:functionals, test:generators, test:integration together
rake test:all                           # Run tests quickly by merging all types and not resetting db
rake test:all:db                        # Run tests quickly, but also reset db
rake time:zones:all                     # Displays all time zones, also available: time:zones:us, time:zones:local -- filter with OFFSET parameter, e.g., OFFSET=-6
rake tmp:clear                          # Clear session, cache, and socket files from tmp/ (narrow w/ tmp:sessions:clear, tmp:cache:clear, tmp:sockets:clear)
rake tmp:create                         # Creates tmp directories for sessions, cache, sockets, and pids

свои задачи:

desc "I am short, but comprehensive description for my cool task" task task_name: [:prerequisite_task, :another_task_we_depend_on] do

Вся магия тут

Разрешен любой код Ruby

end

вызов задач:

rake task_name

11 Кэширование

по умолчанию включено только для production

включить:

config.action_controller.perform_caching = true

доступны 3 механизма:

  • кэширование страницы
  • кэширование экшена
  • кэширование фрагмента

кэширование страницы (в rails 4 перемещено из классов ядра в gem 'actionpack-page_caching')

class ProductsController < ActionController
 
  caches_page :index
 
  def index
    @products = Products.all
  end
end

кэширование экшена (в rails 4 перемещено из классов ядра в gem 'actionpack-action_caching')

class ProductsController < ActionController
 
  before_filter :authenticate
  caches_action :index
 
  def index
    @products = Product.all
  end
 
  def create
    expire_action :action => :index
  end
 
end

кэширование фрагмента

Если хотите показать все заказы, размещенные на веб сайте, в реальном времени и не хотите кэшировать эту часть страницы, но хотите кэшировать часть страницы, отображающей все доступные продукты, можете использовать следующий кусок кода:

<% Order.find_recent.each do |o| %>
  <%= o.buyer.name %> bought <%= o.product.name %>
<% end %>
 
<% cache do %>
  All available products:
  <% Product.all.each do |p| %>
    <%= link_to p.name, product_url(p) %>
  <% end %>
<% end %>

если хотите кэшировать несколько фрагментов на экшн, следует предоставить action_suffix в вызове cache:

<% cache(:action => 'recent', :action_suffix => 'all_products') do %>
  All available products:

инвалидация:

expire_fragment(:controller => 'products', :action => 'recent', :action_suffix => 'all_products')

глобальные фрагменты (не привязаны к экшену):

<% cache('all_available_products') do %>
  All available products:
<% end %>

Уборщики (sweepers)

представляют собой обсерверы, автоматически инвалидирующие кэш при изменении состояния объектов (в rails 4 перемещено из классов ядра в gem 'rails-observers')

class ProductSweeper < ActionController::Caching::Sweeper
  observe Product # This sweeper is going to keep an eye on the Product model
 
  # Если наш уборщик обнаружит, что Product был создан, вызываем это
  def after_create(product)
    expire_cache_for(product)
  end
 
  # Если наш уборщик обнаружит, что Product был обновлен, вызываем это
  def after_update(product)
    expire_cache_for(product)
  end
 
  # Если наш уборщик обнаружит, что Product был удален, вызываем это
  def after_destroy(product)
    expire_cache_for(product)
  end
 
  private
  def expire_cache_for(product)
    # Прекращает страницу list теперь, когда мы добавили новый продукт
    expire_page(:controller => 'products', :action => 'index')
 
    # Прекращает фрагмент
    expire_fragment('all_available_products')
  end
end

хранилища:

  • ActiveSupport::Cache::MemoryStore
  • ActiveSupport::Cache::FileStore
  • ActiveSupport::Cache::MemCacheStore
  • ActiveSupport::Cache::NullStore

conditional GET

class ProductsController < ApplicationController
 
  # Это автоматически отошлет :not_modified, если запрос свежий,
  # и отрендерит дефолтный шаблон (product.*), если он устарел.
 
  def show
    @product = Product.find(params[:id])
    fresh_when :last_modified => @product.published_at.utc, :etag => @product
  end
end

12 Asset Pipeline

Asset Pipeline (файлопровод) представляет фреймворк для соединения и минимизации JavaScript и CSS. Также добавляет возможность писать эти ресурсы на других языках и препроцессорах, таких как CoffeeScript, Sass и ERB.

в rails 4 был извлечён из ядра в гем sprockets-rails

Rails 4 автоматически добавляет гемы sass-rails, coffee-rails и uglifier в Gemfile

основные фишки:

Cоединение ресурсов. Sprockets соединяет все JavaScript файлы в один главный файл .js и все CSS файлы в один главный файл .css. Можно настроить эту стратегию, сгруппировав файлы любым способом. В production, Rails вставляет метку MD5 в каждое имя файла, таким образом файл кэшируется браузером. Кэш можно сделать недействительным, изменив эту метку, что происходит автоматически каждый раз, когда изменяется содержимое файла.

Минимизация или сжатие ресурсов. Для CSS выполняется путем удаления пробелов и комментов. Для JS могут быть применены более сложные процессы.

Позволяет писать эти ресурсы на языке высшего уровня с дальнейшей прекомпиляцией до фактического ресурса. Поддерживаемые языки по умолчанию: Sass для CSS, CoffeeScript для JS и ERB для обоих.

включить:

#config/application.rb
require "sprockets/railtie"

установить методы компрессии ресурсов:

config.assets.css_compressor = :yui
config.assets.js_compressor = :uglify

использование:

-все файлы хранятся в app/assets | lib/assets | vendor/assets
-rake assets:precompile компилит их и помещает в папку, указанную в config.assets.prefix (по умолчанию это public/assets, но можно лить куда угодно)
-public/assets отдаётся вэб-сервером как статика, не обращаясь к аппликейшн-серверу

ссылаться на lib/assets/javascripts/subdir/moovinator.js можно вот так:

//= require subdir/moovinator

Asset Pipeline автоматически подставляет нужные имена файлов в следующие хэлперы:

stylesheet_link_tag, javascript_include_tag, image_tag

кроме этого, файлопровод вычисляет ERB, что позволяет писать вот такие строки в CSS:

.class { background-image: url(<%= asset_path 'image.png' %>) }

или в JS/Coffee :

$('#logo').attr({ src: "<%= asset_path('logo.png') %>" });

для SASS используется гем sass-rails, предоставляющий хэлперы -url и -path, например image-url("rails.png") становится url(/assets/rails.png)

пример продакшена: данный код

<%= javascript_include_tag "application" %>
<%= stylesheet_link_tag "application" %>

создаст что-то наподобие этого:

<script src="/assets/application-908e25f4bf641868d8683022a5b62f54.js"></script>
<link href="/assets/application-4dd5b109ee3439da54f5bdfd78a80473.css" media="screen" rel="stylesheet" />

Файлы манифеста и директивы

Sprockets использует файлы манифеста для определения, какие ресурсы включать и отдавать. Эти файлы манифеста содержат директивы - инструкции, говорящие Sprockets, какие файлы требуются для создания отдельного файла CSS или JavaScript. С помощью этих директив Sprockets загружает указанные файлы, при необходимости их обрабатывает, соединяет в отдельный файл и затем сжимает их (если Rails.application.config.assets.compress равно true). При отдаче одного файла, а не нескольких, время загрузки страницы значительно уменьшается, поскольку браузер делает меньше запросов. Компрессия также уменьшает размер файла, что позволяет браузеру быстрее его скачать.

пример manifest.json:

{
    "files": {
        "application-723d1be6cc741a3aabb1cec24276d681.js": {
            "logical_path": "application.js",
            "mtime": "2013-07-26T22:55:03-07:00",
            "size": 302506,
            "digest": "723d1be6cc741a3aabb1cec24276d681"
        },
        "application-12b3c7dd74d2e9df37e7cbb1efa76a6d.css": {
            "logical_path": "application.css",
            "mtime": "2013-07-26T22:54:54-07:00",
            "size": 1560,
            "digest": "12b3c7dd74d2e9df37e7cbb1efa76a6d"
        },
        "favicon-a9c641bf2b81f0476e876f7c5e375969.ico": {
            "logical_path": "favicon.ico",
            "mtime": "2013-07-26T23:00:10-07:00",
            "size": 1406,
            "digest": "a9c641bf2b81f0476e876f7c5e375969"
        },
        "my_image-231a680f23887d9dd70710ea5efd3c62.png": {
            "logical_path": "my_image.png",
            "mtime": "2013-07-26T23:00:27-07:00",
            "size": 6646,
            "digest": "231a680f23887d9dd70710ea5efd3c62"
        }
    },
    "assets": {
        "application.js": "application-723d1be6cc741a3aabb1cec24276d681.js",
        "application.css": "application-1c5752789588ac18d7e1a50b1f0fd4c2.css",
        "favicon.ico": "favicona9c641bf2b81f0476e876f7c5e375969.ico",
        "my_image.png": "my_image-231a680f23887d9dd70710ea5efd3c62.png"
    }
}

чтобы ощутить преимущества диджест меток файлов, необходимо настроить вэб-сервер на вечное (или хотябы очень долгое) кэширование. например, для nginx :

location ~ ^/assets/ {
  expires 1y;
  add_header Cache-Control public;
  gzip_static on; # to serve pre-gzipped version
  add_header ETag "";
  break;
}

13 Engine

Engine можно рассматривать как миниатюрное приложение, предоставляющее функционал содержащим их приложениям. Приложение Rails фактически всего лишь "прокачанный" engine с классом Rails::Application, унаследовавшим большую часть своего поведения от Rails::Engine.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •