├── .gitignore ├── Icon ├── LICENSE ├── convert_html └── README └── manuscript ├── Book.txt ├── Sample.txt ├── Subset.txt ├── about-author.txt ├── booklist.txt ├── changelog.txt ├── chapter-00.txt ├── chapter-01-00-ext-01.txt ├── chapter-01-00-ext-02.txt ├── chapter-01-00.txt ├── chapter-01-1-0-extra-2.txt ├── chapter-01-1-0-extra-3.txt ├── chapter-01-1-0-extra.txt ├── chapter-01-1-0.txt ├── chapter-01-1-1.txt ├── chapter-01-1-2.txt ├── chapter-01-1-3.txt ├── chapter-01-1-4-1.txt ├── chapter-01-1-4.txt ├── chapter-01-1-5.txt ├── chapter-01-1-6-1.txt ├── chapter-01-1-6.txt ├── chapter-01-1-7.txt ├── chapter-02-1-0.txt ├── chapter-02-1-1.txt ├── chapter-02-1-2.txt ├── chapter-02-1-3.txt ├── chapter-02-1-4.txt ├── chapter-02-1-5.txt ├── chapter-02-1-6.txt ├── chapter-02-1-7.txt ├── chapter-02.txt ├── chapter-03-0.txt ├── chapter-03-1.txt ├── chapter-03-2.txt ├── chapter-03-3.txt ├── chapter-03.txt ├── chapter-04-1-2.txt ├── chapter-04-1-3.txt ├── chapter-04-1.txt ├── chapter-04.txt ├── chapter-05-1.txt ├── chapter-05-2.txt ├── chapter-05-3.txt ├── chapter-05-4.txt ├── chapter-05.txt ├── chapter-06-1.txt ├── chapter-06-2.txt ├── chapter-06-3.txt ├── chapter-06-4.txt ├── chapter-06.txt ├── chapter-07-1.txt ├── chapter-07.txt ├── chapter-08-1.txt ├── chapter-08-2.txt ├── chapter-08-3.txt ├── chapter-08.txt ├── chapter-09-1.txt ├── chapter-09-2.txt ├── chapter-09-3.txt ├── chapter-09-4.txt ├── chapter-09.txt ├── extra.txt ├── how-to-use.txt ├── images ├── RESTful.jpg ├── Ruby-on-Rails-process.jpg ├── bird_32_gray.png ├── bird_32_gray_fail.png ├── code_bg.png ├── dotted-border.png ├── email.png ├── ex0-browser.png ├── ex0-hello-world.png ├── ex0-pow.png ├── ex0-route.png ├── ex1-list-board.png ├── ex1-nest-list-board.png ├── ex1-nest-new-post.png ├── ex1-new-post.png ├── ex2-current_user.png ├── ex2-general.png ├── ex2-signin.png ├── ex2-signup.png ├── ex4-board-show.png ├── ex4-default-scope.png ├── ex4-recent-scope.png ├── ex4-will-paginate.png ├── ex5-upload-form.png ├── ex5-upload-result.png ├── ex6-seed.png ├── ex7-github.png ├── group-scaffold.png ├── helloworld.png ├── line-tile.png ├── noise.png ├── post-new.png ├── rails-101-cover.png ├── rails-init.png ├── rss.png ├── search.png └── title_page.png ├── install-on-mac.txt ├── preface.txt ├── recap.txt └── requirement.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dropbox 3 | preview/ 4 | published -------------------------------------------------------------------------------- /Icon : -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/Icon -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2011-2014 Yi-Ting Cheng(xdite). All rights reserved 2 | -------------------------------------------------------------------------------- /convert_html/README: -------------------------------------------------------------------------------- 1 | This folder is used to convert HTML files to Markdown and place them in your book. 2 | 3 | To do this: 4 | 5 | 1) Place the HTML files in this folder 6 | 2) Go to the import page for your book (E.g. http://leanpub.com/yourbookname/import) 7 | 3) Click on the "Start Conversion" button. 8 | 9 | We will take all files that end in .html, convert them to markdown and place them in your manuscript folder. 10 | 11 | The HTML files will be placed in convert_done after they are converted. 12 | 13 | -------------------------------------------------------------------------------- /manuscript/Book.txt: -------------------------------------------------------------------------------- 1 | changelog.txt 2 | about-author.txt 3 | preface.txt 4 | how-to-use.txt 5 | requirement.txt 6 | install-on-mac.txt 7 | chapter-00.txt 8 | chapter-01-00.txt 9 | chapter-01-00-ext-01.txt 10 | chapter-01-00-ext-02.txt 11 | chapter-01-1-0.txt 12 | chapter-01-1-0-extra.txt 13 | chapter-01-1-1.txt 14 | chapter-01-1-2.txt 15 | chapter-01-1-3.txt 16 | chapter-01-1-4.txt 17 | chapter-01-1-4-1.txt 18 | chapter-01-1-5.txt 19 | chapter-01-1-6.txt 20 | chapter-01-1-7.txt 21 | chapter-01-1-0-extra-2.txt 22 | chapter-01-1-0-extra-3.txt 23 | chapter-02.txt 24 | chapter-02-1.txt 25 | chapter-02-1-0.txt 26 | chapter-02-1-1.txt 27 | chapter-02-1-2.txt 28 | chapter-02-1-3.txt 29 | chapter-02-1-4.txt 30 | chapter-02-1-5.txt 31 | chapter-02-1-6.txt 32 | chapter-02-1-7.txt 33 | chapter-03.txt 34 | chapter-03-0.txt 35 | chapter-03-1.txt 36 | chapter-03-2.txt 37 | chapter-03-3.txt 38 | chapter-04.txt 39 | chapter-04-1.txt 40 | chapter-04-1-2.txt 41 | chapter-04-1-3.txt 42 | chapter-05.txt 43 | chapter-05-1.txt 44 | chapter-05-2.txt 45 | chapter-05-3.txt 46 | chapter-05-4.txt 47 | chapter-06.txt 48 | chapter-06-1.txt 49 | chapter-06-2.txt 50 | chapter-06-3.txt 51 | chapter-06-4.txt 52 | chapter-07.txt 53 | chapter-07-1.txt 54 | chapter-07-2.txt 55 | chapter-07-3.txt 56 | chapter-08.txt 57 | chapter-08-1.txt 58 | chapter-08-2.txt 59 | chapter-08-3.txt 60 | chapter-09.txt 61 | chapter-09-1.txt 62 | chapter-09-2.txt 63 | chapter-09-3.txt 64 | chapter-09-4.txt 65 | recap.txt 66 | booklist.txt 67 | extra.txt -------------------------------------------------------------------------------- /manuscript/Sample.txt: -------------------------------------------------------------------------------- 1 | about-author.txt 2 | preface.txt 3 | how-to-use.txt 4 | requirement.txt -------------------------------------------------------------------------------- /manuscript/Subset.txt: -------------------------------------------------------------------------------- 1 | chapter-00.txt 2 | -------------------------------------------------------------------------------- /manuscript/about-author.txt: -------------------------------------------------------------------------------- 1 | # 作者介紹 2 | 3 | 4 | 我是 [xdite](http://blog.xdite.net)。以 Ruby on Rails 撰寫網站已經累積接近 6 年的時間 ( Since 2007 )。 5 | 6 | 我有一個以 Web 開發經驗為主的 blog [Blog.XDite.net](http://blog.xdite.net),不定期會發表各式各類以 Rails 開發為主軸相關的文章。 7 | 8 | 我曾經受邀至 Ruby Taiwan Conf、Ruby China Conf、Reddot Conf ( Singapore Ruby Conf ) 發表 Rails 開發相關的演說。 9 | 10 | 我曾經以 Ruby on Rails 作為開發技術,奪得 [Facebook World Hack 2013 Global Grand Prize](http://paperclip.io/pages/about)。 11 | 12 | 我現在在 [Rocodev](http://rocodev.com) 工作,這是我持有的軟體公司。 13 | 14 | 15 | ## Logdown 16 | 17 | [Logdown](http://logdown.com) 是 [Rocodev](http://rocodev.com) 開發的一個 Blog 系統,非常適合拿來寫技術筆記。 18 | 19 | 購買本書的讀者可以使用 rails101 這個 coupon 獲得 USD $10 的 Logdown 折扣。 20 | 21 | 22 | ### 致謝 23 | 24 | 感謝讀者 sdlong 幫忙此書的校對與範例圖片繪製 -------------------------------------------------------------------------------- /manuscript/booklist.txt: -------------------------------------------------------------------------------- 1 | # 推薦書單 2 | 3 | ### 基礎 4 | 5 | 如果你不懂任何 Ruby 或 Rails,你應該從這些 書 / 教材開始練習: 6 | 7 | * Code School [Try Ruby](http://www.codeschool.com/courses/try-ruby) 8 | * Code School [Try Git](http://www.codeschool.com/courses/try-git) 9 | * Code School [Git Real](http://www.codeschool.com/courses/git-real) 10 | * Peepcode [Meet Command Line](https://peepcode.com/screencasts) 11 | * Peepcode [Advanced Command Line](https://peepcode.com/products/advanced-command-line) 12 | * Zed Shaw [Learn Ruby The Hard Way](http://ruby.learncodethehardway.org/) 13 | 14 | ### Learning Rails 15 | 16 | 使用這些教材,寫出一個簡單 Application,例如一個小論壇... 17 | 18 | 19 | * Code School [Rails for Zombies Redux](http://www.codeschool.com/courses/rails-for-zombies-redux) 20 | * Code School [Rails for Zombies 2](http://www.codeschool.com/courses/rails-for-zombies-2) 21 | 22 | ## 初階基礎網頁設計 23 | 24 | * CodeSchool [Try jQuery](https://www.codeschool.com/courses/try-jquery) 25 | * CodeSchool [JavaScript Road Trip Part 1](https://www.codeschool.com/courses/javascript-road-trip-part-1) 26 | * CodeSchool [JavaScript Road Trip Part 1](https://www.codeschool.com/courses/javascript-road-trip-part-2) 27 | * CodeSchool [CSS Cross-Country](http://www.codeschool.com/courses/css-cross-country) 28 | * Codecademy [Javascripts](http://www.codecademy.com/zh/tracks/javascript) 29 | 30 | ## 初階 Ruby on Rails 31 | 32 | 如果你不知道怎麼開始使用 TDD 寫程式,可以從這幾本書開始... 33 | 34 | * Michael Hartl [Rails Turtorial](http://ruby.railstutorial.org/) 35 | * Ryan Bigg [Rails in Action 4](http://www.manning.com/bigg2/) 36 | 37 | UT on Rails is also a excellent learning material 38 | 39 | * Schneems [UT on Rails](http://schneems.com/ut-rails) 40 | 41 | ## 測試 Testing 42 | 43 | * Code School [Rails testing for zombies](http://www.codeschool.com/courses/rails-testing-for-zombies) 44 | * Code School [Testing with Rspec](http://www.codeschool.com/courses/testing-with-rspec) 45 | * Noel Rappin [Rails Test Prescriptions: Keeping Your Application Healthy](http://pragprog.com/book/nrtest/rails-test-prescriptions) 46 | * Thougutbot [Learn Test-Driven Development using RSpec and Capybara.](https://learn.thoughtbot.com/workshops/18-test-driven-rails) 47 | 48 | ## 進階基礎網頁設計 49 | 50 | * Code School [Jounry into Mobile](http://www.codeschool.com/courses/journey-into-mobile) 51 | * Code School [The Anatomy of Backbone](http://www.codeschool.com/courses/anatomy-of-backbonejs) 52 | * Code School [CoffeeScript](http://www.codeschool.com/courses/coffeescript) 53 | * Code School [Assembling Sass](http://www.codeschool.com/courses/assembling-sass) 54 | * Code School [Assembling Sass Part2](http://www.codeschool.com/courses/assembling-sass) 55 | 56 | ## 重構 Ruby / Rails code 57 | 58 | * Codschool [Rails Best Practices](http://www.codeschool.com/courses/rails-best-practices) 59 | * Chad Pytel / Tammer Saleh : [Rails Antipattern](http://railsantipatterns.com/) 60 | * John Athayde / Bruce Williamsp [The Rails View: Create a Beautiful and Maintainable User Experience](http://pragprog.com/book/warv/the-rails-view) 61 | * Eric Davis [Refacotoring Redmine](http://www.refactoringredmine.com/) 62 | * Code Climate [7 Patterns to Refactor Fat ActiveRecord Models](http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/) 63 | 64 | 65 | ## 寫出更漂亮的 Ruby code 66 | 67 | * Code School [Code Ruby Bits](http://www.codeschool.com/courses/ruby-bits) 68 | * Code School [Code Ruby Bits Part 2](http://www.codeschool.com/courses/ruby-bits-part-2) 69 | * David A. Black [The Well-Grounded Rubyist](http://www.manning.com/black2/) 70 | * Russ Olsen [Eloquent Ruby](http://www.amazon.com/Eloquent-Ruby-Addison-Wesley-Professional/dp/0321584104) 71 | * Avdi Grimm [Confident Ruby](http://devblog.avdi.org/2012/06/05/confident-ruby-beta/) 72 | * Avdi Grimm [Exceptional Ruby](http://exceptionalruby.com/) 73 | * Stefan Kaes [Writing Efficient Ruby Code (Digital Short Cut)](http://www.informit.com/store/writing-efficient-ruby-code-digital-short-cut-9780321540034) 74 | 75 | ### Podcast / Journal of writing better Ruby/Rails code 76 | 77 | * [Ruby Tapas](http://devblog.avdi.org/rubytapas/) 78 | * [Destroy All Software](https://www.destroyallsoftware.com/screencasts) 79 | * [Practicing Ruby](https://practicingruby.com/) 80 | 81 | ## Object-oriend Design in Ruby on Rails 82 | 83 | * thoughtbot [Ruby Science](https://learn.thoughtbot.com/products/13) 84 | * Avdi Grimm [Object on Rails](http://objectsonrails.com/) 85 | * Russ Olsen [Design Patterns in Ruby](http://www.amazon.com/Design-Patterns-Ruby-Russ-Olsen/dp/0321490452) 86 | * Jay fields [Refacoting : Ruby Edition](http://www.amazon.com/Design-Patterns-Ruby-Russ-Olsen/dp/0321490452) 87 | * Sandi Metz [Practical Object-Oriented Design in Ruby: An Agile Primer](http://www.amazon.com/dp/0321721330) 88 | 89 | ## 如何更瞭解 Rails 底層 90 | 91 | * José Valim [Crafting Rails Applications: Expert Practices for Everyday Rails Development](http://pragprog.com/book/jvrails/crafting-rails-applications) 92 | * Marc-André Cournoyer [Owning Rails: The Rails Online Master Class](http://owningrails.com/) 93 | * Railscast [Rails Initialization Walkthrough](http://railscasts.com/episodes/299-rails-initialization-walkthrough) 94 | * Railscast [Rails Middleware Walkthrough](http://railscasts.com/episodes/319-rails-middleware-walkthrough) 95 | * Railscast [Rack App from Scratch](http://railscasts.com/episodes/317-rack-app-from-scratch) 96 | * Railscast [Rails Modularity](http://railscasts.com/episodes/349-rails-modularity) 97 | * Railscast [Hacking with Arel](http://railscasts.com/episodes/355-hacking-with-arel) 98 | * Railscast [Authorization from Scratch Part 1](http://railscasts.com/episodes/385-authorization-from-scratch-part-1) 99 | * Railscast [Authorization from Scratch Part 2](http://railscasts.com/episodes/386-authorization-from-scratch-part-2) 100 | * Railscast [Action Controller Walkthrough](http://railscasts.com/episodes/395-action-controller-walkthrough) 101 | * Railscast [Action View Walkthrough](http://railscasts.com/episodes/397-action-view-walkthrough) -------------------------------------------------------------------------------- /manuscript/changelog.txt: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | * 2014/01 4 | * Fix 筆誤 5 | * 更新圖表 6 | 7 | * 2013/12 8 | * Fix 第四章錯誤 9 | * Upgrade Bootstrappers 10 | * Fix Bootstrappers 錯誤 11 | * 把內建 mysql 改為 SQLite 避免更多錯誤 12 | * Fix production hack 13 | * 把 TODO 解說補完 14 | * 新增 RESTFul 兩篇文章 15 | * 加上 Recap 16 | * 加上 Facebook 社團 17 | 18 | * 2013/11 19 | - 修復 Bootstrapper, 更新專案至 Rails 4.0.0 書中錯誤, 加上程式碼的 Repo 20 | - 加上 Helper 章節 21 | - 加上 Partial 章節 22 | - 加上 Scope 章節 23 | - 加上 Capistrano 章節 24 | - 加上 Asset Pipeline 章節 25 | - 加上 Rails 4.0.0 production hack 26 | 27 | * 2013/06 更新至 Ch6 -------------------------------------------------------------------------------- /manuscript/chapter-00.txt: -------------------------------------------------------------------------------- 1 | # 練習作業 0 - Hello World 2 | 3 | ### 作業目標 4 | 5 | 建立一個 Rails 專案,邁開第一步。 6 | 7 | ## 吸收觀念 8 | 9 | 建立一個 Rails 專案,實作第一個頁面 Hello World。 10 | 11 | * [Bundler](http://gembundler.com/) 12 | * [Pow](http://pow.cx) 13 | * [Rails 目錄結構](http://ihower.tw/rails3/environment-and-bundler.html) 14 | 15 | {::pagebreak :/} 16 | 17 | 18 | ### 作業解答 19 | 20 | 安裝 Rails 4.0.0 21 | 22 | ~~~~~~~ 23 | gem install rails --version 4.0.0 24 | ~~~~~~~ 25 | 26 | 打開 Terminal,在 ~/projects 下輸入指令,建立一個叫做 groupmy 的 Rails 專案 27 | 28 | ~~~~~~~~ 29 | rails new groupmy 30 | ~~~~~~~~ 31 | 32 | 進入 groupmy 目錄 33 | 34 | ~~~~~~~~ 35 | $ cd groupmy 36 | ~~~~~~~~ 37 | 38 | 此時, `rails new groupmy` 已經幫我們建立了一系列會用到的目錄檔案。 39 | 40 | 41 | {::pagebreak :/} 42 | 43 | 44 | ### Bundler 45 | 46 | 在這裡我們要先岔開話題,先介紹幾個工具,第一個是 [Bundler](http://gembundler.com/)。 47 | 48 | [Bundler](http://gembundler.com/) 是一套可以解決外部工具及其相依關係的好用工具,現在基本上所有以 Ruby 開發的工具,基本上都是使用這套工具管理專案上的套件相依關係。 49 | 50 | 接下來,我們要先使用 Bundler 安裝這個專案會用到的套件。 51 | 52 | Bundler 依據 `Gemfile` 這個檔案安裝以及判斷套件相依性。你的 Rails 中的 Gemfile 內會是長這樣 53 | 54 | ~~~~~~~~ 55 | source 'https://rubygems.org' 56 | 57 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 58 | gem 'rails', '4.0.0' 59 | 60 | # Use sqlite3 as the database for Active Record 61 | gem 'sqlite3' 62 | 63 | # Use SCSS for stylesheets 64 | gem 'sass-rails', '~> 4.0.0' 65 | 66 | # Use Uglifier as compressor for JavaScript assets 67 | gem 'uglifier', '>= 1.3.0' 68 | 69 | # Use CoffeeScript for .js.coffee assets and views 70 | gem 'coffee-rails', '~> 4.0.0' 71 | 72 | # See https://github.com/sstephenson/execjs#readme for more supported runtimes 73 | # gem 'therubyracer', platforms: :ruby 74 | 75 | # Use jquery as the JavaScript library 76 | gem 'jquery-rails' 77 | 78 | # Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks 79 | gem 'turbolinks' 80 | 81 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 82 | gem 'jbuilder', '~> 1.2' 83 | 84 | group :doc do 85 | # bundle exec rake doc:rails generates the API under doc/api. 86 | gem 'sdoc', require: false 87 | end 88 | 89 | # Use ActiveModel has_secure_password 90 | # gem 'bcrypt-ruby', '~> 3.0.0' 91 | 92 | # Use unicorn as the app server 93 | # gem 'unicorn' 94 | 95 | # Use Capistrano for deployment 96 | # gem 'capistrano', group: :development 97 | 98 | # Use debugger 99 | # gem 'debugger', group: [:development, :test] 100 | 101 | ~~~~~~~~ 102 | 103 | I>## 常用 Bundler 指令: 104 | I> 105 | I> bundle check : 檢查此專案的套件是否漏失 106 | I> 107 | I> bundle install : 安裝此專案所需要的套件 108 | 109 | 我們先會使用 `bundle install` 這個指令確定這個專案裡面,套件都被安裝了。(套件必須都被安裝,才能啓動專案。) 110 | 111 | 112 | 113 | {::pagebreak :/} 114 | 115 | ### Pow 116 | 117 | 下一個步驟是啓動專案,Rails 內建一個 Webserver,只要使用 `rails s` 就可以跑起來,這個專案會跑在 port 3000,打開 就可以訪問。 118 | 119 | 但是在這本書裡面,我們推薦另外一種方式掛起我們的開發版本網站:[Pow](http://pow.cx)。 120 | 121 | 我們在上一章介紹過 Pow 這個軟體,使用 Pow 最大的好處是我們可以使用 這種網址掛起網站,而非使用 `rails s` 跑在 port 3000。 122 | 123 | 使用 `rails s` 的壞處在於,如果你更改了什麼設定需要改變的話,你必須要要 `Ctrl-C` 終止這個指令,然後再重新啓動一次,這個過程十分緩慢。 124 | 125 | 而使用 Pow,我們只要 `touch tmp/restart.txt` 就可以重啓 webserver,重新訪問被更新的網站了 。 126 | 127 | I>## 什麼時候需要重啓網站? 128 | I> 129 | I> 大多時候更改網站內容,不需要重開 Server。但是若更改 `config/routes.rb`、 `config/enviroments/*.rb` 、`config/database.yml` 等等的檔案,都需要重開 Server (終止 Ctrl-C 再 `rails s`)。 130 | 131 | 132 | ### Pow 與 .rvmrc 與 .powrc 133 | 134 | 我們推薦使用 `powder` 這個 gem 管理 Pow。安裝 powder 的方式是 `gem install powder`。我們可以用 `powder install` 這個指令把 Pow 裝起來。 135 | 136 | 再使用 `powder link`,把這個專案掛起來。比如你的專案目錄夾是 `groupmy/`,在這個目錄打 `powder link`,則建立的連結關係就會是 連結到 groupmy/ 這個專案。 137 | 138 | 我們還要再作幾件事讓 可以跑起來。 139 | 140 | 第一件事是開一個空的新檔案 .rvmrc。在裡面置入: 141 | 142 | `rvm 2.0.0` 143 | 144 | 第二件事是再開一個空的新檔案 .powrc。在裡面置入: 145 | 146 | ~~~~~~~~ 147 | if [ -f "$rvm_path/scripts/rvm" ] && [ -f ".rvmrc" ]; then 148 | source "$rvm_path/scripts/rvm" 149 | source ".rvmrc" 150 | fi 151 | ~~~~~~~~ 152 | 153 | 這時候在終端機打 `powder open`,基本上應該就可以把專案開起來了。 154 | 155 | 156 | I>## 網站開不起來怎麼辦? 157 | I> 158 | I> 試試這幾個指令: 159 | I> 160 | I> rvm reload 161 | I> 162 | I> bundle install 163 | I> 164 | I> powder restart 165 | I> 166 | I> touch tmp/restart.txt 167 | I> 168 | 169 | 170 | ### 監視 development.log 171 | 172 | `rails s` 的壞處是,只要設定檔案變更,就要結束掉重起。但它也有好處,就是可以直接看 Live Log,方便 Debug。那麼使用 Pow 之後,要怎麼樣繼續看 Log 去 debug 呢? 173 | 174 | 答案是:使用 `tail -f log/development.log` 這個技巧,持續追蹤 Log。 175 | 176 | {::pagebreak :/} 177 | 178 | ## 建立 Hello World 頁面 179 | 180 | 當首次打開 ,映入眼簾的會是這樣一個畫面。 181 | 182 | ![](images/rails-init.png) 183 | 184 | 這是 Rails 預設的最初歡迎頁面。接下來我們要將根目錄顯示的預設畫面,換成自訂的頁面,顯示 "Hello World"。 185 | 186 | 而 Hello World 將會放在 pages 這個 controller 下的 wecleom 這個 action。 187 | 188 | {::pagebreak :/} 189 | 190 | ### 步驟 1: 產生 pages controller 191 | 192 | 193 | [~/projects/groupmy] (master) $ rails g controller pages 194 | create app/controllers/pages_controller.rb 195 | invoke erb 196 | create app/views/pages 197 | invoke test_unit 198 | create test/functional/pages_controller_test.rb 199 | invoke helper 200 | create app/helpers/pages_helper.rb 201 | invoke test_unit 202 | create test/unit/helpers/pages_helper_test.rb 203 | 204 | 205 | ### 步驟 2: 建立 welcome action 206 | 207 | 打開 app/controllers/pages_controller.rb,填入 208 | 209 | ~~~~~~~~ 210 | class PagesController < ApplicationController 211 | def welcome 212 | end 213 | end 214 | ~~~~~~~~ 215 | 216 | ### 步驟 3: 建立 welcome 的 HTML view 217 | 218 | 新增 app/views/pages/welcome.html.erb,填入 219 | 220 | ~~~~~~~~ 221 | Hello World! 222 | ~~~~~~~~ 223 | 224 | ### 步驟 4 : 225 | 226 | 設定 config/routes.rb 將 root 設到 pages#welcome 上。 227 | 228 | ~~~~~~~~ 229 | root :to => "pages#welcome" 230 | ~~~~~~~~ 231 | 232 | 233 | 現在你可以成功的見到了 Hello World! 234 | 235 | ![Hello world](images/helloworld.png) 236 | 237 | {::pagebreak :/} 238 | 239 | ## Rails 的 Routing 240 | 241 | Rails 的路徑,是透過 `config/routes.rb` 下的設定進行 mapping。 242 | 243 | 常見出現的 routing 寫法,有以下幾種: 244 | 245 | ~~~~~~~~ 246 | 247 | get "subscriptions/new" 248 | 249 | resources :posts do 250 | resources :comments 251 | end 252 | 253 | namespace :admin do 254 | resources :posts 255 | end 256 | 257 | match '/search' => "search#index", :as => "search" 258 | root :to => "pages#welcome" 259 | 260 | ~~~~~~~~ 261 | 262 | 關於它們的作用,我們會在後面的章節進行解釋。 263 | 264 | 265 | 266 | ![Ruby on Rails 框架運作原理](images/Ruby-on-Rails-process.jpg) 267 | 268 | 269 | 270 | -------------------------------------------------------------------------------- /manuscript/chapter-01-00-ext-01.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 1.0 (補充) CRUD 懶人大法 Scaffold 4 | 5 | Rails 裡面預設了相當 powerful 的一個指令: `scaffold`,字面上的意思是「鷹架」。 6 | 7 | 懶得寫 Rails 程式碼嗎?使用 rails g scaffold [MODEL],可以快速的產出 model 及其 controller 內的 CRUD action 與 view,快速的寫出網站程式。 8 | 9 | 以下只是示範,請停住你的鍵盤不要跟著按。 10 | 11 | ### scaffold 產生 Groups 與 Posts 12 | 13 | 以 1.1 內的作業題目為例,其實你可以這樣進行快速解 14 | 15 | * Group 需要有名稱 name 與 description 16 | 17 | 自動產生 group scaffold 18 | 19 | ~~~~~~~~ 20 | $ rails g scaffold group title:string description:text 21 | ~~~~~~~~ 22 | 23 | 24 | * Post 需要有文章內容 content。 25 | 26 | 自動產生 post scaffold 27 | 28 | ~~~~~~~~ 29 | $ rails g scaffold post content:text 30 | ~~~~~~~~ 31 | 32 | * 執行 db:migrate 33 | 34 | ~~~~~~~~ 35 | $ rake db:migrate 36 | ~~~~~~~~ 37 | 38 | ~~~~~~~~ 39 | [~/projects/groupmy] (master) $ rake db:migrate 40 | (in /Users/xdite/projects/groupmy) 41 | 42 | == CreateGroups: migrating =================================================== 43 | -- create_table(:groups) 44 | -> 0.0013s 45 | == CreateGroups: migrated (0.0014s) ========================================== 46 | 47 | == CreatePosts: migrating ==================================================== 48 | -- create_table(:posts) 49 | -> 0.0012s 50 | == CreatePosts: migrated (0.0013s) =========================================== 51 | ~~~~~~~~ 52 | 53 | 54 | 打開 55 | 56 | ![](images/group-scaffold.png) 57 | 58 | {::pagebreak :/} 59 | 60 | 打開 61 | 62 | ![](images/post-new.png) 63 | 64 | {::pagebreak :/} 65 | 66 | ### Scaffold 作了哪些事? 67 | 68 | * 自動產生 groups 與 post 的 model 及其 migration 69 | * 自動產生 groups 與 posts 的 CRUD 之 controller / action / view 70 | * 自動在 config/routes 裡加上 resources :groups 與 resources :posts 71 | 72 | ### What is 「db migration」? 73 | 74 | 在傳統開發 web 應用程式的流程中,開發者多半是使用 phpmyadmin 開設 db 欄位。 75 | 76 | 因此在學習 Rails 之初,剛入門的開發者往往會提出一個質疑:為何必須撰寫 db migration 檔去生欄位,而不是使用傳統的 phpmyadmin? 77 | 78 | 理由是 db migration 是一個經過實證的最佳解。當多人同時在一個 project 內工作時,縱然程式碼可以受到版本控制,但是 db schema 卻可能是無法透過版本控制的一個隱憂。 79 | 80 | 當多人都可以自由的刪改 db 欄位不受監督時,程式碼就很難與 db 欄位有著一致性,將會有出錯的可能性。為了 automation 以及也能對 db schema 做版本控制,所以才有了 db migration file 的設計。 81 | 82 | 83 | ### 小結 84 | 85 | 至此,我們擁有了 group 與 post 兩份的 CRUD。 86 | 87 | 88 | ### 警告 89 | 90 | 在新手的學習過程中,我並不推薦使用 `scaffold` 這個指令,因為 CRUD 及其背後 convention 的原理是基本中的基本,「應」熟練再熟練。寫個十幾遍都不為過,練到閉著眼睛都會寫才行。 91 | 92 | 然而新手在練習作業中,很容易不小心就忘記 CRUD 的寫法。故此章先行提示 Rails 內建自動 CRUD 的指令 `scaffold`,但我建議日常開發中不要使用這個指令。 93 | -------------------------------------------------------------------------------- /manuscript/chapter-01-00-ext-02.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 1.0 (補充) 建站懶人包 Bootstrappers 4 | 5 | 6 | [Bootstrappers](https://github.com/xdite/bootstrappers) 是我在 2012 年開發的一個懶人架站機。 7 | 8 | 它的特點是內建 [Boostrap](http://twitter.github.io/bootstrap/) 這個 Theme、一些常用熱門 Gem,還有一些 Rails Best Practice。可以讓你在寫網站的一剛開始就加速好幾倍... 9 | 10 | ### 改用 `bootstrappers` 生專案。 11 | 12 | #### 安裝 bootstrappers 13 | 14 | ~~~~~~~~ 15 | gem install bootstrappers -v=4.2.1 16 | ~~~~~~~~ 17 | 18 | #### 用 bootstrappers 建立新專案 19 | 20 | ~~~~~~~~ 21 | bootstrappers groupme 22 | ~~~~~~~~ 23 | 24 | 25 | 在這裡我建議你用 Bootstrappers 重新建立一個專案,再循 Chapter 1.1 的方式,把 `groupme.dev` 用 Pow 掛起來,因為本書接下來都會以 Bootstrappers 生成的專案作為示範。 26 | -------------------------------------------------------------------------------- /manuscript/chapter-01-00.txt: -------------------------------------------------------------------------------- 1 | # 練習作業 1 - 建立 Group - CRUD 與 RESTful 2 | 3 | 4 | ### 作業目標 5 | 6 | 本書的練習專案會是一個以 Group 為主的討論區 7 | 8 | * 使用者可以建立、管理 Group。 9 | * 使用者可以加入、退出社團。 10 | * 使用者加入此社團後可以發表文章 11 | 12 | 開發一個簡易社群討論系統。系統要有 Group 與 Post 兩個 model,寫出 CRUD 介面,並且文章網址是使用 這種表示。 13 | 14 | ### 本章練習主題 15 | 16 | * 學會寫出 CRUD 七個 action 的 controller 與 view 17 | * 學會利用 migration 新增資料庫欄位 18 | * 學會撰寫「表單」 19 | * 學會設定 Route 20 | * 學會 resources 的設定 (單層 resources) 21 | * 對 Rails RESTful 有初步的理解 22 | * 知道 before_action 使用的場景,並如何應用 23 | 24 | 25 | 26 | {::pagebreak :/} 27 | 28 | ## Ch 1.0 CRUD 29 | 30 | CRUD 指的是 Create(新增)、Read(讀取)、Update(更新)、Destroy(刪除)四種操作資料的基本方式,這也是開發網站時幾乎大家最常寫到的四種功能。 31 | 32 | Rails 在開發上極具優勢的其中一個原因,就是使用了 RESTful 的機制以及 內建的 Convention,在實作 CRUD-like 功能時,隔外顯現開發速度上的優勢。 33 | 34 | ### 實作課題 35 | 36 | 本章將會實作以下課題 37 | 38 | * 產生 Group 與 Post 這兩個 Model 39 | * Group 需要有名稱 title, description 40 | * Post 需要有文章標題 文章內容 content 41 | * 寫出 Group 的 CRUD controller 與 View 42 | * 寫出 Posts 的 CRUD controller 與 View 43 | * 在 routing 中對 Group 和 Posts 分別宣告它們都是 resources 44 | 45 | -------------------------------------------------------------------------------- /manuscript/chapter-01-1-0-extra-2.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## 補充章節:RESTful on Rails 4 | 5 | Representational State Transfer,簡稱 REST。是 Roy Fielding 博士在2000年他的博士論文中提出來的一種軟體架構風格。目前是一種相當風行的 Web Services 實現手法,因為 REST 風格的 Web Services 遠比傳統的 SOAP 與 XML-RPC 來的簡潔。在近年,幾乎所有各大主流網站的 API 都已採用此種風格進行設計。 6 | 7 | ### What is REST? 8 | 9 | REST 提出了一些設計概念和準則: 10 | 11 | 1. 網路上的所有事物都被將被抽象成資源 (resource) 12 | 2. 每個資源對應一個唯一的 resource identifier 13 | 3. 通過通用的介面 (generic connector interface) 對資源進行操作 14 | 4. 對資源的各種操作不會改變 resource idetifier 15 | 5. 所有的操作都是無態 (stateless) 的 16 | 17 | 對照到 web services 上來說: 18 | 19 | * resource indentifier 是 URI 20 | * generic connector interface 是 HTTP 21 | 22 | {::pagebreak :/} 23 | 24 | ### RESTful Web Services 25 | 26 | RESTful Web Services 是使用 HTTP 並遵循 REST 設計原則的 Web Services。它從以下三個方面資源進行定義: 27 | 28 | * URI,比如:http://example.com/resources/。 29 | * Web Services accept 與 return 的 media type,如:JSON,XML ,YAML 等。 30 | * Web Services 在該 resources 所支援的一系列 request methid : 如:POST,GET,PUT 或 DELETE。 31 | 32 | 以下列出在實現 RESTful Web 服務時HTTP請求方法的典型用途。 33 | 34 | #### GET 35 | 36 | |資源 | GET | 37 | |-------------------|-----------| 38 | |一組資源的URI,比如http://example.com/resources/ | 使用給定的一組資源替換當前整組資源。| 39 | |單個資源的URI,比如http://example.com/resources/142 | 獲取 指定的資源的詳細信息,格式可以自選一個合適的 media type(如:XML、JSON等)| 40 | 41 | #### PUT 42 | 43 | |資源 | PUT| 44 | |-------------------|-----------| 45 | |一組資源的URI,比如http://example.com/resources/ | 列出 URI,以及該資源組中每個資源的詳細信息(後者可選)。| 46 | |單個資源的URI,比如http://example.com/resources/142 | 替換/創建 指定的資源。並將其追加到相應的資源組中。| 47 | 48 | #### POST 49 | 50 | |資源 | POST| 51 | |-------------------|-----------| 52 | |一組資源的URI,比如http://example.com/resources/ | 在本組資源中創建/追加一個新的資源。 該操作往往返回新資源的URL。| 53 | |單個資源的URI,比如http://example.com/resources/142 | 把指定的資源當做一個資源組,並在其下創建/追加一個新的元素,使其隸屬於當前資源。| 54 | 55 | {::pagebreak :/} 56 | 57 | #### DELETE 58 | 59 | |資源 | DELETE| 60 | |-------------------|-----------| 61 | |一組資源的URI,比如http://example.com/resources/ | 刪除 整組資源。| 62 | |單個資源的URI,比如http://example.com/resources/142 | 刪除 指定的元素。| 63 | 64 | {::pagebreak :/} 65 | 66 | ### 制約即解放 67 | 68 | DHH 在 [Discovering a world of Resources on Rails](http://www.slideshare.net/vishnu/discovering-a-world-of-resources-on-rails) 提到一個核心概念:Constraints are liberating 。 69 | 70 | 很多剛踏入 Rails 這個生態圈的開發者,對於 REST 總有股強烈的反抗心態,認為 REST 是一個討厭的限制。但事實上,DHH 卻認為引入 REST 的制約卻反為 Rais 開發帶來了更大的解放,而這也是他引入這個設計的初衷。 71 | 72 | #### 維護性:解決了程式碼上的風格不一 73 | 74 | 在傳統的開發方式中,對於有效組織網頁應用中的程式碼,大家並沒有什麼共識。所以很可能在專案中會出現這種風格不一致的程式碼: 75 | 76 | ``` ruby 77 | def add_friend 78 | end 79 | 80 | def remove_friend 81 | end 82 | 83 | def create_post 84 | end 85 | 86 | def delete_post 87 | end 88 | 89 | ``` 90 | 透過 REST 的包裝(對於 resource 的操作),可以變得更簡潔直觀,且具有統一的寫法。 91 | 92 | ``` ruby 93 | class FriendShipController 94 | def create 95 | end 96 | 97 | def destroy 98 | end 99 | end 100 | 101 | class PostController 102 | def create 103 | end 104 | 105 | def destroy 106 | end 107 | end 108 | 109 | ``` 110 | 111 | ### 介面統一:Action as Resource 112 | 113 | 大眾最常對 REST 產生的一個誤解,就是以為 resource 只有指的是 data。其實 resource 指的是:data + representation (表現形式,如 html, xml, json 等)。 114 | 115 | 在網頁開發中,網站所需要的表現格式,不只有 HTML 而已,有時候也需要提供 json 或者 xml。Rails 透過 responder 實現了 116 | 117 | 在 Resource Oriented Architecture (ROA,一組 REST 架構實現 guideline) 中有一條: `A resource can use file extension in the URI, instead of Content-Type negotiation `。 118 | 119 | Rails 透過 responder 的設計,讓一個 action 可以以 file extension ( .json, .xml, .csv ...etc.) 的形式,提供不同類型的 representation。 120 | 121 | ``` ruby 122 | class PostsController < ApplicationController 123 | # GET /posts 124 | # GET /posts.xml 125 | def index 126 | @posts = Post.all 127 | 128 | respond_to do |format| 129 | format.html # index.html.erb 130 | format.xml { render :xml => @posts } 131 | end 132 | end 133 | 134 | ``` 135 | 136 | 137 | 138 | ### 開發速度:透過 CRUD 七個 action + HTTP 四個 verb + 表單 Helper 與 URL Helper 達到高速開發 139 | 140 | REST 之所以能簡化開發,是因為其所引入的架構約束。Rails 中的 REST implementation 將 controller 的 method 限制在七個: 141 | 142 | * index 143 | * show 144 | * new 145 | * edit 146 | * create 147 | * update 148 | * destroy 149 | 150 | 實際上就是整個 CRUD。而在實務上,web services 的需要的操作行為,其實也不脫這七種。Rails 透過 HTTP 作為 generic connector interface,使用 HTTP 的四種 verb : GET、POST、PUT、DELETE 對資源進行操作。 151 | 152 | {::pagebreak :/} 153 | 154 | #### GET 155 | 156 | ``` ruby 157 | <%= link_to("List", posts_path) %> 158 | <%= link_to("Show", post_path(post)) %> 159 | <%= link_to("New", new_post_path) %> 160 | <%= link_to("Edit", edit_post_path(post)) %> 161 | ``` 162 | 163 | #### POST 164 | ``` ruby 165 | <%= form_for @post , :url => posts_path , :html => {:method => :post} do |f| %> 166 | ``` 167 | 168 | #### PUT 169 | ``` ruby 170 | <%= form_for @post , :url => post_path(@post) , :html => {:method => :put} do |f| %> 171 | ``` 172 | 173 | #### Destroy 174 | ``` ruby 175 | <%= link_to("Destroy", post_path(@post), :method => :delete ) 176 | ``` 177 | 178 | {::pagebreak :/} 179 | 180 | #### Form 綁定 Model Attribute 的設計 181 | 182 | Rails 的表單欄位,是對應 Model Attribute 的: 183 | 184 | ``` ruby 185 |

New post

186 | 187 | <%= form_for @post , :url => posts_path do |f| %> 188 | <%= f.error_messages %> 189 |
<%= f.text_field :subject %>
190 |
<%= f.text_area :content %>
191 | <%= f.submit "Submit", :disable_with => 'Submiting...' %> 192 | <% end -%> 193 | 194 | 195 | <%= link_to 'Back', posts_path %> 196 | ``` 197 | 198 | 透過 form_for 傳送出來的表單,會被壓縮包裝成一個 parameter: params[:post], 199 | 200 | ``` ruby 201 | def create 202 | @post = Post.new(params[:post]) 203 | if @post.save 204 | flash[:notice] = 'Post was successfully created.' 205 | redirect_to post_path(@post) 206 | else 207 | render :action => "new" 208 | end 209 | end 210 | ``` 211 | 212 | 如此一來,原本創造新資源的一連串繁複動作就可以大幅的被簡化,開發速度達到令人驚艷的地步。 213 | 214 | ### 參考資料: 215 | 216 | * [百度百科:REST](http://baike.baidu.com/view/1077487.htm) 217 | * [Wikipedia: REST](http://zh.wikipedia.org/wiki/REST) 218 | * [Wikipedia: ROA](http://en.wikipedia.org/wiki/Resource-oriented_architecture) 219 | * [DHH: Discovering a world of Resource on Rails](http://en.wikipedia.org/wiki/Resource-oriented_architecture) 220 | * [ihower: Rails RESTful 制約即解放](http://ihower.tw/blog/archives/1566) 221 | * [ihower: 什麼是 REST 與 RESTful](http://ihower.tw/blog/archives/1542) -------------------------------------------------------------------------------- /manuscript/chapter-01-1-0-extra-3.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## 補充章節:More about RESTful on Rails 4 | 5 | 6 | 有時候開發中也會出現超過這七種 action 之外的動作。那要如何整合進 RESTful 的 route 中呢? 7 | 8 | ### Collection & Member 9 | 10 | #### Member 11 | 12 | 宣告這個動作是屬於單個資源的 URI。可以透過 `/photos/1/preview` 進行 GET。有 `preview_photo_path(photo)` 或 `preview_photo_url(photo)` 這樣的 Url Helper 可以用。 13 | 14 | ``` ruby 15 | resources :photos do 16 | member do 17 | get 'preview' 18 | end 19 | end 20 | ``` 21 | 22 | 也可以使用這種寫法: 23 | 24 | ``` ruby 25 | resources :photos do 26 | get 'preview', :on => :member 27 | end 28 | ``` 29 | 30 | #### Collection 31 | 32 | 宣告這個動作是屬於一組資源的 URI。可以透過 `/photos/search` 進行 GET。有 `search_photos_path` 或 `search_photos_url` 這樣的 Url Helper 可以用。 33 | 34 | 35 | ``` ruby 36 | resources :photos do 37 | collection do 38 | get 'search' 39 | end 40 | end 41 | ``` 42 | 43 | {::pagebreak :/} 44 | 45 | 也可以使用這種寫法: 46 | 47 | ``` ruby 48 | resources :photos do 49 | get 'search', :on => :collection 50 | end 51 | ``` 52 | 53 | ### Nested Resources 54 | 55 | 有時候我們會需要使用雙重 resources 來表示一組資源。比如說 Post 與 Comment : 56 | 57 | ``` ruby 58 | resources :posts do 59 | resources :comments 60 | end 61 | ``` 62 | 63 | 可以透過 `/posts/123/comments` 進行 GET。有 `post_comments_path(post)` 或 `post_comments_url(post)` 這樣的 Url Helper 可以用。 64 | 65 | 而這樣的 URL 也很清楚的表明,這組資源就是用來存取 編號 123 的 post 下所有的 comments。 66 | 67 | 而要拿取 Post 的 id 可以透過這樣的方式取得: 68 | 69 | ``` ruby 70 | class CommentsController < ApplicationController 71 | 72 | before_filter :find_post 73 | 74 | def index 75 | @comments = @post.comments 76 | end 77 | 78 | protected 79 | 80 | def find_post 81 | @post = Post.find(params[:post_id]) 82 | end 83 | end 84 | ``` 85 | 86 | {::pagebreak :/} 87 | 88 | ### Namespace Resources 89 | 90 | 但是像 /admin/posts/1/edit 這種 URL,用 Nested Resources 應該造不出來吧?沒錯,這樣的 URL 應該要使用 Namespace 去建構: 91 | 92 | ``` ruby 93 | namespace :admin do 94 | # Directs /admin/products/* to Admin::PostsController 95 | # (app/controllers/admin/posts_controller.rb) 96 | resources :posts 97 | end 98 | ``` 99 | 可以透過 `/admin/posts/123/edit` 進行 GET。有 `edit_admin_post_path(post)` 或 `edit_admin_post_url(post)` 這樣的 Url Helper 可以用。 100 | 101 | ### 延伸閱讀 102 | 103 | * [Rails Guide: Rails Routing from the Outside In](http://guides.rubyonrails.org/routing.html) 104 | -------------------------------------------------------------------------------- /manuscript/chapter-01-1-0-extra.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 1.1 (補充) RESTful 4 | 5 | 6 | Rails 在 1.2 版本的時候引進了 RESTful 這一套設計風格。而在 2.0 版強迫正式成為開發預設值。 7 | 8 | 說到 RESTful,幾乎每個剛踏進 Rails 框架中的開發者都皺起眉頭。幾乎沒有人能夠在初學階段搞的懂這是什麼玩意。 9 | 10 | 偏偏初學者往往有一個「死扣細節」的習慣:沒「弄懂」就「不敢用」,結果在第一個關卡挫折感就堆的如山高,把玩 RESTful 後打退堂鼓,從此放棄 Rails。 11 | 12 | ### 想學 Rails 就先把 RESTful 背起來 13 | 14 | RESTful 是 Rails 裡面使用的最基礎最頻繁的一個設計手法,偏偏它也是最難一次講解清楚讓剛入門者理解的一個主題。因為在傳統 web 應用程式開發流程中,根本沒有這樣的概念。更別提在市面上這麼多網頁框架,只有 Rails 將之視為預設值。 15 | 16 | 別說是初學者,就連筆者和一些 Rails 界的前輩,在 Rails 剛納入 RESTful 為預設風格時,也沒能通透其中原理。 17 | 18 | 但是不熟練 RESTful 的開發架構,在開發 Rails 時幾乎會寸步難行。在訓練新 Developer 時如何讓他短時間就熟練便上手呢? 19 | 20 | 方法很簡單:強迫他背起來,然後重複寫十遍。 21 | 22 | 聽起來很唬爛,但其實真的紮紮實實寫過十遍以後。這時候再重新講解一次 RESTful 的概念,原本模糊的概念一下就變得豁然開朗了。 23 | 24 | {::pagebreak :/} 25 | 26 | ### 初步熟悉使用 Rails RESTful 27 | 28 | #### 在 config/routes.rb 內加入 resources 29 | 30 | 31 | 在 config/routes.rb 加入 `resources :groups`,就是具體在 rails 對一個 controller 實作 RESTful 的方式(對一個 controller 宣告成為 resources)。 32 | 33 | ~~~~~~~~ 34 | 35 | Groupme::Application.routes.draw do 36 | resources :groups 37 | end 38 | 39 | ~~~~~~~~ 40 | 41 | ![RESTful 路徑對照表](images/RESTful.jpg) 42 | 43 | 44 | -------------------------------------------------------------------------------- /manuscript/chapter-01-1-0.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 1.1 建立 Group 4 | 5 | ### 建立 Group 這個 model 6 | 7 | 執行 8 | 9 | `rails g model group title:string description:text` 10 | 11 | ~~~~~~~~ 12 | invoke active_record 13 | create db/migrate/20130529180541_create_groups.rb 14 | create app/models/group.rb 15 | invoke test_unit 16 | create test/models/group_test.rb 17 | create test/fixtures/groups.yml 18 | ~~~~~~~~ 19 | 20 | 生成 Group 這個 model。 21 | 22 | 然後跑 `rake db:migrate`。 23 | 24 | ~~~~~~~~ 25 | == CreateGroups: migrating =================================================== 26 | -- create_table(:groups) 27 | -> 0.1951s 28 | == CreateGroups: migrated (0.1952s) ========================================== 29 | ~~~~~~~~ 30 | 31 | 32 | {::pagebreak :/} 33 | 34 | ### 建立 groups 這個 controller 35 | 36 | 執行 `rails g controller groups` 37 | 38 | ~~~~~~~~ 39 | 40 | create app/controllers/groups_controller.rb 41 | invoke erb 42 | create app/views/groups 43 | invoke test_unit 44 | create test/controllers/groups_controller_test.rb 45 | invoke helper 46 | create app/helpers/groups_helper.rb 47 | invoke test_unit 48 | create test/helpers/groups_helper_test.rb 49 | invoke assets 50 | invoke coffee 51 | create app/assets/javascripts/groups.js.coffee 52 | invoke scss 53 | create app/assets/stylesheets/groups.css.scss 54 | ~~~~~~~~ 55 | 56 | 57 | ### 把 groups 加入 routing 58 | 59 | 到 `config/routes.rb` 加入 60 | 61 | ~~~~~~~~~ 62 | resources :groups 63 | ~~~~~~~~~ 64 | 65 | -------------------------------------------------------------------------------- /manuscript/chapter-01-1-1.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 1.1.1 建立 Groups Controller 裡的 index 4 | 5 | 到 `app/controllers/groups_controller.rb` 加入 6 | 7 | ~~~~~~~~~ 8 | def index 9 | @groups = Group.all 10 | end 11 | 12 | ~~~~~~~~~ 13 | 14 | {::pagebreak :/} 15 | 16 | #### 補上 view: 17 | 18 | `touch app/views/groups/index.html.erb` 19 | 20 | ~~~~~~~~ 21 | 22 |
23 |
24 | <%= link_to("New group", new_group_path , :class => "btn btn-mini btn-primary pull-right" ) %> 25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | <% @groups.each do |group| %> 37 | 38 | 39 | 40 | 41 | 44 | 45 | <% end %> 46 | 47 |
# Title Description
# <%= link_to(group.title, group_path(group)) %> <%= group.description %> <%= link_to("Edit", edit_group_path(group), :class => "btn btn-mini" ) %> 42 | <%= link_to("Delete", group_path(group), :class => "btn btn-mini", :method => :delete, :confirm => "Are you sure?" ) %> 43 |
48 | 49 |
50 | ~~~~~~~~ 51 | 52 | 53 | 54 | {::pagebreak :/} 55 | 56 | 57 | ### 解說 58 | 59 | 60 | 通常會放置列表。因此 Group.all 是拉出 group 這個 model 所有的資料。 61 | 62 | ~~~~~~~~ 63 | 64 | def index 65 | @groups = Group.all 66 | end 67 | 68 | ~~~~~~~~ 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /manuscript/chapter-01-1-2.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 1.1.2 建立 Groups Controller 裡的 show 4 | 5 | 在 `app/controllers/groups_controller.rb` 加入 `show` 這個 action 6 | 7 | ~~~~~~~~ 8 | def show 9 | @group = Group.find(params[:id]) 10 | end 11 | ~~~~~~~~ 12 | 13 | 14 | ### 補上 view 15 | 16 | touch app/views/groups/show.html.erb 17 | 18 | 加入 19 | 20 | ~~~~~~~~ 21 | 22 | 23 |
24 | 25 |
26 | <%= link_to("Edit", edit_group_path(@group) , :class => "btn btn-mini btn-primary pull-right" ) %> 27 |
28 | 29 |

<%= @group.title %>

30 | 31 |

<%= @group.description %>

32 | 33 |
34 | 35 | ~~~~~~~~ 36 | 37 | {::pagebreak :/} 38 | 39 | ### 解說 40 | 41 | 42 | 秀出單筆資料。 Group.find(123) 是指找 Post model 裡 id 為 123 的資料。http://groupme.dev/groups/123 的 groups 是 controller、show 是 action;如果在 route 裡面沒有特別指定,則 123 通常就是 params[:id] 。 43 | 44 | ~~~~~~~~ 45 | 46 | def show 47 | @group = Group.find(params[:id]) 48 | end 49 | 50 | ~~~~~~~~ -------------------------------------------------------------------------------- /manuscript/chapter-01-1-3.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 1.1.3 建立 Groups Controller 裡的 new 4 | 5 | 在 `app/controllers/groups_controller.rb` 加入 `new` 這個 action 6 | 7 | ~~~~~~~~ 8 | def new 9 | @group = Group.new 10 | end 11 | ~~~~~~~~ 12 | 13 | 解說:建立一個新的 Group object。 14 | 15 | ### 補上 view 16 | 17 | touch `app/views/groups/new.html.erb` 18 | 19 | 加入 20 | 21 | ~~~~~~~~ 22 | 23 | 24 |
25 | 26 | <%= simple_form_for @group do |f| %> 27 | <%= f.input :title, :input_html => { :class => "input-xxlarge"} %> 28 | <%= f.input :description, :input_html => { :class => "input-xxlarge"} %> 29 | 30 |
31 | <%= f.submit "Submit", :disable_with => 'Submiting...', :class => "btn btn-primary" %> 32 |
33 | <% end %> 34 | 35 |
36 | 37 | ~~~~~~~~ 38 | 39 | {::pagebreak :/} 40 | 41 | 42 | ### 解說 43 | 44 | initial 一個新的 Post object。 45 | 46 | ~~~~~~~~ 47 | def new 48 | @group = Group.new 49 | end 50 | ~~~~~~~~ 51 | -------------------------------------------------------------------------------- /manuscript/chapter-01-1-4-1.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 1.1.4 (補充) Strong Parameters 4 | 5 | 還記得 2012 年初開發圈曾經騰動一時的: [Github 被入侵事件](http://blog.xdite.net/posts/2012/03/05/github-hacked-rails-security/) 嗎? 6 | 7 | 事件的起因是因為 Rails 內建的 mass assignment,無法容易保護資料的安全性。而原先內建的 attr_accessible / attr_protected 的設計並不足夠實務使用(無法強制 Developer 使用)。 8 | 9 | 在該事件發生後,Rails 核心團隊在 3.2.3 之後的版本,都開啟了 config.whitelist_attributes = true 的選項作為預設。也就是專案自動會對所有的 model 都自動開啟白名單模式,你必須手動對每一個 model 都加上 attr_accessible。這樣表單送值才會有辦法運作。 10 | 11 | 此舉好處是:「夠安全」,能強迫開發者在設計表單時記得審核 model 該欄位是否適用於 mass-assign。但這樣的機制也引發開發者「不實用」「找麻煩」的議論。 12 | 13 | ### 臨時 Hack 找麻煩 14 | 15 | 首先會遇到的第一個問題是:「新手容易踩中地雷」 16 | 17 | 首先最麻煩的當然是,新手會被這一行設定整到。新手不知道此機制為何而來,出了問題也不知道如何關掉這個設定。更麻煩的是撰寫新手教學的人,必須又花上一大篇幅解釋 mass-assignment 的設計機制,為何重要,為何新手需要重視…etc. 18 | 19 | 第二個問題: 「不是很實用」 20 | 21 | 手動一個一個加上 attr_accessible 真的很煩人,因為這也表示,若新增一個欄位,開發者也要手動去加上 attr_accessible,否則很可能在某些表單直接出現異常現象。 22 | 23 | 而最麻煩的還是,其實 attr_accessible 不敷使用,因為一個系統通常存在不只一種角色,普通使用與 Admin 需要的 mass-assignment 範圍絕對不盡相同。 24 | 25 | 雖然 Rails 在 3.1 加入了 scoped mass assignment。但這也只能算是 model 方面的解決手法。一旦系統內有更多其他流程需求,scoped mass assignment 的設計頓時就不夠解決問題了… 26 | 27 | {::pagebreak :/} 28 | 29 | ### 癥結點:欄位核准與否應該由 controller 管理,而非 model 30 | 31 | 大家戰了一陣子,終於收斂出一個結論。一切的癥結點在於之前的設計想法都走錯了方向,欄位核准與否應該由 controller 決定。因為「流程需求」本來就應該作在 controller 裡面。新的 solution [strong_parameters](https://github.com/rails/strong_parameters/) 就是這個解法。 32 | 33 | 在當時:Rails 之父 DHH 提供了他的最佳實務: 34 | 35 | 36 | ~~~~~~~~ 37 | 38 | class PostsController < ActionController::Base 39 | def create 40 | Post.create(post_params) 41 | end 42 | 43 | def update 44 | Post.find(params[:id]).update_attributes!(post_params) 45 | end 46 | 47 | private 48 | def post_params 49 | params[:post].slice(:title, :content) 50 | end 51 | end 52 | ~~~~~~~~ 53 | 54 | 使用 slice 去把真正需要的部分切出來,所以就算 hacker 打算送其他 parameter 也會被過濾掉(不會有 exception)。 55 | 56 | {::pagebreak :/} 57 | 58 | ### Strong Parameters 的作法 59 | 60 | 而 strong_parameters 的作法是必須過一段 permit,允許欄位。如果送不允許的欄位進來,會 throw exception。 61 | 62 | ~~~~~~~~ 63 | class PeopleController < ActionController::Base 64 | def update 65 | person.update_attributes!(person_params) 66 | redirect_to :back 67 | end 68 | 69 | private 70 | def person_params 71 | params.require(:person).permit(:name, :age) 72 | end 73 | end 74 | ~~~~~~~~ 75 | 76 | ### 進階 Strong Parameters 77 | 78 | 當然,每一段 controller 都要來上這麼一段,有時候也挺煩人的。Railscast 也整理了一些[進階招數](http://railscasts.com/episodes/371-strong-parameters): 79 | 80 | * Nested Attributes 81 | * Orgngized to Class 82 | -------------------------------------------------------------------------------- /manuscript/chapter-01-1-4.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 1.1.4 建立 Groups Controller 裡的 create 4 | 5 | 在 `app/controllers/groups_controller.rb` 加入 `create` 這個 action 6 | 7 | ~~~~~~~~ 8 | 9 | def create 10 | @group = Group.new(params[:group]) 11 | @group.save 12 | 13 | redirect_to groups_path 14 | end 15 | 16 | 17 | ~~~~~~~~ 18 | 19 | 不過此時,我們發現好像少了一種實際狀況?如果一個 Group 沒有 title,應該算是不合法的 group 吧? 20 | 21 | 我們應該要限制沒有輸入 title 的 group,必須要退回到 `new` 這個表單。 22 | 23 | 24 | 修改 `app/models/group.rb`,限制 group 一定要有標題 25 | 26 | ~~~~~~~~ 27 | 28 | class Group < ActiveRecord::Base 29 | 30 | validates :title, :presence => true 31 | 32 | end 33 | 34 | ~~~~~~~~ 35 | 36 | 同時修改 `app/controllers/groups_controller.rb` 中 `create` 這個 action 37 | 38 | 改成以下內容 39 | 40 | ~~~~~~~~ 41 | 42 | def create 43 | @group = Group.new(group_params) 44 | 45 | if @group.save 46 | redirect_to groups_path 47 | else 48 | render :new 49 | end 50 | end 51 | 52 | 53 | private 54 | 55 | 56 | def group_params 57 | params.require(:group).permit(:title, :description) 58 | end 59 | 60 | ~~~~~~~~ 61 | 62 | 63 | 64 | {::pagebreak :/} 65 | 66 | 67 | ### 解說 68 | 69 | 這邊要搭配 `app/views/groups/new.html.erb` 這個 view 並列一起看。 70 | 71 | ~~~~~~~~ 72 | 73 | 74 |
75 | 76 | <%= simple_form_for @group do |f| %> 77 | <%= f.input :title, :input_html => { :class => "input-xxlarge"} %> 78 | <%= f.input :description, :input_html => { :class => "input-xxlarge"} %> 79 | 80 |
81 | <%= f.submit "Submit", :disable_with => 'Submiting...', :class => "btn btn-primary" %> 82 |
83 | <% end %> 84 | 85 |
86 | 87 | ~~~~~~~~ 88 | 89 | 90 | 在 RESTful Rails 的寫法中,對 `groups_path` 丟 POST 就是對應到 create 的動作。而 Rails 在設計上,form 是綁 model 的,因此整個 form 的內容會被包成一個 hash,在這裡就是 params[:group]。create action 初始一個 object ,並把 params[:group] 整包塞進這個 object 裡。如果 @group 能夠成功的儲存,就「重導」到 index action ,失敗則「退回」到 new action 。 91 | -------------------------------------------------------------------------------- /manuscript/chapter-01-1-5.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 1.1.5 建立 Groups Controller 裡的 edit 4 | 5 | 在 `app/controllers/groups_controller.rb` 加入 `edit` 這個 action 6 | 7 | ~~~~~~~~ 8 | def edit 9 | @group = Group.find(params[:id]) 10 | end 11 | ~~~~~~~~ 12 | 13 | 解說: query 出指定的Group model object,然後進行編輯。 14 | 15 | ### 補上 view 16 | 17 | touch app/views/groups/edit.html.erb 18 | 19 | 加入 20 | 21 | ~~~~~~~~ 22 | 23 | 24 |
25 | 26 | <%= simple_form_for @group do |f| %> 27 | <%= f.input :title, :input_html => { :class => "input-xxlarge"} %> 28 | <%= f.input :description, :input_html => { :class => "input-xxlarge"} %> 29 | 30 |
31 | <%= f.submit "Submit", :disable_with => 'Submiting...', :class => "btn btn-primary" %> 32 |
33 | <% end %> 34 | 35 |
36 | 37 | ~~~~~~~~ 38 | 39 | 40 | {::pagebreak :/} 41 | 42 | ### 解說 43 | 44 | query 出指定的 Group model object,然後進行編輯。 45 | 46 | ~~~~~~~~ 47 | def edit 48 | @group = Group.find(params[:id]) 49 | end 50 | ~~~~~~~~ -------------------------------------------------------------------------------- /manuscript/chapter-01-1-6-1.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 1.1.6 (補充) update_attributes 與 update 4 | 5 | 在 Rails 4.0.0 前的 update 表單一向是使用 `update_attributes` 這個 API。而到了 Rails 4 之後改用 update。其最大的原因是因為 Security 架構改變。這部份的歷史因素可以參考 Ch 1.1.4。 6 | -------------------------------------------------------------------------------- /manuscript/chapter-01-1-6.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 1.1.6 建立 Groups Controller 裡的 update 4 | 5 | 6 | 在 `app/controllers/groups_controller.rb` 加入 `update` 這個 action 7 | 8 | ~~~~~~~~ 9 | def update 10 | @group = Group.find(params[:id]) 11 | 12 | if @group.update(group_params) 13 | redirect_to group_path(@group) 14 | else 15 | render :edit 16 | end 17 | end 18 | ~~~~~~~~ 19 | 20 | ### 解說 21 | 22 | 這邊也要翻回 app/views/posts/edit.html.erb 這個 view 一起來看。 23 | 24 | ~~~~~~~~ 25 | 26 | 27 |
28 | 29 | <%= simple_form_for @group do |f| %> 30 | <%= f.input :title, :input_html => { :class => "input-xxlarge"} %> 31 | <%= f.input :description, :input_html => { :class => "input-xxlarge"} %> 32 | 33 |
34 | <%= f.submit "Submit", :disable_with => 'Submiting...', :class => "btn btn-primary" %> 35 |
36 | <% end %> 37 | 38 |
39 | 40 | ~~~~~~~~ 41 | 42 | 在 RESTful Rails 的寫法中,對 `group_path` 丟 PUT 就是對應到 update 的動作。form 會背包成一個 hash,如果 @group 能夠吃進 params[:group] 進行更新且成功儲存,就會「重導」到 show action,失敗則「退回」到 edit action。 43 | -------------------------------------------------------------------------------- /manuscript/chapter-01-1-7.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 1.1.7 建立 Groups Controller 裡的 destroy 4 | 5 | 在 `app/controllers/groups_controller.rb` 加入 `destroy` 這個 action 6 | 7 | ~~~~~~~~ 8 | def destroy 9 | @group = Group.find(params[:id]) 10 | 11 | @group.destroy 12 | 13 | redirect_to groups_path 14 | end 15 | ~~~~~~~~ 16 | 17 | 18 | 至此,我們完成了 Group 的 CRUD。 19 | 20 | ### 把首頁改成 Group 的 Index 頁 21 | 22 | 最後修改 `config/routes.rb`,把 root 指向 group 的 index 頁 23 | 24 | `root :to => "groups#index"` 25 | 26 | 27 | {::pagebreak :/} 28 | 29 | ### 解說 30 | 31 | 32 | 這邊要翻回 app/views/posts/index.html.erb 這個 view 一起來看。 33 | 34 | ~~~~~~~~ 35 | 36 | 37 | # 38 | <%= link_to(group.title, group_path(group)) %> 39 | <%= group.description %> 40 | <%= link_to("Edit", edit_group_path(group), :class => "btn btn-mini" ) %> 41 | <%= link_to("Delete", group_path(group), :class => "btn btn-mini", :method => :delete, :confirm => "Are you sure?" ) %> 42 | 43 | 44 | ~~~~~~~~ 45 | 46 | 47 | 找到該筆資料並刪除之。在 RESTful Rails 的寫法中,對 `group_path` 丟一個 DELETE,就會對應到 destroy 的動作。可看一下 `link_to "Destroy"`那一行。 -------------------------------------------------------------------------------- /manuscript/chapter-02-1-0.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 2.1 建立 Post 4 | 5 | ### 建立 Post 這個 model 6 | 7 | 執行 8 | 9 | `rails g model post content:text group_id:integer` 10 | 11 | ~~~~~~~~ 12 | invoke active_record 13 | create db/migrate/20130529200707_create_posts.rb 14 | create app/models/post.rb 15 | invoke test_unit 16 | create test/models/post_test.rb 17 | create test/fixtures/posts.yml 18 | ~~~~~~~~ 19 | 20 | 生成 Post 這個 model。 21 | 22 | 然後跑 `rake db:migrate`。 23 | 24 | ~~~~~~~~ 25 | == CreatePosts: migrating ==================================================== 26 | -- create_table(:posts) 27 | -> 0.1297s 28 | == CreatePosts: migrated (0.1298s) =========================================== 29 | ~~~~~~~~ 30 | 31 | 32 | {::pagebreak :/} 33 | 34 | ### 建立 posts 這個 controller 35 | 36 | 執行 `rails g controller posts` 37 | 38 | ~~~~~~~~ 39 | create app/controllers/posts_controller.rb 40 | invoke erb 41 | create app/views/posts 42 | invoke test_unit 43 | create test/controllers/posts_controller_test.rb 44 | invoke helper 45 | create app/helpers/posts_helper.rb 46 | invoke test_unit 47 | create test/helpers/posts_helper_test.rb 48 | invoke assets 49 | invoke coffee 50 | create app/assets/javascripts/posts.js.coffee 51 | invoke scss 52 | create app/assets/stylesheets/posts.css.scss 53 | ~~~~~~~~ 54 | 55 | 56 | ### 把 posts 加入 routing 57 | 58 | 到 `config/routes.rb` 修改原有的 59 | 60 | ~~~~~~~~~ 61 | resources :groups 62 | ~~~~~~~~~ 63 | 64 | 變成 65 | 66 | 67 | ~~~~~~~~~ 68 | resources :groups do 69 | resources :posts 70 | end 71 | ~~~~~~~~~ 72 | 73 | {::pagebreak :/} 74 | 75 | 76 | ### 加入 Group 與 Post 的關聯 77 | 78 | 修改 `app/models/group.rb` 加入 `has_many :posts` 79 | 80 | ~~~~~~~~~ 81 | class Group < ActiveRecord::Base 82 | 83 | has_many :posts 84 | validates :title, :presence => true 85 | 86 | end 87 | ~~~~~~~~~ 88 | 89 | 修改 `app/models/post.rb` 加入 `belongs_to :group` 90 | 91 | ~~~~~~~~~ 92 | class Post < ActiveRecord::Base 93 | belongs_to :group 94 | end 95 | 96 | ~~~~~~~~~ 97 | 98 | 99 | -------------------------------------------------------------------------------- /manuscript/chapter-02-1-1.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 2.1.1 在 Groups controller 的 show 裡面撈出相關的 Post 4 | 5 | 到 `app/controllers/groups_controller.rb`,將 show 修改成以下內容 6 | 7 | ~~~~~~~~~ 8 | def show 9 | @group = Group.find(params[:id]) 10 | @posts = @group.posts 11 | end 12 | 13 | ~~~~~~~~~ 14 | 15 | {::pagebreak :/} 16 | 17 | #### 補上 view: 18 | 19 | 修改 `app/views/groups/show.html.erb` 20 | 21 | ~~~~~~~~ 22 | 23 |
24 |
25 | <%= link_to("Edit", edit_group_path(@group) , :class => "btn btn-mini ")%> 26 | 27 | <%= link_to("New Post", new_group_post_path(@group) , :class => "btn btn-mini btn-primary")%> 28 |   29 |
30 |

<%= @group.title %>

31 |

<%= @group.description %>

32 | 33 | 34 | 35 | 36 | 37 | <% @posts.each do |post| %> 38 | 39 | 40 | 42 | 43 | <% end %> 44 | 45 |
<%= post.content %> <%= link_to("Edit", edit_group_post_path(post.group, post), :class => "btn btn-mini")%> 41 | <%= link_to("Delete", group_post_path(post.group, post), :class => "btn btn-mini", :method => :delete, :confirm => "Are you sure?" ) %>
46 |
47 | 48 | 49 | ~~~~~~~~ 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /manuscript/chapter-02-1-2.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 2.1.2 建立 Posts Controller 裡的 new 4 | 5 | 在 `app/controllers/posts_controller.rb` 加入 `new` 這個 action 6 | 7 | ~~~~~~~~ 8 | def new 9 | @group = Group.find(params[:group_id]) 10 | @post = @group.posts.build 11 | end 12 | ~~~~~~~~ 13 | 14 | 15 | ### 補上 view 16 | 17 | `touch app/views/posts/new.html.erb` 18 | 19 | 加入 20 | 21 | ~~~~~~~~ 22 | 23 |
24 | 25 | <%= simple_form_for [@group,@post] do |f| %> 26 | <%= f.input :content, :input_html => { :class => "input-xxlarge"} %> 27 | 28 |
29 | <%= f.submit "Submit", :disable_with => 'Submiting...', :class => "btn btn-primary" %> 30 |
31 | <% end %> 32 | 33 |
34 | 35 | ~~~~~~~~ 36 | 37 | -------------------------------------------------------------------------------- /manuscript/chapter-02-1-3.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 2.1.3 建立 Posts Controller 裡的 create 4 | 5 | 在 `app/controllers/posts_controller.rb` 加入 `create` 這個 action 6 | 7 | ~~~~~~~~ 8 | 9 | def create 10 | @group = Group.find(params[:group_id]) 11 | @post = @group.posts.new(post_params) 12 | 13 | if @post.save 14 | redirect_to group_path(@group) 15 | else 16 | render :new 17 | end 18 | end 19 | 20 | private 21 | 22 | 23 | def post_params 24 | params.require(:post).permit(:content) 25 | end 26 | 27 | ~~~~~~~~ 28 | 29 | 不過此時,我們發現 Post 其實也需要被驗證。如果一個 Post 沒有 content,應該也算是不合法的 post? 30 | 31 | 我們應該要限制沒有輸入 content 的 post,必須要退回到 `new` 這個表單。 32 | 33 | 34 | 修改 `app/models/post.rb`,限制 post 一定要有內容 35 | 36 | ~~~~~~~~ 37 | 38 | class Post < ActiveRecord::Base 39 | 40 | belongs_to :group 41 | validates :content, :presence => true 42 | 43 | end 44 | 45 | ~~~~~~~~ 46 | 47 | 48 | {::pagebreak :/} 49 | 50 | 51 | ### 解說 52 | 53 | Rails 的 ORM 內建一系列 Validation 的 API,可以幫助 Developer 快速驗證資料的類型與格式。 54 | 55 | 56 | -------------------------------------------------------------------------------- /manuscript/chapter-02-1-4.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 2.1.4 建立 Posts Controller 裡的 edit 4 | 5 | 在 `app/controllers/groups_controller.rb` 加入 `edit` 這個 action 6 | 7 | ~~~~~~~~ 8 | def edit 9 | @group = Group.find(params[:group_id]) 10 | @post = @group.posts.find(params[:id]) 11 | end 12 | 13 | ~~~~~~~~ 14 | 15 | 16 | ### 補上 view 17 | 18 | touch app/views/posts/edit.html.erb 19 | 20 | ~~~~~~~~ 21 | 22 |
23 | 24 | <%= simple_form_for [@group,@post] do |f| %> 25 | <%= f.input :content, :input_html => { :class => "input-xxlarge"} %> 26 | 27 |
28 | <%= f.submit "Submit", :disable_with => 'Submiting...', :class => "btn btn-primary" %> 29 |
30 | <% end %> 31 | 32 |
33 | 34 | ~~~~~~~~ 35 | -------------------------------------------------------------------------------- /manuscript/chapter-02-1-5.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 2.1.5 建立 Posts Controller 裡的 update 4 | 5 | 6 | 在 `app/controllers/posts_controller.rb` 加入 `update` 這個 action 7 | 8 | ~~~~~~~~ 9 | def update 10 | @group = Group.find(params[:group_id]) 11 | @post = @group.posts.find(params[:id]) 12 | 13 | if @post.update(post_params) 14 | redirect_to group_path(@group) 15 | else 16 | render :edit 17 | end 18 | end 19 | ~~~~~~~~ 20 | -------------------------------------------------------------------------------- /manuscript/chapter-02-1-6.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 2.1.6 建立 Posts Controller 裡的 destroy 4 | 5 | 在 `app/controllers/groups_controller.rb` 加入 `destroy` 這個 action 6 | 7 | ~~~~~~~~ 8 | def destroy 9 | @group = Group.find(params[:group_id]) 10 | @post = @group.posts.find(params[:id]) 11 | 12 | @post.destroy 13 | 14 | redirect_to group_path(@group) 15 | end 16 | ~~~~~~~~ 17 | 18 | 19 | 至此,我們完成了 Post 的 CRUD。 20 | -------------------------------------------------------------------------------- /manuscript/chapter-02-1-7.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 2.1.7 以 before_action 整理重複的程式碼 4 | 5 | 有沒有覺得 Posts 裡的每個 action 裡面都有一行 6 | 7 | ~~~~~~~~ 8 | @group = Group.find(params[:group_id]) 9 | ~~~~~~~~ 10 | 11 | 很冗餘呢? 12 | 13 | 我們可以利用 `before_action` 這個技巧,把重複的程式碼去掉: 14 | 15 | 首先在 private 底下,新增一個 `find_group` method 16 | 17 | 18 | ~~~~~~~~ 19 | def find_group 20 | @group = Group.find(params[:group_id]) 21 | end 22 | ~~~~~~~~ 23 | 24 | 然後在 `class PostsController < ApplicationController` 下,加一行 25 | 26 | ~~~~~~~~ 27 | before_action :find_group 28 | ~~~~~~~~ 29 | 30 | 再把每個 action 的這一行砍掉 31 | 32 | ~~~~~~~~ 33 | @group = Group.find(params[:group_id]) 34 | ~~~~~~~~ 35 | 36 | 最後的成品會長這樣 37 | 38 | ~~~~~~~~ 39 | class PostsController < ApplicationController 40 | 41 | before_action :find_group 42 | 43 | def new 44 | @post = @group.posts.build 45 | end 46 | 47 | def create 48 | 49 | @post = @group.posts.new(post_params) 50 | 51 | if @post.save 52 | redirect_to group_path(@group) 53 | else 54 | render :new 55 | end 56 | end 57 | 58 | def edit 59 | @post = @group.posts.find(params[:id]) 60 | end 61 | 62 | def update 63 | 64 | @post = @group.posts.find(params[:id]) 65 | 66 | if @post.update(post_params) 67 | redirect_to group_path(@group) 68 | else 69 | render :edit 70 | end 71 | end 72 | 73 | 74 | def destroy 75 | 76 | @post = @group.posts.find(params[:id]) 77 | 78 | @post.destroy 79 | 80 | redirect_to group_path(@group) 81 | end 82 | 83 | 84 | private 85 | 86 | 87 | def find_group 88 | @group = Group.find(params[:group_id]) 89 | end 90 | 91 | def post_params 92 | params.require(:post).permit(:content) 93 | end 94 | end 95 | 96 | ~~~~~~~~ 97 | 98 | {::pagebreak :/} 99 | 100 | ### 解說 101 | 102 | before_action 是一個常見的 controller 技巧,用來收納重複的程式碼。 103 | 104 | before_action 可以用 only,指定某些 action 執行: 105 | 106 | ~~~~~~~~ 107 | before_action :find_group, :only => [:edit, :update] 108 | ~~~~~~~~ 109 | 110 | 或者使用 except,排除某些 action 不執行: 111 | 112 | ~~~~~~~~ 113 | before_action :find_group, :except => [:show, :index] 114 | ~~~~~~~~ 115 | -------------------------------------------------------------------------------- /manuscript/chapter-02.txt: -------------------------------------------------------------------------------- 1 | # 練習作業 2 - 在 Group 裡面發表文章 - 雙層 RESTFul 2 | 3 | 上一章我們完成了 Group 的 CRUD,這一章的練習目標是在 Group 裡面發表文章。並且文章網址是使用 http://groupme.dev/group/1/post/2 這種網址表示。 4 | 5 | ### 本章練習主題 6 | 7 | * 學會 has_many, belongs_to 8 | * 學會 resources 的設定 ( 雙層 resources ) 9 | * 使用 before_action 整理程式碼 10 | 11 | -------------------------------------------------------------------------------- /manuscript/chapter-03-0.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 3.0 devise 與 Rails 4 4 | 5 | ### 安裝 gem 6 | 7 | Rails 內,安裝 gem 的方式是透過 Bundler 這個工具。具體方式是修改 `Gemfile` 這個檔案,加入所需要安裝的 gem,再執行 `bundle install`,即能安裝完畢。 8 | 9 | ### 安裝 devise 10 | 11 | 12 | [devise](https://github.com/plataformatec/devise) 是目前 Rails 界最被廣為使用的認證系統。其彈性的設計支援很多實務上的需求,如:鎖定賬號(Lockable)、需要認證(Confirmable)、取回帳號(Recoverable)、與第三方認證如 Facebook 整合( OmniAuthable)。 13 | 14 | 在本書裡面我們使用的 Bootstrappers 內建即幫我們安裝好 `devise` 這個 gem。 15 | 16 | 不過你還是可以開一個空專案,實際裝一下練習看看。 17 | 18 | 19 | 修改 `Gemfile` 20 | 21 | ~~~~~~~~~ 22 | gem 'devise' 23 | ~~~~~~~~~ 24 | 25 | 使用 bundle 安裝 26 | 27 | `bundle install` 28 | 29 | 產生必要檔案 30 | 31 | `rails g devise:install` 32 | 33 | 產生 user model 檔案 34 | 35 | `rails generate devise user` 36 | 37 | 38 | #### devise 相關連結 39 | 40 | * 註冊 `<%= link_to( "Sign Up" ,new_user_registration_path) %> ` 41 | * 登入 `<%= link_to( "Login", new_user_session_path ) %> ` 42 | * 登出 `<%= link_to("Logout",destroy_user_session_path, :method => :delete ) %> ` 43 | 44 | 45 | 46 | ### devise 相關 method 47 | 48 | 1. 判斷現在使用者是否登入了,可以使用 current_user.blank?。 49 | 2. 要取現在這個登入的使用者資料,可以使用 current_user 50 | 51 | {::pagebreak :/} 52 | 53 | ### login_required 54 | 55 | Bootstrappers 在 `app/controller/application_controller.rb` 也先預幫開發者準備好一個 method:`login_required`。 56 | 57 | 58 | ~~~~~~~~~ 59 | 60 | def login_required 61 | if current_user.blank? 62 | respond_to do |format| 63 | format.html { 64 | authenticate_user! 65 | } 66 | format.js{ 67 | render :partial => "common/not_logined" 68 | } 69 | format.all { 70 | head(:unauthorized) 71 | } 72 | end 73 | end 74 | 75 | end 76 | 77 | ~~~~~~~~~ 78 | 79 | 如果開發者只是單純想限制哪一個 action 需要登入才能使用,只要掛上 before_action ,再指定即可。 80 | 81 | 82 | {::pagebreak :/} 83 | 84 | ### 客製化 devise 85 | 86 | 原始的 devise 設計,只有 email 與 password 的設計,並沒有 name 的欄位。雖然 Bootstrappers 幫忙也生了 name 的欄位,但是我們還是得在 devise 的註冊表單加進去 name 才行,否則註冊進去的 user 都會沒有 name。 87 | 88 | devise 的 view 預設是隱藏起來,直接使用 gem 內的 view。要客製化必須要先使用 generator 生出來,再修改複寫。 89 | 90 | 具體的步驟是執行: 91 | 92 | `rails g devise:views` 93 | 94 | 95 | 會生成一堆檔案 96 | 97 | ~~~~~~~~~ 98 | invoke Devise::Generators::SharedViewsGenerator 99 | create app/views/devise/shared 100 | create app/views/devise/shared/_links.erb 101 | invoke simple_form_for 102 | create app/views/devise/confirmations 103 | create app/views/devise/confirmations/new.html.erb 104 | create app/views/devise/passwords 105 | create app/views/devise/passwords/edit.html.erb 106 | create app/views/devise/passwords/new.html.erb 107 | create app/views/devise/registrations 108 | create app/views/devise/registrations/edit.html.erb 109 | create app/views/devise/registrations/new.html.erb 110 | create app/views/devise/sessions 111 | create app/views/devise/sessions/new.html.erb 112 | create app/views/devise/unlocks 113 | create app/views/devise/unlocks/new.html.erb 114 | invoke erb 115 | create app/views/devise/mailer 116 | create app/views/devise/mailer/confirmation_instructions.html.erb 117 | create app/views/devise/mailer/reset_password_instructions.html.erb 118 | create app/views/devise/mailer/unlock_instructions.html.erb 119 | ~~~~~~~~~ 120 | 121 | {::pagebreak :/} 122 | 123 | ### 修改註冊表單 124 | 125 | 註冊表單的檔案是 `app/views/devise/registrations/new.html.erb`,加入一行 name 使之成為 126 | 127 | ~~~~~~~~~ 128 |

Sign up

129 | 130 | <%= simple_form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %> 131 | <%= f.error_notification %> 132 | 133 |
134 | <%= f.input :email, :required => true, :autofocus => true %> 135 | <%= f.input :name, :required => true %> 136 | <%= f.input :password, :required => true %> 137 | <%= f.input :password_confirmation, :required => true %> 138 |
139 | 140 |
141 | <%= f.button :submit, "Sign up" %> 142 |
143 | <% end %> 144 | 145 | <%= render "users/shared/links" %> 146 | ~~~~~~~~~ 147 | 148 | ### 加入 strong_parameters 與 devise 整合的 hack 149 | 150 | 在 `app/controller/application_controller` 加上這些設定: 151 | 152 | ~~~~~~ 153 | 154 | before_filter :configure_permitted_parameters, if: :devise_controller? 155 | 156 | 157 | protected 158 | 159 | def configure_permitted_parameters 160 | devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:name, :email, :password, :password_confirmation) } 161 | end 162 | 163 | ~~~~~~ 164 | 165 | I> 166 | 167 | -------------------------------------------------------------------------------- /manuscript/chapter-03-1.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 3.1 對需要登入才能使用的 Action 加入限制 4 | 5 | 根據需求:使用者必須能夠 註冊 / 登入,登入後才可以開設 Group,與發表 Post。 6 | 7 | 所以我們要在 Groups controller 加入: 8 | 9 | ~~~~~~~~~ 10 | class GroupsController < ApplicationController 11 | 12 | before_action :login_required, :only => [:new, :create, :edit,:update,:destroy] 13 | ~~~~~~~~~ 14 | 15 | 在 Posts controller 加入: 16 | 17 | ~~~~~~~~~ 18 | class PostsController < ApplicationController 19 | 20 | before_action :login_required, :only => [:new, :create, :edit,:update,:destroy] 21 | ~~~~~~~~~ 22 | 23 | 24 | -------------------------------------------------------------------------------- /manuscript/chapter-03-2.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 3.2 讓 Group 與 User 產生關聯: 4 | 5 | 新增一條 migration: `rails g migration add_user_id_to_group` 6 | 7 | ~~~~~~~~~ 8 | invoke active_record 9 | create db/migrate/20130531141923_add_user_id_to_group.rb 10 | ~~~~~~~~~ 11 | 12 | 填入以下內容 13 | 14 | ~~~~~~~~~ 15 | class AddUserIdToGroup < ActiveRecord::Migration 16 | def change 17 | add_column :groups, :user_id, :integer 18 | end 19 | end 20 | ~~~~~~~~~ 21 | 22 | 執行 `rake db:migrate` 23 | 24 | ~~~~~~~~~ 25 | == AddUserIdToGroup: migrating =============================================== 26 | -- add_column(:groups, :user_id, :integer) 27 | -> 0.0213s 28 | == AddUserIdToGroup: migrated (0.0214s) ====================================== 29 | ~~~~~~~~~ 30 | 31 | 32 | 修改 `app/models/user.rb` 加入 `has_many :groups` 33 | 34 | 內容如下 35 | 36 | ~~~~~~~~~ 37 | class User < ActiveRecord::Base 38 | # Include default devise modules. Others available are: 39 | # :token_authenticatable, :confirmable, 40 | # :lockable, :timeoutable and :omniauthable 41 | 42 | has_many :groups 43 | 44 | extend OmniauthCallbacks 45 | 46 | devise :database_authenticatable, :registerable, 47 | :recoverable, :rememberable, :trackable, :validatable, :omniauthable 48 | 49 | 50 | end 51 | ~~~~~~~~~ 52 | 53 | {::pagebreak :/} 54 | 55 | 修改 `app/models/group.rb` 加入 56 | 57 | ~~~~~~~~~ 58 | belongs_to :owner, :class_name => "User", :foreign_key => :user_id 59 | 60 | def editable_by?(user) 61 | user && user == owner 62 | end 63 | ~~~~~~~~~ 64 | 65 | 66 | 內容如下: 67 | 68 | ~~~~~~~~~ 69 | class Group < ActiveRecord::Base 70 | 71 | belongs_to :owner, :class_name => "User", :foreign_key => :user_id 72 | has_many :posts 73 | 74 | validates :title, :presence => true 75 | 76 | 77 | def editable_by?(user) 78 | user && user == owner 79 | end 80 | end 81 | 82 | 83 | ~~~~~~~~~ 84 | 85 | {::pagebreak :/} 86 | 87 | 接著我們要把 Groups 的幾個 action 內容替換掉: 88 | 89 | ### create 90 | 91 | ~~~~~~~~~ 92 | 93 | def create 94 | @group = current_user.groups.build(group_params) 95 | if @group.save 96 | redirect_to groups_path 97 | else 98 | render :new 99 | end 100 | end 101 | ~~~~~~~~~ 102 | 103 | ### edit 104 | 105 | ~~~~~~~~~ 106 | def edit 107 | @group = current_user.groups.find(params[:id]) 108 | end 109 | ~~~~~~~~~ 110 | 111 | 112 | ### update 113 | 114 | ~~~~~~~~~ 115 | def update 116 | @group = current_user.groups.find(params[:id]) 117 | 118 | if @group.update(group_params) 119 | redirect_to group_path(@group) 120 | else 121 | render :edit 122 | end 123 | end 124 | ~~~~~~~~~ 125 | 126 | {::pagebreak :/} 127 | 128 | 129 | ### destroy 130 | 131 | ~~~~~~~~~ 132 | def destroy 133 | @group = current_user.groups.find(params[:id]) 134 | 135 | @group.destroy 136 | 137 | redirect_to groups_path 138 | end 139 | ~~~~~~~~~ 140 | 141 | {::pagebreak :/} 142 | 143 | 把 app/views/groups/index.html.erb 的內容換掉 144 | 145 | ~~~~~~~~~ 146 | 147 |
148 |
149 | <%= link_to("New group", new_group_path , :class => "btn btn-mini btn-primary pull-right")%> 150 |
151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | <% @groups.each do |group| %> 161 | 162 | 163 | 164 | 165 | 166 | 172 | 173 | <% end %> 174 | 175 |
# Title Description Owner
# <%= link_to(group.title, group_path(group)) %> <%= group.description %> <%= group.owner.name %> 167 | <% if current_user && group.editable_by?(current_user) %> 168 | <%= link_to("Edit", edit_group_path(group), :class => "btn btn-mini")%> 169 | <%= link_to("Delete", group_path(group), :class => "btn btn-mini", :method => :delete, :confirm => "Are you sure?" ) %> 170 | <% end %> 171 |
176 |
177 | 178 | ~~~~~~~~~ 179 | 180 | {::pagebreak :/} 181 | 182 | ### 解說 183 | 184 | 在前面一章的例子裡面。我們都是使用 ` @group = Group.find(params[:id])`。 185 | 186 | 但是這樣無法確保這個 action 的安全性。其實這幾個 action 必須是要「限定」本人才能修改的。但是初學者可能會寫出這樣的 code。 187 | 188 | ~~~~~~~~~ 189 | def edit 190 | @group = Group.find(params[:id]) 191 | 192 | if @group.user != current_user 193 | flash[:warning] = "No Permission" 194 | redirec_to root_path 195 | end 196 | 197 | end 198 | ~~~~~~~~~ 199 | 200 | 事實上,我們可以只用 `current_user.groups` 這樣的天然限制。就足以阻擋掉這樣的非法存取了。 201 | 202 | ~~~~~~~~~ 203 | def edit 204 | @group = current_user.groups.find(params[:id]) 205 | end 206 | ~~~~~~~~~ 207 | 208 | 在這個情況下,Rails 在 Production 環境上面會直接跳 404 找不到頁面。直接確保該頁面的安全性。 209 | 210 | 以下的 create 這個例子也是類似的狀況。原先開發者可能會寫出 211 | 212 | ~~~~~~~~~ 213 | 214 | def create 215 | @group = Group.new(group_params) 216 | @group.user = current_user 217 | if @group.save 218 | redirect_to groups_path 219 | else 220 | render :new 221 | end 222 | end 223 | ~~~~~~~~~ 224 | 225 | {::pagebreak :/} 226 | 227 | 利用內建的 association 就可以改寫成以下範例: 228 | 229 | ~~~~~~~~~ 230 | 231 | def create 232 | @group = current_user.groups.build(group_params) 233 | if @group.save 234 | redirect_to groups_path 235 | else 236 | render :new 237 | end 238 | end 239 | ~~~~~~~~~ 240 | 241 | 效果是一樣的。 242 | -------------------------------------------------------------------------------- /manuscript/chapter-03-3.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 3.3 讓 Post 與 User 產生關聯: 4 | 5 | 6 | 新增一條 migration: `rails g migration add_user_id_to_post` 7 | 8 | ~~~~~~~~~ 9 | invoke active_record 10 | create db/migrate/20130531153435_add_user_id_to_post.rb 11 | ~~~~~~~~~ 12 | 13 | 填入以下內容 14 | 15 | ~~~~~~~~~ 16 | class AddUserIdToPost < ActiveRecord::Migration 17 | def change 18 | add_column :posts, :user_id, :integer 19 | end 20 | end 21 | 22 | ~~~~~~~~~ 23 | 24 | 25 | 執行 `rake db:migrate` 26 | 27 | ~~~~~~~~~ 28 | == AddUserIdToPost: migrating ================================================ 29 | -- add_column(:posts, :user_id, :integer) 30 | -> 0.0176s 31 | == AddUserIdToPost: migrated (0.0177s) ======================================= 32 | ~~~~~~~~~ 33 | 34 | 35 | 修改 `app/models/user.rb` 加入 `has_many :posts` 36 | 37 | 38 | 內容如下 39 | 40 | ~~~~~~~~~ 41 | class User < ActiveRecord::Base 42 | # Include default devise modules. Others available are: 43 | # :token_authenticatable, :confirmable, 44 | # :lockable, :timeoutable and :omniauthable 45 | 46 | has_many :groups 47 | has_many :posts 48 | 49 | extend OmniauthCallbacks 50 | 51 | devise :database_authenticatable, :registerable, 52 | :recoverable, :rememberable, :trackable, :validatable, :omniauthable 53 | 54 | 55 | end 56 | 57 | 58 | ~~~~~~~~~ 59 | 60 | 61 | 修改 `app/models/post.rb` 加入 62 | 63 | ~~~~~~~~~ 64 | belongs_to :author, :class_name => "User", :foreign_key => :user_id 65 | 66 | def editable_by?(user) 67 | user && user == author 68 | end 69 | ~~~~~~~~~ 70 | 71 | 內容如下: 72 | 73 | ~~~~~~~~~ 74 | 75 | class Post < ActiveRecord::Base 76 | 77 | belongs_to :group 78 | validates :content, :presence => true 79 | 80 | belongs_to :author, :class_name => "User", :foreign_key => :user_id 81 | 82 | def editable_by?(user) 83 | user && user == author 84 | end 85 | 86 | end 87 | ~~~~~~~~~ 88 | 89 | {::pagebreak :/} 90 | 91 | 接著我們要把 Posts 的幾個 action 內容替換掉: 92 | 93 | ### create 94 | 95 | ~~~~~~~~ 96 | def create 97 | 98 | @post = @group.posts.new(post_params) 99 | @post.author = current_user 100 | 101 | if @post.save 102 | redirect_to group_path(@group) 103 | else 104 | render :new 105 | end 106 | end 107 | ~~~~~~~~ 108 | 109 | ### edit 110 | 111 | ~~~~~~~~ 112 | def edit 113 | @post = current_user.posts.find(params[:id]) 114 | end 115 | ~~~~~~~~ 116 | 117 | 118 | ### update 119 | 120 | ~~~~~~~~ 121 | def update 122 | 123 | @post = current_user.posts.find(params[:id]) 124 | 125 | if @post.update(post_params) 126 | redirect_to group_path(@group) 127 | else 128 | render :edit 129 | end 130 | end 131 | ~~~~~~~~ 132 | 133 | {::pagebreak :/} 134 | 135 | 136 | ### destroy 137 | 138 | ~~~~~~~~ 139 | def destroy 140 | 141 | @post = current_user.posts.find(params[:id]) 142 | 143 | @post.destroy 144 | 145 | redirect_to group_path(@group) 146 | end 147 | ~~~~~~~~ 148 | 149 | 把 app/views/groups/show.html.erb 的內容換掉 150 | 151 | ~~~~~~~~ 152 | 153 |
154 | 155 | 156 |
157 | <% if current_user && @group.editable_by?(current_user) %> 158 | <%= link_to("Edit", edit_group_path(@group) , :class => "btn btn-mini ")%> 159 | <% end %> 160 | 161 | <%= link_to("New Post", new_group_post_path(@group) , :class => "btn btn-mini btn-primary") if current_user )%> 162 |   163 |
164 | 165 |

<%= @group.title %>

166 | 167 |

<%= @group.description %>

168 | 169 | 170 | 171 | 172 | 173 | <% @posts.each do |post| %> 174 | 175 | 182 | 183 | <% if current_user && post.editable_by?(current_user) %> 184 | 186 | 187 | <% end %> 188 | <% end %> 189 | 190 |
176 | 177 | Author : <%= post.author.name %> 178 |

179 | <%= post.content %> 180 |

181 |
<%= link_to("Edit", edit_group_post_path(post.group, post), :class => "btn btn-mini")%> 185 | <%= link_to("Delete", group_post_path(post.group, post), :class => "btn btn-mini", :method => :delete, :confirm => "Are you sure?" ) %>
191 |
192 | 193 | ~~~~~~~~ 194 | -------------------------------------------------------------------------------- /manuscript/chapter-03.txt: -------------------------------------------------------------------------------- 1 | # 練習作業 3 - 為 Group 與 Post 加入使用者機制 2 | 3 | 在上一章我們完成了在 Group 裡面發表文章的功能。但是通常一個討論區的機制,必須是先加入會員才能進行相關動作。所以我們必須為討論區加入使用者機制: 4 | 5 | 使用者必須能夠 註冊 / 登入,登入後才可以開設 Group,與發表 Post,不然只能瀏覽。只有自己的 Group,Post 才能進行修改與刪除。 6 | 7 | ### 本章練習主題 8 | 9 | * 安裝 gem 10 | * 設定 devise 11 | * 撰寫全域的 method `login_required` 12 | * 利用 before_action 結合 login_required 加入登入判斷 13 | * session 的使用:current_user 14 | 15 | ### 本章參考資料 16 | 17 | * [devise](https://github.com/plataformatec/devise/wiki) 18 | 19 | 20 | -------------------------------------------------------------------------------- /manuscript/chapter-04-1-2.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 4.1.2 model method 與 after create 4 | 5 | 實作以下 method 在 User model 內 6 | 7 | ~~~~~~~~~~ 8 | 9 | def join!(group) 10 | participated_groups << group 11 | end 12 | 13 | def quit!(group) 14 | participated_groups.delete(group) 15 | end 16 | 17 | def is_member_of?(group) 18 | participated_groups.include?(group) 19 | end 20 | 21 | ~~~~~~~~~~ 22 | 23 | 這樣我們之後,就可以在 controller ,使用 current_user.join!(group) 這樣的語法來加入 group。 24 | 25 | 26 | ### 產生 Group 後自動加入 group 作為 group 的一員 27 | 28 | 在一般的認知中,Group 的開創者應該就要是 group 的一員。這有兩種作法,一種是到 `app/controller/groups_controller.rb` 裡的 `create` action 裡面加入 `current_user.join!(@group)` 29 | 30 | ~~~~~~~~~~ 31 | def create 32 | @group = current_user.groups.build(group_params) 33 | if @group.save 34 | current_user.join!(@group) 35 | redirect_to groups_path 36 | else 37 | render :new 38 | end 39 | end 40 | ~~~~~~~~~~ 41 | 42 | 或者是你也可以使用 ActiveRecord 的 after_create,更漂亮的實作: 43 | 44 | ~~~~~~~~~~ 45 | 46 | class Group < ActiveRecord::Base 47 | 48 | belongs_to :owner, :class_name => "User", :foreign_key => :user_id 49 | has_many :posts 50 | 51 | has_many :group_users 52 | has_many :members, :through => :group_users, :source => :user 53 | 54 | 55 | validates :title, :presence => true 56 | 57 | after_create :join_owner_to_group 58 | 59 | def editable_by?(user) 60 | user && user == owner 61 | end 62 | 63 | def join_owner_to_group 64 | members << owner 65 | end 66 | 67 | end 68 | 69 | 70 | ~~~~~~~~~~ 71 | 72 | ### 解說 73 | 74 | #### after_create 75 | 76 | `after_create` 是 ActiveRecord 提供的 callbacks,意旨在簡化程式碼。類似的 callbacks 還有 77 | 78 | * before_create 79 | * before_update 80 | * after_create 81 | * after_update 82 | 83 | 等等... 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /manuscript/chapter-04-1-3.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | 4 | ## Ch 4.1.3 join 與 quit action 5 | 6 | 在 Groups Controller 加入以下這兩個 action 7 | 8 | ~~~~~~~~~~ 9 | 10 | def join 11 | @group = Group.find(params[:id]) 12 | 13 | if !current_user.is_member_of?(@group) 14 | current_user.join!(@group) 15 | else 16 | flash[:warning] = "You already joined this group." 17 | end 18 | redirect_to group_path(@group) 19 | end 20 | 21 | def quit 22 | @group = Group.find(params[:id]) 23 | 24 | if current_user.is_member_of?(@group) 25 | current_user.quit!(@group) 26 | else 27 | flash[:warning] = "You are not member of this group." 28 | end 29 | 30 | redirect_to group_path(@group) 31 | 32 | end 33 | ~~~~~~~~~~ 34 | 35 | 然後在 config/routes.rb 修改 `resources :groups` 這一段,變成: 36 | 37 | ~~~~~~~~~~ 38 | resources :groups do 39 | member do 40 | post :join 41 | post :quit 42 | end 43 | resources :posts 44 | end 45 | ~~~~~~~~~~ 46 | 47 | 再修改 `app/views/groups/show.html.erb` 加入這一段: 48 | 49 | ~~~~~~~~~~ 50 | <% if current_user %> 51 |
52 | <% if current_user.is_member_of?(@group) %> 53 | <%= link_to("Quit Group", quit_group_path(@group), :method => :post, :class => "btn btn-mini") %> 54 | <% else %> 55 | <%= link_to("Join Group", join_group_path(@group), :method => :post, :class => "btn btn-mini") %> 56 | <% end %> 57 |
58 | <% end %> 59 | ~~~~~~~~~~ 60 | 61 | 62 | 修改 `app/views/groups/show.html.erb` 原先的 63 | 64 | ~~~~~~~~~~ 65 | <%= link_to("New Post", new_group_post_path(@group) , :class => "btn btn-mini btn-primary") if current_user %> 66 | ~~~~~~~~~~ 67 | 68 | 變成 69 | 70 | ~~~~~~~~~~ 71 | <%= link_to("New Post", new_group_post_path(@group) , :class => "btn btn-mini btn-primary") if current_user.is_member_of?(@group) %> 72 | 73 | ~~~~~~~~~~ 74 | 75 | ### 在 controller 裡加入 member_required 76 | 77 | 在 Post 這個 controller 裡面加入另一個 before_action 78 | 79 | ~~~~~~~~~~ 80 | before_action :member_required, :only => [:new, :create ] 81 | ~~~~~~~~~~ 82 | 83 | 在 `private` 下新增 `member_required` 這個 method 84 | 85 | ~~~~~~~~~~ 86 | def member_required 87 | if !current_user.is_member_of?(@group) 88 | flash[:warning] = " You are not member of this group!" 89 | redirect_to group_path(@group) 90 | end 91 | end 92 | ~~~~~~~~~~ 93 | -------------------------------------------------------------------------------- /manuscript/chapter-04-1.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 4.1 使用者必須要是這個社團的成員才能發表文章 4 | 5 | 在第 2 章、第 3 章我們完成了 Group 與 Post 的新增與管理。在這一章我們要挑戰一點進階的課題。 6 | 7 | ### 4.1.1 has_many :through 8 | 9 | 新增 group_user 這個 model:`rails g model group_user group_id:integer user_id:integer` 10 | 11 | 執行 migration:`rake db:migrate` 12 | 13 | 14 | ~~~~~~~~~ 15 | 16 | == CreateGroupUsers: migrating =============================================== 17 | -- create_table(:group_users) 18 | -> 0.0238s 19 | == CreateGroupUsers: migrated (0.0239s) ====================================== 20 | 21 | ~~~~~~~~~ 22 | 23 | 在 User model 加入 24 | 25 | ~~~~~~~~~ 26 | has_many :group_users 27 | has_many :participated_groups, :through => :group_users, :source => :group 28 | ~~~~~~~~~ 29 | 30 | 內容變成以下 31 | 32 | ~~~~~~~~~ 33 | class User < ActiveRecord::Base 34 | 35 | has_many :groups 36 | has_many :posts 37 | 38 | has_many :group_users 39 | has_many :participated_groups, :through => :group_users, :source => :group 40 | 41 | extend OmniauthCallbacks 42 | 43 | devise :database_authenticatable, :registerable, 44 | :recoverable, :rememberable, :trackable, :validatable, :omniauthable 45 | 46 | 47 | def join!(group) 48 | participated_groups << group 49 | end 50 | 51 | def quit!(group) 52 | participated_groups.delete(group) 53 | end 54 | 55 | def is_member_of?(group) 56 | participated_groups.include?(group) 57 | end 58 | 59 | end 60 | 61 | ~~~~~~~~~ 62 | 63 | 64 | {::pagebreak :/} 65 | 66 | 67 | 在 `app/models/group.rb` 加入 68 | 69 | ~~~~~~~~~ 70 | has_many :group_users 71 | has_many :members, :through => :group_users, :source => :user 72 | ~~~~~~~~~ 73 | 74 | 75 | 76 | 內容變成以下 77 | 78 | ~~~~~~~~~ 79 | class Group < ActiveRecord::Base 80 | 81 | belongs_to :owner, :class_name => "User", :foreign_key => :user_id 82 | has_many :posts 83 | has_many :group_users 84 | has_many :members, :through => :group_users, :source => :user 85 | 86 | validates :title, :presence => true 87 | 88 | 89 | def editable_by?(user) 90 | user && user == owner 91 | end 92 | end 93 | 94 | ~~~~~~~~~ 95 | 96 | 修改 `app/models/group_user.rb` 成 97 | 98 | ~~~~~~~~~ 99 | class GroupUser < ActiveRecord::Base 100 | belongs_to :group 101 | belongs_to :user 102 | end 103 | ~~~~~~~~~ 104 | 105 | 106 | ### 解說 107 | 108 | #### Many to Many 關係 109 | 110 | ~~~~~~~~~~ 111 | has_many :group_users 112 | has_many :members, :through => :group_users, :source => :user 113 | ~~~~~~~~~~ 114 | 115 | Rails 常見的關係有 `has_one`,`belongs_to` 與 `has_many`。`has_many :through` 是最後一種。 116 | 117 | 多數用在多對多(表間列表)的關係上。 118 | -------------------------------------------------------------------------------- /manuscript/chapter-04.txt: -------------------------------------------------------------------------------- 1 | # 練習作業 4 - User 可以加入、退出社團 2 | 3 | ### 作業目標 4 | 5 | * user 可以在 group 頁面加入社團 / 退出社團 6 | * user 必須要是這個社團的成員才能發表文章 7 | 8 | ### 本章練習主題 9 | 10 | * has_many_belongs_to 11 | * custom routes 12 | 13 | 14 | 15 | 16 | 17 | ~~~~~~~~~ 18 | 19 | 20 | class PostsController < ApplicationController 21 | 22 | before_action :find_group 23 | before_action :login_required, :only => [:new, :create, :edit,:update,:destroy] 24 | before_action :member_required, :only => [:new, :create ] 25 | 26 | 27 | def new 28 | @post = @group.posts.build 29 | end 30 | 31 | def create 32 | @post = @group.posts.new(post_params) 33 | @post.author = current_user 34 | 35 | if @post.save 36 | redirect_to group_path(@group) 37 | else 38 | render :new 39 | end 40 | end 41 | 42 | def edit 43 | @post = current_user.posts.find(params[:id]) 44 | end 45 | 46 | def update 47 | @post = current_user.posts.find(params[:id]) 48 | 49 | if @post.update(post_params) 50 | redirect_to group_path(@group) 51 | else 52 | render :edit 53 | end 54 | end 55 | 56 | def destroy 57 | @post = current_user.posts.find(params[:id]) 58 | 59 | @post.destroy 60 | 61 | redirect_to group_path(@group) 62 | end 63 | 64 | private 65 | 66 | def post_params 67 | params.require(:post).permit(:content) 68 | end 69 | 70 | def find_group 71 | @group = Group.find(params[:group_id]) 72 | end 73 | 74 | def member_required 75 | if !current_user.is_member_of?(@group) 76 | flash[:warning] = " You are not member of this group!" 77 | redirect_to group_path(@group) 78 | end 79 | end 80 | end 81 | 82 | ~~~~~~~~~ -------------------------------------------------------------------------------- /manuscript/chapter-05-1.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 5.1 User 必須要在使用者後台可以看到自己參加的 Group 4 | 5 | 6 | 新增 account/groups controller:`rails g controller account/groups` 7 | 8 | ~~~~~~~~ 9 | identical app/controllers/account/groups_controller.rb 10 | invoke erb 11 | exist app/views/account/groups 12 | invoke test_unit 13 | identical test/controllers/account/groups_controller_test.rb 14 | invoke helper 15 | identical app/helpers/account/groups_helper.rb 16 | invoke test_unit 17 | identical test/helpers/account/groups_helper_test.rb 18 | invoke assets 19 | invoke coffee 20 | identical app/assets/javascripts/account/groups.js.coffee 21 | invoke scss 22 | identical app/assets/stylesheets/account/groups.css.scss 23 | ~~~~~~~~ 24 | 25 | 26 | 新增 `app/controllers/account/groups_controller.rb` 裡的內容 27 | 28 | ~~~~~~~~ 29 | class Account::GroupsController < ApplicationController 30 | before_action :login_required 31 | 32 | def index 33 | @groups = current_user.participated_groups 34 | end 35 | end 36 | ~~~~~~~~ 37 | 38 | 39 | `touch app/views/account/groups/index.html.erb` 新增裡面的內容 40 | 41 | ~~~~~~~~ 42 |
43 | 44 |

My Groups

45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | <% @groups.each do |group| %> 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | <% end %> 65 | 66 |
# Title Description Post Count Last Update
# <%= link_to(group.title, group_path(group)) %> <%= group.description %> <%= group.posts.count %> <%= group.updated_at %>
67 |
68 | 69 | ~~~~~~~~ 70 | 71 | 修改 `config/routes.rb` 加入 72 | 73 | ~~~~~~~~ 74 | namespace :account do 75 | resources :groups 76 | end 77 | ~~~~~~~~ 78 | 79 | 修改 `app/views/common/_user_nav.html.erb` 裡的 80 | 81 | ~~~~~~~~ 82 | <%= render_list :class => "dropdown-menu" do |li| 83 | li << link_to("Logout",destroy_user_session_path, :method => :delete ) 84 | end %> 85 | ~~~~~~~~ 86 | 87 | 變成 88 | 89 | ~~~~~~~~ 90 | <%= render_list :class => "dropdown-menu" do |li| 91 | li << link_to("My Group", account_groups_path) 92 | li << link_to("Logout",destroy_user_session_path, :method => :delete ) 93 | end %> 94 | ~~~~~~~~ 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /manuscript/chapter-05-2.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 5.2 User 必須要在使用者後台可以看到自己發表的文章 4 | 5 | 新增 account/posts controller:`rails g controller account/posts` 6 | 7 | ~~~~~~~~ 8 | create app/controllers/account/posts_controller.rb 9 | invoke erb 10 | create app/views/account/posts 11 | invoke test_unit 12 | create test/controllers/account/posts_controller_test.rb 13 | invoke helper 14 | create app/helpers/account/posts_helper.rb 15 | invoke test_unit 16 | create test/helpers/account/posts_helper_test.rb 17 | invoke assets 18 | invoke coffee 19 | create app/assets/javascripts/account/posts.js.coffee 20 | invoke scss 21 | create app/assets/stylesheets/account/posts.css.scss 22 | ~~~~~~~~ 23 | 24 | 新增 `app/controllers/account/groups_controller.rb` 裡的內容 25 | 26 | 27 | ~~~~~~~~ 28 | class Account::PostsController < ApplicationController 29 | 30 | before_action :login_required 31 | 32 | def index 33 | @posts = current_user.posts 34 | end 35 | 36 | end 37 | ~~~~~~~~ 38 | 39 | {::pagebreak :/} 40 | 41 | `touch app/views/account/posts/index.html.erb` 新增裡面的內容 42 | 43 | 44 | ~~~~~~~~ 45 |
46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | <% @posts.each do |post| %> 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 70 | 71 | <% end %> 72 | 73 |
Content Group name Last Update

<%= post.content %>

<%= post.group.title %> <%= post.updated_at %> <%= link_to("Edit", edit_group_post_path(post.group, post), :class => "btn btn-mini")%> 69 | <%= link_to("Delete", group_post_path(post.group, post), :class => "btn btn-mini", :method => :delete, :confirm => "Are you sure?" ) %>
74 |
75 | 76 | ~~~~~~~~ 77 | 78 | 79 | 修改 `config/routes.rb` 加入 `resources :posts` 80 | 81 | ~~~~~~~~ 82 | namespace :account do 83 | resources :groups 84 | resources :posts 85 | end 86 | ~~~~~~~~ 87 | 88 | 89 | {::pagebreak :/} 90 | 91 | 修改 `app/views/common/_user_nav.html.erb` 裡的 92 | 93 | ~~~~~~~~ 94 | <%= render_list :class => "dropdown-menu" do |li| 95 | li << link_to("My Group", account_groups_path) 96 | li << link_to("Logout",destroy_user_session_path, :method => :delete ) 97 | end %> 98 | ~~~~~~~~ 99 | 100 | 變成 101 | 102 | ~~~~~~~~ 103 | <%= render_list :class => "dropdown-menu" do |li| 104 | li << link_to("My Group", account_groups_path) 105 | li << link_to("My Post", account_posts_path) 106 | li << link_to("Logout",destroy_user_session_path, :method => :delete ) 107 | end %> 108 | ~~~~~~~~ 109 | 110 | -------------------------------------------------------------------------------- /manuscript/chapter-05-3.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 5.3 文章列表的排序要以發表時間 DESC 排序 4 | 5 | 你應該有注意到,在 My Post 裡的每篇文章是以文章先後發表順序排列的,也就是越晚發表的文章排越下面。但是這樣預設的排列方式違反一般開發者的使用習慣。 6 | 7 | 比較適當的排列方式應該是以文章的「修改時間」「倒序排列」。也就是最近修改的文章要排在越上面才行。 8 | 9 | 10 | 修改 `app/controllers/account/posts_controller.rb` 裡的內容 11 | 12 | 13 | ~~~~~~~~ 14 | class Account::PostsController < ApplicationController 15 | 16 | before_action :login_required 17 | 18 | def index 19 | @posts = current_user.posts.order("updated_at DESC") 20 | end 21 | 22 | end 23 | ~~~~~~~~ 24 | 25 | 這樣就能達到想要的效果了。 26 | 27 | -------------------------------------------------------------------------------- /manuscript/chapter-05-4.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 5.4 Group 的排序要以文章數量的熱門度 DESC 排序 4 | 5 | 在 `app/views/account/groups/index.html.erb` 裡面你應該有發現這一段 `<%= group.posts.count %>` code。 6 | 7 | ~~~~~~~~ 8 |
9 | 10 | 11 | <% @groups.each do |group| %> 12 | 13 | # 14 | <%= link_to(group.title, group_path(group)) %> 15 | <%= group.description %> 16 | <%= group.posts.count %> 17 | <%= group.updated_at %> 18 | 19 | <% end %> 20 | 21 | 22 |
23 | 24 | ~~~~~~~~ 25 | 26 | 27 | `group.posts.count` 這一段 code 是不太健康的,因為它產生了這樣的 SQL query。 28 | 29 | ~~~~~~~~ 30 | (0.2ms) SELECT COUNT(*) FROM `posts` WHERE `posts`.`group_id` = 1 31 | ~~~~~~~~ 32 | 33 | 如果你有開發過網頁程式 application 的經驗,就知道在迴圈裡面跑 count 對效能是相當傷的。實務上我們相當不建議這麼做。 34 | 35 | 那麼這一段程式碼要怎麼改善呢?比較直觀的想法,就是在 groups 這個 table 再開一欄叫作 `posts_count` 的欄位。然後在 36 | create action 裡面對 `posts_count` +1 37 | 38 | ~~~~~~~~ 39 | 40 | def create 41 | 42 | @post = @group.posts.new(post_params) 43 | @post.author = current_user 44 | 45 | if @post.save 46 | Group.increment_counter(:posts_count, @group.id) 47 | redirect_to group_path(@group) 48 | else 49 | render :new 50 | end 51 | end 52 | 53 | 54 | ~~~~~~~~ 55 | 56 | {::pagebreak :/} 57 | 58 | 在 `destroy action` 裡面對 `posts_count` - 1 59 | 60 | ~~~~~~~~ 61 | def destroy 62 | 63 | @post = current_user.posts.find(params[:id]) 64 | @post.destroy 65 | 66 | Group.decrement_counter(:posts_count, @group.id) 67 | redirect_to group_path(@group) 68 | end 69 | 70 | ~~~~~~~~ 71 | 72 | 73 | 不過,其實倒也不用這麼麻煩。Rails 內建一個叫作 `counter_cache` 的機制,只要內建子關係的 count 欄位,如 `posts_count`。 74 | 75 | `rails g migration add_posts_count_to_group` 76 | 77 | ~~~~~~~~ 78 | invoke active_record 79 | create db/migrate/20130531183331_add_posts_count_to_group.rb 80 | ~~~~~~~~ 81 | 82 | 填入 83 | 84 | ~~~~~~~~ 85 | 86 | class AddPostsCountToGroup < ActiveRecord::Migration 87 | def change 88 | add_column :groups, :posts_count, :integer, :default => 0 89 | end 90 | end 91 | 92 | ~~~~~~~~ 93 | 94 | 執行 `rake db:migrate` 95 | 96 | 97 | ~~~~~~~~ 98 | 99 | == AddPostsCountToGroup: migrating =========================================== 100 | -- add_column(:groups, :posts_count, :integer, {:default=>0}) 101 | -> 0.0232s 102 | == AddPostsCountToGroup: migrated (0.0232s) ================================== 103 | 104 | ~~~~~~~~ 105 | 106 | 然後在 `app/models/post.rb` 的欄位裡,這樣設定: 107 | 108 | ~~~~~~~~ 109 | belongs_to :group, :counter_cache => true 110 | ~~~~~~~~ 111 | 112 | 以後只要 post 遇到 create 或者是 destroy,就會自動對這個欄位 +1 / -1 。不需要在 controller 裡面另外動作。 113 | 114 | 115 | {::pagebreak :/} 116 | 117 | 而上了 counter_cache 之後,`<%= group.posts.size %>` 以後執行時,也會優先去找 `posts_count` 裡的值,而不是真的下 MySQL 的 COUNT 去實際算值。 118 | 119 | 而加了這個 posts_count 欄位之後,我們就可以修改 index 裡的排序規則變成以文章數量的熱門度 DESC 排序 120 | 121 | 122 | ~~~~~~~~ 123 | def index 124 | @groups = current_user.participated_groups.order("posts_count DESC") 125 | end 126 | ~~~~~~~~ 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /manuscript/chapter-05.txt: -------------------------------------------------------------------------------- 1 | # 練習作業 5 - 實作簡單的 Account 後台機制 2 | 3 | ### 作業目標 4 | 5 | * user 可以在 account 頁面找到自己曾經加入的社團 6 | * user 可以至 account 找到自己曾經發表過的文章 7 | * 每個 Group 要秀出現在有多少 post 數量 8 | * 文章列表的排序要以發表時間以 DESC 排序。 9 | 10 | ### 練習主題 11 | 12 | * 使用 [counter_cache](http://railscasts.com/episodes/23-counter-cache-column) 取代直接對資料庫的 count 13 | * 了解 ORM 的基本用法 14 | 15 | 16 | -------------------------------------------------------------------------------- /manuscript/chapter-06-1.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 6.1 使用系統 helper 整理 code 4 | 5 | 我們已經初步完成了這個系統。但有一些設計看起來還是令人不太滿意的。比如說 6 | 7 | 8 | `post.updated_at` 與 `group.updated_at`。 9 | 10 | 顯示出來的會是 `2013-05-31 14:47:31 UTC` 這種格式。 11 | 12 | 13 | ### date.to_s 14 | 15 | 但其實我們想要顯示的好看一點,可以改成這樣的寫法: 16 | 17 | 18 | * `post.updated_at.to_s(:long)` => May 31, 2013 18:04 19 | * `group.updated_at.to_s(:short)` => 31 May 18:04 20 | 21 | 22 | ### simple_format 23 | 24 | 當我們在顯示 `post.content` 時,有個困擾。我們在輸入框輸入 25 | 26 | ~~~~~ 27 | a 28 | b 29 | c 30 | ~~~~~ 31 | 32 | 但在系統輸出的時候,HTML 並不會自動斷行,會顯示成 33 | 34 | ~~~~~ 35 | a b c 36 | ~~~~~ 37 | 38 | 用 Enter 斷行的 `\r` 或 `\n` 在 HTML 裡面並不起作用。若要在 HTML 裡面正確斷行必須要使用 `
`。 39 | 40 | 若在以往,要漂亮顯示文本,開發者必須自行寫一段 nl2br 的轉換 helper。不過 Rails 裡面內建 `simple_format` 這個 helper,可以自動幫我們處理這種雜事。 41 | 42 | `<%= simple_format(post.content) %>` 43 | 44 | 45 | ### truncate 46 | 47 | 48 | 在顯示 group.title 時,有時候我們也會遇到標題太長的問題,導致破版。所以要砍字,再加上 "..." 49 | 50 | Rails 內建了一個 `truncate` helper 可以快速辦到這個效果 51 | 52 | `<%= truncate(@group.title, :length => 17 ) %>` 53 | -------------------------------------------------------------------------------- /manuscript/chapter-06-2.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 6.2 自己撰寫的 helper 包裝 html 4 | 5 | Helper 是一些使用在 Rails 的 View 當中,用 Ruby 產生/整理 HTML code 的一些小方法。通常被放在 `app/helpers` 下。預設的 Helper 名字是對應 Controller 的,產生一個 Controller 時,通常會產生一個同名的 Helper。如 `PostsController` 與 `PostsHelper`。 6 | 7 | ### 使用情境 8 | 9 | 使用 Helper 的情境多半是: 10 | 11 | * 產生的 HTML code 需要與原始程式碼進行一些邏輯混合,但不希望 View 裡面搞得太髒。 12 | * 需要與預設的 Rails 內建的一些方便 Helper 交叉使用。 13 | 14 | 使用 Helper 封裝程式碼可以帶給專案以下一些優點: 15 | 16 | * Don't repeat yourself(DRY)程式碼不重複 17 | * Good Encapsulation好的封裝性 18 | * 提供 view 模板良好的組織 19 | * 易於修改程式碼 20 | 21 | ### 範例 22 | 23 | 在剛剛的專案當中,顯示 Post 的程式碼如下: 24 | 25 | ~~~~~~~~~ 26 | <%= @post.content %> %> 27 | ~~~~~~~~~ 28 | 29 | 隨著專案變遷,這樣的程式碼,可能會依需求改成:(需要內容斷行) 30 | 31 | ~~~~~~~~~ 32 | <%= simple_format(@post.content) %> %> 33 | ~~~~~~~~~ 34 | 35 | 之後又改成 (只顯示頭一百字) 36 | 37 | ~~~~~~~~~ 38 | <%= truncate(simple_format(@post.content), :lenth => 100) %> 39 | ~~~~~~~~~ 40 | 41 | 42 | 而麻煩的是,這樣類似的內容,常常在專案出現。每當需求變更,開發者就需要去找出來,有十個地方,就需要改十遍,很是麻煩。 43 | 44 | {::pagebreak :/} 45 | 46 | Helper 就是用在這樣的地方。與其一開始寫下 47 | 48 | ~~~~~~~~~ 49 | <%= @post.content %> %> 50 | ~~~~~~~~~ 51 | 52 | 不如,一開始就設計一個 Helper ` <%= render_post_content(@post) %>` 53 | 54 | ~~~~~~~~~ 55 | def render_post_content(post) 56 | truncate(simple_format(post.content), :lenth => 100) 57 | end 58 | ~~~~~~~~~ 59 | 60 | 以後變更需求就只要修改一個地方即可。 61 | 62 | ### 更多的 Partial 用法 63 | 64 | 65 | -------------------------------------------------------------------------------- /manuscript/chapter-06-3.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 6.3 使用 partial 整理 html 4 | 5 | Partial 也是 Rails 提供用來整理 View 的一種方式,開發者可以使用 Partial 技巧將太長的 View 整理切該成好維護的小片程式碼。 6 | 7 | 8 | Helper 是一些使用在 Rails 的 View 當中,用 Ruby 產生/整理 HTML code 的一些小方法。通常被放在 `app/helpers` 下。預設的 Helper 名字是對應 Controller 的,產生一個 Controller 時,通常會產生一個同名的 Helper。如 `PostsController` 與 `PostsHelper`。 9 | 10 | ### 使用情境 11 | 12 | 什麼時候應該將把程式碼搬到 Partial 呢? 13 | 14 | *long template | 如果當檔 HTML 超過兩頁 15 | *highly duplicated | HTML 內容高度重複 16 | *independent blocks | 可獨立作為功能區塊 17 | 18 | ### 常見範例 19 | 20 | * nav/user_info 21 | * nav/admin_menu 22 | * vendor_js/google_analytics 23 | * vendor_js/disqus_js 24 | * global/footer 25 | 26 | 27 | 28 | {::pagebreak :/} 29 | 30 | 本書範例 project 裡面的 layout/application.html.erb 就是很好的範例。 31 | 32 | ~~~~~~~~~ 33 | <%= render :partial => "common/menu" %> 34 | 35 |
36 | 37 | <%= notice_message %> 38 | 39 |
40 |
41 | <%= yield %> 42 | <%= yield (:sidebar) %> 43 |
44 | 45 |
46 | 47 | <%= render :partial => "common/footer" %> 48 |
49 | 50 | 51 | 52 | <%= render :partial => "common/bootstrap_modal" %> 53 | <%= render :partial => "common/facebook_js" %> 54 | <%= render :partial => "common/google_analytics" %> 55 | 56 | <%= javascript_include_tag "application" %> 57 | 58 | <%= yield :javascripts %> 59 | 60 | ~~~~~~~~~ 61 | 62 | ### 更多的 Partial 用法 63 | 64 | -------------------------------------------------------------------------------- /manuscript/chapter-06-4.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 6.4 使用 scope 整理 query 4 | 5 | 在 Ch 5.3 裡面,我們在 index controller 裡面設計了一行程式碼,讓文章永遠按照最新的時間降序排列。 6 | 7 | 8 | ~~~~~~~~ 9 | class Account::PostsController < ApplicationController 10 | 11 | def index 12 | @posts = current_user.posts.order("updated_at DESC") 13 | end 14 | 15 | end 16 | ~~~~~~~~ 17 | 18 | `posts.order("updated_at DESC")` 其實是一段可能會很常用到的程式碼。如果 View 需要 Helper 包裝整理,其實我們也需要工具來整理 Query。 19 | 20 | 這段程式碼比較理想的方式其實是: 21 | 22 | ~~~~~~~~ 23 | class Account::PostsController < ApplicationController 24 | 25 | def index 26 | @posts = current_user.posts.recent 27 | end 28 | 29 | end 30 | ~~~~~~~~ 31 | 32 | 讓維護者可以從程式碼裡面知道這一個 controller 提供的是「最近的文章」。Rails 在 ORM 中提供了 Scope,可以讓開發者包裝 query: 33 | 34 | ~~~~~~~~ 35 | class Post < ActiveRecord::Base 36 | scope :recent, -> { order("updated_at DESC") } 37 | end 38 | ~~~~~~~~ 39 | 40 | {::pagebreak :/} 41 | 42 | ### Scope 串接 43 | 44 | 甚至也提供串接的功能: 45 | 46 | ~~~~~~~~ 47 | class Post < ActiveRecord::Base 48 | scope :recent, -> { order("updated_at DESC") } 49 | scope :published, -> { where(:published => true) } 50 | end 51 | ~~~~~~~~ 52 | 53 | 你可以用不同的條件用「語意」串接組起想要撈出的資料。 54 | 55 | ~~~~~~~~ 56 | class Account::PostsController < ApplicationController 57 | 58 | before_action :login_required 59 | 60 | def index 61 | @posts = current_user.posts.published.hot 62 | end 63 | 64 | end 65 | ~~~~~~~~ 66 | 67 | 68 | ### 更多的 scope 用法 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /manuscript/chapter-06.txt: -------------------------------------------------------------------------------- 1 | # 練習作業 6 - Refactor code 2 | 3 | 4 | ### 作業目標 5 | 6 | * 使用系統 helper / 自己撰寫的 helper 包裝 html 7 | * 使用 partial 整理 html 8 | * 使用 scope 整理 query 9 | 10 | 11 | ### 練習主題 12 | 13 | * 練習使用內建 helper 14 | * 練習拆 partial 15 | * 練習使用 scope 機制包裝不同 condition 的 SQL query 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /manuscript/chapter-07-1.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 7.1 Rake 4 | 5 | Rake 的意思就是字面上直觀的 Ruby Make,Ruby 版 Make:用 Ruby 開發的 Build Tool。 6 | 7 | 你也許會問,Ruby 是直譯語言,不需要 compile,為何還需要 Build Tool。 8 | 9 | 與其說 Rake 是一套 Build Tool,還不如說是一套 task 管理工具。我們通常會使用 Ruby based 的 Rake 在 Rails 專案裡編寫 task。 10 | 11 | ### Rails 內的 rake tasks 12 | 13 | `rake -T` 會秀出一大串這個 Rails project 可用的 rake tasks (包含 plugin 內建的 task 也會秀出來)。 14 | 15 | ~~~~~~~~ 16 | rake about # List versions of all Rails frameworks and the environment 17 | rake assets:clean # Remove compiled assets 18 | rake assets:precompile # Compile all the assets named in config.assets.precompile 19 | rake db:create # Create the database from config/database.yml for the current Rails.env (use db:create:all to create all dbs in the config) 20 | rake db:drop # Drops the database for the current Rails.env (use db:drop:all to drop all databases) 21 | ..... 22 | 23 | ~~~~~~~~ 24 | 25 | 26 | ### 清空系統重跑 migration 與種子資料的 rake dev:build 27 | 28 | 29 | 在還未上線的開發階段,我們有時會不是那麼喜歡被 db migration 拘束,有時候下錯了 migration,或想直接清空系統重來,實在是件麻煩事。因此寫支 rake 檔自動化是個良招。 30 | 31 | 新增 `lib/tasks/dev.rake` 32 | 33 | ~~~~~~~~ 34 | namespace :dev do 35 | desc "Rebuild system" 36 | task :build => ["tmp:clear", "log:clear", "db:drop", "db:create", "db:migrate"] task :rebuild => [ "dev:build", "db:seed" ] 37 | end 38 | ~~~~~~~~ 39 | 40 | 執行 rake dev:build 看看會發生什麼事吧: 41 | 42 | ~~~~~~~~ 43 | $ rake dev:build 44 | ~~~~~~~~ 45 | 46 | 47 | `rake dev:build` 是個無敵大絕:清空系統重來! 48 | 49 | 不過系統清空重來還是很麻煩,我們還是需要一些種子資料,如種子看板,種子文章,管理者帳號,測試用一般帳號。 50 | 51 | 52 | ### rake db:seed 53 | 54 | `db/seed.rb` 就是讓你撰寫這些種子資料,可以在專案開始時自動產生一些種子資料。 55 | 56 | 57 | I> ## 注意 58 | I> 59 | I> 請注意!seed.rb 裡放的是種子資料!不是假資料!假資料應該要放在 rake 檔裡,而不是放在種子檔裡! 60 | I> 61 | 62 | ~~~~~~~~ 63 | 64 | admin = User.new(:email => "admin@example.org", :password => "123456", 65 | :password_confirmation => "123456") 66 | admin.is_admin = true 67 | admin.save! 68 | 69 | normal_user = User.new(:email => "user1@example.org", :password => "123456", 70 | :password_confirmation => "123456") 71 | normal_user.save! 72 | 73 | board = Board.create!(:name => "System Announcement") 74 | 75 | post = board.posts.build(:title => "First Post", :content => "This is a demo post") 76 | post.user_id = admin.id 77 | post.save! 78 | 79 | ~~~~~~~~ 80 | 81 | 82 | 83 | 84 | 85 | X>## 練習作業 86 | X> 87 | X> * 撰寫一個 task 可以自動連續執行 rake db:drop ; rake db:create ; rake db:migrate ; rake db:seed 88 | X> * 撰寫一個 task 可以執行 rake dev:fake 生假資料 ( 自己寫 namespace : dev, 裡面放一個 task 叫做 fake,fake 資料用 Populator 生) # 請自行練習 89 | 90 | -------------------------------------------------------------------------------- /manuscript/chapter-07.txt: -------------------------------------------------------------------------------- 1 | # 練習作業 7 - 撰寫自動化 Rake 以及 db:seed 2 | 3 | ### 作業目標 4 | 5 | 用 Rake 撰寫自動化步驟,生假資料。 6 | 7 | 寫一個 rake 可以達成以下步驟:「砍 db => 建 db => 跑 migration => 生種子資料」,另一個 rake 是生假 Group 與假文章。 8 | 9 | ### 練習主題 10 | 11 | * 操作 rake -T 12 | * 撰寫一個 task 可以自動連續執行 rake db:drop ; rake db:create ; rake db:migrate ; rake db:seed 13 | * 撰寫一個 task 可以執行 rake dev:fake 生假資料 ( 自己寫 namespace : dev, 裡面放一個 task 叫做 fake,fake 資料用 Populator 生) # 請自行練習 14 | 15 | 16 | ### 參考資料 17 | 18 | * [Ruby on Rails Rake Tutorial](http://jasonseifer.com/2010/04/06/rake-tutorial) 19 | * [What’s New in Edge Rails: Database Seeding](http://ryandaigle.com/articles/2009/5/13/what-s-new-in-edge-rails-database-seeding) 20 | 21 | -------------------------------------------------------------------------------- /manuscript/chapter-08-1.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 8.1 佈署 Rails Production 所需要的環境 4 | 5 | 佈署 Rails Application 是件說簡單很簡單,說複雜也很複雜的事。佈署的手法有很多種搭配,筆者最推薦的其實是在 Ubuntu / Debian 安裝 Ruby Enterprise Edition,web sever 使用 nginx + mod_rails 的組合。之後再撰寫 Capistrano 的 recipe 來 deploy。 6 | 7 | 8 | ### 為什麼要用獨立的 Ruby 版本,而不使用系統 Ruby? 9 | 10 | 因為系統 Ruby 通常綑綁了背後的套件系統(RubyGem),Rails 是個腳步前進很快的生態圈。而各樣相依套件有時候也會限定 RubyGem 的版本。很多時候,在開發或佈署上就會踩到大地雷。 11 | 12 | 所以會建議在系統上跑獨立的 Ruby。 13 | 14 | ### 為什麼要用 Ubuntu / Debian ,而不是使用 CentOS? 15 | 16 | 還是跟 Rails 是個腳步前進很快的生態圈有相當大的關係。Gem 的前進腳步很快,很多時候只 compatible 新的 library,而 CentOS 上很多 package 都已經 outdate 了。實際佈署上會踩到很多雷。而 Ubuntu / Debian 上的 package 更新速度非常快。所以也是首選。 17 | 18 | 19 | ### 有沒有 Best Practice 懶人包? 20 | 21 | 有。其實佈署真的不算是件易事,在佈署中最容易踩到的雷當數 ImageMagick ( rmagick gem) 與 MySQL ( mysql2 gem)。偏偏這幾乎是每個網站最常會用到的兩個 gem。而且裝爛了很難重裝。 22 | 23 | 這是我們公司標準用來裝機的 Step by Step guide: 基本上已經排除不少裝機時可能會遇到的狀況。 24 | 25 | 26 | ### 哪裡買域名和租 VPS? 27 | 28 | 29 | 我是在 enom.com 買域名,管理介面還算蠻好用的。 30 | 31 | 至於 VPS 是去 [Linode](http://linode.com/) 租的。算便宜大碗,速度上也能接受。若純練習也可到 [AWS EC2](http://aws.amazon.com/ec2/) 租東京的 micro instance。 32 | -------------------------------------------------------------------------------- /manuscript/chapter-08-2.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Ch 8.2 Capistrano 4 | 5 | [Capistrano](https://github.com/capistrano/capistrano/wiki) 是 37 signals 開發的一套 automate deploy tool。也是許多 Rails Developer 推薦的一套佈署工具。 6 | 7 | 8 | 也許你要問,為什麼要用工具 deploy 專案呢?那是因為佈署 application 是由數道繁瑣的手續構成。首先,Rails 佈署程式並不像 php 那樣簡單,上傳檔案成功就完事了。要佈署一個 Rails Application,你必須連到遠端 server,然後 checkout 程式碼,跑一些 migration,重開 server 生效,重開 memcached,重開 search daemon .... 9 | 10 | 這些連續動作有時候往往一閃神即是災難。 11 | 12 | 而手動 deploy 在只有一個人一台機器時還勉強說得過去。當開發人員一多或機器一多,馬上就會要了大家的命。 13 | 14 | Capistrano 固然強大,但官方網站的文件卻讓人不易讀懂。 Bootstrappets 內建了一個已經寫好的 recipe,可以直接使用。 15 | 16 | (放在 config/deploy.rb ) 17 | 18 | {::pagebreak :/} 19 | 20 | ~~~~~~~~ 21 | # -*- encoding : utf-8 -*- 22 | 23 | raw_config = File.read("config/config.yml") 24 | APP_CONFIG = YAML.load(raw_config) 25 | 26 | require "./config/boot" 27 | require "bundler/capistrano" 28 | require "rvm-capistrano" 29 | 30 | default_environment["PATH"] = "/opt/ruby/bin:/usr/local/bin:/usr/bin:/bin" 31 | 32 | set :application, "groupme" 33 | set :repository, "git@github.com:example/#{application}.git" 34 | set :deploy_to, "/home/apps/#{application}" 35 | 36 | set :branch, "master" 37 | set :scm, :git 38 | 39 | set :user, "apps" 40 | set :group, "apps" 41 | 42 | set :deploy_to, "/home/apps/#{application}" 43 | set :runner, "apps" 44 | set :deploy_via, :remote_cache 45 | set :git_shallow_clone, 1 46 | set :use_sudo, false 47 | set :rvm_ruby_string, '1.9.3' 48 | 49 | set :hipchat_token, APP_CONFIG["production"]["hipchat_token"] 50 | set :hipchat_room_name, APP_CONFIG["production"]["hipchat_room_name"] 51 | set :hipchat_announce, false # notify users? 52 | 53 | role :web, "groupme.com" # Your HTTP server, Apache/etc 54 | role :app, "groupme.com" # This may be the same as your `Web` server 55 | role :db, "groupme.com" , :primary => true # This is where Rails migrations will run 56 | 57 | set :deploy_env, "production" 58 | set :rails_env, "production" 59 | set :scm_verbose, true 60 | set :use_sudo, false 61 | 62 | 63 | namespace :deploy do 64 | 65 | desc "Restart passenger process" 66 | task :restart, :roles => [:web], :except => { :no_release => true } do 67 | run "touch #{current_path}/tmp/restart.txt" 68 | end 69 | end 70 | 71 | 72 | namespace :my_tasks do 73 | task :symlink, :roles => [:web] do 74 | run "mkdir -p #{deploy_to}/shared/log" 75 | run "mkdir -p #{deploy_to}/shared/pids" 76 | 77 | symlink_hash = { 78 | "#{shared_path}/config/database.yml" => "#{release_path}/config/database.yml", 79 | "#{shared_path}/config/s3.yml" => "#{release_path}/config/s3.yml", 80 | "#{shared_path}/uploads" => "#{release_path}/public/uploads", 81 | } 82 | 83 | symlink_hash.each do |source, target| 84 | run "ln -sf #{source} #{target}" 85 | end 86 | end 87 | 88 | end 89 | 90 | namespace :remote_rake do 91 | desc "Run a task on remote servers, ex: cap staging rake:invoke task=cache:clear" 92 | task :invoke do 93 | run "cd #{deploy_to}/current; RAILS_ENV=#{rails_env} bundle exec rake #{ENV['task']}" 94 | end 95 | end 96 | 97 | after "deploy:finalize_update", "my_tasks:symlink" 98 | 99 | ~~~~~~~~ 100 | -------------------------------------------------------------------------------- /manuscript/chapter-08-3.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | 4 | ## Ch 8.3 Capistrano 常用指令 5 | 6 | ### cap deploy:setup 7 | 8 | 第一次使用,運行此行指令,Capistrano 就會遠端到機器上幫你把 Capistrano 所需的一些目錄和檔案先預備好 9 | 10 | ### cap deploy 11 | 12 | Deploy 專案到遠端 13 | 14 | ### cap deploy:migrate 15 | 16 | 遠端執行 migration 17 | 18 | ### cap deploy:rollback 19 | 20 | deploy 的這一版本爛了,想回到沒爛的上一版本。 21 | 22 | ### cap deploy:restart 23 | 24 | 純粹重開 application 25 | 26 | {::pagebreak :/} 27 | 28 | ## Ch 8.4 Deploy with Rails 4 29 | 30 | Rails 4 在部署上 Server 時有時候會遇到 Asset Compile 不過的問題。 31 | 32 | ### Rails 3 33 | 34 | 35 | `config/production.rb` 36 | 37 | ~~~~~~~~ 38 | config.serve_static_assets = false 39 | config.assets.compile = false 40 | ~~~~~~~~ 41 | 42 | 43 | ### Rails 4 44 | 45 | 在 Rails 4 上要修改成 46 | 47 | ~~~~~~~~ 48 | config.serve_static_assets = true 49 | config.assets.compile = true 50 | config.assets.compress = true 51 | config.assets.configure do |env| 52 | env.logger = Rails.logger 53 | end 54 | ~~~~~~~~ -------------------------------------------------------------------------------- /manuscript/chapter-08.txt: -------------------------------------------------------------------------------- 1 | # 練習作業 8 - 將專案 deploy 到租來的 VPS 2 | 3 | 4 | ### 作業目標 5 | 6 | 在租來的 VPS 上面建置 Ruby on Rails production 環境,使用 Ruby Enterprise Edition 與 mod_rails。使用 [Capistrano](https://github.com/capistrano/capistrano/wiki) 佈署 application。 7 | 8 | ### 練習主題 9 | 10 | * 學會如何自動部署專案 11 | * 使用 capistrano 自動部署專案 12 | * 操作 cap deploy:setup 13 | * 操作 cap deploy 14 | * 操作 cap deploy:rollback 15 | * 操作 cap deploy:restart 16 | 17 | 18 | ### 參考資料 19 | 20 | * [rails-nginx-passenger-ubuntu](https://github.com/jnstq/rails-nginx-passenger-ubuntu) 21 | * [AWDR4](http://pragprog.com/titles/rails4/agile-web-development-with-rails) deploy 章節 22 | -------------------------------------------------------------------------------- /manuscript/chapter-09-1.txt: -------------------------------------------------------------------------------- 1 | 2 | ## SCSS 3 | 4 | SCSS 是一套新型的 Asset 語言,可以用「更合理的方式」撰寫 CSS。舉例來說: 5 | 6 | ~~~~~~~~ 7 | .content{ margin: 2em 0;} 8 | .content h1{ font-size: 2em;} 9 | ~~~~~~~~ 10 | 11 | 在 SCSS 裡可以寫成巢狀的 12 | 13 | ~~~~~~~~ 14 | .content{ 15 | margin: 2em 0; 16 | h1{font-size: 2em;} 17 | } 18 | ~~~~~~~~ 19 | 20 | 21 | 編譯器會自動幫你編譯成 CSS。如此一來好維護多了。 22 | 23 | 24 | {::pagebreak :/} 25 | 26 | 此外,SCSS 還支援一些強大的功能:如變數、函數、數學、繼承、mixin …等等。 27 | 28 | 這些內建功能,有多方便呢?就拿變色來說吧。在進行網頁 protyping 時,更改全站配色或者是直接提供兩個以上的設計,對設計師來說是家常便飯的事。 29 | 30 | 但更改全站配色卻是相當麻煩的一件事,因為「尋找 + 全數取代」,並不能保證最後會有正確的結果。很有可能:你更改了所有 CSS 中涉及連結的顏色,卻發現在全數取代的過程中,不小心也改到邊框的顏色。 31 | 32 | 但是 SCSS 可以讓我們使用變數去指定特定 style 的顏色。相當厲害。 33 | 34 | ~~~~~~~ 35 | $border-color: #3bbfce; 36 | $link-color: #3bbfcf' 37 | .content{ 38 | border-color: $border-color; 39 | a{ color: $link-color; } 40 | } 41 | ~~~~~~~ 42 | 43 | 會產生出 44 | 45 | 46 | ~~~~~~~ 47 | .content{ border-color: #3bbfce; } 48 | .content a{color: #3bbfcf; } 49 | ~~~~~~~ 50 | 51 | ### Compass 52 | 53 | 而 SCSS 上的 [Compass](http://compass-style.org/) 這套 Framework 更是厲害,它提供了不少 mixin 可以讓 Developer 更加省事。 54 | 55 | 就如我們常常使用的圓角框技巧來說好了,以往我們要設計一個圓角框,必須囉哩八嗦的這樣寫: 56 | 57 | ~~~~~~~ 58 | #border-radius { 59 | -moz-border-radius: 25px; 60 | -webkit-border-radius: 25px; 61 | -o-border-radius: 25px; 62 | -ms-border-radius: 25px; 63 | -khtml-border-radius: 25px; 64 | border-radius: 25px; 65 | } 66 | ~~~~~~~ 67 | 68 | 而使用 Compass 我們只要這樣寫就可以了: 69 | 70 | ~~~~~~~ 71 | #border-radius { @include border-radius(25px); } 72 | ~~~~~~~ 73 | 74 | 75 | -------------------------------------------------------------------------------- /manuscript/chapter-09-2.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## CoffeeScript 4 | 5 | Asset Pipeline 也支援了 [CoffeeScript](http://jashkenas.github.com/coffee-script/)。CoffeeScript 本身也是一種程式語言,開發者可以透過撰寫 CoffeeScript,編譯產生 JavaScript。它的語法有點像是 Ruby 與 Python 的混合體。 6 | 7 | Plurk 前創辦人 amix 曾寫過一篇這樣的 post:[CoffeeScript: The beautiful way to write Javascript](http://amix.dk/blog/post/19612) 來這樣形容 CoffeeScript:「以更漂亮的方式撰寫 JavaScript」。 8 | 9 | 他認為目前 JavaScript 存在幾種問題: 10 | 11 | * JavaScript 是 functional language 12 | * 雖然是 OOP,但卻是 prototype-based 的 JavaScript 是 dynamic language,更像 Lisp 而不是 C/Java,但卻用了 C/Java 的語法。 13 | * 名字裡面有 Java,但卻和 Java 沒什麼關係。 14 | * 明明是 functional & dynamic laungaue,更偏向 Ruby / Python,卻使用了 C / Java 的 syntax,原本可以是一門很美的語言,卻活生生的變成了悲劇。 15 | 16 | 而 CoffeeScript 的誕生,原因就是就是為了扭正這樣的局面,重新讓寫 JavaScript 這件事也可以變得「很美」。 17 | 18 | ### CoffeeScript : The Good Part 19 | 20 | CoffeeScript 留下了 JavaScript 的 Good Parts,而在設計上極力消除 JavaScript 原生特性會產生的缺點,例如: 21 | 22 | #### 消除到處污染的全域變數 23 | 24 | 開發者在寫 JavaScript 時,常不自覺的使用全域變數,導致很多污染問題。而透過 CoffeeScript 生出來的 JavaScript,變數一律為區域變數( 以 var 開頭) 25 | 26 | #### Protected code 27 | 28 | 使用 CoffeeScript 撰寫 function,產生出來的 JavaScript 必以一個 anonymous function: function(){}(); 自我包裹,獨立運作不干擾到其他 function。 29 | 30 | #### 使用 -> 和 indent(縮排) 讓撰寫 function 更不容易出錯 31 | 32 | 在撰寫 JavaScript 時,最令人不爽的莫過於 `function(){}();`,這些複雜的括號和分號稍微一漏,程式就不知道死在哪裡了…. 33 | 34 | CoffeeScript 自動產生出來的 JavaScript 能夠確保括號們絕對不會被漏掉。 35 | 36 | #### 更容易偵測 syntax error 並攔阻 37 | 38 | JavaScript 在 syntax error 時,非常難以偵測錯誤,幾乎是每個程式設計師的夢靨。而 CoffeeScript 是一門需要 compile 的語言,可以藉由這樣的特性擋掉 syntax error 的機會。 39 | 40 | #### 實作物件導向更簡單 41 | 42 | Javascript 是一種物件導向語言,裡面所有東西幾乎都是物件。但 JavaScript 又不是一種真正的物件導向語言,因為它的語法裡面沒有 class(類別)。 43 | 44 | 在 JavaScript 中,我們要實作 OOP 有很多種方式,你可以使用 prototype、function 或者是 Object。但無論是哪一種途徑,其實都「不簡單」。 45 | 46 | 但 CoffeeScript 讓這件事變簡單了,比如以官網的這個例子: 47 | 48 | 49 | ~~~~~~~ 50 | class Animal 51 | constructor: (@name) -> 52 | 53 | move: (meters) -> 54 | alert @name + " moved #{meters}m." 55 | 56 | class Snake extends Animal 57 | move: -> 58 | alert "Slithering..." 59 | super 5 60 | 61 | class Horse extends Animal 62 | move: -> 63 | alert "Galloping..." 64 | super 45 65 | 66 | sam = new Snake "Sammy the Python" 67 | tom = new Horse "Tommy the Palomino" 68 | 69 | sam.move() 70 | tom.move() 71 | ~~~~~~~ 72 | 73 | 在往常不容易寫的漂亮的 JavaScript OO,可以被包裝得相當乾淨好維護。 74 | 75 | 76 | -------------------------------------------------------------------------------- /manuscript/chapter-09-3.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Asset Pipeline 的架構 4 | 5 | Asset Pipeline 對於 assets 位置的定義。根據預設,你可以把 assets 放在以下三個資料夾內: 6 | 7 | * app/assets 8 | * lib/assets 9 | * vendor/assets 10 | 11 | 12 | 理論上,你把 assets 丟在這三個資料夾內,在 application.css / application.js 內 require 都可以動。 13 | 14 | 15 | ### 掛上其他 library 16 | 17 | 要引用 3rd party vendor assets,只要在 application.css 或者 application.js 進行 require 就可以使用了。 18 | 19 | ~~~~~~~ 20 | //= require jquery 21 | //= require bootstrap 22 | ~~~~~~~ 23 | 24 | 25 | ### 目錄夾的分類 26 | 27 | #### app/assets 28 | 29 | 在 Rails 3.1.x 之後的版本,rails g controler posts,會自動在 assets/styelsheets/ 和 assets/javascripts/ 中產生對應的 scss 與 coffeescript 檔案。所以 app/assets 是讓開發者放「自己為專案手寫的 assets」的地方。 30 | 31 | #### lib/assets 32 | 33 | lib 是 library 的簡寫,這裡是放 LIBRARY 的地方。所以如果你為專案手寫的 assets 漸漸形成了 library 規模,比如說 mixin 或者是自己為專案整理了簡單的 bootstrap,應該放在 lib/ 下。 34 | 35 | #### vendor/assets 36 | 37 | verdor 是「供應商」的意思,也就是 「別人寫的」assets 都應該放在這裡。比如說: 38 | 39 | * jquery.*.js 40 | * fanfanfan icons 41 | * tinymce / ckeditor 42 | 43 | 等等… 44 | 45 | 46 | ### Rails Asset Gem 47 | 48 | 透過 Asset Pipleline 的架構,開發者可以很容易透過 Gem 的機制,將 3rd party 的 CSS Framework 打包封裝,直接掛在 Rails 專案裡面使用。 49 | 50 | 比如說知名的 Fontawesome 這個 icon 專案,也有自己的 Rails gem 51 | 52 | 在 application.css 裡面掛上 53 | 54 | ~~~~~~~ 55 | //= require font-awesome 56 | ~~~~~~~ 57 | 58 | 這樣就可以直接使用了。不需要像以前開發網站一樣,把整個 framwork COPY 到專案裡面,既髒又難維護。 59 | 60 | 61 | -------------------------------------------------------------------------------- /manuscript/chapter-09-4.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## Rails 4 with Asset Pipeline 4 | 5 | 在 Rails 4 上掛上 asset pipeline 跟 Rails 3 的方式有些許的不同。 6 | 7 | 8 | ### Rails 3 9 | 10 | 在 Rails 3 ,asset gem 基本上是被放在 asset 這個 group 的。 11 | 12 | ~~~~~~~ 13 | group :assets do 14 | gem 'sass-rails', '~> 3.2.3' 15 | gem 'coffee-rails', '~> 3.2.1' 16 | gem 'uglifier', '>= 1.0.3' 17 | gem "compass-rails" 18 | end 19 | ~~~~~~~ 20 | 21 | ### Rails 4 22 | 23 | 但在 Rails 4 上,這些 gem 卻要移出來不放在 asset 這個 group 裡。另外若要使用 compass 的話,因為一些技術問題,也需要安裝 1.1.2 以上的 gem,才能正常引用 compass 24 | 25 | ~~~~~~~ 26 | gem 'uglifier', '>= 1.3.0' 27 | gem 'coffee-rails', '~> 4.0.0' 28 | gem 'sass-rails', '~> 4.0.0' 29 | gem "compass-rails", "~> 1.1.2" 30 | ~~~~~~~ -------------------------------------------------------------------------------- /manuscript/chapter-09.txt: -------------------------------------------------------------------------------- 1 | # 補充章節: Asset Pipeline 2 | 3 | 4 | Rails 在 3.1 之後,提供一套 Asset 開發流程,稱之 Asset Pipeline 。是一套讓開發者很方便能夠削減 (minify) 以及 壓縮 (compress) Javascript / CSS 檔案的框架。它同時也提供你直接利用其他語言如 CoffeeScript / SASS / ERB ,直接撰寫 assets ( 指的是 stylesheets / javascripts / images 這些靜態檔案) 的可能性。 5 | 6 | 7 | -------------------------------------------------------------------------------- /manuscript/extra.txt: -------------------------------------------------------------------------------- 1 | # 附錄 2 | 3 | 4 | ## Resources of latest Ruby 5 | 6 | * [Ruby5](http://ruby5.envylabs.com/) 7 | * [Ruby Weekly](http://rubyweekly.com/) 8 | * [Ruby Inside](http://www.rubyinside.com/) 9 | * [RubyFlow](http://www.rubyflow.com/) 10 | * [RubyRogues](http://rubyrogues.com/) 11 | * [Thoughtbot Podcast](http://learn.thoughtbot.com/podcast) 12 | * [Railscast](http://railscasts.com/) 13 | * [Confreaks](http://www.confreaks.com/) 14 | -------------------------------------------------------------------------------- /manuscript/how-to-use.txt: -------------------------------------------------------------------------------- 1 | # 本書使用方法 2 | 3 | 我自 2009 年以來,就開始訓練 Rails Developer。訓練方式是使用一系列題目,培養開發者對於 Rails 相關工具的熟悉度。這一本書是該系列題目的答案本。 4 | 5 | 這本書的前身,其實是一套訓練新進 Developer 的基礎教材。目的是希望能夠讓一個剛接觸 Rails 的開發者,快速熟悉 Rails 生態圈裡面的基本工具,以及 練熟 / 背熟 日常所需要用的知識。 6 | 7 | 我不建議各位讀者以「讀」的方式去使用這本書,相反地,我希望你動手實作。 8 | 9 | 解完這本書裡面所有的題目才是重點,你會在解題的過程裡面學到從無到有 build 起一個 Rails 網站,所需要的所有基本常識。 10 | 11 | 如果你真的解不開這裡面的題目,我才希望你看解答。 12 | 13 | 如果你偏好當面問也住在台北,我們 host 了一個每週二舉辦的 Rails Meetup ,歡迎帶著你手邊的問題來這裡問。這邊的同好對於新手都很友善,會十分樂於回答你的問題而已。 14 | 15 | 如果你不住在台北,我開了一個 Facebook 社團 ,這裡可以詢問本書相關的問題。 16 | 17 | 發問請貼程式碼,可將程式碼貼在 [Gist](http://gist.github.com) 上,再轉貼到論壇上發問。 18 | 19 | 20 | ### 本書程式碼 21 | 22 | 本書程式碼放在: 23 | 24 | ### 開發環境 25 | 26 | 我力求提供讀者一個能夠上手的 Rails 開發環境。但是要能夠建築出一個開發 Rails 的環境,變因實在太大。從 27 | 28 | * MacOS 的版本 10.7, 10.8, 10.9 的 command line 工具安裝方式與位置都不同 29 | * Debian 5,6 / Ubunbtu 10,11,12 的 apt-get 安裝 lib 位置與相依都不同 30 | * Ruby 從 1.8.7 -> 1.9.3 -> 2.0.0 的語言變更導致 library 不相容 31 | * Rails 從 3.0 -> 3.1 -> 3.2 到 4.0.0 rc1, beta1, official 的官方配置與 deploy 環境都不同 32 | 33 | 如果本書提供的環境安裝方式無效,請儘量試試在 [Stackoverflow](http://stackoverflow.com/) 上找找,也許很快就能解決你的問題。 34 | -------------------------------------------------------------------------------- /manuscript/images/RESTful.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/RESTful.jpg -------------------------------------------------------------------------------- /manuscript/images/Ruby-on-Rails-process.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/Ruby-on-Rails-process.jpg -------------------------------------------------------------------------------- /manuscript/images/bird_32_gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/bird_32_gray.png -------------------------------------------------------------------------------- /manuscript/images/bird_32_gray_fail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/bird_32_gray_fail.png -------------------------------------------------------------------------------- /manuscript/images/code_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/code_bg.png -------------------------------------------------------------------------------- /manuscript/images/dotted-border.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/dotted-border.png -------------------------------------------------------------------------------- /manuscript/images/email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/email.png -------------------------------------------------------------------------------- /manuscript/images/ex0-browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex0-browser.png -------------------------------------------------------------------------------- /manuscript/images/ex0-hello-world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex0-hello-world.png -------------------------------------------------------------------------------- /manuscript/images/ex0-pow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex0-pow.png -------------------------------------------------------------------------------- /manuscript/images/ex0-route.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex0-route.png -------------------------------------------------------------------------------- /manuscript/images/ex1-list-board.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex1-list-board.png -------------------------------------------------------------------------------- /manuscript/images/ex1-nest-list-board.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex1-nest-list-board.png -------------------------------------------------------------------------------- /manuscript/images/ex1-nest-new-post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex1-nest-new-post.png -------------------------------------------------------------------------------- /manuscript/images/ex1-new-post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex1-new-post.png -------------------------------------------------------------------------------- /manuscript/images/ex2-current_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex2-current_user.png -------------------------------------------------------------------------------- /manuscript/images/ex2-general.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex2-general.png -------------------------------------------------------------------------------- /manuscript/images/ex2-signin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex2-signin.png -------------------------------------------------------------------------------- /manuscript/images/ex2-signup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex2-signup.png -------------------------------------------------------------------------------- /manuscript/images/ex4-board-show.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex4-board-show.png -------------------------------------------------------------------------------- /manuscript/images/ex4-default-scope.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex4-default-scope.png -------------------------------------------------------------------------------- /manuscript/images/ex4-recent-scope.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex4-recent-scope.png -------------------------------------------------------------------------------- /manuscript/images/ex4-will-paginate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex4-will-paginate.png -------------------------------------------------------------------------------- /manuscript/images/ex5-upload-form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex5-upload-form.png -------------------------------------------------------------------------------- /manuscript/images/ex5-upload-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex5-upload-result.png -------------------------------------------------------------------------------- /manuscript/images/ex6-seed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex6-seed.png -------------------------------------------------------------------------------- /manuscript/images/ex7-github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex7-github.png -------------------------------------------------------------------------------- /manuscript/images/group-scaffold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/group-scaffold.png -------------------------------------------------------------------------------- /manuscript/images/helloworld.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/helloworld.png -------------------------------------------------------------------------------- /manuscript/images/line-tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/line-tile.png -------------------------------------------------------------------------------- /manuscript/images/noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/noise.png -------------------------------------------------------------------------------- /manuscript/images/post-new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/post-new.png -------------------------------------------------------------------------------- /manuscript/images/rails-101-cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/rails-101-cover.png -------------------------------------------------------------------------------- /manuscript/images/rails-init.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/rails-init.png -------------------------------------------------------------------------------- /manuscript/images/rss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/rss.png -------------------------------------------------------------------------------- /manuscript/images/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/search.png -------------------------------------------------------------------------------- /manuscript/images/title_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/title_page.png -------------------------------------------------------------------------------- /manuscript/install-on-mac.txt: -------------------------------------------------------------------------------- 1 | # Ruby on Rails 安裝最佳實踐 2 | 3 | 4 | ### 打造 Bug Free 的 Rails 開發環境 5 | 6 | 許多新手初入門 Rails,除了對 Rails 版本號更迭過快感到十分抓狂,開發環境的建置也常令人一個頭兩個大。不是卡在 lib 編不過,就是 gem 抓不到。 7 | 8 | OSX 10.8、MySQL、ImageMagick、readline .... 9 | 10 | project 還沒開始寫半行,先被困在莫名其妙的套件相依性上。有沒有比較簡單且不容易踩中蟲的安裝步驟呢?有。 11 | 12 | 我們公司 [Rocodev](http://rocodev.com) 寫了一份 Ruby on Rails 安裝最佳實踐,這份教學幾乎是 Bug Free。 13 | 14 | 15 | I> ## 最新版本 16 | I> 17 | I> 最新版本在此: 18 | I> 如果書中的內容過期,請到此頁面找尋最新版解法。 19 | 20 | 21 | 22 | ### Mac 是最好的 Ruby on Rails 開發環境,馬上買一台! 23 | 24 | 看到標題,也許你心裡浮出了問號?我只是想試看看 Rails,有必要這樣大手筆的購買設備嗎? 25 | 26 | 有!如果你立志相成為一個 Rails Developer 的話。 27 | 28 | 29 | 世界上絕大多數的 Rails Developer 開發都是使用 MacBook +。這不僅僅只是 Best Practices / 神兵利器( brew、Livereload ...)只存在 Mac 的關係。更重要的是每當 OS 更新、Gem 版本更新、 Rails 地雷 ... 30 | 31 | Linux 使用者都會因為 package system maintainer 不是 Rails Developer 而不熟生態圈的關係,被雷炸的像次等公民。 32 | 33 | 開發者的時間就是金錢,想想好的設備會為你的生產力帶來多大的改善?好的 Framework 會多節省你的開發時間? 34 | 35 | 你都已經打算開始學習 Rails 改善你悲慘的開發人生了?為什麼不買一台 Mac 讓自己更節省力氣呢? 36 | 37 | 38 | {::pagebreak :/} 39 | 40 | ## 安裝步驟 41 | 42 | 43 | `強烈警告:請絕對不要跳著裝!如果疏漏步驟有可能導致無法復原需要重灌。` 44 | 45 | ### 系統套件 46 | 47 | 1. 進行 Mac 系統更新到最新版 10.9 48 | 2. 從 Apple Store 上取得 Xcode 5.0.4 安裝 (內建 Command Tools ) 49 | 50 | ### 安裝 Homebrew 51 | 52 | ~~~~~~~~~~~~~~~ 53 | $ ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)" 54 | $ brew install git 55 | $ brew update 56 | 57 | $ brew tap homebrew/dupes 58 | $ brew install apple-gcc42 59 | ~~~~~~~~~~~~~~~ 60 | 61 | ### 安裝 XQuartz 62 | 63 | 安裝 ImageMagick 需先有 X11 的 support,OSX 10.8 拿掉了... 64 | 65 | 66 | 67 | 68 | {::pagebreak :/} 69 | 70 | 71 | ### ImageMagick / MySQL 72 | 73 | #### 安裝 Imagemagick 74 | 75 | ~~~~~~~~~~~~~~~ 76 | $ brew install imagemagick 77 | ~~~~~~~~~~~~~~~ 78 | 79 | #### 安裝 MySQL 80 | 81 | ~~~~~~~~~~~~~~~ 82 | 83 | $ brew install mysql 84 | $ unset TMPDIR 85 | $ mysql_install_db --verbose --user=`whoami` --basedir="$(brew --prefix mysql)" --datadir=/usr/local/var/mysql -- tmpdir=/tmp 86 | $ mysql.server start 87 | $ mysqladmin -u root password '123456' 88 | $ mkdir -p ~/Library/LaunchAgents 89 | $ find /usr/local/Cellar/mysql/ -name "homebrew.mxcl.mysql.plist" -exec cp {} ~/Library/LaunchAgents/ \; 90 | $ launchctl load -w ~/Library/LaunchAgents/homebrew.mxcl.mysql.plist 91 | 92 | ~~~~~~~~~~~~~~~ 93 | 94 | 若無法順利進行安裝,可換下載 [MySQL官網](http://dev.mysql.com/downloads/mysql/) 上的 Mac OS X ver. 10.6 (x86, 64-bit), DMG Archive 來安裝 MySQL。 95 | 96 | 97 | {::pagebreak :/} 98 | 99 | 100 | ### 安裝 RVM 與 Ruby 2.0 101 | 102 | 在建制 Rails 環境的時候,我們可能會有跑不同版本的 Ruby 或者不同的 getsemt 的需求。[Ruby Version Manager](https://rvm.beginrescueend.com/) 是一個能夠讓我們用很優雅的方式切換 Ruby 版本的工具。同時使用系統 Ruby,其實很容易弄髒環境和產生一些靈異現象的 bug。於是我們在建制環境時,通常第一時間就會裝起 RVM。 103 | 104 | #### 安裝 RVM 105 | 106 | ~~~~~~~~~~~~~~~ 107 | $ bash -s stable < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer) 108 | $ . ~/.profile 109 | $ source ~/.profile 110 | ~~~~~~~~~~~~~~~ 111 | 112 | 113 | #### 安裝 Ruby 2.0 114 | 115 | ~~~~~~~~~~~~~~~ 116 | $ brew install libyaml 117 | $ rvm pkg install openssl 118 | $ rvm install 2.0.0 \ 119 | --with-openssl-dir=$HOME/.rvm/usr \ 120 | --verify-downloads 1 121 | $ rvm use 2.0.0 122 | ~~~~~~~~~~~~~~~ 123 | 124 | 125 | I>## 注意事項 126 | I> 127 | I> 使用 RVM 安裝 gem 和 passenger-install-apache2-module 不需要加上 sudo , 因為使用 sudo 會使用非 RVM 的 ruby 環境, 安裝目錄也不一樣.) 128 | 129 | 130 | 131 | 132 | 133 | ### 安裝必要 Ruby gems 134 | 135 | ~~~~~~~~~~~~~~~ 136 | $ gem install rails --version 4.0.0 137 | $ gem install mysql2 138 | $ gem install capistrano 139 | $ gem install capistrano-ext 140 | ~~~~~~~~~~~~~~~ 141 | 142 | {::pagebreak :/} 143 | 144 | 145 | ### 設定 HTTP Server (使用 Pow) 146 | 147 | #### 使用 Pow 作為 HTTP Server 148 | 149 | [Pow](http://pow.cx) 是 [37 Signals](http://37signals.com/) open-source 出來的一套 Rack Server。其標榜的就是 Zero Config。 150 | 151 | Pow 的原理原理是攔截 routing,導到 Pow 上。所以新增 project 不需要更改 /etc/hosts 就會生效。也因為 Pow 是 rack-based,支援 [rack](http://rack.rubyforge.org/) 的 framework 掛了就能跑。 152 | 153 | 154 | 相較起來,以往的 [Passenger](http://www.modrails.com/) 搭配 Mac 本機端的 apache 的 solution 就顯得太笨重了。 155 | 156 | #### Installation 157 | 158 | [Pow](http://pow.cx) 的安裝相當簡單。 159 | 160 | ~~~~~~~~~~~~~~~ 161 | $ curl get.pow.cx | sh 162 | ~~~~~~~~~~~~~~~ 163 | 164 | 即完成安裝。 165 | 166 | #### Setting 167 | 168 | [Pow](http://pow.cx) 預設的目錄是在 ~/.pow 下。 169 | 170 | 171 | 因此若要讓 project 跑在 Pow 之下。以我的 wiki 為例 : 172 | 173 | ~~~~~~~~~~~~~~~ 174 | $ cd ~/.pow/ 175 | $ ln -s ~/projects/wiki 176 | ~~~~~~~~~~~~~~~ 177 | 178 | 打開瀏覽器,輸入 http://wiki.dev 就完成了。以往的 http://localhost:3000/ 實在太噁心了,別再用它了! 179 | 180 | T> 小技巧 181 | T> 182 | T> 或者是直接在 project/wiki 下打 `powder link` 也可以。 183 | 184 | 185 | 186 | 187 | 188 | #### 使用 Powder 管理 Pow 189 | 190 | [Powder](https://github.com/Rodreegez/powder) 是後來衍生出來的一套管理工具。 191 | 192 | 因為 Pow 的管理有點不易,所有有人寫了這個工具把一些常用的功能包裝起來。 193 | 194 | 安裝方法: 195 | 196 | ~~~~~~~~~~~~~~~ 197 | $ gem install powder 198 | ~~~~~~~~~~~~~~~ 199 | 200 | 通常我只拿來做 `powder restart` 和 `powder log` 而已。 201 | 202 | I> ## 注意事項 203 | I> 204 | I> 若電腦預設非使用系統 Ruby 的開發者需注意此點。Pow 很可能會抓到系統 Ruby 及其 gemset 而無法啓動。我個人的解法是安裝 RVM 管控 Ruby,再在欲使用 Pow 之 project 目錄放置 .rvmrc 即可。 205 | 206 | 207 | 208 | 209 | .rvmrc 內容如下: 210 | 211 | ~~~~~~~~~~~~~~~ 212 | rvm 2.0.0 213 | ~~~~~~~~~~~~~~~ 214 | 215 | -------------------------------------------------------------------------------- /manuscript/preface.txt: -------------------------------------------------------------------------------- 1 | # 如何排除障礙 2 | 3 | 1. 加入 https://www.facebook.com/groups/rails101/ FB 社團。在社團上問問題 4 | 2. 在 Google 或 Stack Overflow 上用錯誤訊息關鍵字找答案。 5 | 3. 你是不是忘了加 `@` 呢? 6 | 4. 你是不是忘了跑 migration 呢? 7 | 5. 你是不是忘記加欄位呢? 8 | 6. 你是不是忘記 touch tmp/restart.txt 呢? 9 | 7. 在 http://guides.rubyonrails.org 找線索 10 | 8. 檢查 development.log 或者是 Chrome 的 DevTools Console 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /manuscript/recap.txt: -------------------------------------------------------------------------------- 1 | {::pagebreak :/} 2 | 3 | ## 總複習 4 | 5 | 到目前為止。這本書提到的主題有 6 | 7 | * rvm 8 | * pow 9 | * CRUD 10 | * RESTFUL 11 | * 雙層 RESTFUL 12 | * 登入(Devise) 13 | * Scope 14 | * Helper 15 | * Partial 16 | * Capistrano 17 | * Asset Pipeline 18 | 19 | 這本書原始的想法,其實是把一個初學者需要自學「一年」(我沒有開玩笑)的主題,濃縮到幾個禮拜。所以基本上這本書不能用「看」的,必須要動手實作。並且重複練習 3 次以上。 20 | 21 | 所以看到這裡,還請你重新再來一次。(認真) 22 | 23 | 不過這次你可以嘗試不一樣的作法。 24 | 25 | 26 | #### 第二遍 27 | 28 | 每個 chapter 都開一個 git branch 實作。並且推到 Github 上。 29 | 30 | #### 第三遍 31 | 32 | 幫 Groupme 加上 admin 後台,以及其他功能。並且推到 Github 上。 33 | 34 | 相信做到第三遍之後,你應該可以自然而然看懂原本像天書一般的 Rails API。 35 | 36 | -------------------------------------------------------------------------------- /manuscript/requirement.txt: -------------------------------------------------------------------------------- 1 | # 學習 Rails 前置所需要的技能 2 | 3 | 經過這些年的 Rails Developer 培訓之後,我強烈建議開始學習 Rails 之前,請先學會以下相關技能: 4 | 5 | * Git 6 | * 熟習 Vim 以及 SublimeText2 其中一種開發工具 7 | * 熟悉 Linux Command Line 的操作 8 | 9 | ## Git 學習資源 10 | 11 | Codeshool 出了三套 Git 課程。請先練習過後,再開始學習 Rails 開發,我相當不建議讀者跳過 Git 技巧的練習。 12 | 13 | * TryGit 14 | * GitReal 15 | * GitReal2 (進階技巧,可以之後再學) 16 | 17 | 18 | X>## 練習作業 19 | X> 20 | X> 1. 上 [Github](https://github.com) 註冊一個帳號 21 | X> 22 | X> 2. 練習以下 Git 指令 23 | X> 24 | X> * git commit 25 | X> * git push 26 | X> * git pull 27 | X> * git branch 28 | X> * git checkout 29 | X> * git merge 30 | 31 | {::pagebreak :/} 32 | 33 | ## 編輯器學習資源 34 | 35 | ### Vim 36 | 37 | * c9s 的 [Vim Hacks](http://c9s.blogspot.com/2009/08/vim-hacks-coscup.html) 是相當好的 vim 學習資源 38 | 39 | ### Sublimtext 2 40 | 41 | * Sunlimtext 2 下載網址: 42 | 43 | * [Sublime Text 台灣](https://www.facebook.com/SublimeTextTW) 社群最近也撰寫了一本相當不錯的[SublimeText 學習手冊](http://docs.sublimetext.tw/) 44 | 45 | ## Linux Command Line 學習資源 46 | 47 | * PeepCode 的 [Meet the Command Line](http://peepcode.com/products/meet-the-command-line) 48 | * PeepCode 的 [Advanced Command Line](http://peepcode.com/products/advanced-command-line) 49 | 50 | 51 | X>## 練習作業 52 | X> 53 | X> * 練習 [Meet the Command Line](http://peepcode.com/products/meet-the-command-line) 54 | X> * 練習 [Advanced Command Line](http://peepcode.com/products/advanced-command-line) 55 | 56 | 57 | --------------------------------------------------------------------------------