-
Stwórz nowy prohjek
cd ˜/workspace
rails new sample_app
cd sample_app/
rails server
-
Modify Gemfile
source 'https://rubygems.org'
gem 'rails', '5.1.4'
gem 'puma', '3.9.1'
gem 'sass-rails', '5.0.6'
gem 'uglifier', '3.2.0'
gem 'coffee-rails', '4.2.2'
gem 'jquery-rails', '4.3.1'
gem 'turbolinks', '5.0.1'
gem 'jbuilder', '2.7.0'
group :development, :test do
gem 'sqlite3', '1.3.13'
gem 'byebug', '9.0.6', platform: :mri
end
group :development do
gem 'web-console', '3.5.1'
gem 'listen', '3.1.5'
gem 'spring', '2.0.2'
gem 'spring-watcher-listen', '2.0.1'
end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
-
gem install bundler
bundle config --delete without
bundle config --delete with
bundle install
bundle install --without production
-
git init
git add .
git commit -m "Initialize repository"
# git remote add origin https://przem85@bitbucket.org/statistic_solutions/sample_app.git
# git push -u origin --all
-
Adding a hello action to the Application controller. app/controllers/application_controller.rb
def hello
render html: "hello, world!"
end
-
Setting the root route. config/routes.rb
root 'application#hello'
-
git commit -am "Add hello"
git push
heroku create
git push heroku master
-
Static pages
-
To get started with static pages, we’ll first generate a controller
rails generate controller StaticPages home help
-
- UWAGA możemy cofać zmiany
rails destroy controller StaticPages home help
rails generate controller StaticPages home help
rails db:migrate
rails db:rollback
-
The Static Pages controller automatically updates the routes file (config/routes.rb)
See ... static_pages/home
-
<h1>Sample App</h1>
<p>
<a href="http://ruby-on-rails-tutorial.x25.pl">Ruby on Rails Tutorial</a>
</p>
-
Getting started with testing
See test/controllers/static_pages_controller_test.rb
rails test
- Add
test "should get about" do
get static_pages_about_url
assert_response :success
end
and
rails test
-
ZADANIE dodaj wszystkie elemnty tak by testy przeszły.
-
The rails new command creates a layout file by default, but it’s instructive to ignore it initially, which we can do by changing its name:
mv app/views/layouts/application.html.erb layout_file
-
We’ll write simple tests for each of the titles
require 'test_helper'
class StaticPagesControllerTest < ActionDispatch::IntegrationTest
test "should get home" do
get static_pages_home_url
assert_response :success
assert_select "title", "Home | Ruby on Rails Tutorial Sample App"
end
test "should get help" do
get static_pages_help_url
assert_response :success
assert_select "title", "Help | Ruby on Rails Tutorial Sample App"
end
test "should get about" do
get static_pages_about_url
assert_response :success
assert_select "title", "About | Ruby on Rails Tutorial Sample App"
end
end
-
ZADANIE dodaj wszystkie elemnty tak by testy przeszły.
rails test
-
<!DOCTYPE html>
<html>
<head>
<title>Home | Ruby on Rails Tutorial Sample App</title>
</head>
<body>
<h1>Sample App</h1>
<p>
This is the home page
</p>
</body>
</html>
-
“Ruby on Rails Tutorial Sample App”, is the same for every title test. Using the special function setup, which is automatically run before every test
require 'test_helper'
class StaticPagesControllerTest < ActionDispatch::IntegrationTest
def setup
@base_title = "Ruby on Rails Tutorial Sample App"
end
test "should get home" do
get static_pages_home_url
assert_response :success
assert_select "title", "Home | #{@base_title}"
end
test "should get help" do
get static_pages_help_url
assert_response :success
assert_select "title", "Help | #{@base_title}"
end
test "should get about" do
get static_pages_about_url
assert_response :success
assert_select "title", "About | #{@base_title}"
end
end
-
Layouts
<% provide(:title, "Home") %>
<!DOCTYPE html>
<html>
<head>
<title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
</head>
<body>
<h1>Sample App</h1>
<p>
This is the home page
</p>
</body>
</html>
-
ZADANIE dodaj wszystkie elemnty tak by testy przeszły.
rails test
-
Możemy już przywrócić nasz styl.
mv layout_file app/views/layouts/application.html.erb
-
<!DOCTYPE html>
<html>
<head>
<title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
<%= csrf_meta_tags %>
<%= stylesheet_link_tag 'application', media: 'all',
'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>
<body>
<%= yield %>
</body>
</html>
-
Dodaj do app/views/static_pages/home.html.erb
<% provide(:title, "Home") %>
<h1>Sample App</h1>
<p>
This is the home page
</p>
-
Setting the root route to the Home page. config/routes.rb
Rails.application.routes.draw do
root 'static_pages#home'
get 'static_pages/home'
get 'static_pages/help'
get 'static_pages/about'
end
-
git add .
git commit -m "Finish static page"
# git push
git push heroku
-
Site navigation
<!DOCTYPE html>
<html>
<head>
<title><%= yield(:title) %></title>
<%= csrf_meta_tags %>
<%= stylesheet_link_tag 'application', media: 'all',
'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/r29/html5.min.js">
</script>
<![endif]-->
</head>
<body>
<header class="navbar navbar-fixed-top navbar-inverse">
<div class="container">
<%= link_to "sample app", '#', id: "logo" %>
<nav>
<ul class="nav navbar-nav navbar-right">
<li><%= link_to "Home", '#' %></li>
<li><%= link_to "Help", '#' %></li>
<li><%= link_to "Log in", '#' %></li>
</ul>
</nav>
</div>
</header>
<div class="container">
<%= yield %>
</div>
</body>
</html>
-
Downloading an image rails.png.
curl -o app/assets/images/rails.png -OL railstutorial.org/rails.png
-
Add hyperlink by image_tag
<%= link_to image_tag("rails.png", alt: "Rails logo"), 'http://rubyonrails.org/' %>
-
Bootstrap and custom CSSAdding the bootstrap-sass gem to the Gemfile
gem 'bootstrap-sass', '3.3.7'
and run
bundle install
-
Add styleshit to app/assets/stylesheets/custom.scss
touch app/assets/stylesheets/custom.scss
and add
@import "bootstrap-sprockets";
@import "bootstrap";
/* universal */
body {
padding-top: 60px;
}
section {
overflow: auto;
}
textarea {
resize: vertical;
}
.center {
text-align: center;
}
.center h1 {
margin-bottom: 10px;
}
-
Dodaj do app/views/static_pages/home.html.erb
<div class="center jumbotron">
<h1>Welcome to the Sample App</h1>
<h2>
This is the home page.
</h2>
<%= link_to "Sign up now!", '#', class: "btn btn-lg btn-primary" %>
</div>
<%= link_to image_tag("rails.png", alt: "Rails logo"), 'http://rubyonrails.org/' %>
-
Add to app/assets/stylesheets/custom.scss
/* typography */
h1, h2, h3, h4, h5, h6 {
line-height: 1;
}
h1 {
font-size: 3em;
letter-spacing: -2px;
margin-bottom: 30px;
text-align: center;
}
h2 {
font-size: 1.2em;
letter-spacing: -1px;
margin-bottom: 30px;
text-align: center;
font-weight: normal;
color: #777;
}
p {
font-size: 1.1em;
line-height: 1.7em;
}
-
Converts the text of logo to uppercase and modifies its size, color, and placement add to app/assets/stylesheets/custom.scss
/* header */
#logo {
float: left;
margin-right: 10px;
font-size: 1.7em;
color: #fff;
text-transform: uppercase;
letter-spacing: -1px;
padding-top: 9px;
font-weight: bold;
}
#logo:hover {
color: #fff;
text-decoration: none;
}
-
Partials
Add ne filles:
app/views/layouts/_shim.html.erb
app/views/layouts/_header.html.erb
app/views/layouts/_footer.html.erb
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/r29/html5.min.js">
</script>
<![endif]-->
-
<header class="navbar navbar-fixed-top navbar-inverse">
<div class="container">
<%= link_to "sample app", '#', id: "logo" %>
<nav>
<ul class="nav navbar-nav navbar-right">
<li><%= link_to "Home", '#' %></li>
<li><%= link_to "Help", '#' %></li>
<li><%= link_to "Log in", '#' %></li>
</ul>
</nav>
</div>
</header>
-
<footer class="footer">
<small>
Footer
</small>
<nav>
<ul>
<li><%= link_to "About", '#' %></li>
<li><%= link_to "Contact", '#' %></li>
</ul>
</nav>
</footer>
-
Change app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= yield(:title) %></title>
<%= csrf_meta_tags %>
<%= stylesheet_link_tag 'application', media: 'all',
'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
<%= render 'layouts/shim' %>
</head>
<body>
<%= render 'layouts/header' %>
<div class="container">
<%= yield %>
<%= render 'layouts/footer' %>
</div>
</body>
</html>
-
Add
/* footer */
footer {
margin-top: 45px;
padding-top: 5px;
border-top: 1px solid #eaeaea;
color: #777;
}
footer a {
color: #555;
}
footer a:hover {
color: #222;
}
footer small {
float: left;
}
footer ul {
float: right;
list-style: none;
}
footer ul li {
float: left;
margin-left: 15px;
}
-
Layout links
<%= link_to "About", about_path %>
and add config/routes.rb
get '/help', to: 'static_pages#help'
get '/about', to: 'static_pages#about'
-
User signup: A first step
rails generate controller Users new
-
Add to config/routes.rb
get '/signup', to: 'users#new'
-
<%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %>
-
<% provide(:title, 'Sign up') %>
-
git add .
git commit -m "Finish layout and routes"
git checkout master
git merge filling-in-layout
git push
git push heroku
-
Modeling users
-
Command for making a model is generate model, which we can use to generate a User model with name and email attribute
rails generate model User name:string email:string
One of the results of the generate command is a new file called a migration.
The full data model represented by the migration:
We can run the migration, known as “migrating up”, using the db:migrate command as follows:
-
rails db:migrate
-
We can see the structure of the database by opening development.sqlite3
with DB Browser for SQLite http://sqlitebrowser.org/
rails console --sandbox
User.new
user = User.new(name: "Przemysław Spurek", email: "spurek@example.com")
user.valid?
user.save
user
ser.name
user.email
user.updated_at
User.create(name: "Aaa", email: "aaa@example.org")
foo = User.create(name: "Bbb", email: "bbb@example.org")
foo.destroy
-
Finding user objects
User.find(1)
User.find(3)
User.find_by(email: "spurek@example.com")
User.first
User.all
-
Updating user objects
user # Just a reminder about our user's attributes
user.email = "mhartl@example.net"
user.save
user
user.created_at
user.updated_at
-
user.update_attributes(name: "The Dude", email: "dude@abides.org")
user.name
user.email
-
user.update_attribute(:name, "El Duderino")
-
User validations
Add tests to test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
def setup
@user = User.new(name: "Example User", email: "user@example.com")
end
test "should be valid" do
assert @user.valid?
end
end
-
rails test:models
Perhaps the most elementary validation is presence, which simply verifies that a given attribute is present.
-
test "name should be present" do
@user.name = " "
assert_not @user.valid?
end
-
rails test:models
-
Add to app/models/user.rb
class User < ApplicationRecord
validates :name, presence: true
end
-
rails test:models
-
rails console --sandbox
user = User.new(name: "", email: "mhartl@example.com")
user.valid?
user.errors.full_messages
user.save
- ZADANIE Dodaj test validację
test "email should be present" do
@user.email = " "
assert_not @user.valid?
end
-
validates :email, presence: true
-
maximum length validation
test "name should not be too long" do
@user.name = "a" * 51
assert_not @user.valid?
end
test "email should not be too long" do
@user.email = "a" * 244 + "@example.com"
assert_not @user.valid?
end
-
validates :name, presence: true, length: { maximum: 50 }
validates :email, presence: true, length: { maximum: 255 }
-
Format validation
test "email validation should accept valid addresses" do
valid_addresses = %w[user@example.com USER@foo.COM A_US-ER@foo.bar.org
first.last@foo.jp alice+bob@baz.cn]
valid_addresses.each do |valid_address|
@user.email = valid_address
assert @user.valid?, "#{valid_address.inspect} should be valid"
end
end
test "email validation should reject invalid addresses" do
invalid_addresses = %w[user@example,com user_at_foo.org user.name@example.
foo@bar_baz.com foo@bar+baz.com]
invalid_addresses.each do |invalid_address|
@user.email = invalid_address
assert_not @user.valid?, "#{invalid_address.inspect} should be invalid"
end
end
-
rails test
-
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
http://www.rubular.com/
-
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX }
-
rails test:models
Uniqueness validation
-
test "email addresses should be unique" do
duplicate_user = @user.dup
@user.save
assert_not duplicate_user.valid?
end
-
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: true
-
rails test
-
case-insensitivity
test "email addresses should be unique" do
duplicate_user = @user.dup
duplicate_user.email = @user.email.upcase
@user.save
assert_not duplicate_user.valid?
end
-
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
-
rails test
-
Database indices
When creating a column in a database, it is important to consider whether we will need to find records by that column.
Consider, for example, the email attribute created by the migration.
When we allow users to log in to the sample app, we will need to find the user record corresponding
to the submitted email address. Unfortunately, based on the naive data model,
the only way to find a user by email address is to look through each user row in
the database and compare its email attribute to the given email—which means we might
have to examine every row (since the user could be the last one in the database).
This is known in the database business as a full-table scan,
and for a real site with thousands of users it is a Bad Thing.
-
The email index represents an update to our data modeling
rails generate migration add_index_to_users_email
-
Add to db/migrate/[timestamp]_add_index_to_users_email.rb
class AddIndexToUsersEmail < ActiveRecord::Migration[5.0]
def change
add_index :users, :email, unique: true
end
end
-
rails db:migrate
-
rails test
-
Delete all from test/fixtures/users.yml
-
rails test
-
Ensuring email uniqueness by downcasing the email attribute
before_save { self.email = email.downcase }
-
Adding a secure password
Most of the secure password machinery will be implemented using a single Rails method called has_secure_password,
which we’ll include in the User model.
The only requirement for has_secure_password to work its magic is for the corresponding
model to have an attribute called password_digest.
rails generate migration add_password_digest_to_users password_digest:string
-
rails db:migrate
-
To make the password digest, has_secure_password uses a state-of-the-art hash function called
bcrypt.
To use bcrypt in the sample application, we need to add the bcrypt gem to our Gemfile
gem 'bcrypt', '3.1.12'
-
bundle install
-
Add to app/models/user.rb
has_secure_password
-
rails test
-
Add to test/models/user_test.rb
def setup
@user = User.new(name: "Example User", email: "user@example.com",
password: "foobar", password_confirmation: "foobar")
end
-
rails test
-
Minimum password standards
test "password should be present (nonblank)" do
@user.password = @user.password_confirmation = " " * 6
assert_not @user.valid?
end
test "password should have a minimum length" do
@user.password = @user.password_confirmation = "a" * 5
assert_not @user.valid?
end
-
rails test
-
validates :password, presence: true, length: { minimum: 6 }
-
rails test
Creating and authenticating a user
-
rails console
User.create(name: "Michael Hartl", email: "mhartl@example.com",
password: "foobar", password_confirmation: "foobar")
user = User.find_by(email: "mhartl@example.com")
user.password_digest
user.authenticate("not_the_right_password")
user.authenticate("foobaz")
user.authenticate("foobar")
!!user.authenticate("foobar")
-
Adding some debug information to the site layout
<%= debug(params) if Rails.env.development? %>
-
Make the debug output look nice
/* mixins, variables, etc. */
$gray-medium-light: #eaeaea;
@mixin box_sizing {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
/* miscellaneous */
.debug_dump {
clear: both;
float: left;
width: 100%;
margin-top: 45px;
@include box_sizing;
}
-
Adding a Users resource to the routes file config/routes.rb
Rails.application.routes.draw do
root 'static_pages#home'
get '/help', to: 'static_pages#help'
get '/about', to: 'static_pages#about'
get '/contact', to: 'static_pages#contact'
get '/signup', to: 'users#new'
resources :users
end
-
-
Create app/views/users/show.html.erb
< @user.name %>, <%= @user.email %>
-
Add to app/controllers/users_controller.rb
def show
@user = User.find(params[:id])
end
-
Add a “globally recognized avatar”, or Gravatar.
Add to app/views/users/show.html.erb
<% provide(:title, @user.name) >
<h1>
<%= gravatar_for @user %>
<%= @user.name %>
</h1>
-
Add to app/helpers/users_helper.rb
# Returns the Gravatar for the given user.
def gravatar_for(user)
gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}"
image_tag(gravatar_url, alt: user.name, class: "gravatar")
end
-
<% provide(:title, @user.name) %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<h1>
<%= gravatar_for @user %>
<%= @user.name %>
</h1>
</section>
</aside>
</div>
-
Add to app/assets/stylesheets/custom.scss
/* sidebar */
aside {
section.user_info {
margin-top: 20px;
}
section {
padding: 10px 0;
margin-top: 20px;
&:first-child {
border: 0;
padding-top: 0;
}
span {
display: block;
margin-bottom: 3px;
line-height: 1;
}
h1 {
font-size: 1.4em;
text-align: left;
letter-spacing: -1px;
margin-bottom: 3px;
margin-top: 0px;
}
}
}
.gravatar {
float: left;
margin-right: 10px;
}
.gravatar_edit {
margin-top: 15px;
}
-
Signup form
Add to app/controllers/users_controller.rb
def new
@user = User.new
end
-
A form to sign up new users
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(@user) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :email %>
<%= f.email_field :email %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation %>
<%= f.submit "Create my account", class: "btn btn-primary" %>
<% end %>
</div>
</div>
-
app/assets/stylesheets/custom.scss
/* forms */
input, textarea, select, .uneditable-input {
border: 1px solid #bbb;
width: 100%;
margin-bottom: 15px;
@include box_sizing;
}
input {
height: auto !important;
}
-
def create
@user = User.new(params[:user]) # Not the final implementation!
if @user.save
# Handle a successful save.
else
render 'new'
end
end
-
Since user_params will only be used internally by the Users controller and need not
be exposed to external users via the web, we’ll make it private using Ruby’s private keyword
def create
@user = User.new(user_params)
if @user.save
# Handle a successful save.
else
render 'new'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
-
Signup error messages
Add to app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(@user) do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
<%= f.label :email %>
<%= f.email_field :email, class: 'form-control' %>
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation, class: 'form-control' %>
<%= f.submit "Create my account", class: "btn btn-primary" %>
<% end %>
</div>
</div>
-
mkdir app/views/shared
-
Add to app/views/shared/_error_messages.html.erb
<% if @user.errors.any? %>
<div id="error_explanation">
<div class="alert alert-danger">
The form contains <%= pluralize(@user.errors.count, "error") %>.
</div>
<ul>
<% @user.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
-
#error_explanation {
color: red;
ul {
color: red;
margin: 0 0 30px 0;
}
}
.field_with_errors {
@extend .has-error;
.form-control {
color: $state-danger-text;
}
}
-
Adding a signup route responding to POST requests
Add to config/routes.rb
Rails.application.routes.draw do
root 'static_pages#home'
get '/help', to: 'static_pages#help'
get '/about', to: 'static_pages#about'
get '/contact', to: 'static_pages#contact'
get '/signup', to: 'users#new'
post '/signup', to: 'users#create'
resources :users
end
-
add to app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(@user, url: signup_path) do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
<%= f.label :email %>
<%= f.email_field :email, class: 'form-control' %>
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation, class: 'form-control' %>
<%= f.submit "Create my account", class: "btn btn-primary" %>
<% end %>
</div>
</div>
-
Add to app/controllers/users_controller.rb
def create
@user = User.new(user_params)
if @user.save
redirect_to @user
else
render 'new'
end
end
-
The flash
def create
@user = User.new(user_params)
if @user.save
flash[:success] = "Welcome to the Sample App!"
redirect_to @user
else
render 'new'
end
end
-
The first signup
rails db:migrate:reset
-
We can also use
<% flash.each do |message_type, message| %>
<%= content_tag(:div, message, class: "alert alert-#{message_type}") %>
<% end %>