close

因為項目變有點多了,請使用Ctrl+F搜尋標題,( )內是這篇大綱不用加入搜尋

1.Rails四大觀點 (命名規則,MVC)

2.在Cloud9下指令執行網頁 (用Scaffold支架)

3.DIY之1 (命名錯誤悲劇,瞭解Rails MVC流程,看懂錯誤訊息)

4.ERB (在網頁執行Ruby語法)

5.資料表關連 (一對多,多對多,hirb-unicode套件)

6.更改Schema.rb內容 (改欄名,改欄屬,增加欄位,刪除欄位)

7.快速美化頁面與表格分頁 (bootstrap-sass和Kaminari套件)

8.form形式的dropdownlist (單純的下拉式欄位和連資料關連的下拉式欄位)

9.限制輸入框

10.驗證格式 (其實Rails也有一套驗證規則)

11.強大Try

12.相對路徑的圖片

13.製作後台路徑

14.DIY之2 (寫出CRUD,before_action,model.find)

15.不同頁面傳值問題

16.datetime轉換格式

17.render載入

18.flash與session

19.指定layout版面

20.routes中as用處 (幫路徑取別名)

21.Helper

22.多國語系

23.寄Email (Gmail,Mandrill,Mandrill Template)

24.現在寄 VS 待會寄 (delayed_job套件)

25.SQL語句 (以前SQL語句+Rails偷懶寫法=少好幾行程式碼)

26.問題主鍵是Id (解決複合鍵與屬性String問題)

 


Rails四大觀點


CoC:慣例優於構造

先提一下CoC命名規則(後面DIY會示範命名錯誤產生悲劇)

1.resource :複數

2.Model檔名單數

3.db table名稱為複數

4.Controller檔名複數

5.雖然英開頭大小無差,但最好小寫

Rails命名規則參考網址:http://fireqqtw.logdown.com/posts/206952-rails-naming-conventions

DRY:不要重複程式

CRUD:增讀更改刪除

MVC:模組視窗控制

以下為MVC分工原則(對不起我畫的很糟...)

MVC


在Cloud9下指令執行網頁


首先在workspace路徑下創個Rails專案,並切到資料夾下

rails new 專案名稱

cd 專案名稱/

成功之後在左視窗會看到創好專案,接著來介紹專案底下默默地成員們 

app資料夾:放M(模組)V(前端)C(後端)主要地方 

config資料夾:放初始與環境值設定 

db資料夾:sqllite資料庫與migrate(支架) 

test資料夾:測試程式

Gemfile檔案:安裝套裝版本設定,關於>= 2.3.0是大於等於2.3.0版本就更新,~>2.3.0是2.3系列小版本更新但2.4以上大版本不更新

瞭解這些成員後,要開始建立支架(設定存在db/migrate/XXXX.rb)

rails generate scaffold 表格名稱 欄位1:欄位1屬性 欄位2:欄位2屬性...  

關於支架有四點注意:

1.欄位屬性如果沒設定會默認String

2.不需創id:integer,Rails會自動生成

3.不需創created_at:date,Rails會自動生成

4.不需創updated_at:date,Rails會自動生成

因為支架只是想像(俗稱沒存檔),想放進資料倉庫就用這指令(設定存在db/schema.rb或db/development.sqllite3...其他資料庫)

rake db:migrate

最後執行Rails要開啟伺服器

rails server -p $PORT -b $IP

如果要中斷Ctrl+C


DIY之1(示範CoC命名規則重要性)


前面用Scaffold支架做出MVC,連CRUD都自動弄好,但是會造成你永遠搞不清楚Rails背後運作原理。

因此這回不用Scaffold,通通自己來做做看和除錯誤!

老慣例創個新專案並切到資料夾下(對不起個人英文單字很差)

rails new fishstore

cd fishstore/

接著來認識控制老大--"routes.rb" (config/routes.rb)

他是控管瀏覽器的網址列,例如預設首頁或某資料夾下某網頁

預設首頁範例

Rails.application.routes.draw do
    root 'firstpage#index' #root 'controller name#def name' 
end

格式為

root '控制名稱#方法名稱'   <<小寫英文開頭

透過另一台終端機掛著rails server -p $PORT -b $IP,發生錯誤

uninitialized constant FirstpageController  (就是找不到firstpage_controller.rb)

因為在app/controllers下是沒有這玩兒,於是我們需生出來

rails generate controller firstpage

然後再去執行看看又出錯

The action 'index' could not be found for FirstpageController (你說的index方法沒有寫啊)

雖然幫我們創C的檔案和V資料夾,可是Rails不會幫我們生方法,所以要寫程式了。(app/controllers/firstpage_controller.rb)

class FirstpageController < ApplicationController
      def index
      end
end

接著跑去執行,想當然又錯

Missing template firstpage/index, application/index with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:erb, :builder, :raw, :ruby, :coffee, :jbuilder]}. Searched in: * "/home/ubuntu/workspace/fishstore/app/views"

(你的Views/firstpage下沒有index網頁啊)

於是我們創個index.html.erb (Views/firstpage/index.html.erb)

注意創V網頁檔格式別忘能翻譯ruby的副檔名erb

XXX.html.erb

執行後終於沒紅字!不過這只有C和V聊天,於是我們再加上M來玩玩。

rails generate model Fish  <<大寫英文開頭

接著在migrate發現新的支架rb檔(db/migrate/XXX_create_fish.rb)

class CreateFish < ActiveRecord::Migration
    def change
       create_table :fish do |t|

       t.timestamps
       end
   end
end

新增一些欄位與屬性

t.string :fish_name
t.string :fish_type
t.integer:fish_price
t.boolean:is_onsale

如果這時去執行會有這錯誤

Migrations are pending. To resolve this issue, run: bin/rake db:migrate RAILS_ENV=development (沒有Schema.rb)

用rake db:migrate確定建表格就沒此紅字,我們可以常用rake db:migrate:status知道目前migrate的狀態


前面花大半時間生出MVC,可是沒生出CRUD,接下來就繼續努力

回到老大---routes.rb,增加http動作get指定新路徑

Rails.application.routes.draw do
    root 'firstpage#index' #root 'controller name#def name'
    get '/new', to: 'firstpage#new' #get 'path', to: 'controller name#def name'
end

格式為

get '路徑',to: '控制名#方法名'

關於路徑怎麼知道,可以用rake routes觀察到firstpage#new往前看要GET是/new,firstpage#index往前看要GET是/

Prefix Verb URI Pattern Controller#Action
root GET / firstpage#index
new GET /new(.:format) firstpage#new

接著views/firstpage下創個new.html.erb,並隨意打個內容

have flowers  <<單純測試有跑到這頁

把index.html.erb增加連結

<a href="/new">add fish</a>   <<適合外連
<%= link_to "add fish", new_path %>  <<適合內連,格式為<%= link_to "連結名稱", 路徑_path %>

至於我們在routes有說有def new,當然要改firstpage_controller.rb

class FirstpageController < ApplicationController
    def index
    end
    def new
    end
end

最後來執行發現"沒問題",但是如果我們要CRUD,必須在routes.rb寫路徑共8次,真是不夠DRY!((喂

因此Rails增加新成員,用"resources:XXXs"方法自動獲得8個路徑

根據Rails慣例resources的名稱必須複數,於是又根據我爛英文取個fishs (正確是:fishes)

Rails.application.routes.draw do
    resources :fishs #auto to have eight urls,path name have add "s" ex:fish"s"    <<注意這裡因為英文單字錯誤導致後續連環錯
    root 'firstpage#index' #root 'controller name#def name'
end

接著如果你去執行會發現錯誤

Showing /home/ubuntu/workspace/fishstore/app/views/firstpage/index.html.erb where line #2 raised:

undefined local variable or method `new_path' for #<#<Class:0x0000000497e600>:0x0000000497d228>

Extracted source (around line #2):

1
2          
 
<a href="/new">add fish</a>
<%= link_to "add fish", new_path %>

 

喔,因為我們路徑進化了,用rake routes查

Prefix Verb URI Pattern Controller#Action
fishs GET /fishs(.:format) fishs#index
POST /fishs(.:format) fishs#create
new_fish GET /fishs/new(.:format) fishs#new
edit_fish GET /fishs/:id/edit(.:format) fishs#edit
fish GET /fishs/:id(.:format) fishs#show
PATCH /fishs/:id(.:format) fishs#update
PUT /fishs/:id(.:format) fishs#update
DELETE /fishs/:id(.:format) fishs#destroy
root GET / firstpage#index   <<不是自動出8個路徑之一,而是一開始我們早設好的預設首頁

沒錯我們的new_path進化成new_fish_path,真是酷阿!不過別開心太早,因為點下連結後又出現紅字

uninitialized constant FishsController (沒這fishs_controller.rb)

由此可得知當resources一出,前面C啊V啊又要生出一遍

rails g controller fishs

在views/fishs/index,new....創新網頁檔

然後在這時我竟然有點錯亂,有兩個index到底是會怎麼繞?

以下是用Cloud9網頁說明網址差別,也證明routes是真老大!

https://rails-maro150cm.c9.io/    <<root 因此是firstpage家index.html.erb

https://rails-maro150cm.c9.io/fishs  <<resources 因此是fishs家index.html.erb

好像只有我這白癡忘記看路徑

由於我們用resources把firstpage的new GET權利搶走(?),因此要出現需要用get

Rails.application.routes.draw do
    resources :fishs #auto to have eight urls,path name have add "s" ex:fish"s"
    root 'firstpage#index' #root 'controller name#def name'
    get '/new', to: 'firstpage#new' #get 'path', to: 'controller name#def name'
end


搞好路徑後,開始寫一段新增

fishs_controller.rb

class FishsController < ApplicationController
     def index
        @fishs=Fish.all    <<主頁顯示全部資料
     end
     def new
        @fish=Fish.new    <<新增一筆資料
     end
     def create
        @fish=Fish.new(fish_params)   <<新增時會傳入fish_params方法,可以防止駭客多傳參數
           if @fish.save       <<當@fish存入時
               redirect_to fishs_path    <<自動轉回index.html.erb
           else
               render :new     <<給予new.html.erb
           end
         end
     private    <<避免被人知道參數有幾個而宣告private
         def fish_params
            params.require(:fish).permit(:fish_name,:fish_type,:fish_price)   <<根據permit要求僅能傳入這三個參數
         end
end

fishs/index.html.erb

<h1>fish list</h1>
<ul>
   <% @fishs.each do |fish| %>   <<把@fishs全部魚資料分別顯示出來
       <li><%= fish %></li>     <<顯示預存Prc
       <li><%= fish.fish_name %></li>   <<顯示魚姓名欄位值
   <% end %>
</ul>
<a href="/fishs/new">add fish</a>
<%= link_to "add fish", new_fish_path %>

fishs/new.html,erb

<h2>fish list</h2>
<%= form_for(@fish) do |f| %>  <<表單模式,資料與@fish傳過來形式對照
<%= f.label :fish_name,'Name' %>
<%= f.text_field :fish_name %>
<%= f.label :fish_type,'Type' %>
<%= f.text_field :fish_type %>
<%= f.label :fish_price,'Price' %>
<%= f.number_field :fish_price %>
<%= f.submit %>
<% end %>
<%= link_to 'Back', fishs_path %>

 接著竟想不到執行出錯

Showing /home/ubuntu/workspace/fishstore/app/views/fishs/new.html.erb where line #2 raised:

undefined method `fish_index_path' for #<#<Class:0x007f92382e1e50>:0x007f923806d6d8>

Extracted source (around line #2):

1
2
3
4
5          
 
<h2>fish list</h2>
<%= form_for(@fish) do |f| %>
<%= f.label :fish_name,'Name' %>
<%= f.text_field :fish_name %>
<%= f.label :fish_type,'Type' %>

 

說實話這段我卡超久,明明book就OK,跳到fish就不OK

上網找來找去發現Rails有"可數"慣例,簡單來說因為我model fish,自動生出table fish,但最主要是resource fishs導致@fish錯誤 (下次我會乖乖上Google查英文)

所以Rails命名超級重要,不然會狂砍專案重練

1.resources :可數  (例如剛剛取fishs應該要改成fishes)

2.model:單數   (例如剛剛取fish應該要改成fishe)

3.create_table :可數   (例如剛剛取fish應該要改成fishes)

還有"千萬別刪"migrate檔,會造成rake db:migrate出現錯誤

XX_index_path錯誤參考網址:http://stackoverflow.com/questions/23819312/rails-undefined-method-user-index-path

最後來執行網頁發現可以增加與看結果

底下主標題被痞客轉型變字超小,請不要在意他

https://rails-maro150cm.c9.io/fishes

fish list

  • #<Fish:0x00000006142b10>
  • fish1
  • #<Fish:0x000000061424d0>
  • fish2

add fish add fish

後來詢問專家有一個方法,可以解救我這連fish都以為可數的英文白癡

rails console  <<進入Rails命令機狀態

"fish".pluralize  <<詢問Rails對此單字複數是?

=> "fish"    <<果然我白癡,不~~~~~

如果要離開Ctrl+D


ERB Embedded Ruby


簡單來說就是在網頁執行Ruby,以下有三種,不過常用都上面兩個

<% %> 只執行程式碼,不顯示出來

<%= %> 不僅執行程式碼,還需要顯示出來

<%# %> 單純Ruby註解,給自己看的


資料表關連


我們都知資料表關連有三種,分別為一對多、多對一、多對多,至於要打造關連是在Model內設定,說真的挺讓我意外!

首先我們先來創兩個Model,一個是產品另一個是商店,關係先設定一個商店有N個產品

rails g model Product

rails g model Store

接著來到db/migrate下找到我們兩個剛生出支架rb檔,增加資料表的欄位和屬性

create_table :products do |t|
t.string :title
t.text :description
t.integer :price
t.integer :store_id

create_table :stores do |t|   
t.string :name

下個rake db:migrate成功加入Schema內一員後,開始增加Model內容

class Product < ActiveRecord::Base
   belongs_to :store #belongs_to :PK (唯一值欄位)
end

class Store < ActiveRecord::Base
   has_many :products #has_many :FK(重複值欄位)
end

透過Rails Console來確認是否成功

rails c

Product.create(title:"ASP.NET 1",price:100)  

訊息說他自動產生以下SQL語法

(0.2ms) begin transaction
SQL (1.0ms) INSERT INTO "products" ("title", "price", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["title", "ASP.NET 1"], ["price", 100], ["created_at", "2015-03-16 13:57:23.843995"], ["updated_at", "2015-03-16 13:57:23.843995"]]
(15.3ms) commit transaction
=> #<Product id: 1, title: "ASP.NET 1", description: nil, price: 100, store_id: nil, created_at: "2015-03-16 13:57:23", updated_at: "2015-03-16 13:57:23">

Store.create(name:"ASP.NET Home")

訊息說他自動產生以下SQL語法

(0.1ms) begin transaction
SQL (1.2ms) INSERT INTO "stores" ("name", "created_at", "updated_at") VALUES (?, ?, ?) [["name", "ASP.NET Home"], ["created_at", "2015-03-16 13:58:54.702741"], ["updated_at", "2015-03-16 13:58:54.702741"]]
(18.9ms) commit transaction
=> #<Store id: 1, name: "ASP.NET Home", created_at: "2015-03-16 13:58:54", updated_at: "2015-03-16 13:58:54">

這時來看看Product內有多少資料...

Product.all

Product Load (1.0ms) SELECT "products".* FROM "products"
=> #<ActiveRecord::Relation [#<Product id: 1, title: "ASP.NET 1", description: nil, price: 100, store_id: nil, created_at: "2015-03-16 13:57:23", updated_at: "2015-03-16 13:57:23">, #<Product id: 2, title: "ASP.NET 2", description: nil, price: 200, store_id: nil, created_at: "2015-03-16 13:58:17", updated_at: "2015-03-16 13:58:17">]>

要實驗下方需再生一兩個產品,個人已經先做好了

p1=Product.find(1)   <<id:1的Product用p1實體

p2=Product.find(2)

s1=Store.find(1)

p1.store=s1    <<把Product store_id 1跟Store id 1連一起

p2.store=s1

s1.products   <<列出Store目前有連一起產品的資料

 => #<ActiveRecord::Associations::CollectionProxy []> 

@@看不懂這怪玩兒,於是需要安裝新套件來幫助我們-----"Hirb"

先Ctrl+D跳出Rails console,把Gemfile檔案打開,下方增加....

gem 'hirb-unicode'

接著下"bundle update"命令,讓Rails專案重新讀取Gemfile檔並安裝

跑完之後繼續把上面步驟重做一遍(因為Console只是測試),記得要下'Hirb.enable'命令才能打開功能

Product Load (1.1ms) SELECT "products".* FROM "products" WHERE "products"."store_id" = ? [["store_id", 1]]
+--+-------------+-------------+-------+----------+--------------+---------------+
| id | title | description | price | store_id | created_at | updated_at |
+--+-------------+-------------+-------+----------+--------------+---------------+
| 1 | ASP.NET 1 | | 100 | 1 | 2015-03-16 13:57:23 UTC | 2015-03-16 14:04:41 UTC |
| 2 | ASP.NET 2 | | 200 | 1 | 2015-03-16 13:58:17 UTC | 2015-03-16 14:04:44 UTC |
+--+-------------+-------------+-------+----------+--------------+---------------+
2 rows in set

排的好整齊,看起來真好!T^T

既然一對多多對一能用belongs_to和has_many做出,那多對多也不用說!

根據資料庫原則,關於多對多是增小三...不對...是一個總管理表,因此是連到Product,Store的共兩個FK欄位,最重要此資料表PK(Rails上會自動生id,無視)

增資料表=要先增Model

rails g model StoreProductManager

create_table :store_product_managers do |t|
t.belongs_to :product # t.integer :product_id FK
t.belongs_to :store # t.integer :store_id FK

t.belongs_to意思這是來自一個資料表id,而我要跟隨他,注意後面欄位名稱是單數(慣例很重要,不聽話就砍專案)

關於前面我們在Product多的store_id要刪除,因此要多migrate寫方法改寫Schema

rails g migration delete_store_id_fromProduct

class DeleteStoreIdFromProduct < ActiveRecord::Migration
     def change
         remove_column:products,:store_id
     end
end

rake db:migrate 後會發現

== 20150316144129 CreateStoreProductManagers: migrating =======================
-- create_table(:store_product_managers)
-> 0.0038s
== 20150316144129 CreateStoreProductManagers: migrated (0.0041s) ==============

== 20150316145223 DeleteStoreIdFromProduct: migrating =========================
-- remove_column(:products, :store_id)                                                      <<有從product刪除store_id欄位
-> 0.0098s
== 20150316145223 DeleteStoreIdFromProduct: migrated (0.0101s) ================

當然在Model上也是要改一些

class StoreProductManager < ActiveRecord::Base
     belongs_to :product
     belongs_to :store
end

class Product < ActiveRecord::Base
     has_many :store_product_managers
     has_many :stores,:through => :store_product_managers #don't through store_product_managers   <<運作中不經過store_product_managers
end

class Store < ActiveRecord::Base
     has_many :store_product_managers
     has_many :products,:through => :store_product_managers #don't through store_product_managers   <<運作中不經過store_product_managers
end

接著回到Rails Console,達成多對多又要增加資料

Product.create(title:"Ruby",price:500)

Store.create(name:"Computer Home")

把5個通通生出實體後,開始連結

s1.products=[p1,p2]

s2.products=[p2,p3]

把Hirb.enable開啟後

s1.products
+----+-----------+-------------+-------+------------------+------------------+
| id | title | description | price | created_at | updated_at |
+----+-----------+-------------+-------+------------------+------------------+
| 1 | ASP.NET 1 | | 100 | 2015-03-16 13:57:23 UTC | 2015-03-16 14:04:41 UTC |
| 2 | ASP.NET 2 | | 200 | 2015-03-16 13:58:17 UTC | 2015-03-16 14:04:44 UTC |
+----+-----------+-------------+-------+------------------+------------------+
2 rows in set

s2.products

+----+-----------+-------------+-------+-----------------+-------------------+
| id | title | description | price | created_at | updated_at |
+----+-----------+-------------+-------+-----------------+-------------------+
| 2 | ASP.NET 2 | | 200 | 2015-03-16 13:58:17 UTC | 2015-03-16 14:04:44 UTC |
| 3 | Ruby | | 500 | 2015-03-16 15:21:38 UTC | 2015-03-16 15:21:38 UTC |
+----+-----------+-------------+-------+-----------------+-------------------+
2 rows in set

p2.stores

+----+---------------+-------------------------+-------------------------+
| id | name | created_at | updated_at |
+----+---------------+-------------------------+-------------------------+
| 1 | ASP.NET Home | 2015-03-16 13:58:54 UTC | 2015-03-16 13:58:54 UTC |
| 2 | Computer Home | 2015-03-16 15:20:56 UTC | 2015-03-16 15:20:56 UTC |
+----+---------------+-------------------------+-------------------------+ 


更改Schema.rb內容


假如今天顧客突然要改欄位名稱,或是屬性換掉,還是自己手殘打錯,千萬不可以直接改或刪掉!(其實是為了要知道偷改過程...)

改欄名

rails g migration rename_column

class RenameColumn < ActiveRecord::Migration

  def change

    rename_column:books,:title,:name  #rename_column:資料表,:原欄名,:新欄名

  end

end

改欄屬

rails g migration change_column

class ChangeColumn < ActiveRecord::Migration

  def change

     change_column:books,:title,:text  #change_column:資料表,:欄名,:新屬性

  end

end

增加欄位

class AddStoreId < ActiveRecord::Migration
   def change
      add_column:products,:store_id,:integer  #add_column:資料表,:欄名,:屬性
   end
end

刪除欄位

class DeleteStoreId < ActiveRecord::Migration
   def change
      remove_column:products,:store_id   #remove_column:資料表,:欄名
   end
end 


快速美化頁面與表格分頁


既然Rails是以幾分鐘生出CRUD(增讀改刪)聞名,那美化部分當然就用bootstrap-sass外掛套件(就是bootstrap加上sass)

首先在Gemfile加入並存檔

gem 'bootstrap-sass', '~> 3.3.4' #bundle update

然後下bundle update指令讓他重讀Gemfile檔,記得連正在跑的伺服器也要重開

等他安裝完成後,我們可以在css和js檔載入

stylesheets/application.scss    <<副檔名改為scss

/*
*= require_tree .
*= require_self
*/
@import "bootstrap-sprockets";
@import "bootstrap";

javascripts/application.js

//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require_tree .
//= bootstrap-sprockets

使用bootstrap-sass參考網址:https://github.com/twbs/bootstrap-sass

接著來說說關於Rails的網頁模組,簡單來說就是以前學過的框架

layouts/application.html.erb

<!DOCTYPE html>
<html>
<head>
    <title>ModelNewbook</title>    <<每頁都會顯示標題
    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
    <%= csrf_meta_tags %>
</head>
<body>

<div class="container">     <<套用bootstrap css
      <%= yield %>              <<共同區塊,例如載入index內容,new內容...
</div>
</body>
</html>

其實在ASP.NET也有MasterPage(T^T當初不知這個做了好幾十個頁面),但是Rails恐怖在於連表單都能模組化,真是貫徹DRY精神!(讓我們不要放棄Ruby and Rails)

_form.html.erb   <<表單頁面

edit.html.erb   <<編輯頁面

<h1>Editing Namecard</h1>

<%= render 'form' %>   <<載入表單頁面

<%= link_to 'Show', @namecard %> | 
<%= link_to 'Back', namecards_path %>

最後我們要顯示分頁按鈕,感謝Kaminari大大願意共享,讓腦細胞少壞幾個

gem 'kaminari' #bundle update         <<分頁功能

gem 'bootstrap-kaminari-views' #bundle update      <<分頁美化功能

使用分頁在Controller檔

namecards_controller.rb

@namecards = Namecard.page(params[:page]).per(3)

當然在網頁檔也要增加

<%= paginate @namecards %>    <<單純分頁按鈕,沒套用bootstrap 

<%= paginate @namecards,:theme => 'twitter-bootstrap-3' %>  <<套用bootstrap

使用分頁參考網址:https://github.com/amatsuda/kaminari

分頁美化參考網址:https://github.com/matenia/bootstrap-kaminari-views

來看看最後網頁美化結果

demo


form形式的dropdownlist


我們知道下拉式清單有兩種,差別跟連不連資料表有關

第1種不用連資料表,以下為範例

<select name="selection">                  <<Html input需要用name="selection",不然會無法傳遞參數
<option value="1">我是選項1</option>
<option value="2">我是選項2</option>
</select>

第2種要連資料表,以下為範例

<%= f.collection_select(:event_id,Event.all,:id,:event_title) %>

<%= f.collection_select(FK欄位,運作模組,傳入值,顯示值) %>

f是前頭<%= form_for(@invite) do |f| %>

FK欄位:event_id是在invites資料表中,身分是FK欄位連接events資料表

運作模組:Event.all是指透過模組Event顯示全部

傳入值:id是指events資料表內鍵id

顯示值:event_title是指events資料表


限制輸入框


這些限制輸入都寫在model中,而不是在view

首先最常用就是不准NULL、空白

validates :person_email, presence: { message: '請填寫 Email' }

再來用regular expression限制輸入格式

validates :person_email, presence: { message: '請填寫 Email' },
format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, message:'Email 格式有誤' }

最後是強迫使用者不准輸入同個值

validates :person_email, presence: { message: '請填寫 Email' },
format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, message:'Email 格式有誤' },
uniqueness: { scope: [:event_id], message: '這個 Email 已經填寫過了' }


Validates Format驗證格式


某天我好奇把以前Asp.Net的規則運算式拿出來測試看看,果然Rails跑出錯誤

/home/ubuntu/workspace/10236010moneydetails/app/models/cost.rb:4: syntax error, unexpected $undefined, expecting ']' ... format: {with: ([\u4e00-\u9fa5]{0,}|[A-Za-z]{0,... ... ^ /home/ubuntu/workspace/10236010moneydetails/app/models/cost.rb:4: syntax error, unexpected { arg, expecting ')' ... ([\u4e00-\u9fa5]{0,}|[A-Za-z]{0,}|[0-9]{0,})+$ ,message:'... ... ^ /home/ubuntu/workspace/10236010moneydetails/app/models/cost.rb:4: syntax error, unexpected { arg, expecting ')' ...\u9fa5]{0,}|[A-Za-z]{0,}|[0-9]{0,})+$ ,message:'細項格式... ... ^ /home/ubuntu/workspace/10236010moneydetails/app/models/cost.rb:4: `$ ' is not allowed as a global variable name

痾這一長串錯誤在搞啥,其實他意思是說你不符合Rails驗證規則

原來在format有規定的,以下為格式

/\A中間為規則運算式\z/

最前"/"和最尾"/"就類似Asp.Net的" "來包覆規則,至於\A和\z是為開頭到結尾

Rails驗證規則參考網址:http://apidock.com/rails/ActiveModel/Validations/HelperMethods/validates_format_of

規則運算式簡介參考網址:https://support.google.com/analytics/answer/1034324?hl=zh-Hant


強大Try


說句實話之前碰其他語言,本人根本沒寫過Try,因為個人覺得要控制輸入者乖乖打輸入框

當然不是只有輸入框才要Try,其實提示輸入者你做錯我是常寫的,說這麼多反正要講Rails Try

以下為抓來範例的表格

<tbody>

    <% @invites.each do |invite| %>

      <tr>

        <td><%= invite.person_name %></td>

        <td><%= invite.person_email %></td>

        <td><%= invite.event.event_title %></td>

      </tr>

    <% end %>

  </tbody>

如果這時我把events資料表一筆會影響這顯示的資料刪掉,相信你會看到錯誤

Showing /home/ubuntu/workspace/hasevent/app/views/invites/index.html.erb where line #24 raised:

undefined method `event_title' for nil:NilClass
Extracted source (around line #24):
22
23
24
25
26
27         
 
<td><%= invite.person_name %></td>
<td><%= invite.person_email %></td>
<td><%= invite.event.event_title %></td>
</tr>
<% end %>
</tbody>

 

因為我把events資料通通刪光光,所以當他跑到invite的親家event找event_title發現是Null,毫不猶豫直接送你錯誤!

這時挖咧又要寫大串try,如果每個都要這樣玩,那一點都不DRY!

於是Rails送給我們-----try方法

<td><%= invite.event.try(:event_title) %></td>

接著可以看到Invite Event那欄的值是空白

Person Name Person Email Invite Event  
Happy have@yahoo.com  

相對路徑的圖片


如果圖片要用相對路徑的話,在Rails上會超級大不同!

當初在套用時一直出不了圖,想說奇怪我寫那麼久相對路徑(p.s. 是在寫Jsp或Asp.Net網頁)怎麼會有問題!

後來上網爬個文章,才知Rails img套用相對路徑會很麻煩,首先是.....

1.一定要app/assets/images底下

2.<img src="/assets/logo.png" alt="Logo">

難怪我看一些網頁他們路徑是用絕對路徑,說實話這時才知原因

img_tag參考網址:http://blog.csdn.net/wanghaoming100/article/details/9141863


製作後台路徑


如果要做後台路徑,首先在routes.rb透過namespace方法製作出來

routes.rb

namespace :back do
    resources :blogs
end

然後在終端機用rake routes可以發現他路徑改變

back_blogs GET    /back/blogs(.:format)          back/blogs#index

如果你曾有用其他程式語言寫過,應該知道路徑前面back是資料夾

因此在終端機下新增controller指令時,可以在很多地方發現有新大陸

rails g controller back/blogs

1.controller路徑改變與class有Back

路徑為controllers/back/blogs_controller.rb

class Back::BlogsController < ApplicationController

2.helpers同controller狀況

3.views路徑改變

路徑為views/back/blogs

最後在views頁面需要改變有兩點

1.path改變,其實在rake routes中可以看出他變長

new_back_blog GET    /back/blogs/new(.:format)      back/blogs#new

2.form for改變,需要用陣列包裝並呼叫back這個namespace

<%= form_for [:back,@blog] do |f| %>

注意:Model不用照路徑走,依舊是Blog


DIY之2(示範寫出CRUD)


一直嘗試不用Scaffold寫出,但是卡住destory的路徑很久,後來才搞出來

根據觀察Scaffold可以知道運作流程

C--新增(其實沒show轉到首頁也OK,但是常理要給人家看增加資料)

在controller需要

before_action :set_blog, only: [:show]

def new
    @blog=Blog.new
end

def create
    @blog = Blog.new(blog_params)
    if @blog.save
         render :show
    else
         render :new
    end
end

private

def set_blog
     @blog = Blog.find(params[:id])
end

def blog_params
   params.require(:blog).permit(:title, :author)
end

在views上需要new.html.erb

在views上需要show.html.erb外加內容

<p>
     <strong>Title:</strong>
     <%= @blog.title %>
</p>

<p>
     <strong>Author:</strong>
     <%= @blog.author %>
</p>

R--讀取

在controller需要

before_action :set_blog, only: [:show]

def show
end

private

def set_blog
     @blog = Blog.find(params[:id])
end

在views上需要show.html.erb外加內容

<p>
     <strong>Title:</strong>
     <%= @blog.title %>
</p>

<p>
     <strong>Author:</strong>
     <%= @blog.author %>
</p>

U--更新

在controller需要

before_action :set_blog, only: [:show, :edit, :update]

def edit
end

def update
     if @blog.update(blog_params)
          render :show
     else
          render :edit
     end
end

private

def set_blog
     @blog = Blog.find(params[:id])
end

在views上需要edit.html.erb

D--刪除

在controller需要

before_action :set_blog, only: [:show, :edit, :update,:destroy]

def destroy
     @blog.destroy
     redirect_to back_blogs_path
end

private

def set_blog
     @blog = Blog.find(params[:id])
end

在index.html.erb

<%= link_to 'Delete',back_blog_path(blog), method: :delete, data: { confirm: 'Are you OK?' } %>

 

相信大家也看出來,根據邏輯來說讀取,更新,刪除是要有id的

所以我們才需要

private

def set_blog
     @blog = Blog.find(params[:id])
end

那在什麼時候開始查詢,當然是最前,而且僅在讀取,編輯,更新,刪除這狀態下路徑可用

before_action :set_blog, only: [:show, :edit, :update,:destroy]


不同頁面傳值問題


最近做"月帳表"有點小挫折,原因在於觀念上

例如想說在controller寫個值

def show
   @re_mon=@cost.cost_date.strftime("%m").to_i-1
end

想說這樣可以在index.html上JQuery取到值  <<異想天開

當然結果是沒有的,因為他僅能在show頁面取到,就算在index中有同名變數,那只會被當成同名不同人

 

宣告變數僅在同def的頁面使用,不同頁面時需要用網址傳遞

例如說我們常常看到網址上有http://www.XXXX.XXX?a=XXX&b=XXX

因此是在轉址時做的....

redirect_to action: :index, re_mon: @cost.cost_date.strftime("%m").to_i-1

redirect_to是轉址

action是想連結過去的網頁

re_mon是自己增加標籤,也是附加在網址上參數

再來這串程式碼放show其實不恰當,因為是在create和update中才會想要立即在index看到

最後是在controller index中接收網址參數

@re_mon = params[:re_mon]

至於@re_mon是在JQuery中

<script>
    $(document).ready(function(){
         $('.nav li:eq(<%= @re_mon %>) a').tab('show')
    });
</script>

參數參考網址:http://rails.ruby.tw/action_controller_overview.html

redirect_to參考網址:http://apidock.com/rails/ActionController/Base/redirect_to


datetime轉換格式


如果要把datetime轉成不同型態,可以用.strftime

屬性datetime欄位或變數.strftime("XXXX")

舉個小例子

@date.strftime("%Y-%m-%d")

這樣他就會轉成2015-04-16

根據doc我列出常用幾類:

%Y   西元年,4位數

%m  月份,2位數

%B  月份,英文字

%d  日號,2位數

%j   一年中天數,1~366  <<疑不是365天?

%H 24小時制,2位數

%I  12小時制,2位數

%P 顯示"am"或"pm"

%M 分鐘,2位數

%S 秒數,2位數

%A 星期,英文字

strftime參考網址:http://ruby-doc.org/core-2.2.1/Time.html#method-i-strftime


render載入


當用scaffold架起簡單網站時,可以發現render是載入意思

例如一: new,edit的html有寫到載入form

<%= render 'form' %> 

form是指"_form.html.erb",前面底線檔名跟CoC有關

例如二: controller中寫到載入某標籤

render :new

根據標籤會載入new.html.erb這頁面

 

當然render不止這些,後面可以再加料...

1.partial(部分樣板),意思是抽出想要網頁放入這裡

其實我們很常用,像<%= render 'form' %>他的長版寫法是<%= render partial: 'form'%>

2.locals(當地參數),意思是設個參數與值給即將載入網頁

在原網頁寫<%= render partial: 'form',locals:{name:'apple'} %>

在即將載入網頁寫<%= name %>

3.collection跟as(集合與個體),意思是陣列與陣列內成員

<%= render partial: 'form',collection:@books,as: :book %>

跟<% @blogs.each do |blog| %>同樣,把寫在一個頁面分割成二個頁面,達到能共用的愉悅感


flash與session


在支架controller有寫這段

format.html { redirect_to :index, notice: 'Cost was successfully created.' }

這樣寫法挺糟糕,因為在index網址上出現?notice=XXXXXX,中間傳遞參數直接給眾人看,可能會造成資安危機

因此我們改用flash解決問題

1.在轉址前傳遞參數

controller

flash[:notice]="Cost was successfully created."

format.html { redirect_to :index }

2.在頁面透過flash接收

view

<= flash[:notice]>

 

既然flash這麼方便,那是不是能當session呢?當然是不行的!

其實flash運作方式跟session大同小異,只差flash存活一瞬間,接收成功後就丟棄

要存活長時間還是session,使用步驟跟flash一樣

1.在轉址前傳遞參數

controller

session[:myid]="XXXX"

format.html { redirect_to :index }

2.在頁面透過flash接收

view

<= session[:myid]>


指定layout版面


1.Controller最上面,指定全部Views套用此layout

layout "檔名"

當然也可僅限在特定action

layout "檔名", only: [:index,:show...]  

2.Render時,針對一些特殊狀態改變版面

render layout: '檔名',action: :index


routes中as用處


在render中有出現collection跟as,意思是集合中一個成員

但在routes也能用as,不過意思是Prefix取名

get 'back/blogs',to: 'back/blogs#index',as: :get_index

用rake routes可以發現

Prefix        Verb   URI Pattern                           Controller#Action

get_index GET    /back/blogs/index(.:format)    blogs#index

為何要幫一個絕對路徑取名?

當然是假如有一天路徑突然改名稱,那不就每個有寫到 link_to 絕對路徑要改!

所以我們要用as,讓網頁寫法不再絕對路徑,而是相對路徑!

<%= link_to "get",get_index_path %>


Helper


看到Helper內容空空,還以為他沒做什麼事,其實你用的很多是靠小幫手(Helper)

例如:

link_to "link name",path   <<UrlHelper

image_tag "file name",alt: "img",size: '300x300'  <<AssetTagHelper

tag :br    <<TagHelper 

<%= content_tag :p,class: 'text' do%>     <<TagHelper 

  <%=book.title%>

<% end %> 

<%= form_tag url_for(action: :create) do%>    <<FormTagHelper

<% end %>

接著我們要寫一段Helper,達到寫程式簡化

def link_img(img_url,to_url)

    link_to image_tag(img_url),to_url

end

在網頁就能透過Helper呼叫link_img方法

link_img("http://...",books_path)


多國語系


跟android思想同樣,希望語系可以切換

首先語系在config/locales/en.yml    <<rails預設為en

en:

  activerecord:

    attributes:     

      blog:                   <<這裡blog是model blog檔名

        hello: 作者

        hello2: 玩家

接著在網頁上用human_attribute_name指定yml中標籤 

<%= Blog.human_attribute_name :hello%>
<%= Blog.human_attribute_name :hello2%>


寄Email


1.Gmail篇(結果GG)

在Rails有Mailer可以設定Email,接著搭配SMTP Server就行!(不過在Gmail很艱辛...)

首先在終端機下新增指令

rails g mailer mail    

P.S. 其實要取"專門為什麼事而做"的變數名稱,不應該取"mail"這不明不白名稱,個人是因為"測試寄信"關係才取

如果很熟MVC的話,就算跳到Mailer也不用怕,以下舉application和樣板mailer寫法可以發現運作一樣!

mailers/application_mailer.rb

class ApplicationMailer < ActionMailer::Base

  default from: "haveagoodday8496@gmail.com"   <<預設寄送Email地址

  layout 'mailer'               <<樣板套用mailer

end

接著觀看樣板layouts/mailer.html.erb

<html>

  <body>

    <%= yield %>   <<不用說當然套用views/mail_mailer/XXX.html.erb

  </body>

</html>

既然知道就回到主軸,第二步要撰寫Mailer Controller的方法

mailers/mail_mailer.rb

class MailMailer < ApplicationMailer

    def mail_notify(name,email,note)

        @name=name

        @mail=email

        @note=note

        mail(to: email,from:'haveagoodday8496@gmail.com',subject:'Test Mail')    #send email

    end

end

第三步新增網頁檔並撰寫內容

views/mail_mailer/mail_notify.html.erb

Hello,Hello,Hello,<%= @mail %><br /><br />

your name is <%= @name %> ,and <br />

your note is <%= @note %>

第四步環境設定(根據不同狀態選擇development.rb還production.rb)

以下是個人設定在development環境,利用gmail環境自己寄自己收

 config.action_mailer.raise_delivery_errors = true

 config.action_mailer.perform_deliveries = true

 config.action_mailer.default_url_options = { :host => 'localhost:3000' }

 config.action_mailer.default :charset => "utf-8"

 config.action_mailer.delivery_method = :smtp

 config.action_mailer.smtp_settings = {

      address:'smtp.gmail.com',

      port:587,

      domain:'gmail.com',

      user_name:'login gmail user_name',

      password:'login gmail password',

      authentication:'plain',

      enable_starttls_auto: true

 }

第五步在主要Controller設定寄送機關,以下是當index跳到calc頁面時開啟機關

controllers/mails_controller

class MailsController < ApplicationController

    def index

    end

    def calc

      @name=params[:name] #params is string

      @email=params[:email]

      @note=params[:note]

      #render text:params     

      MailMailer.mail_notify(@name,@email,@note).deliver_now

      #MailMailer.mail_notify(@name,@email,@note).deliver_later

    end

end

最終個人在gmail的收信箱找到,卻是警告信! T^T

Gmail官方宣言:"有個駭客想用你的帳密寄信,我們已經阻止他!"

Gmail詳細資料:"有個來自XXX.XXX.XXX.XXX的IP試圖強行登入,我們已經阻止他!"

可惡別阻止阿娘強入Google,我要成功拉!!!

2.Mandrill篇(結果GG)

其實會要寄信都是大量的,因此直接學如何使用第三方SMTP如Mandrill比較實在!XD

只要更改smtp設定

config.action_mailer.smtp_settings = {

      address:'smtp.mandrillapp.com',

      port:587,

      domain:'XXX.com',

      user_name:'login mandrill email',

      password:'API Key',

      authentication:'plain',

      enable_starttls_auto: true

 }

以上兩篇結論:

原因在於

1.要真的Server! T^T (個人就是窮學生哪來Server)

2.即使丟到heroku app連接像heroku.com domain還是沒辦法! T^T (個人沒錢拿來升級帳戶阿)

Mandrill+Heroku通關方法:https://devcenter.heroku.com/articles/mandrill

個人根據通關方法卡在第一步,於是開始爬文

發問區:http://stackoverflow.com/questions/23319451/heroku-mandrill-addon-mandrill-starter-add-on-for-heroku-installation-failed

根據回答者表示:升級帳戶意思就是叫你拿錢贊助他們!

3.Mandrill Template+Rails Console篇(美中不足)

樣板部分

1

 

2

 

3

程式部分

更改一:gemfile增加內容並bundle install

gem 'mandrill-api',require: "mandrill"

更改二:增加mandrill_client方法,運用message傳送參數給Mandrill Template

mailers/mail_mailer.rb

class MailMailer < ApplicationMailer

    def mandrill_client

        @mandrill_client ||= Mandrill::API.new Mandrill_Api_Key

    end

   

    def mail_notify(name,email,note)

        #@name=name

        #@mail=email

        #@note=note

        #mail(to: email,from:'haveagoodday8496@gmail.com',subject:'Test Mail')

        #use Mandrill Template Send Email

        template_name = "test-mail"

        template_content = []

        message = {

            to: [{email: email}],

            subject: "This is May send to you,her email is #{email}",

            auto_text: true,

            #this is have to use to everyone.

            global_merge_vars: [

                {

                    name: "CompanyName",

                    content: "Nesting Doll Emporium"

                },

                {

                    name:"HELLO",

                    content: name

                },

                {

                    name:"EMAIL",

                    content: email

                },

                {

                    name:"NOTE",

                    content: note

                }

            ]

        }

        mandrill_client.messages.send_template template_name,template_content,message

    end

end

更改三:環境設定

個人是development.rb

Mandrill_Api_Key = "your API Key"

 

Rails.application.configure do

  ....

  ....

 config.action_mailer.raise_delivery_errors = true

 config.action_mailer.perform_deliveries = true

 config.action_mailer.default_url_options = { :host => 'localhost:3000' }

 config.action_mailer.default :charset => "utf-8"

 config.action_mailer.delivery_method = :smtp

 config.action_mailer.smtp_settings = {

      address:'smtp.mandrillapp.com',

      port:587,

      domain:'heroku.com',

      user_name:'haveagoodday8496@gmail.com',

      password: Mandrill_Api_Key,

      authentication:'plain',

      enable_starttls_auto: true

 }

end

執行結果

在終端機下指令

$ rails c

> MailMailer.mail_notify("yoyo","haveagoodday8496@gmail.com","Hello")


現在寄 VS 待會寄


中途大家應該有發現...

MailMailer.mail_notify(@name,@email,@note).deliver_now

跟MailMailer.mail_notify(@name,@email,@note).deliver_later

.deliver_now現在寄:發個問題等人回答,等確定回答後才會跳到下個頁面,會讓使用者不耐煩,以為伺服器很差!((喂

.deliver_later待會寄:發個問題等人回答,不用等有回答直接跳到下個頁面,會讓使用者不會煩,而且會覺得順暢!((疑

對使用者來說,看到結果頁面比收到信還重要!!((遭毆

簡單來說待會寄是個優勢,因此有兩個提供待放服務...

1.Redis => 串接RAM  需要架Redis Server ,這效能好

2.delayed-Job => 串接硬碟 需要gem套件

個人(窮學生)直接考慮用delayed-job!

delayed-job GitHub(附送使用說明):https://github.com/collectiveidea/delayed_job

個人是用在Active Record,因此gemfile檔是增加這行

gem 'delayed_job_active_record'

如果要讓他像背景程式一樣一直執行,那要多安裝這個才能

gem "daemons"

接著在終端機下bundle install

完畢後開始創個草稿資料表與存成正式資料表

rails generate delayed_job:active_record
rake db:migrate

最後只要在config/application.rb加入這行就OK!

module Mail
   class Application < Rails::Application
        ...
        ...
        config.active_job.queue_adapter = :delayed_job    <<請把他放在這裡
   end
end


SQL語句


最常看到model.all這種"搜尋一個資料表的全部資料",但不是完全適用一些狀況,以下我列出常用的語句......

其實跟以前學的SQL差不多,只是在Rails可以偷懶一下下

1.where

model.where(["欄名1 = ? and 欄名2 = ? and ...",第一個?對應參數,第二個?對應參數,...])

等同於

Select * From model對應table Where  欄名1=第一個參數 and 欄名2=第二個參數....

2.order

model.order("欄名1 desc,欄名2 asc,...")

等同於

Select * From model對應table Order by 欄名1 desc,欄名2 asc,...

3.select

model.select("欄名1,欄名2,...") 等同於 Select 欄名1,欄名2 From model對應table 

model.select("*") 等同於 Select * From model對應table

model.select("欄名").distinct 等同於 Select DISTINCT 欄名 From model對應table

4.group

model.select("....").group("欄名") 等同於 Select ... From model對應table Group by 欄名

5.having

model.select("....").group("欄名").having(["欄名1 = ? and 欄名2 = ? ...",第一個?參數,第二個?參數...])

等同於

Select ... From model對應table Group by 欄名 Having 欄名1=第一個參數 and  欄名2=第二個參數...

6.joins

model.joins("Left outer join table1 on table1.欄名1=model對應table.欄名1...")    <<也可以使用Right outer join,inner join

等同於

Select model對應table.* From model對應table Left outer join table1 on table1.欄名1=model對應table.欄名1....

model.joins(:table1,...)

等同於

Select model對應table.* From model對應table inner join table1 on table1.欄名1=model對應table.欄名1....

7.組合

舉例1 model.joins("...").where("...").select("...")

舉例2 model.where("...").group("...").having("...")

舉例3 model.where("...").sum("...")

舉例4 model.where("...").count

...等等奇妙組合!!((喂

SQL語法參考網址:http://guides.rubyonrails.org/active_record_querying.html#joining-tables


問題主鍵是Id


第一次對Rails發瘋,因為他的PK直接預設id,不能讀也不能取

好吧其實沒關係,PK給你就給,反正我要的複合鍵RoR並不支援!!((翻桌

不過卻很妙的可以用"Model"解決複合鍵和String問題

model uniqueness限制使用者輸入,以下為範例

validates :欄名1, presence: { message: '請填寫 欄名1' },
uniqueness: { scope: [:欄名2,:欄名3], message: '這個 欄名1,欄名2,欄名3 已經填寫過了' }

用uniqueness and scope[add 1 column,add 2 column,...]達到送出前檢查資料唯一性,而且可以DIY錯誤訊息!!((默默把桌放正

 

arrow
arrow
    全站熱搜

    o迷苓o 發表在 痞客邦 留言(0) 人氣()