Когда используется единственное число, а когда множественное?
- таблицы в БД - множественное число
- модели - единственное число
- контроллеры - множественное число
В книге Rails. Гибкая разработка веб-приложений (Руби, Томас, Хэнссон) прочитать про соглашения об именах.
Тип 1 - * (one-to-many)
Article | Comment
has_many :comments | belongs_to :article
id | id, article_id
Тип 1 - 1 (one-to-one) - помогает нормализовать БД
Order | Address
has_one :address | belongs_to :order
id | id, order_id
Нормализация, денормализация. Плюсы и минусы подходов. В варианте выше потребуется ещё 1 запрос к базе данных.
Понятие complex_type - сложный тип (изучить)
Тип * - * (many-to-many)
Tag | Article
таблица tags | таблица articles
id | id
has_and_belongs_to_many :articles | has_and_belongs_to_many :tags
для связи между ними создаётся ещё одна таблица tags_articles (tag_id, article_id)
Изучить: http://www.rusrails.ru/active-record-associations#foreign_key
Добавим в /app/views/articles/show.html.erb:
<h3>Комментарии:</h3>
<%= 'Без комментариев' if @article.comments.empty? %>
<% @article.comments.each do |comment| %>
<p><strong>Автор:</strong> <%= comment.author %></p>
<p><%= comment.body %></p>
<hr>
<% end %>
Вспомним CRUD:
- Create - (new) - .create - .save
- Read - .where - .find(3), .all
- Update - (update)
- Delete - (destroy)
https://guides.rubyonrails.org/action_controller_overview.html#more-examples
Вспомним REST:
- resource
- resources
Можно вкладывать, получаются длинные URL:
resources :article do
resources :comments
end
x = 2 != 3
puts x #=> true
gem install rspec
Запуск тестов:
rspec
В Rspec существует 2 слова:
- describe
- it
Создадим и протестируем героя компьютерной игры.
https://github.com/krdprog/rspec-demo-rubyschool - репозиторий Rspec-demo
Создадим файл hero.rb
class Hero
def initialize(name, health=100)
@name = name.capitalize
@health = health
end
attr_reader :name
def power_up
@health += 10
end
def power_down
@health -= 10
end
def hero_info
"#{@name} has #{@health} health"
end
end
Создадим файл hero_spec.rb:
require './hero'
describe Hero do
it "has a capitalized name" do
hero = Hero.new 'foo'
expect(hero.name).to eq 'Foo'
end
it "can power up" do
hero = Hero.new 'foo'
expect(hero.power_up).to eq 110
end
end
И, запустим тест:
rspec hero_spec.rb
Написание тестов в большом приложении - это вклад в будущее, защита приложения от ошибок.
Продолжим рассматривать Rspec и тестирование.
Добавим к нашим тестам в файле hero_spec.rb
it "can power down" do
hero = Hero.new 'foo'
expect(hero.power_down).to eq 90
end
it "displays full hero info" do
hero = Hero.new 'foo'
expect(hero.hero_info).to eq "Foo has 100 health"
end
У нас часто повторяется код hero = Hero.new 'foo', и это не совпадает с DRY (Don`t Repeat Yourself), ниже мы его оптимизируем, добавив before-do-end
Рефакторинг hero_spec.rb:
require './hero'
describe Hero do
before do
@hero = Hero.new 'foo'
end
it "has a capitalized name" do
expect(@hero.name).to eq 'Foo'
end
it "can power up" do
expect(@hero.power_up).to eq 110
end
it "can power down" do
expect(@hero.power_down).to eq 90
end
it "displays full hero info" do
expect(@hero.hero_info).to eq "Foo has 100 health"
end
end
Тесты должны быть:
- надёжные (reliable) - дают тот же результат - без зависимостей от соединения, от БД и т.п.
- easy to write - если тест пишется не легко, то что-то не так с тем, что тестируем.
- easy to understand - лёгкие для понимания другими программистами.
- скорость не особо важна
- не важно DRY
Изучить ссылку: https://relishapp.com/rspec/rspec-expectations/docs/built-in-matchers
Сделаем ещё одно приложение и тест:
car.rb:
class Car
Miles_Per_Gallon = 20
attr_reader :fuel
def initialize
@fuel = 0
end
def add_fuel amount
@fuel += amount
end
# Как далеко мы сможем проехать:
def range
@fuel * Miles_Per_Gallon
end
end
# car = Car.new
# car.add_fuel 10
# puts "Range is #{car.range}"
car_spec.rb:
require "./car"
describe Car do
it "must return range" do
# arrange
car = Car.new
# act
car.add_fuel 10
# assert
expect(car.range).to eq 200
end
end
Структура тестов:
# arrange
# act
# assert
# arrange - подготовка объекта для проведения теста
car = Car.new
# act - действие
result = car.add_fuel 10
# assert - проверка действия
expect(result) ...
Заметка по Issue на Github. У issue есть номер (например #12), если при коммите указать этот номер, то issue сама закроектся и пометится как выполненная.
Devise - гем для авторизации
Добавим в Gemfile:
gem 'devise'
bundle
Вывести в терминал список генераторов в системе:
rails g
Devise:
devise
devise:controllers
devise:install
devise:views
Введём:
rails g devise:install
План, как подключать devise:
- gem 'devise' в Gemfile
- rails g devise:install
Проверить есть ли строка в файле config/environments/development.rb:
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
Проверить, что в /config/routes.rb указано:
root to: "home#index"
Добавить в app/views/layouts/application.html.erb для флеш-сообщений:
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
Для кастомизации форм:
rails g devise:views
Далее, создадим модель пользователя:
rails g devise User
Devise создал параметры: e-mail, зашифрованный пароль, токен для сброса пароля.
Наберём:
rake db:migrate
И, запустим:
rails s
Откроем /app/controllers/articles_controller.rb и добавим:
Примечание: начиная с Rails 5 синтаксис before_filter устарел и заменён на before_action
before_action :authenticate_user!
Добавим в /app/views/layouts/application.html.erb:
<p><a href="/users/sign_in">Sign In</a> | <a href="/users/sign_out" data-method="delete">Sign Out</a></p>
Далее, мы заменим эти ссылки на ссылки с именноваными маршрутами.
Документация по гему devise - https://github.com/plataformatec/devise
Статья на Хабре по devise - https://habr.com/ru/post/208056/
Посмотреть примеры - https://github.com/plataformatec/devise/wiki/Example-applications
Заменим жёстко прописанные ссылки, на ссылки с именованными маршрутами:
<p><%= link_to "Sign In", new_user_session_path %> | <%= link_to "Sign Out", destroy_user_session_path, method: :delete %></p>
Далее, выведем имя пользователя при авторизации, и уберём Sign Out ссылку, когда мы не авторизованы.
https://github.com/plataformatec/devise#controller-filters-and-helpers
<% if user_signed_in? %>
Hello, <%= current_user.email %> | <%= link_to "Sign Out", destroy_user_session_path, method: :delete %>
<% else %>
<%= link_to "Sign In", new_user_session_path %>
<% end %>
- Аутентификация - проверка пользователя и пароля
- Авторизация - наделение определёнными правами в зависимости от роли (юзер/админ)
Login, password
User ------------------> Server
<------------------
Cookies (*)
/config/initializers/devise.rb
# config.secret_key =
session['key'] = 'value'
Cookie выдаётся при первом обращении к серверу, и затем без авторизации идёт обмен данными.
Следующий пример показывает JSON-представление объекта, описывающего человека. В объекте есть строковые поля имени и фамилии, объект, описывающий адрес, и массив, содержащий список телефонов. Как видно из примера, значение может представлять собой вложенную структуру.
{
"firstName": "Иван",
"lastName": "Иванов",
"address": {
"streetAddress": "Московское ш., 101, кв.101",
"city": "Ленинград",
"postalCode": "101101"
},
"phoneNumbers": [
"812 123-1234",
"916 123-4567"
]
}
Добавление username в наш блог, и вставка вместо поля автора комментария, username залогиненого пользователя:
rails g migration add_username
Добавим в существующую таблицу новый столбец.
https://api.rubyonrails.org/classes/ActiveRecord/Migration.html
/db/migrate/20190129063426_add_username.rb
class AddUsername < ActiveRecord::Migration[5.2]
def change
add_column :users, :username, :string
add_index :users, :username, unique: true
end
end
rake db:migrate
Индекс - увеличивается время вставки, но уменьшается время выборки по определённому полю.
Все контроллеры наследуются от базового контроллера ApplicationController и чтобы задать для всех контроллеров один параметр, надо прописать это в ApplicationController.
before_action (в ранних версиях Rails это было before_filter), фильтрует методы в контроллерах до того как их обработать.
/app/controllers/application_controller.rb:
class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:username])
end
end
Сгенерируем набор views:
rails g devise:views
Добавим в /app/views/devise/registrations/new.html.erb код поля ввода username:
<div class="field">
<%= f.label :username %><br />
<%= f.text_field :username, autocomplete: "username" %>
</div>
<% if user_signed_in? %>
Hello, <%= current_user.username %> | <%= link_to "Sign Out", destroy_user_session_path, method: :delete %>
<% else %>
<%= link_to "Sign In", new_user_session_path %>
<% end %>
Авторизация только для создания и редактирования статьи /app/controllers/articles_controller.rb:
before_action :authenticate_user!, :only => [:new, :create, :edit, :update, :destroy]
И, для комментирования /app/controllers/comments_controller.rb:
before_action :authenticate_user!, :only => [:create]
Домашнее задание:
- Найти и изучить, что такое индексы в БД.
- Сделать так, чтобы комментарии оставлялись под именем залогиненого пользователя.
https://andreyex.ru/bazy-dannyx/uchebnoe-posobie-po-sql/sql-indeksy/
На upwork.com указывать, что все fees берёт на себя заказчик. Т.е. при $30 будет отображаться $33 (пример требующий проверки).
Далее в этом уроке рассматривается Bootstrap. Конспекты по нему не фиксировались.
Домашнее задание:
- Читать про хелперы
- Внедрить в RailsBlog - bootstrap
- Убрать поле author из view комментария, и заменить на имя залогиненого (если не сделано на уроке 43)
Принцип работы метода params в контроллерах: в params хранятся все параметры которые передаются из браузера в приложение.
private
def article_params
params.require(:article).permit(:title, :text)
end
https://api.rubyonrails.org/classes/ActionController/Parameters.html#method-i-require
https://api.rubyonrails.org/classes/ActionController/Parameters.html#method-i-permit
https://api.rubyonrails.org/classes/ActionController/Parameters.html
Browser ===> Server ===> Controller ===> ActiveRecord ===> Database
||
===> Error
Правило: никогда не верьте тому, что передаёт клиент.
https://stackoverflow.com/questions/7493767/are-redirect-to-and-render-exchangeable
Если используется render когда юзер обновляет страницу, то он засабмитит предыдущий POST-запрос, это может отправить дополнительные данные.
Если используется redirect_to, то это будет выгдядеть как редирект.
Post/Redirect/Get (PRG) pattern:
Все хелперы расположены в каталоге /app/helpers/
Для каждого контроллера существует хелпер, можно вызывать различные методы из этих хелперов, из разных представлений. Хелпер - работает между контроллером и представлением. Чтобы не вставлять код в представление.
Чтобы не дублировать код в каждом представлении.
Логику в представлениях писать неправильно, надо выносить в хелперы. Представления предназначены для того, чтобы отображать данные. Нехорошо размазывать логику по всему приложению.
Один хелпер создаётся для одного контроллера, но все хелперы доступны всем контроллерам.
Существуют также встроенные хелперы в Rails, например debug, выведет список параметров.
<%= debug(params) %>
Ещё посмотреть:
<%= simple_format(@foo) %>
И, см. автоматическая подсветка ссылок - autolinks
А, также truncate - если есть длинная строка, то обрезается под указанный размер:
<%= truncate(@foo, length: 20) %>
И, уже известный нам хелпер link_to.
- CI - continuous integration - непрерывная интеграция - автотест при коммитах в общий репозиторий
- CD - continuous delivery - непрерывная доставка - автозаливка на веб-сервер
programmer owner
| ______|
hipchat ______ github -------
|__________ test server__|
|
www (staging) --- QA
Integration tests - watir, selenium
KANBAN-доска: http://kanbanflow.com/
Домашнее задание:
- поставить Vargant
- привязать сущность article в блоге к пользователю. Сделать так, чтобы другие пользователи не могли редактировать статьи.
Продолжение конспекта: Урок 46-50 - https://github.com/krdprog/rubyschool-notes/blob/master/rubyschool-notes-08.md
N | N | N | N |
---|---|---|---|
Урок 01-14 | Урок 15-19 | Урок 20-25 | Урок 26-30 |
Урок 31-35 | Урок 36-40 | Урок 41-45 | Урок 46-50 |