2016年11月22日 星期二

Polymorphic Associations 多型關連

狀況:兩個 table 有相同的欄位,將之整合成同一個 table

方法:建一個新的 table 取代原先兩個;在新 table 使用 新table名able_type 這個欄位,來判讀是原先哪一個 table;再用 新table名able_id 判斷其id


範例:
有兩個 model ,post 和 comment,為做其 vote 功能,須各別新增 PostVote 和 CommentVote model。但這兩個mobel的欄位是有重覆,各別是
post table :              vote post_id user_id
comment table :   vote  comment_id user_id

因此新增一個table/model取代這兩個


1, 生出migration 檔
$ rails g migration create_votes
class CreateVotes < ActiveRecord::Migration
  def change
    create_table :votes do |t|
      t.boolean :vote
      t.integer :user_id
      t.string  :voteable_type
      t.integer :voteable_id

      t.timestamps
    end
  end
end

$ rake db:migrate

voteable_typ 和 voteable_id 的功能如上所述

2, 在 model 建立關聯
# + app/models/vote.rb
class Vote < ActiveRecord::Base
  belongs_to :user, foreign_key: :user_id
  belongs_to :voteable, polymorphic: true
end

# app/models/psot.rb
class Post < ActiveRecord::Base
  has_many :votes, as: :voteable
end

class Comment < ActiveRecord::Base
  has_many :votes, as: :voteable
end

3, 去 rails console 測試
post = Post.first
post.votes
Vote
vote = vote.create(vote: true, user_id: 1)
vote
沒問題就表示建立的關聯是對的


多型關連大致是這樣。


有沒有覺得又很像副程式?一種 function 的概念。大概是 Ruby 裡任何東西都是物件,所以可以把資料庫像副程式一樣搞;當然也是因為ORM,資料庫物件化的緣故。

參考:
http://rails.ruby.tw/association_basics.html
台大資工Rails 課程上課筆記

2016年11月20日 星期日

validation 資料驗證

為了確認使用者輸入的資料是我們要的,就要資料驗證

使用方法很簡單,在model裡,對要驗證的欄位這樣做即可,像這樣

# app/models/group.rb
+ validates :title, presence: true
這就是要求 group table 的 title 欄位必須有資料,才能儲存

參考:
Rails 101
其他使用方法見 https://ihower.tw/rails/activerecord-lifecycle.html

Strong Parameters

Strong Parameters 是 Devise 的功能;是用來限制使用者資料的資料輸入

params 是brower 傳向sever 的資料 hash;為了避免欄位被猜中遭hack,

Strong Parameters 用 .permit 方法限制允許的欄位資料通過

那麼怎麼用呢?在 controller 裡設一個 before_action ;def 裡面使用devise_parameter_sanitizer.permit 去決定設定限制的欄位

Rails 102 的範例程式
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  before_action :configuer_permitted_parameters, if: :devise_controller?

  protected

  def configuer_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up) do |u|
      u.permit(:name, :email, :password, :password_confirmation)
    end
  end
end

上面除了:sign_up,還有 :sign_in 和 :account_update


Devise 的 Github
https://github.com/plataformatec/devise
搜尋 Strong Parameters 會找到標題,裡面有介紹上面 :sign_up 三個 action


Rails 101 的程式
http://courses.growthschool.com/courses/rails-101-4-0/lectures/229607

Rails 102 的範例程式
https://rocodev.gitbooks.io/rails-102/content/chapter1-mvc/c/strong_parameters.html

關於 if: :devise_controller?
http://www.rubydoc.info/github/plataformatec/devise/Devise%2FControllers%2FHelpers%3Adevise_controller%3F

還有
http://www.jianshu.com/p/6a0280bd2130

2016年11月16日 星期三

從 view 回溯 controller 的 CRUD 筆記

controller 裡有七個 action,對應到view的動作

這篇記錄從 view 回溯 controller 的 CRUD 筆記(這些動作加起來就是CRUD了)

index
# app/views/groups/index.html.erb
<% @groups.each do |group| %>
  <%= link_to(group.title, group_path(group)) %>
  <%= group.description %>
<% end %>
上面 @groups,來自
# app/controllers/groups_controller.rb (以下皆同)
def index
    @groups = Group.all
  end


new和edit,則用 form 或 simple_form 傳回去
<%= simple_form_for @group do |f| %>
上面 @group,來自
def new
    @group = Group.new
  end
def edit
    @group = current_user.groups.find(params[:id])
  end
(new action 和create action是一組的;前者負責生出一筆新的資料,後者則是把網頁的這筆新資料填回資料庫,共用new.html.erb。
edit/update 和 new/create 的關係差不多,後者填回去資料庫,共用 edit.html.erb。
所以你看不到create和update的view )


show
# 點 app/views/groups/index.html.erb 的 title
# 會進入 app/views/groups/show.html.erb
上面 show 的 @group,來自
def index
    @groups = Group.all
  end


delete 沒有另開view頁面,放在上面 index 的 <% @groups.each do |group| %>裡
  <%= link_to("Delete", group_path(group), class: "btn btn-sm btn-default",
                 method: :delete, data: { confirm: "Are you sure?" }) %>

原則上就是,controller裡的action實例變數,這個實例變數,在和action同名的view中使用。

程式參考:rails 101

關於實例變數的說明,可以參考這位大大的文章:
Rails新手易混淆的變數使用

這篇文還說明 params[:id] 和Group開頭大寫的意思,值得新手一看。

2016年11月11日 星期五

rake 的撰寫

如何撰寫 rake(uby make)?

主要動作:
1, 在 lib/tasks/ 資料夾裡撰寫.rake檔,把原本在終端機輸入的指令放進新 task 裡面;
2, 到終端機輸入rake rake檔名:task名稱

如新增這個檔
# + lib/tasks/dev.rake
namespace :dev do
  desc "Rebuild system"
  task :build => ["tmp:clear", "log:clear", "db:drop", "db:create", "db:migrate"]
  task :rebuild => ["dev:build", "db:seed"]
end


再到終端機
$ rake dev:build
# 會執行task :build 內的指令;即可把專案資料一切清空歸零重建
# 重開 rails ,去 localhost:3000 會發現全刪了;也可以去 DB 檢查

再執行
$ rake dev:rebuild
# 同樣的,會執行task :rebuild 內的指令;即可重建完再跑 seed
# 再重開 rails ,去 localhost:3000

參考:rails 101


如何使用 scope 整理 controller?

使用狀況:controller 裡有想整理的程式碼

使用方法:把controller 裡那段程式碼,放到model 新增的scope 裡;在controller 把該scope名稱當方法用即可

類似副程式的用法;在model寫,在view 用


1, controllers
如 controllers 原本有如此程式碼
# app/controllers/account/posts_controller.rb
def index
  @posts = current_user.posts.order("updated_at DESC")
end

將 order("updated_at DESC") 剪下


2, models
# app/models/post.rb
新增一個scope,並貼上上面剪下的程式碼
scope :recent, -> { order("updated_at DESC") }
可以看出來,定義一個scope 名字叫recent,內容是上面的程式碼


3, controllers
接著到原本的controllers裡
# app/controllers/account/posts_controller.rb
def index
  @posts = current_user.posts.recent
end
原本程式碼的位置改為 recent 就可以了

http://localhost:3000/ 呈現出的效果是一樣的


整理一下三個類似的東西

view partial:把 view 當中重覆的程式碼,放到一個做為 partial 的 view 檔

helper:把 view 中的程式碼放到某 helper.rb 的方法中,此後在 view 當方法用即可

scope:把 controller 中的程式碼放到 modle 的 scope,在 controller 使用該 scope;等於在 controller 使用 modle 裡寫好的副程式

都是類似副程式的東西,建立、使用時也類似副程式



參考:Rails 101

2016年11月10日 星期四

如何使用 partial 整理 view

使用狀況:兩個 html.rb 裡面有相同的程式碼

使用方法:將兩個 html.rb 相同的程式碼剪下,貼到新增的 partial 檔中;接著,在兩個 html.rb 程式原處,引用partial

1, 在原本的兩個 view 檔
如這兩個檔有相同程式碼,就把相同程式碼剪下
# app/views/groups/edit.html.erb
# app/views/groups/new.html.erb


2, 到新的 view partial 檔
把剪下的程式碼,貼到新增的 partial 檔中
# + app/views/groups/_form.html.erb
注意,這個檔的開頭第一個字必須為底線_

3, 回到原本的兩個 view 檔
接著,在原本兩個檔裡引用partial
# app/views/groups/edit.html.erb
+  <%= render "form", group: @group %>
# app/views/groups/new.html.erb
+  <%= render "form", group: @group %>


另,如果你的partial 檔放在另一個資料夾如 app/views/share/裡
那就這樣引用
<%= render "share/form", group: @group %>


參考:Rails 101

如何用 helper 整理 view?

使用狀況:view裡面的變數弄得太複雜了。


說明:helper 是全域變數,在app/helpers/的.rb檔裡

可以統一整理單一變數和把 view 單純化


使用方法:把原本在view裡面使用的邏輯/方法,放進上述檔案的def 方法中

以後在view裡面,呼叫該方法,就可以使用


如,原本view裡面的程式是長這樣

<%= simple_format(post.content) %>


1, 在 helper 寫好 def

# app/helpers/posts_helper.rb 去某個.rb裡面新增一個def方法
module PostsHelper
  def render_post_content(post)
    simple_format(post.content)
  end
end


2, 在 view 引用

原本view裡面的程式,可以改成這樣

<%= render_post_content(post) %>


post.content 從此放在 helper 統一整理

不須要在view中一個個改了

因為它是全域變數,其他地方也可以用

參考:Rails 101

2016年11月9日 星期三

counter_cache 的使用

1, 在原有的group table裡,新增一個欄位取名叫posts_count
rails g migration add_posts_count_to_group posts_count:integer

# db/migrate/(一堆數字)_add_posts_count_to_group.rb
-   add_column :groups, :posts_count, :integer
+   add_column :groups, :posts_count, :integer, default: 0
rake db:migrate

# 這樣 group 這個資料表就多了 posts_count 這個欄位

2, 到Model 設定
# app/models/post.rb
- belongs_to :group
+ belongs_to :group, counter_cache: :posts_count

# 設定好 counter_cache: :posts_count (即剛才建立的欄位名稱);此後post 只要 create 跟 destroy,就會自動在 posts_count 欄位 +1 跟 -1

3, 到瀏覽器增刪post,並去終端機確認
rails c
a =  Group.find(1)
# 1為增刪的post的group
# 注意 posts_count: 的數字,代表資料庫posts_count欄位,隨posts的增刪而變動,如下
=> #<Group id: 1, title: "new group", description: "test", created_at: "2016-11-07 13:15:55", updated_at: "2016-11-07 13:15:55", user_id: 1, posts_count: 1>

4, 修改 views
# 假使你原本有用SQL 一篇篇抓post數量

<%= group.posts.count %>

# 那就改成這樣

<%= group.posts.size %>

參考:Rails 101

2016年11月6日 星期日

Devise Gem 安裝使用方法

# Gemfile
gem "devise", "~> 4.0.0 "

bundle install

# 設定 Devise
# 安裝 devise 必須檔案
rails g devise:install

# 產生一個有登入功能的 user model
rails g devise user

rake db:migrate

# devise 支援客制修改 template,可以用這行叫出(原本是隱藏的) devise views,以便未來修改
rails g devise:views

重開 rails server


另,Devise 預設其他methods

before_filter :authenticate_user!
放在controller,會自動驗證使用者是否登入,才能使用某些actions
  if yes (已登入 ) => 繼續下面的程序
  if no (未登入)=> 轉到登入畫面
  before_action :authenticate_user!
  上行表示全部action都要使用者登入才能動作
  before_action :authenticate_user!, only: [:new, :edit, :create, :update, :destroy]
  上行表示上面五個action要使用者登入才能動作

user_signed_in?
看一個用戶有沒有登入


current_user
找到現在登入的用戶
  你可以在view裡使用current_user來判斷是否有使用者
  如<% if !current_user %>
  # !variable(跑出布林值的反值,這裡判斷有沒有使用者)
  也可以用它來捉到使用者的欄位,如current_user.name
  如<%= current_user.user %>

user_session
看用戶的session


Devise 的 Github
https://github.com/plataformatec/devise


參考:Rails 101、10天學會Ruby on Rails :Web 2.0 網站架設速成

2016年11月4日 星期五

bootstrap gem 的安裝方法

輸入
# Gemfile
gem 'bootstrap-sass', '~> 3.2.0'
gem 'autoprefixer-rails'

終端機:
$ bundle install
$ rename app/assets/stylesheets/application.css to app/assets/stylesheets/application.css.sass


# app/assets/stylesheets/application.css.sass 在最底下貼上
@import "bootstrap-sprockets"
@import "bootstrap"

# app/assets/javascripts/application.js
# 注意:在 //= require jquery 這一行的下面貼上
//= require bootstrap-sprockets
# //= require_tree .( 但是在這行之前)


參考:
https://launchschool.com/blog/integrating-rails-and-bootstrap-part-1/

客製化路由的做法

# ap/config/routes.rb
Rails.application.routes.draw do
  resources :fruit do
    collection do
      get 'apple'
    end
    member do
      get 'orange'
    end
end
結果會產生這樣的路徑
apple_fruits  GET   /fruits/apple        fruits#apple
orange_fruits GET   /fruits/:id/orange   fruits#orange

很容易看出兩者的差別,一個中間有fruits的id,一個沒有。