Rails
Getting Started
Create controller and views
-
list all the things rails know how to generate
$ rails generate
-
Information about how to generate controller
$ rails generate controller
Stubs out a new controller and its views. Pass the controller name, either CamelCased or under_scored, and 一个coder加了一个column,另一个只要运行migration,就能达到同样状态 as arguments.
Hello World!
$rails generate controller demo index
- 修改app/controllers/demo_controller.rb
class DemoController < ApplicationController
layout false
def index
end
end
- 修改app/views/demo/index.html.erb
<h1>Demo#index</h1>
<p>Hello World!</p>
- 在
config/
下会生成文件routes.rb
Rails.application.routes.draw do
get 'demo/index'
end
File Structure
.
├── Gemfile
├── Gemfile.lock
├── README.rdoc
├── Rakefile
├── app
│ ├── assets
│ │ ├── images
│ │ ├── javascripts
│ │ │ ├── application.js
│ │ │ └── demo.js
│ │ └── stylesheets
│ │ ├── application.css
│ │ └── demo.css
│ ├── controllers
│ │ ├── application_controller.rb
│ │ ├── concerns
│ │ └── demo_controller.rb
│ ├── helpers
│ │ ├── application_helper.rb
│ │ └── demo_helper.rb
│ ├── mailers
│ ├── models
│ │ └── concerns
│ └── views
│ ├── demo
│ │ └── index.html.erb
│ └── layouts
│ └── application.html.erb
├── bin
│ ├── bundle
│ ├── rails
│ ├── rake
│ ├── setup
│ └── spring
├── config
│ ├── application.rb
│ ├── boot.rb
│ ├── database.yml
│ ├── environment.rb
│ ├── environments
│ │ ├── development.rb
│ │ ├── production.rb
│ │ └── test.rb
│ ├── initializers
│ │ ├── assets.rb
│ │ ├── backtrace_silencers.rb
│ │ ├── cookies_serializer.rb
│ │ ├── filter_parameter_logging.rb
│ │ ├── inflections.rb
│ │ ├── mime_types.rb
│ │ ├── session_store.rb
│ │ └── wrap_parameters.rb
│ ├── locales
│ │ └── en.yml
│ ├── routes.rb
│ └── secrets.yml
├── config.ru
├── db
│ └── seeds.rb
├── lib
│ ├── assets
│ └── tasks
├── log
│ └── development.log
├── public
│ ├── 404.html
│ ├── 422.html
│ ├── 500.html
│ ├── favicon.ico
│ └── robots.txt
├── spring-1000
│ ├── 4b53b2ff8b7674a6a782c3be813c38da
│ └── 4b53b2ff8b7674a6a782c3be813c38da.pid
├── test
│ ├── controllers
│ │ └── demo_controller_test.rb
│ ├── fixtures
│ ├── helpers
│ ├── integration
│ ├── mailers
│ ├── models
│ └── test_helper.rb
├── tmp
│ ├── cache
│ │ └── assets
│ ├── pids
│ ├── sessions
│ └── sockets
└── vendor
└── assets
├── javascripts
└── stylesheets
- models, views, controllers都在app下
- 在models和controllers下的concerns文件夹用来存放它们模块间需要共享的代码
- helpers里放views需要的helper method
- mailer for sending emails
- assets for images javascripts and stylesheets
- bin command line executable
- config包含database、application以及environment的config
- config/initializer包含application launch需要的代码
- config/locale internationalization
- config.ru for Rack based applications to work with Rails.
- db/ code to manage database migrations
- Gemfile Gemfile.lock work with Bundler
- lib/ storing libraries of code
- log/ logging information
- public/ 静态文件
- test/ test code
- vendor/ third party code outside gem
Basic route types
Simple route (matching exact string)
Rails.application.routes.draw do
get 'demo/index'
end
# 完整版本是
match 'demo/index',
:to => 'demo#index;,
:via => :get
因为simple route需要完全匹配,所以去掉index无法route
Default route
structure: :controller/:action/:id
GET /students/edit/52
对应 StudentsController, edit action
,写法:
match ':controller(/:action(/:id))'
:via => :get
# 括号表示可选项,默认match到index
match ':controller(/:action(/:id(.:format)))'
:via => :get
# 可以添加format例如json
Default route通常放在Root和Simple Route的下面
Root route
root :to => 'demo#index'
# 或者
root 'demo#index'
Controllers, Views and Dynamic Content
render template
routes呼叫controller,controller会根据定义的method做回应,如果未定义,则会寻找view里有没有match的html文件。如果有,就render给client。一般来说最好显式地定义controller,例如:
class DemoController < ApplicationController
layout false
def index
# render(:template => 'demo/hello')
# render('demo/hello')
render('hello')
end
def hello
end
end
在console可以看到过程
Started GET "/demo/index" for xxx.xx.xx.xxx at 2016-03-01 08:11:57 +0000
Processing by DemoController#index as HTML
Rendered demo/hello.html.erb (0.1ms)
Completed 200 OK in 4ms (Views: 3.3ms | ActiveRecord: 0.0ms)
Redirect actions
案例:
An user request a password required area, controller check if he logged in. If he is, get the page, else, redirect to a login page.
class DemoController < ApplicationController
def other_hello
# redirect_to(:controller => 'demo', :action => 'index')
redirect_to(:action => 'index')
# 在同一个controller下,controller => 'demo'可以省略
end
def example # 也可以跳转其它网站
redirect_to('http://example.com')
end
end
cosole
Started GET "/demo/other_hello" for xxx.xx.xx.xxx at 2016-03-01 08:24:49 +0000
Processing by DemoController#other_hello as HTML
Redirected to https://ror-nickyfoto.c9users.io/
Completed 302 Found in 1ms (ActiveRecord: 0.0ms)
Started GET "/" for xxx.xx.xx.xxx at 2016-03-01 08:24:49 +0000
Processing by DemoController#index as HTML
Rendered demo/hello.html.erb (0.1ms)
Completed 200 OK in 5ms (Views: 4.3ms | ActiveRecord: 0.0ms)
haml
Examples:
%h1 Demo#hello
%p Hello World!
- @array.each do |i|
= i
%br/
= link_to 'Hello Page 1', 'hello'
<!--相当于-->
<a href="/demo/hello">Hello Page 1</a>
= link_to 'Hello with params', {:action => 'hello', :page => 5, :id => 20}
<!--相当于-->
<a href="/demo/hello/20?page=5">Hello with params</a>
- % 表示tag
- - 表示ruby code(not recommended)
- @variable instance variable
- =表示eval这个值
- link_to 创建链接,第一个参数表示超链接字符,第二个表示要链接到的action。
- 如果要传递参数到url,用hash给出。当用户访问这个url时,参数会以params传递给controller,params是特殊的hash,叫做HashWithInifferentAccess,取值时它中括号内可以是symbol也可以是string。
- 注意rails里id在问号左边。
debugging
- Read the Error Message!
-
利用log来debug
all controller method have
logger
:logger.debug(@movie.inspect) # also logger also has warning and error level
-
A very common message:
undefined method 'foo' for nil:NilClass
Often it means an assignment silently failed and you didn’t error check.
因为很多ruby method 有错误时并不报错,而是返回nil。例如:
@m = Movie.find(id) #could be nil
@m.title # will fail: 'undefined method'
- print to views via haml
= debug(@movie) # 更好
= @movie.inspect
- use rails console
Database and Migrations
- 1 table = 1 model
- table 名都是小写复数
- attributes corresponds to columns
rake
A simple ruby helper program help perform tasks by running scripts. “rake” = “ruby make”, for example:
ask rake to dump our database schema
rake db:schema:dump
它的配置文件是根目录的Rakefile。更多的task可以放在lib/tasks下
$ rake -T # 返回它所有能执行的tasks list
$ rake -T db # 只返回跟db有关的
我们可以给rake传递环境变量,用全大写作为变量名,等于相应的值。
$ rake db:schema:dump RAILS_ENV=production
$ rake db:schema:dump RAILS_ENV=development
Migration
What is migration?
- Set of database instruction written in ruby
- “Migrate” your database from one state to another
- Describe database changes
- create table
- add column
- change name of column
- drop a table
- …
- Contains instructions for:
- Moving “up” to a new state
- Moving back “down” to the previous state
Why use migrations?
- Keeps database schema with application code
- Executable and repeatable
- Allow sharing schema changes
- Helps with versioning
- Allows writing ruby instead of SQL
Generating migrations
$ rails generate migration <name>
会在db/migrate下生成一个文件
class DoNothingYet < ActiveRecord::Migration
# def change
# end
def up
end
def down
end
end
为了更清晰,我们可以把这个文件中的change
改为up
和down
Generate Model
- Command line
- rails generate model SingularName
- rails generate model Subject
- Create file in db/migrate
- File name: 20130101000000_create_subjects.rb
- Class name: CreateSubjects
- Inherits from: ActiveRecord::Migration
- create_table :subjects, drop_table :subjects
- Create file in app/models
- File name: subject.rb
- Class name: Subject
- Inherits from: ActiveRecord::Base
- File names, class names and table names matter
Example:
$ rails generate model User
Running via Spring preloader in process 602
invoke active_record
create db/migrate/20160302022648_create_users.rb
create app/models/user.rb
invoke test_unit
create test/models/user_test.rb
create test/fixtures/users.yml
Files generated and modified:
class CreateUsers < ActiveRecord::Migration
# def change
def up
# create_table :users, {:id => false} do |t|
# suppress automatic additon of id
create_table :users do |t|
t.column 'first_name', :string, :limit => 25
t.string 'last_name', :limit => 50
t.string 'email', :default =>'', :null => false
t.string 'password', limit => 40
# t.datetime 'created_at'
# t.datetime 'updated_at'
# 'created_at' and 'updated_at' are
# special column names, rails will autoupdate them for us
t.timestamps null: false #short hand for aboveå
end
end
def down
drop_table :users
end
end
Table column types
binary, boolean, date, datetime, decimal, float, integer, string, text, time
Table column options
- :limit => size
- :default => value
- :null => true/false
- :precision => number
- :scale => number
Running Migrations
$ rake db:migrate <RAILS_ENV=development>
所有创建了但没有运行过的migration,rake都会run。
进入mysql查看
mysql> SHOW TABLES;
+----------------------------------+
| Tables_in_simple_cms_development |
+----------------------------------+
| schema_migrations |
| users |
+----------------------------------+
2 rows in set (0.00 sec)
mysql> SHOW FIELDS FROM users;
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| first_name | varchar(25) | YES | | NULL | |
| last_name | varchar(50) | YES | | NULL | |
| email | varchar(255) | NO | | | |
| password | varchar(40) | YES | | NULL | |
| created_at | datetime | NO | | NULL | |
| updated_at | datetime | NO | | NULL | |
+------------+--------------+------+-----+---------+----------------+
7 rows in set (0.00 sec)
mysql> SHOW FIELDS FROM schema_migrations;
+---------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+--------------+------+-----+---------+-------+
| version | varchar(255) | NO | PRI | NULL | |
+---------+--------------+------+-----+---------+-------+
1 row in set (0.00 sec)
mysql> SELECT * FROM schema_migrations;
+----------------+
| version |
+----------------+
| 20160302021538 |
| 20160302022648 |
+----------------+
2 rows in set (0.00 sec)
db/schema.rb文件也有变化
ActiveRecord::Schema.define(version: 20160302022648) do
create_table "users", force: :cascade do |t|
# t represents table
t.string "first_name", limit: 25
t.string "last_name", limit: 50
t.string "email", limit: 255, default: "", null: false
t.string "password", limit: 40
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
它代表我们database的当前状态
Migration down
$ rake db:migrate VERSION=0
== 20160302022648 CreateUsers: reverting ======================================
-- drop_table(:users)
-> 0.0070s
== 20160302022648 CreateUsers: reverted (0.0071s) =============================
== 20160302021538 DoNothingYet: reverting =====================================
== 20160302021538 DoNothingYet: reverted (0.0000s) ============================
再回到mysql
mysql> SHOW TABLES;
+----------------------------------+
| Tables_in_simple_cms_development |
+----------------------------------+
| schema_migrations |
+----------------------------------+
1 row in set (0.00 sec)
mysql> SELECT * FROM schema_migrations;
Empty set (0.00 sec)
run特定的migration
$ rake db:migrate:status
database: simple_cms_development
Status Migration ID Migration Name
--------------------------------------------------
down 20160302021538 Do nothing yet
down 20160302022648 Create users
$ rake db:migrate VERSION=20160302021538
== 20160302021538 DoNothingYet: migrating =====================================
== 20160302021538 DoNothingYet: migrated (0.0000s) ============================
$ rake db:migrate
== 20160302022648 CreateUsers: migrating ======================================
-- create_table(:users)
-> 0.0102s
== 20160302022648 CreateUsers: migrated (0.0103s) =============================
# 再回到上一个版本,只会退一步
rake db:migrate VERSION=20160302021538
== 20160302022648 CreateUsers: reverting ======================================
-- drop_table(:users)
-> 0.0080s
== 20160302022648 CreateUsers: reverted (0.0081s) =============================
$ rake db:migrate:status
database: simple_cms_development
Status Migration ID Migration Name
--------------------------------------------------
up 20160302021538 Do nothing yet
down 20160302022648 Create users
还有三个命令我们没提到
$ rake db:migrate:up VERSION=20160302021538 # 只做up method
$ rake db:migrate:down VERSION=20160302021538 # 只做down method
$ rake db:migrate:redo VERSION=20160302021538 # 先做down,后做up
Migration methods
Table migration methods
create_table(table, options) do |t|
# t represents table
...columns...
end
drop_table(table)
rename_table(table, new_name)
Column migration methods
add_column(table, column, type, options) # 不能用short format
remove_column(table, column)
rename_column(table, column, new_name)
change_column(table, column, type, options) # non-destructively
Index migration methods
add_index(table, column, options)
remove_index(table, column)
options:
:unique => true/false :name => “your_custom_name”
Execute migration method
execute("any SQL string")
案例:
$ rails generate migration AlterUsers
Running via Spring preloader in process 1687
invoke active_record
create db/migrate/20160302034635_alter_users.rb
change
method
在migration中change
即可以up,也可以down,例如
class AlterUsers < ActiveRecord::Migration
def change
rename_table('users', 'admin_users')
end
end
但是有些时候change并不能完全返回,例如你删除了一个column,是无法恢复的。所以最好还是用up和down,这样每一步做什么都很清晰。
测试:
class AlterUsers < ActiveRecord::Migration
# def change
def up
rename_table('users', 'admin_users')
add_column('admin_users', 'username', :string, :limit => 25,
:after => 'email')
#:after works with mysql, need to check availability for other db system
change_column('admin_users', 'email', :string, :limit => 100)
rename_column('admin_users', 'password', 'hashed_password')
puts "*** Adding an index is next ***"
add_index('admin_users', 'username')
# always add index to foreign key
end
def down
remove_index('admin_users', 'username')
rename_column('admin_users', 'hashed_password', 'password')
change_column('admin_users', 'email', :string, :default =>'', :null => false)
remove_column('admin_users', 'username')
rename_table('admin_users', 'users')
end
end
要测试并保证每一个migration正反都能运行。
$ rake db:migrate
$ rake db:migrate VERSION=0
Debug Migration
- Comment out executed code lines to get back on track
- Write SQL commands as a last resort
- Keep migrations small and concise
- Test all migrations thoroughly in development
Read more: