├── .gitignore ├── CNAME ├── LICENSE ├── README.md ├── _config.yml ├── images ├── chapter01 │ ├── c9.io.png │ └── try_ruby.jpg ├── chapter02 │ └── welcome_page.png ├── chapter03 │ └── pending_migration.png ├── chapter04 │ ├── post-scaffold-1.png │ ├── post-scaffold-2.png │ ├── post-scaffold-3.png │ ├── post-scaffold-4.png │ ├── user-scaffold-1.png │ ├── user-scaffold-2.png │ └── user-scaffold-3.png ├── chapter08 │ └── cake_maker.jpg ├── chapter09 │ ├── paging-01.png │ ├── paging-02.png │ ├── paging-03.png │ └── rubygems.png ├── chapter10 │ ├── folder-config.png │ ├── folder-controller.png │ ├── folder-model.png │ ├── folder-view.png │ └── mvc.png ├── chapter11 │ ├── no-action-error.png │ ├── no-controller-error.png │ ├── no-route-error.png │ ├── no-route-error2.png │ └── not-php.png ├── chapter12 │ ├── bmi-1.png │ ├── bmi-2.png │ ├── bmi-3.png │ ├── bmi-4.png │ ├── controller-view-mapping.png │ ├── invalid-authenticity-token-error.png │ ├── puts-to-console.png │ ├── render-hello-world-with-view.png │ ├── render-hello-world.png │ ├── render-params-1.png │ └── render-params-2.png ├── chapter13 │ ├── forbidden-attributes-error.png │ ├── form-vs-mvc.png │ ├── vote-candidate-01.png │ ├── vote-candidate-02.png │ ├── vote-candidate-03.png │ ├── vote-candidate-04.png │ ├── vote-candidate-05.png │ ├── vote-candidate-06.png │ ├── vote-candidate-07.png │ ├── vote-candidate-08.png │ ├── vote-candidate-09.png │ ├── vote-candidate-10.png │ ├── vote-candidate-11.png │ └── vote-candidate-12.png ├── chapter14 │ ├── layout.png │ ├── missing-template-error.png │ ├── render-icon-1.png │ ├── render-icon-2.png │ └── yield-title.png ├── chapter15 │ └── user-table.png ├── chapter16 │ └── pending-migration-error.png ├── chapter17 │ ├── many-to-many-group.png │ ├── many-to-many-model.png │ ├── many-to-many-tables.png │ ├── user-store-model.png │ ├── user-store-product-model.png │ ├── user-store-product-tables.png │ └── user-store-tables.png ├── chapter18 │ └── model-lifecycle.png ├── chapter19 │ ├── sendmail-1.png │ └── sendmail-2.png ├── chapter21 │ ├── index-html.png │ ├── index-json.png │ └── views.png ├── chapter24 │ ├── inheritance-1.png │ ├── inheritance-2.png │ └── user-list-1.png ├── chapter25 │ └── cart.png ├── chapter27 │ ├── fsm.png │ └── state-machine.png ├── chapter28 │ ├── api-keys.png │ ├── braintree-client.png │ ├── braintree.png │ ├── dash-board.png │ ├── paid.png │ ├── payment-form.png │ ├── post-error.png │ ├── show-product.png │ └── v2-v3.png └── chapter29 │ ├── heroku-pricing.png │ └── heroku.png └── markdown ├── chapter00-about.md ├── chapter01-ecosystem-and-introduction.md ├── chapter02-environment-setup.md ├── chapter03-command-line-tools.md ├── chapter04-your-first-rails-application.md ├── chapter05-ruby-basic-1.md ├── chapter06-ruby-basic-2.md ├── chapter07-ruby-basic-3.md ├── chapter08-ruby-basic-4.md ├── chapter09-using-gems.md ├── chapter10-mvc.md ├── chapter11-routes.md ├── chapter12-controllers.md ├── chapter13-crud.md ├── chapter14-layout-render-and-view-helper.md ├── chapter15-model-basic.md ├── chapter16-model-migration.md ├── chapter17-model-relationship.md ├── chapter18-model-validation-and-callback.md ├── chapter19-send-email.md ├── chapter20-background-job.md ├── chapter21-api-mode.md ├── chapter22-testing-with-rspec-part-1.md ├── chapter23-testing-with-rspec-part-2.md ├── chapter24-organize-your-code.md ├── chapter25-shopping-cart-part-1.md ├── chapter26-shopping-cart-part-2.md ├── chapter27-order.md ├── chapter28-payment.md └── chapter29-deployment.md /.gitignore: -------------------------------------------------------------------------------- 1 | resources/ 2 | bin/ 3 | output/ 4 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | learnrails.kaochenlong.com 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 為你自己學 Ruby on Rails 2 | 3 | 「為你自己學 Ruby on Rails」系列文章。 4 | 5 | 如其標題,學習不需要為公司、不需要為長官、同事、不需要為別人,只為你自己。 6 | 7 | 本 Repo 之 markdown 檔案之後可能不會再維護,之後版本更新(Ruby 2.4.1 及 Rails 5.1)以及新增的章節也將會直接上傳至好讀版本而不再更新至此,好讀版本請麻煩前往 8 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman 2 | -------------------------------------------------------------------------------- /images/chapter01/c9.io.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter01/c9.io.png -------------------------------------------------------------------------------- /images/chapter01/try_ruby.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter01/try_ruby.jpg -------------------------------------------------------------------------------- /images/chapter02/welcome_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter02/welcome_page.png -------------------------------------------------------------------------------- /images/chapter03/pending_migration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter03/pending_migration.png -------------------------------------------------------------------------------- /images/chapter04/post-scaffold-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter04/post-scaffold-1.png -------------------------------------------------------------------------------- /images/chapter04/post-scaffold-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter04/post-scaffold-2.png -------------------------------------------------------------------------------- /images/chapter04/post-scaffold-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter04/post-scaffold-3.png -------------------------------------------------------------------------------- /images/chapter04/post-scaffold-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter04/post-scaffold-4.png -------------------------------------------------------------------------------- /images/chapter04/user-scaffold-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter04/user-scaffold-1.png -------------------------------------------------------------------------------- /images/chapter04/user-scaffold-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter04/user-scaffold-2.png -------------------------------------------------------------------------------- /images/chapter04/user-scaffold-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter04/user-scaffold-3.png -------------------------------------------------------------------------------- /images/chapter08/cake_maker.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter08/cake_maker.jpg -------------------------------------------------------------------------------- /images/chapter09/paging-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter09/paging-01.png -------------------------------------------------------------------------------- /images/chapter09/paging-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter09/paging-02.png -------------------------------------------------------------------------------- /images/chapter09/paging-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter09/paging-03.png -------------------------------------------------------------------------------- /images/chapter09/rubygems.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter09/rubygems.png -------------------------------------------------------------------------------- /images/chapter10/folder-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter10/folder-config.png -------------------------------------------------------------------------------- /images/chapter10/folder-controller.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter10/folder-controller.png -------------------------------------------------------------------------------- /images/chapter10/folder-model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter10/folder-model.png -------------------------------------------------------------------------------- /images/chapter10/folder-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter10/folder-view.png -------------------------------------------------------------------------------- /images/chapter10/mvc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter10/mvc.png -------------------------------------------------------------------------------- /images/chapter11/no-action-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter11/no-action-error.png -------------------------------------------------------------------------------- /images/chapter11/no-controller-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter11/no-controller-error.png -------------------------------------------------------------------------------- /images/chapter11/no-route-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter11/no-route-error.png -------------------------------------------------------------------------------- /images/chapter11/no-route-error2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter11/no-route-error2.png -------------------------------------------------------------------------------- /images/chapter11/not-php.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter11/not-php.png -------------------------------------------------------------------------------- /images/chapter12/bmi-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter12/bmi-1.png -------------------------------------------------------------------------------- /images/chapter12/bmi-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter12/bmi-2.png -------------------------------------------------------------------------------- /images/chapter12/bmi-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter12/bmi-3.png -------------------------------------------------------------------------------- /images/chapter12/bmi-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter12/bmi-4.png -------------------------------------------------------------------------------- /images/chapter12/controller-view-mapping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter12/controller-view-mapping.png -------------------------------------------------------------------------------- /images/chapter12/invalid-authenticity-token-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter12/invalid-authenticity-token-error.png -------------------------------------------------------------------------------- /images/chapter12/puts-to-console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter12/puts-to-console.png -------------------------------------------------------------------------------- /images/chapter12/render-hello-world-with-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter12/render-hello-world-with-view.png -------------------------------------------------------------------------------- /images/chapter12/render-hello-world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter12/render-hello-world.png -------------------------------------------------------------------------------- /images/chapter12/render-params-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter12/render-params-1.png -------------------------------------------------------------------------------- /images/chapter12/render-params-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter12/render-params-2.png -------------------------------------------------------------------------------- /images/chapter13/forbidden-attributes-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter13/forbidden-attributes-error.png -------------------------------------------------------------------------------- /images/chapter13/form-vs-mvc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter13/form-vs-mvc.png -------------------------------------------------------------------------------- /images/chapter13/vote-candidate-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter13/vote-candidate-01.png -------------------------------------------------------------------------------- /images/chapter13/vote-candidate-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter13/vote-candidate-02.png -------------------------------------------------------------------------------- /images/chapter13/vote-candidate-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter13/vote-candidate-03.png -------------------------------------------------------------------------------- /images/chapter13/vote-candidate-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter13/vote-candidate-04.png -------------------------------------------------------------------------------- /images/chapter13/vote-candidate-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter13/vote-candidate-05.png -------------------------------------------------------------------------------- /images/chapter13/vote-candidate-06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter13/vote-candidate-06.png -------------------------------------------------------------------------------- /images/chapter13/vote-candidate-07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter13/vote-candidate-07.png -------------------------------------------------------------------------------- /images/chapter13/vote-candidate-08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter13/vote-candidate-08.png -------------------------------------------------------------------------------- /images/chapter13/vote-candidate-09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter13/vote-candidate-09.png -------------------------------------------------------------------------------- /images/chapter13/vote-candidate-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter13/vote-candidate-10.png -------------------------------------------------------------------------------- /images/chapter13/vote-candidate-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter13/vote-candidate-11.png -------------------------------------------------------------------------------- /images/chapter13/vote-candidate-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter13/vote-candidate-12.png -------------------------------------------------------------------------------- /images/chapter14/layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter14/layout.png -------------------------------------------------------------------------------- /images/chapter14/missing-template-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter14/missing-template-error.png -------------------------------------------------------------------------------- /images/chapter14/render-icon-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter14/render-icon-1.png -------------------------------------------------------------------------------- /images/chapter14/render-icon-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter14/render-icon-2.png -------------------------------------------------------------------------------- /images/chapter14/yield-title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter14/yield-title.png -------------------------------------------------------------------------------- /images/chapter15/user-table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter15/user-table.png -------------------------------------------------------------------------------- /images/chapter16/pending-migration-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter16/pending-migration-error.png -------------------------------------------------------------------------------- /images/chapter17/many-to-many-group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter17/many-to-many-group.png -------------------------------------------------------------------------------- /images/chapter17/many-to-many-model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter17/many-to-many-model.png -------------------------------------------------------------------------------- /images/chapter17/many-to-many-tables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter17/many-to-many-tables.png -------------------------------------------------------------------------------- /images/chapter17/user-store-model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter17/user-store-model.png -------------------------------------------------------------------------------- /images/chapter17/user-store-product-model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter17/user-store-product-model.png -------------------------------------------------------------------------------- /images/chapter17/user-store-product-tables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter17/user-store-product-tables.png -------------------------------------------------------------------------------- /images/chapter17/user-store-tables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter17/user-store-tables.png -------------------------------------------------------------------------------- /images/chapter18/model-lifecycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter18/model-lifecycle.png -------------------------------------------------------------------------------- /images/chapter19/sendmail-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter19/sendmail-1.png -------------------------------------------------------------------------------- /images/chapter19/sendmail-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter19/sendmail-2.png -------------------------------------------------------------------------------- /images/chapter21/index-html.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter21/index-html.png -------------------------------------------------------------------------------- /images/chapter21/index-json.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter21/index-json.png -------------------------------------------------------------------------------- /images/chapter21/views.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter21/views.png -------------------------------------------------------------------------------- /images/chapter24/inheritance-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter24/inheritance-1.png -------------------------------------------------------------------------------- /images/chapter24/inheritance-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter24/inheritance-2.png -------------------------------------------------------------------------------- /images/chapter24/user-list-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter24/user-list-1.png -------------------------------------------------------------------------------- /images/chapter25/cart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter25/cart.png -------------------------------------------------------------------------------- /images/chapter27/fsm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter27/fsm.png -------------------------------------------------------------------------------- /images/chapter27/state-machine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter27/state-machine.png -------------------------------------------------------------------------------- /images/chapter28/api-keys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter28/api-keys.png -------------------------------------------------------------------------------- /images/chapter28/braintree-client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter28/braintree-client.png -------------------------------------------------------------------------------- /images/chapter28/braintree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter28/braintree.png -------------------------------------------------------------------------------- /images/chapter28/dash-board.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter28/dash-board.png -------------------------------------------------------------------------------- /images/chapter28/paid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter28/paid.png -------------------------------------------------------------------------------- /images/chapter28/payment-form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter28/payment-form.png -------------------------------------------------------------------------------- /images/chapter28/post-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter28/post-error.png -------------------------------------------------------------------------------- /images/chapter28/show-product.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter28/show-product.png -------------------------------------------------------------------------------- /images/chapter28/v2-v3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter28/v2-v3.png -------------------------------------------------------------------------------- /images/chapter29/heroku-pricing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter29/heroku-pricing.png -------------------------------------------------------------------------------- /images/chapter29/heroku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaochenlong/learn-ruby-on-rails/919fd46901bb7ecbc6be513d8ddb4970717e1514/images/chapter29/heroku.png -------------------------------------------------------------------------------- /markdown/chapter00-about.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | title: 寫在最前面 4 | comments: true 5 | permalink: /chapters/00-about.html 6 | 7 | --- 8 | 9 | # 寫在最前面 10 | 11 | - [為什麼要寫這本書](#why-this-book) 12 | - [誰適合本書](#who-need-this-book) 13 | - [本書內容](#content) 14 | - [如何使用這本書](#how-to-use-this-book) 15 | - [軟體版本](#version) 16 | - [程式碼慣例](#code-convention) 17 | - [程式範例及錯誤更正](#errata) 18 | - [關於我](#about-me) 19 | 20 | ## 為什麼要寫這本書 21 | 22 | Ruby on Rails(以下簡稱 Rails)是一個非常具有生產力的網站開發框架,透過 Rails 本身的設計以及大量的外部第三方套件,可以很快的就把一個網站應用程式的雛型做出來,所以許多創業圈的朋友會選擇使用它來打造產品也是因為這個原因,快速的先把產品做出來,放到市場上試試水溫。 23 | 24 | 不過 Ruby 這個程式語言以及 Rails 這個網站開發框架,都隱藏了許多的細節,所以用起來雖然感覺很好寫、好用,但其實不太好學,在到上手之前的那段路如果沒人指導的話會走得有點辛苦,特別是對完全沒有技術背景基礎的新手來說難度更高。我自己是醫學院畢業的學生,身為不務正業的非資訊相關科班生,我完全可以體會從新手在這條路上會吃哪些苦頭。 因為所有的技術都得自己想辦法摸索、研究,所以也因此知道新手在學習的時候通常在哪邊會跌倒、踩到地雷。在近幾年[五倍紅寶石](https://5xruby.tw)以及國內各大專院校的課程教學中,觀察、整理出新手容易卡關的點,希望這本書可以幫大家快速的度過新手的撞牆期。 25 | 26 | 在 2007 年的時候,我買了第一本 Ruby 的參考書,當時看完之後只覺得 Ruby 這個程式語言的語法很有趣,但平日公司的業務用的是 ASP 跟 PHP,我不知道這個能在我日常工作上幫到什麼忙。直到 2009 年接觸到了 Rails 之後才發現,原來 Ruby 加上 Rails 之後可以讓開發變得這麼有趣,而且可以這麼有生產力,能讓我在短時間之內就把想做的東西做出來,有更多的時間可以玩樂、做自己想做的事(寫更多的程式..)。 27 | 28 | 也許因為個性的關係,在學習新事物的過程中如果有疑惑的地方,總是希望可以搞懂為止,否則知其然而不知其所以然是沒辦法真的把一門技術搞懂。也因為這樣,本書在撰寫的時候也發揮了我愛囉嗦的專長,即使是簡單的小地方,也希望可以解釋的夠清楚。期望可以不只可以教大家如何寫(How),也能讓大家知道在寫什麼(What),以及為什麼(Why)要這樣寫。 29 | 30 | 至於 Rails 的優、缺點就先不多提了,如人飲水,冷暖自知,還請各位自行來體會 Rails 有趣(或不有趣)的地方,這也是本書最主要的目的。 31 | 32 | 雖然本書是以中文撰寫,但程式語言的專有名詞大多還是英文,所以這些名詞或是常用的口語我還是會儘量使用英文來表示。一來是大家的翻譯可能不一樣,例如 "Default" 在繁體中文翻做「預設」,在簡體中文則是翻譯成「默認」;二來也是因為有些英文字翻譯了反而沒有原文貼切,例如 "Context"、"Meta Programming" 等字。最重要的一點,是希望各位能儘早習慣這些英文,因為實際在業界工作時,很多第一手的資料都是英文的,儘早習慣英文對大家絕對是有幫助的。 33 | 34 | 很多人會比較各種程式語言或開發框架的優劣,比較誰的效能好、誰的功能強大、程式碼可讀性高等等的比較,但這種「戰爭」是戰不完的,而且本身也沒有太大的意義,更何況我個人對我不精通的語言我也沒那個份量來批評。在本書中或多或少會提到「我當初在某些程式語言是如何實作,但在 Ruby/Rails 是這樣做的」之類的比較,這並非比較誰優誰劣,僅為了給曾經寫過該程式語言的朋友們能更輕易的體會我想表達的意思。 35 | 36 | 再次強調,各種程式語言或工具之間並沒有絕對的好或不好的問題,只有適不適合的問題。只要能解決問題的,不管是冷門或熱門,都是好的工具。 37 | 38 | ## 誰適合本書 39 | 40 | 不管您是新手或老鳥,只要你對 Rails 這個網站開發框架有興趣都適合。如果您本身已經有其它程式語言或 Web 開發的經驗,在閱讀本書的前半段應該會相對的比較輕鬆。 41 | 42 | ## 本書內容 43 | 44 | ### 會包括以下內容 45 | 46 | - Ruby / Rails 簡介、環境安裝。 47 | - 套件安裝、使用 48 | - Rails 的專案架構 49 | - 其它實用功能,例如 Email 寄發、工作排程、購物車、訂單處理、金流串接等 50 | 51 | ### 不會包括以下內容 52 | 53 | - 不會教你怎麼寫 HTML/CSS/Javascript。 54 | - 不會教你怎麼使用 Git。 55 | - 不會教你所有的 Ruby 語法或是進階的 Meta Programming 技巧。 56 | 57 | 因篇幅有限,沒辦法包山包海,所以以上內容不會收納在本書內容裡,且上面所列的每一個主題,都可以是獨立的一本書,建議再找更專業的參考書籍。 58 | 59 | ### 你需要準備什麼? 60 | 61 | - 一台可以工作的電腦(不一定要 Mac) 62 | - 一款順手的文字編輯器 63 | - 這樣就夠了 :) 64 | 65 | ## 如何使用這本書 66 | 67 | 本書主要分以下幾部份: 68 | 69 | 1. 開發工具、環境安裝篇 70 | 2. Ruby 基本篇 71 | 3. Rails 入門篇 72 | 4. 實作篇 73 | 74 | 雖然每個章節多少都還是會跟前面的章節有相關,但也不一定要依序從第一章開始閱讀(當然這也是一種方式),可依自己需要跳過部份章節。 75 | 76 | ## 軟體版本 77 | 78 | 隨著時間,Ruby 跟 Rails 的版本可能會有些微不同,本書在撰寫當下所使用的版本為: 79 | 80 | - Ruby 2.4.0 81 | - Rails 5.0.1 82 | 83 | ## 程式碼慣例 84 | 85 | 在開發 Ruby/Rails 程式的時候會有很多機會需要在終端機(Terminal)模式下輸入指令,例如: 86 | 87 | $ ruby hello.rb 88 | hello, world 89 | 90 | 或是這樣: 91 | 92 | $ ruby -v 93 | ruby 2.4.0p0 (2016-12-24 revision 57164) [x86_64-darwin15] 94 | 95 | 在最前面的 `$` 符號是系統提示字元,意思是告訴各位這是一個需要在終端機環境下自己手動輸入的指令,而下一行則是這個指令執行的結果。實際在輸入指令的時候請不要跟著輸入 `$`,不然會出現 `command not found` 的錯誤訊息。 96 | 97 | 有時候你可能會看到這樣寫: 98 | 99 | >> puts "Hello, Ruby" 100 | Hello, Ruby 101 | 102 | 這裡的 `>>` 則是表示這行指令是在 `irb` 或 `rails console` 的環境下輸入的,同樣也不需要跟著輸入這個 `>>` 符號。另外,有時會在程式碼的結尾加上一些註解,例如: 103 | 104 | ```ruby 105 | def calc(n) 106 | n * n 107 | end 108 | 109 | puts calc(4) # => 16 110 | ``` 111 | 112 | 在最後一行加上去的註解是說明或是表示這行程式的輸出結果,各位可以不需要跟著輸入。最後,Ruby 目前有好幾種分支實作品(例如 JRuby、mruby、IronRuby 等),各分支實作品也有好幾種版本,如果沒有特別註明,本書中提到的 Ruby 指的都是 CRuby,也就是最常見的 Ruby 版本,目前最新版本是 2.4.0。 113 | 114 | ## 程式範例及錯誤更正 115 | 116 | 本書所有的程式碼在 `Ruby 2.4.0` 及 `Rails 5.0.1` 的環境下均已測試可正常執行,檔案可在我的 GitHub 帳號取得。隨著 Ruby 及 Rails 的版本演進,或是作業系統的不同,範例程式執行的結果可能會有些微的差異(甚至是錯誤)。若有任何問題,或是有哪邊寫錯,還請各位先進不吝留言或來信、留言指教。 117 | 118 | 最後,希望各位會喜歡本書,一起來學習、體驗 Rails 這個極富生產力的網站開發框架 :) 119 | 120 | ## 關於我 121 | 122 | 高見龍,這看起來有點像武俠小說的名字不是筆名,而是我父母給我的本名。目前是兩個小朋友的爸爸,是個愛寫程式而且希望可以寫一輩子程式的阿宅。 123 | 124 | * [五倍紅寶石](https://5xruby.tw)創辦人及負責人 125 | * Blog: 126 | * Facebook: 127 | * Twitter: 128 | * Github: 129 | * Email: eddie@5xruby.tw 130 | 131 | 若發現本書內容有誤,歡迎直接來信,或到 GitHub 上發 Pull Request 修正 :) 132 | 133 | -------------------------------------------------------------------------------- /markdown/chapter01-ecosystem-and-introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | title: 生態圈及簡介 4 | comments: true 5 | permalink: /chapters/01-ecosystem-and-introduction.html 6 | 7 | --- 8 | 9 | # 生態圈及簡介 10 | 11 | - [Ruby 生態圈](#ruby-ecosystem) 12 | - [關於 Ruby](#about-ruby) 13 | - [關於 Rails](#about-rails) 14 | - [常見問題](#faq) 15 | 16 | ## Ruby 生態圈 17 | 18 | Ruby 這個程式語言可以說是因為 Rails 的盛行而興起的也不為過,我認識大部份的人會開始學習 Ruby 或知道 Ruby 這個程式語言,大多是因為 Rails 的緣故。事實上,在 Rails 風行之前,Ruby 這個程式語言可能全世界幾乎只有日本的工程師在使用。 19 | 20 | ## 關於 Ruby 21 | 22 | ### 什麼是 Ruby? 23 | 24 | 很多人因為聽聞 Rails 可以快速開發網站而開始知道有 Ruby 這個程式語言,所以會認為 Ruby 就是用來開發網站,或是以為 Ruby 是個最近幾年才發明的程式語言。事實上 Ruby 是一種泛用的腳本式程式語言,從資料分析、繪圖、3D 建模、系統管理、遊戲開發等程式都可以使用 Ruby 來開發,而且它的年紀已經超過 20 年了。 25 | 26 | Ruby 是由一位名叫[松本行弘](https://zh.wikipedia.org/wiki/%E6%9D%BE%E6%9C%AC%E8%A1%8C%E5%BC%98)的日本人所發明(日文:まつもとゆきひろ,網路上大家通常稱他 Matz)。Ruby 參考了 Perl、Lisp 及 Smalltalk 等程式語言的設計,是一款物件化非常徹底的程式語言。在 1995 年釋出了第一個版本,在早期實際使用 Ruby 在工作上的開發者並不多,相關的技術文件也大多是日文居多,直到 Rails 開始風行之後,才慢慢的有越來越多人關注它。 27 | 28 | ### 為什麼選擇 Ruby 29 | 30 | 引用一句已故大師 Alan Perlis 的話: 31 | 32 | > "A language that doesn't affect the way you think about programming is not worth knowing" — Alan Perlis 33 | 34 | 中文意思是: 35 | 36 | >「如果某種程式語言不會影響你寫程式的思考方式的話,那就不值得去學習它。」 37 | 38 | Ruby 是個很容易學、很容易上手的程式語言,語法寫起來也很自然、有趣,也因為 Ruby 的自然語法,寫久了真的會影響你在寫程式時候的思考或設計方式。 39 | 40 | 因為 Ruby 的語法寫起來很自然,所以用 Ruby 寫出來的程式碼的可閱讀性也相當高。不管是接手別人的專案,或是維護自己幾個月前寫的系統,比較好的程式碼可讀性對開發者來說可以減少不少負擔。 41 | 42 | 另外,現在全世界的 Ruby 社群都相當活躍,要找什麼套件幾乎都有熱心人士幫忙寫好了。除了可以免費取得之外,連原始程式碼都公開給你看。在台灣,Ruby 社群也是十分活躍,每個月甚至每週都有實體的線下聚會,也有大型的國際程式研討會 [RubyConf Taiwan](http://rubyconf.tw),每年都有不少國內外的 Ruby 開發者前來與會,連 Ruby 的發明人松本行弘也會遠從日本來台灣參加。 43 | 44 | 在本文撰寫的當下,官方最新推出的 Ruby 穩定版本是 2.4.0 版,較舊版本的 Ruby(1.8、1.9 或更早之前)的部份功能也可能會被提到,但以下文章仍會以 2.4.0 版本為主。Ruby 2 系列對之前的版本有向下相容的特性,原本在 1.9 版可以正常執行的程式碼,在 2.0 應該也可以正常運作。 45 | 46 | ### 誰在用 Ruby? 47 | 48 | 很多人在評估程式語言的優劣,是看有哪些大公司、單位在使用它,或是使用的開發者人數。老實說我個人不是很關心這個問題,有些人覺得 Ruby 並不是很流行,在 [TIOBE](http://www.tiobe.com) 網站上的排名也不是非常前面,但我個人認為,好的東西不一定要流行,只要能完成任務的工具就是好工具。在 iPhone 還沒流行之前,誰也沒料到開發 iOS app 的 Objective-C 這個語法看起來很奇怪的程式語言有一天可以這麼熱門。 49 | 50 | ## 關於 Rails 51 | 52 | ### 什麼是 Rails? 53 | 54 | Rails 是一款使用 Ruby 程式語言所開發出來的網站開發框架(Web Framework),作者是名為 David Heinemeier Hansson(簡稱 DHH) 的丹麥人。當年他在開發自家的產品的同時,發現好像可以把一些網站開發常用的模組或函式庫組成一個框架,利用這個框架可以大大的縮短網站應用程式開發的時間。DHH 在 2005 年年底釋出第一個版本,並在研討會現場展示如何使用 Rails 在 15 分鐘內開發出一個 Blog,讓所有的人眼睛為之一亮,在那之後 Rails 便慢慢的風行到全世界,現在世面上常見的網頁開發框架的設計,多少也直接或間接的受了 Rails 的影響。 55 | 56 | 一開始的時候大家會把 Ruby on Rails 簡稱為「RoR」,不過因為「RoR」實在不好發音,後來大家開始慢慢的改稱之 Rails,包括本書也是。 57 | 58 | 有些朋友在學習 Rails 過程中曾問道「即然 Rails 這麼方便,那有必要學 Ruby 嗎?」。我的建議是:「是的,有必要。你也許不需要把 Ruby 學得非常熟、不需要知道 Ruby 裡所有的方法,但至少你該學會在 Rails 專案裡常看到的 Ruby 語法」。 59 | 60 | 很多人一開始可能搞不清楚 Ruby 跟 Rails 之間的關係,如果打個比方的話,大家也許看過或玩過樂高(Lego)積木,Rails 就像是一塊一塊的積木,可以讓你很快的把城堡蓋起來;而 Ruby 則像是積木的原料(塑膠),沒有原料就不會有這個積木。 61 | 62 | Ruby 是一款設計很特別、寫起來也很特別的程式語言,如果能花時間更去深入 Ruby 這個程式語言特別的點,相信在寫 Rails 的時候可以寫出更漂亮、簡潔、有效率的語法。 63 | 64 | ### Rails 設計哲學 65 | 66 | Rails 的兩大設計哲學: 67 | 68 | - 慣例優於設定(Convention over Configuration, CoC) 69 | - 不要做重複的事(Don't Repeat Yourself, DRY) 70 | 71 | #### 慣例優於設定(Convention over Configuration, CoC) 72 | 73 | 所謂的「慣例」就像是不成文的規定,當遇到某種情況的時候我們會用特定的方式來解決問題,或是該把某個功能的程式碼放在什麼地方,不過即使不照著慣例寫,也有別的方法可以達到一樣的目的。 74 | 75 | 在 Rails 裡有相當多這樣的慣例,例如像是專案的目錄結構、資料表的關連及命名等,順著 Rails 的慣例,程式碼可以變得更簡潔、優雅。甚至可以說在學習 Rails 的過程,除了學習 Ruby/Rails 的語法之外,也是在學習 Rails 的慣例。 76 | 77 | #### 不要做重複的事(Don't Repeat Yourself, DRY) 78 | 79 | 如果有些程式碼或結構一直重複的出現,就應該把重複的部份抽離出來,整理成為一個方法、類別或模組。這樣不僅可以重複使用,也會因此變得比較好維護,Bug 也比較容易被發現。 80 | 81 | ## 常見問題 82 | 83 | ### 聽說寫 Ruby/Rails 要先買 Mac 電腦? 84 | 85 | 其實不需要的,使用 Mac OS 作業系統只是在開發環境上比較方便,但並不是必需品,即使用一般的 PC 安裝 Linux/Ubuntu 系統一樣可以進行開發。事實上,最後網站部署的環境也是 Linux/Ubuntu,所以不要聽信「要先學 Ruby/Rails 要先買 Mac」之類的江湖謠言。 86 | 87 | 當然,如果經濟許可,使用 Mac 電腦開發 Ruby/Rails 專案是件還滿開心的事。 88 | 89 | ### 聽說 Ruby/Rails 很慢? 90 | 91 | 這點的確不否認,以各家的程式語言來說,Ruby 的確不是最快的程式語言,Rails 也為了功能的完整性,本身也是一個體積有點肥胖的框架。即便如此,Ruby/Rails 並沒有慢到不堪使用的程度。 92 | 93 | Ruby 的重點,在於可以用自然、簡短的語法開發你想要的功能,而 Rails 的重點則是可以快速的打造出產品雛型,早點上線試一下水溫,試一下商業模式是否可以運作,如果真的有人買單,再來擔心效能也不遲。流量如果真的做起來的話,需要比較好的效能的部份也可再考慮改用其它程式語言改寫。 94 | 95 | 線上遊戲有一句名言「死人沒有 DPS」,意思就是有再強大的武器,躺在地上看星星也是沒用的。同理,若使用強大效能的程式語言或框架但開發時程較長而錯過市場時機,效能再快、再好也是沒有用的。 96 | 97 | ### 用 Windows 系統可以嗎? 98 | 99 | 如果只是練習,使用 Windows 作業系統是可以的,有打包好的懶人安裝包([Ruby Installer](https://rubyinstaller.org/) 及 [Rails Installer](http://railsinstaller.org/en)),但在 Windows 平台可能常常會遇到套件安裝失敗或不支援的問題。如果您平常的工作還是在 Windows 平台的話,建議您可以使用 [VirtualBox](https://www.virtualbox.org/) 在 Windows 上安裝個 Linux/Ubuntu 的虛擬作業環境,可以少一些麻煩。 100 | 101 | ### 一定要安裝在自己的電腦裡嗎? 102 | 103 | 也許因為某些因素,你無法在電腦上安裝 Ruby/Rails 環境(例如設備是借來的、公司內部管制,或是設備的記憶體不夠多,跑不動 VirtualBox 之類的軟體等),也有其它線上的環境可以讓你練習。 104 | 105 | 如果只是想練一下 Ruby 的話,可以試試 [Try Ruby](http://tryruby.org/) 網站: 106 | 107 | ![Try Ruby](/images/chapter01/try_ruby.jpg) 108 | 109 | 在畫面的左邊會一直出現要你完成的題目,右邊輸入區則是類似 Ruby 的 irb 環境。 110 | 111 | 如果想要在線上寫 Rails 的話,[c9.io](https://c9.io/) 這個線上平台也是個很不錯的選項: 112 | 113 | ![c9.io](/images/chapter01/c9.io.png) 114 | 115 | c9.io 除了有線上開發環境外,也可直接在線上有預覽的功能。 116 | 117 | 事實上,在各種不同的硬體及作業系統安裝 Ruby/Rails 環境,對剛入門的新手來說是個很大的挑戰,所以不管在我們學校或是企業的教育訓練課程中,我們也常推薦大家直接使用 c9.io 之類的線上平台來進行練習。 118 | 119 | -------------------------------------------------------------------------------- /markdown/chapter02-environment-setup.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | title: 環境設定 4 | comments: true 5 | permalink: /chapters/02-environment-setup.html 6 | 7 | --- 8 | 9 | # 環境設定 10 | 11 | 要開始用 Ruby 或 Rails 來開發網站之前,第一件事(其實可能也是最難的事?)就是要先搞定開發環境。 12 | 13 | - [安裝 Ruby](#install-ruby) 14 | - [用 RVM 來管理 Ruby 版本](#use-rvm) 15 | - [安裝 Rails](#install-rails) 16 | - [建立 Rails 專案](#build-rails-project) 17 | 18 | ## 安裝 Ruby 19 | 20 | ### Unix/Linux 作業系統 21 | 22 | 如果您使用的是 Ubuntu 之類的系統,可以直接使用 `apt-get` 來安裝 Ruby: 23 | 24 | $ sudo apt-get install ruby 25 | 26 | 如果是 CentOS 之類的系統,則是使用 `yum` 來安裝: 27 | 28 | $ sudo yum install ruby 29 | 30 | 因為使用了 `sudo` 指令,所以你應該會被提示需要輸入目前的登入密碼。 31 | 32 | ### Mac 作業系統 33 | 34 | 如果是 Mac 作業系統,比較新的版本均已內建 Ruby 2.0 版本,如果沒有的話,建議可使用 [Homebrew](http://brew.sh/) 這個套件管理工具來安裝 Ruby: 35 | 36 | $ brew install ruby 37 | 38 | ### Windows 作業系統 39 | 40 | 在 Windows 平台可根據您的需求,選擇安裝 [Ruby Installer](http://rubyinstaller.org/) 或 [Rails Installer](http://railsinstaller.org/en),基本上 Rails Installer 跟 Ruby Installer 沒太大的差別,只是後者多加入了 Rails 開發的相關工具(例如 Git、Bundler 等): 41 | 42 | ### 其它系統 43 | 44 | 更多其它平台的安裝方式,或是想要直接下載原始碼自行編譯,請參閱 [Ruby 官方網站](https://www.ruby-lang.org/)的安裝說明。 45 | 46 | ## 用 RVM 來管理 Ruby 版本 47 | 48 | Ruby 有許多的版本(1.8/1.9/2.0/2.1/2.2/2.3/2.4)以及眾多的分支實作品(例如 JRuby/IronRuby/Rubinius/Macruby/mruby 等),算一算有不少排列組合,如果想要在自己機器上安裝不同版本會有點麻煩,而且萬一亂裝把工作環境弄壞了還得花時間重建。如果是要裝在伺服器上,如果你不是系統管理員,還不一定有足夠的權限可以安裝。使用 VirtualBox 的軟體可以來模擬作業環境,玩壞了隨時都可以很快的還原或重建一個新的,即使這樣還是有點麻煩。 49 | 50 | 如果各位跟我一樣都喜歡玩些新玩具,但又擔心環境被弄髒弄壞,推薦大家可以試試 [RVM](https://rvm.io/)(Ruby Version Manager)。有 RVM 的幫忙,你可以安心的在你的電腦裡同時安裝多個不同版本的 Ruby/Rails 而不會搞混,隨時都可以輕鬆的切換。 51 | 52 | RVM 是把程式安裝在你的的個人帳號目錄下,不需要的時候就整個 `~/.rvm` 資料夾刪除就行了,不會影響原來系統的設定。也就是因為 RVM 是安裝在你的個人帳號底下,所以你在安裝過程中不需要管理者(root)的權限就可以安裝其它相關的套件。 53 | 54 | ### 安裝 RVM 55 | 56 | RVM 的安裝滿簡單的,只要二行指令即可完成。安裝步驟請直接參閱 [RVM 官網](https://rvm.io/)的安裝說明。 57 | 58 | ### 使用 RVM 59 | 60 | 接著我們來看一些在 RVM 裡常用的指令。在終端機下輸入 `rvm list known` 會列出目前有哪些可以安裝的列表: 61 | 62 | $ rvm list known 63 | # MRI Rubies 64 | [ruby-]1.8.6[-p420] 65 | [ruby-]1.8.7[-head] # security released on head 66 | [ruby-]1.9.1[-p431] 67 | [ruby-]1.9.2[-p330] 68 | [ruby-]1.9.3[-p551] 69 | [ruby-]2.0.0[-p648] 70 | [ruby-]2.1[.10] 71 | [ruby-]2.2[.6] 72 | [ruby-]2.3[.3] 73 | [ruby-]2.4[.0] 74 | ruby-head 75 | 76 | [...略...] 77 | 78 | macruby[-0.12] 79 | macruby-nightly 80 | macruby-head 81 | 82 | # IronRuby 83 | ironruby[-1.1.3] 84 | ironruby-head 85 | 86 | 幾乎目前常見的 Ruby 分支實作品都有。列表裡的中括號表示那些是可以省略的,所以如果你這樣輸入: 87 | 88 | $ rvm install 2.3 89 | 90 | RVM 會自動找 `[ruby-]2.4[.0]` 這個版本的 Ruby 來安裝。前面提到可以安裝多個不同的版本,所以如果你喜歡,也可以再裝個 `1.9.3` 的版本: 91 | 92 | $ rvm install 1.9.3 93 | 94 | 安裝完成後,我們可以使用 `rvm list` 來查看目前電腦裡已經安裝哪些版本的 Ruby: 95 | 96 | $ rvm list 97 | 98 | ruby-1.9.3-p551 [ x86_64 ] 99 | ruby-2.2.1 [ x86_64 ] 100 | ruby-2.2.2 [ x86_64 ] 101 | ruby-2.3.0 [ x86_64 ] 102 | ruby-2.3.1 [ x86_64 ] 103 | ruby-2.3.3 [ x86_64 ] 104 | =* ruby-2.4.0 [ x86_64 ] 105 | 106 | # => - current 107 | # =* - current && default 108 | # * - default 109 | 110 | 因為工作上需求,所以在我的電腦上裝了好幾個版本的 Ruby。在 2.4.0 版前面的 `=*` 符號則是表示我目前正在使用這個版本。你可以在終端機下輸入這個指令,看看目前 Ruby 的版本: 111 | 112 | $ ruby -v 113 | ruby 2.4.0p0 (2016-12-24 revision 57164) [x86_64-darwin15] 114 | 115 | 如果要切換到其它版本的 Ruby,例如想要切換到 1.9.3 版本: 116 | 117 | $ rvm use 1.9.3 118 | 119 | 想少打幾個字的話,`use` 也可以省略: 120 | 121 | $ rvm 1.9.3 122 | 123 | 再來看一下Ruby的版本: 124 | 125 | $ ruby -v 126 | ruby 1.9.3p551 (2014-11-13 revision 48407) [x86_64-darwin13.4.0] 127 | 128 | 這樣就切換到 Ruby 1.9.3 了,相當便利!不過有個小問題,就是使用 RVM 指定的 Ruby 版本會在每次開啟新的終端機視窗的時候變回預設值(也就是變回系統內建的 Ruby 版本),所以如果你希望每次開終端機視窗的時候都會自動切到 `2.4.0` 版的話: 129 | 130 | $ rvm 2.4.0 --default 131 | 132 | 這樣之後每次開終端機視窗就會自動幫你切換到 2.4.0 版了。如果想切回到原來系統內建的版本,只要執行這個指令: 133 | 134 | $ rvm system 135 | 136 | 想移除某個版本的 Ruby 的話: 137 | 138 | $ rvm uninstall 2.4.0 139 | 140 | 這樣就可以把 `2.4.0` 版本移除掉了。如果是整個 RVM 都不想要了,只要把個人帳號 home 資料夾底下的 `.rvm` 資料夾整個移除,就會整個清潔溜溜了,完全不會動到系統內建的 Ruby。 141 | 142 | ### 運作原理 143 | 144 | 你也許會好奇為什麼 RVM 可以這麼神奇的切換 Ruby 的環境。讓我們來把系統的 PATH 變數印出來看看: 145 | 146 | $ echo $PATH 147 | /Users/user/.rvm/gems/ruby-2.4.0/bin:/Users/user/.rvm/gems/ruby...[略]... 148 | 149 | 然後查看一下 Ruby 的位置: 150 | 151 | $ which ruby 152 | /Users/user/.rvm/rubies/ruby-2.4.0/bin/ruby 153 | 154 | 再查一下 Ruby 版本: 155 | 156 | $ ruby -v 157 | ruby 2.4.0p0 (2016-12-24 revision 57164) [x86_64-darwin15] 158 | 159 | (以上內容是我自己電腦裡的設定,應該跟各位的環境不同) 160 | 161 | 接下來把 RVM 切換到 1.9.3 版本: 162 | 163 | $ rvm 1.9.3 164 | 165 | 再重複把剛剛的那些資訊印出來: 166 | 167 | $ echo $PATH 168 | /Users/user/.rvm/gems/ruby-1.9.3-p551/bin:/Users/user/.rvm/gems...[略]... 169 | 170 | $ which ruby 171 | /Users/user/.rvm/rubies/ruby-1.9.3-p551/bin/ruby 172 | 173 | $ ruby -v 174 | ruby 1.9.3p551 (2014-11-13 revision 48407) [x86_64-darwin13.4.0] 175 | 176 | 仔細看上面的輸出結果,就會發現其實 RVM 是把不同版本的 Ruby 安裝在你的個人帳號底下的 `.rvm` 目錄裡。當你切換不同版本的 Ruby 的時候,RVM 會幫你把系統預設的 PATH 的最前面加上這個 `.rvm` 的資料夾。接下來當你在終端機底下輸入 `ruby` 指令時,系統原本的 `/usr/bin/ruby` 因為在 PATH 的比較後面的位置,所以系統只會先找到 RVM 版本的 Ruby(也就是原來系統的 Ruby 被鬼摭眼了)。如果各位有興趣,也可以試著輸入 `rvm info` 指令來看看 RVM 幫你做了哪些設定。 177 | 178 | ### 除了 RVM 之外... 179 | 180 | 我自己個人習慣使用 RVM,除了 RVM 之外還有其它的選擇,例如 [rbenv](https://github.com/rbenv/rbenv) 及 [chruby](https://github.com/postmodern/chruby),這些 Ruby 版本管理工具各有其優、缺點,還請大家自己去試用看看,然後選一套自己覺得順手的來用吧。 181 | 182 | ## 安裝 Rails 183 | 184 | 完成 Ruby 安裝後,接下來就準備來安裝 Rails。在開放原始碼的圈子,有非常多的善心人士開發好了功能強大又可免費取用的套件,在 Ruby 的世界我們稱它叫 `gem`。Ruby on Rails 這個網站開發框架本身也是一個 gem(更準確的說,應該是一群 gem 的集合體),要安裝 rails 的話,只要使用 `gem install` 指令加上套件名稱即可,像這樣: 185 | 186 | $ gem install rails 187 | Fetching: i18n-0.7.0.gem (100%) 188 | Successfully installed i18n-0.7.0 189 | Fetching: thread_safe-0.3.5.gem (100%) 190 | Successfully installed thread_safe-0.3.5 191 | Fetching: tzinfo-1.2.2.gem (100%) 192 | Successfully installed tzinfo-1.2.2 193 | Fetching: concurrent-ruby-1.0.4.gem (100%) 194 | Successfully installed concurrent-ruby-1.0.4 195 | Fetching: activesupport-5.0.1.gem (100%) 196 | Successfully installed activesupport-5.0.1 197 | ...[略]... 198 | 36 gems installed 199 | 200 | 從安裝過程的訊息可大概看到 `5.0.1` 的字樣。如果過程沒發生錯誤訊息的話,接下來確認一下是不是安裝了正確的版本: 201 | 202 | $ rails -v 203 | Rails 5.0.1 204 | 205 | 搞定!接下來,我們就要用它來建立第一個 Rails 專案了。 206 | 207 | ## 建立 Rails 專案 208 | 209 | Rails 安裝完成後,接下來就用它來產生一個名為 `hello_rails` 的 Rails 專案,建立新專案用的是 `new` 這個參數: 210 | 211 | $ rails new hello_rails 212 | create 213 | create README.md 214 | create Rakefile 215 | create config.ru 216 | create .gitignore 217 | create Gemfile 218 | create app 219 | ...[略]... 220 | create vendor/assets/javascripts/.keep 221 | create vendor/assets/stylesheets 222 | create vendor/assets/stylesheets/.keep 223 | remove config/initializers/cors.rb 224 | run bundle install 225 | Fetching gem metadata from https://rubygems.org/.......... 226 | Fetching version metadata from https://rubygems.org/.. 227 | Fetching dependency metadata from https://rubygems.org/. 228 | ...[略]... 229 | Using rails 5.0.1 230 | Installing sass-rails 5.0.6 231 | Bundle complete! 15 Gemfile dependencies, 62 gems now installed. 232 | Use `bundle show [gemname]` to see where a bundled gem is installed. 233 | run bundle exec spring binstub --all 234 | * bin/rake: spring inserted 235 | * bin/rails: spring inserted 236 | 237 | `rails new hello_rails` 這個幫你產生了一個名為 `hello_rails` 的目錄,接下來請使用 `cd` 指令進到剛剛產生的這個目錄: 238 | 239 | $ cd hello_rails 240 | 241 | 進到這個專案之後,什麼事都不用做,直接啟動 Rails 附的 web server: 242 | 243 | $ rails server 244 | => Booting Puma 245 | => Rails 5.0.1 application starting in development on http://localhost:3000 246 | => Run `rails server -h` for more startup options 247 | Puma starting in single mode... 248 | * Version 3.6.2 (ruby 2.4.0-p0), codename: Sleepy Sunday Serenity 249 | * Min threads: 5, max threads: 5 250 | * Environment: development 251 | * Listening on tcp://localhost:3000 252 | Use Ctrl-C to stop 253 | 254 | 打開瀏覽器,連上網址 `http://localhost:3000/` ,你應該可以看到這個畫面: 255 | 256 | ![image](/images/chapter02/welcome_page.png) 257 | 258 | 恭喜你,你已經順利把 Ruby 跟 Rails 安裝完成,而且建立了一個 Rails 專案並順利跑起來了。雖然環境安裝看起來只是一小步,但這一小步對第一次接觸終端機、要打一堆指令的人來說已經是不小的一步了。 259 | 260 | -------------------------------------------------------------------------------- /markdown/chapter03-command-line-tools.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | title: 開發工具與常用命令列指令 4 | comments: true 5 | permalink: /chapters/03-command-line-tools.html 6 | 7 | --- 8 | 9 | # 開發工具與常用命令列指令 10 | 11 | 在上一個章節介紹安裝 Ruby 及 Rails,在正式開始寫我們第一個應用程式之前,先介紹一下開發工具以及在開發 Rails 專案過程中常會用到的指令。 12 | 13 | - [開發工具](#dev-tools) 14 | - [常用命令列指令](#command-line) 15 | - [不要害怕指令、不要害怕錯誤](#dont-be-scared-of-command-line) 16 | 17 | ## 開發工具 18 | 19 | 剛接觸 Ruby 或 Rails 的朋友常會問道:「我是 Ruby/Rails 的新手,請問有像 Apple 的 Xcode、Microsoft 的 Visual Studio 或至少像 Dreamweaver 之類方便或視覺化的整合開發工具(Integrated Development Environment, IDE)嗎?」 20 | 21 | 簡單的答案:「目前沒有」 22 | 23 | 那大家都用什麼工具在開發?其實每種程式語言的開發環境都不相同,大部份的 Ruby/Rails 開發者,不太使用這樣的東西。 但其實不是不用,而是幾乎沒有這樣的工具可以用。 24 | 25 | 如果程式語法不熟,沒有程式碼提醒或是語法自動補完的工具怎麼辦?Ruby/Rails 語法不熟,就多查手冊、多用、多寫幾次就會熟了。身為一名稱職的開發者,也不應該太過依賴這樣的提醒功能。別太依賴工具,蹲好馬步、練好正拳把基礎打穩才是正途,別被開發工具寵壞了。而且 Ruby/Rails 的語法都短短的,語法本身也相當直覺、易懂,沒有程式碼提醒或語法自動補完也不是太大的問題。 26 | 27 | Ruby/Rails 的開發者只要手上有任何一款文字編輯器就能進行開發(就跟龍五手上只要有槍....類似的概念吧),雖然沒有好用的開發工具對新手來說是個不小的門檻,但根據幾年在學校或課堂上教授 Ruby/Rails 課程的經驗來看,這都不是真正造成學習者會卡關的地方。 28 | 29 | 以下介紹幾款曾經使用比較順手的文字編輯器。 30 | 31 | ### Sublime Text 32 | 33 | [Sublime Text](https://www.sublimetext.com/) 是一款商業軟體,雖然需要付費購買,但即使沒有付費也可使用(超佛心!),它的優點除了有程式碼上色之外,好用的外掛也非常多。 34 | 35 | ### Atom 36 | 37 | [Atom](https://atom.io/) 是由 GitHub 出資的開發的編輯器,不僅完全免費,連原始碼都直接開放了,同樣也有程式碼上色功能,外掛也越來越豐富。 38 | 39 | ### Vim / Emacs 40 | 41 | 這兩款文字編輯器的年紀已經有三、四十歲了,說不定都比大家還要老。雖然很老,但到現在還是很多開發者會使用,而且各有各的擁護者,一款稱之「編輯器之神」,另一款稱「神之編輯器」(可參閱「[編輯器之戰](https://zh.wikipedia.org/wiki/%E7%BC%96%E8%BE%91%E5%99%A8%E4%B9%8B%E6%88%98)」)。 42 | 43 | 我自己目前主要使用 Vim,並不是說它特別強大,主要是它跟終端機可以無縫整合(因為 Vim 本身就在終端機裡)。在 Rails 專案開發過程中,有很多機會需要在終端機環境下輸入指令,所以對我來說開發起來比較順手,另外主要的原因是因為已經用習慣了。 44 | 45 | ### RubyMine 46 | 47 | 說沒有 IDE 其實是騙人的,還是有商業公司推出一套名為 [RubyMine](https://www.jetbrains.com/ruby/) 的整合開發工具。它的優點可以提醒或自動完成語法,對 Ruby 語法還不熟的新手來說應該有幫助;但缺點是執行速度有比較慢一點點,另一個不太算缺點的缺點就是它的收費比其它的軟體要來得貴一些。 48 | 49 | ## 常用命令列指令 50 | 51 | 在 Rails 的開發過程中,許多指令都是在終端機(Terminal)環境操作。由於大部份的初學者較習慣圖形介面工具,不熟悉指令該怎麼輸入,或是輸入的指令是什麼意思,這點是讓新手覺得容易挫折的地方。以下介紹幾個在終端機環境常會用到的指令。 52 | 53 | | 指令 | 說明 | 54 | | ------------- |:-------------------------| 55 | | cd | 切換目錄 | 56 | | pwd | 取得目前所在的位置 | 57 | | ls | 列出目前的檔案列表 | 58 | | mkdir | 建立新的目錄 | 59 | | touch | 建立檔案 | 60 | | cp | 複製檔案 | 61 | | mv | 移動檔案 | 62 | | rm | 刪除檔案 | 63 | | sudo | 暫時取得權限 | 64 | 65 | ### 目錄切換 66 | 67 | 在 Rails 專案開發過程中,指令需要下在正確的目錄裡才能正常運作,所以學會目錄的切換是很重要的。 68 | 69 | # 切換到 /tmp 目錄(絕對路徑) 70 | $ cd /tmp 71 | 72 | # 切換到 my_project 目錄(相對路徑) 73 | $ cd my_project 74 | 75 | # 往上一層目錄移動 76 | $ cd .. 77 | 78 | # 切換到使用者的 home 目錄中的 project 裡的 namecards 目錄 79 | $ cd ~/project/namecards/ 80 | 81 | # 顯示目前所在目錄 82 | $ pwd 83 | /tmp 84 | 85 | ### 檔案列表 86 | 87 | `ls` 指令可列出在目前目錄所有的檔案及目錄,後面接的 `-al` 參數,`a` 是指連小數點開頭的檔案(例如.gitignore)也會顯示,`l` 則是完整檔案的權限、擁有者以及建立、修改時間: 88 | 89 | $ ls -al 90 | total 56 91 | drwxr-xr-x 18 user wheel 612 Dec 18 02:20 . 92 | drwxrwxrwt 24 root wheel 816 Dec 18 02:19 .. 93 | -rw-r--r-- 1 user wheel 543 Dec 18 02:19 .gitignore 94 | -rw-r--r-- 1 user wheel 1729 Dec 18 02:19 Gemfile 95 | -rw-r--r-- 1 user wheel 4331 Dec 18 02:20 Gemfile.lock 96 | -rw-r--r-- 1 user wheel 374 Dec 18 02:19 README.md 97 | -rw-r--r-- 1 user wheel 227 Dec 18 02:19 Rakefile 98 | drwxr-xr-x 10 user wheel 340 Dec 18 02:19 app 99 | drwxr-xr-x 8 user wheel 272 Dec 18 02:20 bin 100 | drwxr-xr-x 14 user wheel 476 Dec 18 02:19 config 101 | -rw-r--r-- 1 user wheel 130 Dec 18 02:19 config.ru 102 | drwxr-xr-x 4 user wheel 136 Dec 18 02:41 db 103 | drwxr-xr-x 4 user wheel 136 Dec 18 02:19 lib 104 | drwxr-xr-x 4 user wheel 136 Dec 18 02:23 log 105 | drwxr-xr-x 9 user wheel 306 Dec 18 02:19 public 106 | drwxr-xr-x 9 user wheel 306 Dec 18 02:19 test 107 | drwxr-xr-x 7 user wheel 238 Dec 18 02:23 tmp 108 | drwxr-xr-x 3 user wheel 102 Dec 18 02:19 vendor 109 | 110 | ### 建立檔案、目錄 111 | 112 | $ touch index.html 113 | 114 | 如果 index.html 這個檔案本來不存在,`touch` 指令會建立一個名為 index.html 的空白檔案;如果本來就已經存在,則只會改變這個檔案的最後修改時間,並不會變更其內容。 115 | 116 | $ mkdir demo 117 | 118 | `mkdir` 指令會在目前所在目錄,建立一個名為 demo 的目錄。 119 | 120 | ### 檔案操作 121 | 122 | # 複製 index.html 成 about.html 123 | $ cp index.html about.html 124 | 125 | # 把 index.html 更名成 info.html 126 | $ mv index.html info.html 127 | 128 | # 刪除 index.html 129 | $ rm index.html 130 | 131 | # 刪除在這個目錄裡所有的 html 檔 132 | $ rm *.html 133 | 134 | ### 取得權限 135 | 136 | 有些指令需要有系統管理權限(root 權限)才能執行(例如要幫使用者變更密碼)。這時可在指令前面再加上 `sudo` 指令,只要你本身有可以使用 `sudo` 指令的權限,就可暫時的透過這個指令取得 root 權限: 137 | 138 | $ sudo passwd john 139 | 140 | ## 不要害怕指令、不要害怕錯誤 141 | 142 | 在 Rails 專案開發過程中會在終端機環境輸入許多指令,對新手來說是個不小的障礙。但請不要擔心,在開發過程用到的指令其實都不會太複雜,應該多用幾次就能上手,千萬不要因為指令輸入錯誤而造成挫折。 143 | 另外,在終端機執行指令後,不管成功或失敗,通常都會有訊息顯示在指令之後,這些訊息請多花幾秒鐘仔細的閱讀。很多的新手以為看到訊息就等於是指令執行成功,但事實上可能是錯誤訊息。 144 | 145 | 看到錯誤訊息不用擔心,因為通常答案就在錯誤訊息中,舉個例子來說: 146 | 147 | ![image](/images/chapter03/pending_migration.png) 148 | 149 | 這紅紅的錯誤訊息 `ActiveRecord::PendingMigrationError` 看起來一開始有點嚇人,但仔細看,它底下寫著一行貼心小提示: 150 | 151 | Migrations are pending. To resolve this issue, run: bin/rails db:migrate RAILS_ENV=development 152 | 153 | 意思就是說你有個 Migration 還沒處理,只要執行一下 `rails db:migrate` 就解決了。 154 | 155 | 不要害怕輸入指令,不要害怕錯誤訊息,加油! 156 | 157 | -------------------------------------------------------------------------------- /markdown/chapter04-your-first-rails-application.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | title: 第一個應用程式(使用 Scaffold) 4 | comments: true 5 | permalink: /chapters/04-your-first-rails-application.html 6 | 7 | --- 8 | 9 | # 第一個應用程式(使用 Scaffold) 10 | 11 | - [使用者功能](#user-scaffold) 12 | - [文章功能](#post-scaffold) 13 | - [Rails 常用快速鍵](#rails-shortcuts) 14 | - [小結](#note) 15 | 16 | 在上一篇,我們建立了一個全新的 Rails 專案,讓我們接著用這個專案繼續往下做。 17 | 18 | ## 你的第一個 Rails 應用程式(Blog 系統) 19 | 20 | 新手上路,讓我們來做一個讓使用者可以發文的 Blog 系統吧!先想一下這個系統的使用者故事(User Story)大概會長什麼樣子: 21 | 22 | - 可以新增使用者(User) 23 | - 每個使用者(User)可以新增、修改或刪除文章(Post) 24 | 25 | 雖然 Ruby 的世界有非常多厲害的套件(gem),像是如果要做會員系統,只要用 [devise](https://github.com/plataformatec/devise) 就可以在幾分鐘甚至是幾十秒內就把會員註冊、登入、登出等基本的功能完成。不過這裡我們先不用任何套件,僅靠 Rails 內建的功能來完成它。 26 | 27 | ## 使用者功能 28 | 29 | ### Step 1: 使用 Scaffold 30 | 31 | 我們先想一下使用者的資料大概會長什麼樣子: 32 | 33 | | 欄位名稱 | 資料型態 | 說明 | 34 | |:---------|:-------------:|:---------------| 35 | |name | 字串(string)| 使用者姓名 | 36 | |email | 字串(string)| 使用者 Email | 37 | |tel | 字串(string)| 聯絡電話 | 38 | 39 | 接下來,我們使用 Rails 內建的 Scaffold 功能來幫我們產生需要的檔案。切換到終端機畫面輸入指令: 40 | 41 | $ rails generate scaffold User name:string email:string tel:string 42 | Running via Spring preloader in process 17922 43 | invoke active_record 44 | create db/migrate/20161220041724_create_users.rb 45 | create app/models/user.rb 46 | invoke test_unit 47 | create test/models/user_test.rb 48 | create test/fixtures/users.yml 49 | invoke resource_route 50 | ..[略].. 51 | create app/views/users/_user.json.jbuilder 52 | invoke assets 53 | invoke coffee 54 | create app/assets/javascripts/users.coffee 55 | invoke scss 56 | create app/assets/stylesheets/users.scss 57 | invoke scss 58 | create app/assets/stylesheets/scaffolds.scss 59 | 60 | 打上面這個指令的時候,記得要先用 `cd` 指令切到 Rails 專案目錄裡,不然會出現不正確的訊息。這個 Scaffold 指令產生了一堆檔案,我們在後面的章節會再做更詳細的介紹,現在你只要先記得這個指令會幫你把 User 的新增、修改、刪除功能一口氣都做出來。 61 | 62 | 工程師其實有著懶惰的美德,所以上面這串很長的指令,可以濃縮成更簡單的樣子: 63 | 64 | 1. `generate` 可以簡寫成 `g` 65 | 2. 如果資料型態是 `string`,可以省略,但如果是其它型態不能省略。 66 | 67 | 所以原來的指令: 68 | 69 | $ rails generate scaffold User name:string email:string tel:string 70 | 71 | 可以簡寫成: 72 | 73 | $ rails g scaffold User name email tel 74 | 75 | ### Step 2 把描述具現化 76 | 77 | 在上一步產生的一堆檔案裡,有一個特別的檔案,在專案的 `db/migrate` 目錄裡,有個可能長得像 `20161220041724_create_users.rb` 的檔案(前面的數字是時間,所以應該會跟各位的檔名不太一樣),裡面的內容大概長這樣: 78 | 79 | ```ruby 80 | class CreateUsers < ActiveRecord::Migration[5.0] 81 | def change 82 | create_table :users do |t| 83 | t.string :name 84 | t.string :email 85 | t.string :tel 86 | 87 | t.timestamps 88 | end 89 | end 90 | end 91 | ``` 92 | 93 | 內容現在看不懂沒關係,之後會再介紹,但大概可以從文字猜得出來它是要建立一個表格(table),裡面有 `name`、`email` 以及 `tel` 三個欄位,分別都是字串(string)型態。 94 | 95 | 在 Rails 專案,這個檔案稱之遷移檔(migration file),是個很重要的檔案,我們會在後面的章節再介紹。 96 | 97 | 現在要做的,就是執行這個遷移檔的描述,在資料庫建立一個名為 `users` 的表格,好讓我們把使用者的資料放進去。 98 | 99 | $ rails db:migrate 100 | == 20161220041724 CreateUsers: migrating ====================================== 101 | -- create_table(:users) 102 | -> 0.0012s 103 | == 20161220041724 CreateUsers: migrated (0.0013s) ============================= 104 | 105 | 這個指令就是做這件事,這樣就把 `users` 表格建好囉! 106 | 107 | 要注意的的是,在 Rails 5 之前,用的指令是 `rake db:migrate`,在 Rails 5 之後,雖然原來的 `rake` 指令也可用,但為了統一,所以許多原來的 `rake` 指令都搬到 `rails` 底下了。 108 | 109 | ### Step 3 啟動 Rails Server 110 | 111 | 到這裡,其實使用者的新增、修改、刪除功能已經完成了!這時候只要啟動 Rails Server 就行了。 112 | 113 | $ rails server 114 | => Booting Puma 115 | => Rails 5.0.1 application starting in development on http://localhost:3000 116 | => Run `rails server -h` for more startup options 117 | Puma starting in single mode... 118 | * Version 3.6.2 (ruby 2.4.0-p0), codename: Sleepy Sunday Serenity 119 | * Min threads: 5, max threads: 5 120 | * Environment: development 121 | * Listening on tcp://localhost:3000 122 | Use Ctrl-C to stop 123 | 124 | 如果想要少打幾個字,`rails server` 指令也可簡化成 `rails s`。接著打開瀏覽器,連上網址 `http://localhost:3000/users`,應該可以看到這個畫面: 125 | 126 | ![image](/images/chapter04/user-scaffold-1.png) 127 | 128 | 試著輸入一些資料資料: 129 | 130 | ![image](/images/chapter04/user-scaffold-2.png) 131 | 132 | 你會發現你根本沒寫到什麼程式碼,一個簡單的 Scaffold 指令,已經把整個新增、修改、刪除的功能都完成了: 133 | 134 | ![image](/images/chapter04/user-scaffold-3.png) 135 | 136 | 相當神奇吧! 137 | 138 | ## 文章功能 139 | 140 | 完成了使用者功能,接著是文章(Post)功能,大致上也是依樣畫葫蘆,但還會加上一些這兩個功能之間的關連性。 141 | 142 | 再讓我們先想一下文章的資料大概會長什麼樣子: 143 | 144 | | 欄位名稱 | 資料型態 | 說明 | 145 | |:------------|:--------------:|:-------------| 146 | |title | 字串(string) | 文章標題 | 147 | |content | 文字(text) | 內文 | 148 | |user_id | 數字(integer)| 使用者編號 | 149 | |is_available | 布林(boolean)| 文章是否上線 | 150 | 151 | 這裡有幾個需要解釋的地方: 152 | 153 | 1. 文字(text)跟字串(string)不同的地方,是在於 `text` 型態可以存放更多的內容(因為通常文章不會只有短短幾個字)。 154 | 2. `user_id` 欄位的目的,是為了可以讓該編文章跟某位使用者連結在一起。 155 | 156 | ### Step 1 使用 Scaffold 157 | 158 | 根據上面這個表格,我們使用 Scaffold 來產生相對應的功能: 159 | 160 | $ rails g scaffold Post title content:text user:references is_available:boolean 161 | Running via Spring preloader in process 18657 162 | invoke active_record 163 | create db/migrate/20161220050455_create_posts.rb 164 | create app/models/post.rb 165 | invoke test_unit 166 | create test/models/post_test.rb 167 | create test/fixtures/posts.yml 168 | invoke resource_route 169 | ..[略].. 170 | invoke assets 171 | invoke coffee 172 | create app/assets/javascripts/posts.coffee 173 | invoke scss 174 | create app/assets/stylesheets/posts.scss 175 | invoke scss 176 | identical app/assets/stylesheets/scaffolds.scss 177 | 178 | 注意事項: 179 | 180 | 1. 除了 `string` 型態之外,其它型態不能省略。 181 | 2. 雖然 `user_id` 也可以用 `user_id:integer`,但使用 `user:references` 會幫你完成更多細節,這部份也一樣會在後面的章節介紹。 182 | 183 | ### Step 2 別忘了把描述具現化 184 | 185 | 跟前面的 User 一樣,Scaffold 又再產生了一個新的遷移檔,所以別忘了再讓這個描述檔執行一下: 186 | 187 | $ rails db:migrate 188 | == 20161220050455 CreatePosts: migrating ====================================== 189 | -- create_table(:posts) 190 | -> 0.0056s 191 | == 20161220050455 CreatePosts: migrated (0.0057s) ============================= 192 | 193 | ### Step 3 檢視成果 194 | 195 | 如果你剛剛的 Rails Server 還沒關掉(通常在開發過程不會特別關掉),打開網址 `http://localhost:3000/posts`: 196 | 197 | ![image](/images/chapter04/post-scaffold-1.png) 198 | 199 | 在 User 的欄位先填寫數字 `1`,表示是 1 號使用者: 200 | 201 | ![image](/images/chapter04/post-scaffold-2.png) 202 | 203 | 這個 User 欄位其實不該讓使用者自己填空,至少是要自動帶入或是使用下拉選單,不過暫時先這樣。然後就可以看到: 204 | 205 | ![image](/images/chapter04/post-scaffold-3.png) 206 | 207 | 這裡出現了看起來有點像亂碼的東西 `#`,事實上它是一個使用者物件,我們可以修正一下程式碼,讓它顯示出使用者的姓名: 208 | 209 | 請打開專案的 `app/views/posts/index.html.erb` 檔案,把第 21 行的 `post.user` 改成 `post.user.name`,像這樣: 210 | 211 | ```erb 212 | <% @posts.each do |post| %> 213 | 214 | <%= post.title %> 215 | <%= post.content %> 216 | <%= post.user.name %> 217 | <%= post.is_available %> 218 | <%= link_to 'Show', post %> 219 | <%= link_to 'Edit', edit_post_path(post) %> 220 | <%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %> 221 | 222 | <% end %> 223 | ``` 224 | 225 | 應該就可以正常顯示了: 226 | 227 | ![image](/images/chapter04/post-scaffold-4.png) 228 | 229 | 其實這裡還有一些效能問題(N+1 Query),不過也讓我們留到以後再說明。 230 | 231 | ## Rails 常用快速鍵 232 | 233 | Rails 專案裡常用到的指令都有簡寫,可以讓你少敲幾個字: 234 | 235 | | 原本的指令 | 簡寫 | 用途 | 236 | |-----------------|----------|------------------------------------------------------------------------------------| 237 | | rails generate | rails g | 用來產生各種需要的檔案,例如 scaffold、controller、model 等等 | 238 | | rails destroy | rails d | 可刪除產生器所產生的檔案 | 239 | | rails server | rails s | 啟動 Rails 伺服器,讓你可以檢視目前專案的成課 | 240 | | rails console | rails c | 進類似乎 Ruby 的 IRB 介面,但是有載入整個 Rails 專案的環境,可以在這裡直接操作資料 | 241 | | rails dbconsole | rails db | 直接進到資料庫裡,使用 SQL 語法對資料庫進行存取 | 242 | | bundle install | bundle | 安裝套件 | 243 | | rake test | rake | 執行測試 | 244 | 245 | ## 小結 246 | 247 | Scaffold 好用歸好用,我當年第一次接觸 Rails 就是被 Scaffold 給騙進來的。但實際在工作的時候不見得常用,比較常見是使用產生器(generator)各別建立 Controller 或 Model,畢竟 Scaffold 一口氣生出太多用不到的檔案,有種用牛刀殺小雞的感覺。 248 | 249 | 基本上 Rails 是不可能靠用聽的或用看的就學得會的,一定多要練習,建議有空可試著照 Rails Guide 的這篇 [Getting Started](http://guides.rubyonrails.org/getting_started.html) 操練一遍,應該就對 Rails 更有概念了,加油! 250 | 251 | -------------------------------------------------------------------------------- /markdown/chapter07-ruby-basic-3.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | title: 方法與程式碼區塊(block) 4 | comments: true 5 | permalink: /chapters/07-ruby-basic-3.html 6 | 7 | --- 8 | 9 | # 方法與程式碼區塊(block) 10 | 11 | Rails 不是一種程式語言,它是一種用 Ruby 這個程式語言所開發出來的網頁開發框架(Web Framework)。 12 | 13 | 接下來幾個章節的目的並不是要詳細的介紹 Ruby 這個程式語言所有的功能,而是希望讓大家對 Ruby 有足夠的基本認識,之後大家在閱讀或撰寫 Rails 專案的時候,會比較知道 Rails 在寫些什麼。 14 | 15 | - [方法(Method)](#method) 16 | - [程式碼區塊((Block)](#block) 17 | 18 | ## 方法(Method) 19 | 20 | ### 定義方法 21 | 22 | 在 Ruby 定義方法,使用的是 `def` 這個關鍵字: 23 | 24 | ```ruby 25 | def say_hello_to(name) 26 | puts "hello, #{name}" 27 | end 28 | ``` 29 | 30 | 這樣就定義了一個 `say_hello_to` 方法,後面的 `name` 是這個方法的參數(parameter),不限定只能傳一個,如果要傳多個參數可使用逗號分開。方法的命名慣例跟一般的區域變數差不多,是使用小寫加底線的組合。 31 | 32 | ### 呼叫方法 33 | 34 | 要執行已經定義的方法,只要直接呼叫方法的名字即可: 35 | 36 | ```ruby 37 | say_hello_to("帥哥") # => hello, 帥哥 38 | ``` 39 | 40 | 也可視情況省略小括號: 41 | 42 | ```ruby 43 | say_hello_to "帥哥" # => hello, 帥哥 44 | ``` 45 | 46 | 在 Ruby 執行方法,經常省略小括號,目的是為了讓程式碼看起來更不像程式碼,反而像是一般的文章。 47 | 48 | ### 參數預設值 49 | 50 | 在定義方法時,可幫參數加上預設值: 51 | 52 | ```ruby 53 | def say_something(message = "something") 54 | "message: #{message}" 55 | end 56 | 57 | p say_something "hi" # => message: hi 58 | p say_something # => message: something 59 | ``` 60 | 61 | 如果有正確傳參數給方法,那就會使用傳進去的參數;如果沒有,則使用預設值。 62 | 63 | ### 方法的回傳值 64 | 65 | 有時候你會希望方法在接收參數並在執行完成之後,回傳執行之後的結果,例如我們可以寫一個 BMI(Body Mass Index,身體質量指數)方法,它可以接收身高與體重,並回傳計算結果: 66 | 67 | ```ruby 68 | # BMI值計算公式: BMI = 體重(單位:公斤)/ 身高平方(單位:公尺) 69 | 70 | def bmi_calculator(height, weight) 71 | return weight / height ** 2 72 | end 73 | 74 | puts bmi_calculator(1.70, 80) # => 27.681 75 | ``` 76 | 77 | 上面這段範例中的 `return` 是指這個方法執行完成之後,把最後的計算結果回傳給呼叫它的方法,在這個範例裡也就是 `puts`,然後會被印出來在畫面上。 78 | 79 | 在 Ruby 方法裡,最後一行的執行結果會自動被回傳,所以上面這個例子的 `return` 也是可以省略的,像這樣: 80 | 81 | ```ruby 82 | def bmi_calculator(height, weight) 83 | weight / height ** 2 84 | end 85 | ``` 86 | 87 | ### puts 不是 return,也沒有回傳值 88 | 89 | 對程式新手來說,有可能會寫出這樣的語法: 90 | 91 | ```ruby 92 | def bmi_calculator(height, weight) 93 | puts weight / height ** 2 94 | end 95 | ``` 96 | 97 | 執行 `bmi_calculator` 方法,的確是會印出內容,但會印出內容是因為在方法裡面直接 `puts` 把內容印出來,並不是因為這個方法回傳所造成的。事實上,`puts` 方法本身是沒有回傳值的喔。 98 | 99 | ### 問號跟驚嘆號也是方法的一部份 100 | 101 | 在 Ruby 定義方法時,方法的名字一般除了使用英文、底線及數字的組合外,也可以使用問號 `?` 跟驚嘆號 `!`(其實等號 `=` 也可以),但僅能放在方法名字的最後面,像這樣: 102 | 103 | ```ruby 104 | def is_adult?(age) 105 | age >= 18 106 | end 107 | ``` 108 | 109 | 在使用的時候跟一般的方法沒什麼差別,但別忘了要把問號加上去: 110 | 111 | ```ruby 112 | if is_adult?(20) 113 | puts "你是成年人了!" 114 | end 115 | ``` 116 | 117 | 在通常會使用問號,慣例上是表示這個方法會回傳布林值(true 或 false),不管是 Ruby 或 Rails,都很常可以看到這樣的慣例: 118 | 119 | ```ruby 120 | puts "".empty? # => true 121 | puts [1, 2, 3, 4, 5].include?(3) # => true 122 | puts "Ruby".start_with?("Ru") # => true 123 | ``` 124 | 125 | 而使用驚嘆號,通常是表示使用這個方法可能會有「副作用」或「驚喜」,舉個例子來說,像是陣列有個叫做 `reverse` 的方法,它可以產生一個跟原來陣列的相反排序的新陣列: 126 | 127 | ```ruby 128 | original_list = [1, 2, 3, 4, 5] 129 | reversed_list = original_list.reverse 130 | 131 | p reversed_list # => [5, 4, 3, 2, 1] 132 | p original_list # => [1, 2, 3, 4, 5] 133 | ``` 134 | 135 | `reverse` 方法會回傳一個新的陣列回來,不會影響原來的資料。但如果是呼叫有驚嘆號版本的 `reverse!` 就不同了: 136 | 137 | ```ruby 138 | original_list = [1, 2, 3, 4, 5] 139 | reversed_list = original_list.reverse! 140 | 141 | p reversed_list # => [5, 4, 3, 2, 1] 142 | p original_list # => [5, 4, 3, 2, 1] 143 | ``` 144 | 145 | `reverse!` 方法除了會回傳一個陣列之外,原來的陣列也會直接跟著一起被影響了。所以如果你這個方法可能會有一些意外驚喜,在慣例上通常會加上一個驚嘆號,提醒一下使用這個方法的人。 146 | 147 | ### 問題:是變數還是方法? 148 | 149 | 因為 Ruby 在執行方法的時候可以適時的省略小括號,可以讓你的方法寫起來像是個區域變數一樣。不過想一下這個情況: 150 | 151 | ```ruby 152 | age = 18 153 | 154 | def age 155 | 20 156 | end 157 | 158 | puts age # => 會得到 18 還是 20? 159 | ``` 160 | 161 | 這裡有個區域變數 `age` 指向數字 18,也有一個方法叫 `age` 會回傳數字 20,請問你認為是會印出 18 還是 20? 162 | 163 | 答案是 18,因為 Ruby 在同一個範圍內,如果遇到同名的區域變數及方法,會以區域變數優先。那如果想要得到 20 的話該怎麼辦?其實超簡單的,就是把最後一行的 `puts age` 改成 `puts age()` 就行了。大家寫 Ruby 省略小括號省到已經習慣了,都忘了其實呼叫方法的基本招使用小括號,這反而是在其它程式語言不太會有的困擾 :) 164 | 165 | ### 問題:參數有幾個? 166 | 167 | 在 Rails 裡常會看到 `link_to` 這樣寫: 168 | 169 | ```erb 170 | <%= link_to '刪除', user, method: :delete, data: { confirm: 'sure?' }, class:'btn' %> 171 | ``` 172 | 173 | 你看得出來上面這段範例中,`link_to` 方法共有幾個參數嗎?如果你是用逗號的數量數出來是 5 個,那你就需要繼續往下看了 :) 174 | 175 | Ruby 很愛省略東西,像是方法的小括號,所以原來上面的 `link_to` 語法原本應該長這樣: 176 | 177 | ```erb 178 | <%= link_to('刪除', user, method: :delete, data: { confirm: 'sure?' }, class:'btn') %> 179 | ``` 180 | 181 | 除了常常省略小括號外,偶爾也會省略大括號。在 Ruby 中如果最後一個參數是 Hash 的話,它的大括號是可以省略的。舉個例子來說: 182 | 183 | ```ruby 184 | def say_hello_to(name, options = {}) 185 | # do something 186 | end 187 | ``` 188 | 189 | 如果要使用這個方法,可以這樣寫: 190 | 191 | ```ruby 192 | say_hello_to "eddie", {age: 18, favorite: 'ruby'} 193 | ``` 194 | 195 | 又,因為最後一個參數是 Hash,所以 Hash 的大括號也可省略: 196 | 197 | ```ruby 198 | say_hello_to "eddie", age: 18, favorite: 'ruby' 199 | ``` 200 | 201 | 如果你了解有什麼東西被省略的話,一開始的那段 link_to 的範例還原之後會變成: 202 | 203 | ```erb 204 | <%= link_to('刪除', user, {method: :delete, data: { confirm: 'sure?' }, class:'btn'}) %> 205 | ``` 206 | 207 | 所以,其實參數個數只有 3 個,最後一個參數是一個 Hash。也因為最後一個是 Hash,Hash 本身是沒有順序的,所以 Hash 裡的 `method` 要放後面或是 `class` 要放前面其實都可已。 208 | 209 | Ruby 的語法可以適時的省略小括號、大括號以及 return,程式碼寫起來雖然會更像在寫文章,但對新手來說可能會容易混淆,需要花一點時間了解到底省略了哪些東西。 210 | 211 | ## 程式碼區塊(Block) 212 | 213 | Block 在 Ruby 或 Rails 裡大量的被使用,像是在使用迴圈的時候,可能都寫過這樣的程式碼: 214 | 215 | ```ruby 216 | 5.times { puts "Hello, Ruby" } # 這會印 5 次的 Hello Ruby 217 | 218 | friends = ["魯夫", "孫悟空", "黑崎一護", "旋渦嗚人"] 219 | friends.each do |friend| 220 | puts friend # 這會把陣列裡的元素一個一個印出來 221 | end 222 | ``` 223 | 224 | 其中,那個大括號 `{ ... }` 以及 `do ... end`,在 Ruby 稱之一個程式碼區塊(Block) 225 | 226 | ### Block 不是物件 227 | 228 | 我們常說,在 Ruby 裡,幾乎什麼東西都是物件,但其實還是有少數的例外,例如 Block 就不是物件。 Block 沒有辦法單獨的存在,也沒辦法把它指定給某個變數,像這樣的寫法都會造成語法錯誤(Syntax Error): 229 | 230 | ```ruby 231 | { puts "Hello, Ruby" } # 這樣會產生語法錯誤 232 | action = { puts "Hello, Ruby" } # 這樣也會產生語法錯誤 233 | ``` 234 | 235 | ### Block 不是參數 236 | 237 | Block 通常得像寄生蟲一樣依附或寄生在其它的方法或物件(或是使用某些類別把它物件化),但它不是參數,例如: 238 | 239 | ```ruby 240 | def say_hello_to(name) 241 | # do something here 242 | end 243 | 244 | say_hello_to("悟空") { 245 | puts "這裡是 Block" 246 | } 247 | 248 | # 或是 do ... end 寫法 249 | say_hello_to("悟空") do 250 | puts "這裡是 Block" 251 | end 252 | ``` 253 | 254 | Block 不是參數,在上面這段範例中,`name` 才是參數,但 Block 不是。上面這段程式碼執行之後不會有任何錯誤,但 Block 裡要執行的動作也不會執行。 255 | 256 | ### 如何執行 Block 的內容? 257 | 258 | 想像一下這段的對話: 259 | 260 | > 某 Block:「嘿,say_hello_to 方法,我要掛在你身上囉」 261 | 262 | > say_hello_to :「隨便啊,你要掛就讓你掛,但要不要讓你執行是我決定的!」 263 | 264 | 如果想要讓附掛的 Block 執行的話,可使用 `yield` 方法,暫時把控制權交棒給 Block,等 Block 執行結束後再把控制權交回來: 265 | 266 | ```ruby 267 | def say_hello 268 | puts "開始" 269 | yield # 把控制權暫時讓給 Block 270 | puts "結束" 271 | end 272 | 273 | say_hello { 274 | puts "這裡是 Block" 275 | } 276 | ``` 277 | 278 | 執行上面這段範例會得到: 279 | 280 | 開始 281 | 這裡是 Block 282 | 結束 283 | 284 | ### 傳參數給 Block 285 | 286 | 有時候你會看到像這樣的寫法: 287 | 288 | ```ruby 289 | 5.times do |i| 290 | puts i 291 | end 292 | ``` 293 | 294 | 那個 `|i|` 是什麼呢?這個在兩根看起來像牆壁中間的 `i`,是在這個 Block 裡專屬的區域變數,Block 執行結束後就會失效了: 295 | 296 | ```ruby 297 | 5.times do |i| 298 | puts i # 這個變數 i 只有在 Block 裡有效,會依序印出數字 0 到 4 299 | end 300 | 301 | puts i # 離開 Block 之後就失效,出現找不到變數的錯誤(NameError) 302 | ``` 303 | 304 | 所以,到底是這個 i 是怎麼來的?事實上,它就只是你在使用 yield 方法把控制權轉讓給 Block 的時候,順便把值帶給 Block 而已: 305 | 306 | ```ruby 307 | def say_hello 308 | puts "開始" 309 | yield 123 # 把控制權暫時讓給 Block,並且傳數字 123 給 Block 310 | puts "結束" 311 | end 312 | 313 | say_hello { |x| # 這個 x 是來自 yield 方法 314 | puts "這裡是 Block,我收到了 #{x}" 315 | } 316 | ``` 317 | 318 | 下回大家再看到 `|i|` 的寫法,應該就知道它是什麼意思了。 319 | 320 | ### Block 的回傳值 321 | 322 | 其實 `yield` 方法除了把控制權暫時的讓給後面的 Block 之外,Block 最後一行的執行結果也會自動變成 Block 的回傳值,所以可把 Block 當做判斷內容: 323 | 324 | ```ruby 325 | def pick(list) 326 | result = [] 327 | list.each do |i| 328 | result << i if yield(i) # 如果 yield 的回傳值是 true 的話... 329 | end 330 | result 331 | end 332 | 333 | p pick([*1..10]) { |x| x % 2 == 0 } # => [2, 4, 6, 8, 10] 334 | p pick([*1..10]) { |x| x < 5 } # => [1, 2, 3, 4] 335 | ``` 336 | 337 | 上面這段範例的 pick 方法,會根據 Block 的條件,挑出符合條件的元素。 338 | 339 | ### 用 return 回傳 Block 的結果? 340 | 341 | Block 的最後一行執行結果自動會變成 Block 的回傳值,這裡並不是省略了 return,而是不能使用 return 回傳結果。所以如果你在上面那個例子,在 Block 裡試圖用 `return` 回傳結果,像這樣: 342 | 343 | ```ruby 344 | pick([*1..10]) { |x| return x % 2 == 0 } 345 | ``` 346 | 347 | 這會產生 LocalJumpError 錯誤。因為,Block 並不是一個方法,所以它不知道你要 Return 到哪裡去而造成錯誤。 348 | 349 | ### 問題:`5.times { ... }` 很好用,但你能自己土砲一個類似的方法嗎? 350 | 351 | ```ruby 352 | def my_times(n) 353 | i = 0 354 | while n > i 355 | i += 1 356 | yield i 357 | end 358 | end 359 | 360 | my_times(5) { |num| 361 | puts "hello, #{num}xRuby" 362 | } 363 | 364 | # 得到結果 365 | # hello, 1xRuby 366 | # hello, 2xRuby 367 | # hello, 3xRuby 368 | # hello, 4xRuby 369 | # hello, 5xRuby 370 | ``` 371 | 372 | 做法就是在執行 `while` 迴圈的同時,不斷的把數字透過 `yield` 傳出來,這樣就可以做出一個類似 `5.times { ... }` 的效果了。 373 | 374 | ### 大括號跟 do ... end 的差別 375 | 376 | 大部份的情況,Block 的大括號的寫法跟 `do ... end` 寫法是可以互換的,像這樣: 377 | 378 | ```ruby 379 | # 使用 do .. end 寫法 380 | 5.times do 381 | puts "哈囉,世界" 382 | end 383 | 384 | # 使用大括號寫法 385 | 5.times { 386 | puts "哈囉,世界" 387 | } 388 | ``` 389 | 390 | 如果 Block 的內容如果有多行,通常會建議使用 `do .. end` 寫法,如果只有一行,則建議使用大括號寫法,可讓語法看起來精簡一些: 391 | 392 | ```ruby 393 | # 使用大括號一行寫法 394 | 5.times { puts "哈囉,世界" } 395 | ``` 396 | 397 | 但事實上,這兩種情況是有一些微妙的差別的,並不是所有情況都互相交換。看看這段程式碼範例: 398 | 399 | ```ruby 400 | p [*1..10].map { |i| i * 2 } 401 | # => 得到 [2, 4, 6, 8, 10, 12, 14, 16, 18, 20] 402 | 403 | p [*1..10].map do |i| i * 2 end 404 | # => 得到 405 | ``` 406 | 407 | 會造成不同結果的原因,有點像是數學的「先乘除後加減」的規則,大括號的優先順序較高: 408 | 409 | ```ruby 410 | p [*1..10].map { |i| i * 2 } 411 | 412 | # 還原省略的小括號 413 | p([*1..10].map { |i| i * 2 }) 414 | ``` 415 | 416 | 但 `do ... end` 的優先順序較低,會有不一樣的解讀: 417 | 418 | ```ruby 419 | p [*1..10].map do |i| i * 2 end 420 | 421 | # 還原省略的小括號 422 | p([*1..10].map) do |i| i * 2 end 423 | ``` 424 | 425 | 因為優先順序較低,所以變成先跟 p 結合了,造成後面附掛的 Block 就不會被處理了。 426 | 427 | ### 把 Block 物件化 428 | 429 | 前面提到,Block 本身並不是物件,它沒辦法單獨的存在 Ruby 的世界裡,需要依附在方法或物件後面。 430 | 431 | 但其實也是可以把 Block 物件化,例如使用 `Proc` 類別: 432 | 433 | ```ruby 434 | greeting = Proc.new { puts "哈囉,世界" } # 使用 Proc 類別可把 Block 物件化 435 | ``` 436 | 437 | 要使用它的時候,只要執行這個物件上的 `call`: 438 | 439 | ```ruby 440 | greeting.call # 印出 "哈囉,世界" 441 | ``` 442 | 443 | 如果要帶參數也可以: 444 | 445 | ```ruby 446 | say_hello_to = Proc.new { |name| puts "你好,#{name}"} 447 | say_hello_to.call("尼特羅會長") 448 | ``` 449 | 450 | ### Proc 呼叫方式 451 | 452 | 要執行一個 Proc 物件,可以使用 `call` 方法,但其實還有其它好幾種使用方法,例如: 453 | 454 | ```ruby 455 | say_hello_to.call("尼特羅會長") # 使用 call 方法 456 | say_hello_to.("尼特羅會長") # 使用小括號(注意,有多一個小數點) 457 | say_hello_to["尼特羅會長"] # 使用中括號 458 | say_hello_to === "尼特羅會長" # 使用三個等號 459 | say_hello_to.yield "尼特羅會長" # 使用 yield 方法 460 | ``` 461 | 462 | 這幾種方式都可以呼叫 Proc 物件。 463 | 464 | 如果是第一次接觸 Ruby 的朋友,使用 Block 一開始可能會有點不習慣。不過因為在 Ruby 或 Rails 的專案裡,Block 被用到的機會非常高,儘早熟悉 Block 的使用是很有幫助的喔,加油! 465 | 466 | -------------------------------------------------------------------------------- /markdown/chapter08-ruby-basic-4.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | title: 類別(Class)與模組(Module) 4 | comments: true 5 | permalink: /chapters/08-ruby-basic-4.html 6 | 7 | --- 8 | 9 | # 類別(Class)與模組(Module) 10 | 11 | Rails 不是一種程式語言,它是一種用 Ruby 這個程式語言所開發出來的網頁開發框架(Web Framework)。 12 | 13 | 接下來幾個章節的目的並不是要詳細的介紹 Ruby 這個程式語言所有的功能,而是希望讓大家對 Ruby 有足夠的基本認識,之後大家在閱讀或撰寫 Rails 專案的時候,會比較知道 Rails 在寫些什麼。 14 | 15 | - [類別(Class)](#class) 16 | - [模組(Module)](#module) 17 | 18 | ## 類別(Class) 19 | 20 | Ruby 是一款物件導向程式語言(Objected-Oriented Programming, OOP),在 Ruby 的世界裡,幾乎所有的東西都是物件。但,到底什麼是「物件」? 21 | 22 | ### 什麼是物件? 23 | 24 | > 物件(object) = 狀態(state) + 行為(behavior) 25 | 26 | 在現實生活中,路上跑的車子、天上飛的鳥,你我他,看得到、摸得到的都可通稱為之物件(Object)。物件會有狀態跟行為,例如我這個人會有是「黑色頭髮」、「黃色皮膚」、「年紀 18 歲(?)」等狀態,也會有「吃飯」、「睡覺」、「走路」、「講話」等行為。 27 | 28 | 為了讓大家更容易學習程式設計,許多程式語言都有引進物件的概念,讓程式架構更容易組織、整理。而且 Ruby 又是一款物件化很徹底的程式語言,在 Ruby 的世界,看的到的幾乎都是物件,數字 1、字串 "hello",陣列、Hash 都是物件。 29 | 30 | ### 等等,為什麼說「幾乎」? 31 | 32 | 「在 Ruby 裡所有東西都是物件」,但其實也是有例外的,在 Ruby 裡的 Block 就不是物件,Block 本身沒辦法單獨的存活在 Ruby 的世界裡。 33 | 34 | ### 什麼是類別? 35 | 36 | 大家也許在夜市有看過有人在賣雞蛋糕,有小貓、小狗或其它可愛動物造型,只要把調配好的麵粉糊倒進模具,壓一下,幾分鐘後就會有香噴噴又造型可愛的雞蛋糕可以吃了。 37 | 38 | ![image](/images/chapter08/cake_maker.jpg) 39 | photo by [Bryan Liu](https://www.flickr.com/photos/bryanliu99/) 40 | 41 | 那個烤盤模具,就是類別(Class)的概念。如果沒意外,一樣形狀的模具,放一樣的原料進去,做出來雞蛋糕的型狀應該都會長得一樣。而這個做出來的雞蛋糕,以物件導向程式設計的概念來說便稱之「實體(instance)」。 42 | 43 | ### 定義類別 44 | 45 | 在 Ruby 要定義一個類別,使用的關鍵字是 `class`: 46 | 47 | ```ruby 48 | class 類別的名字 49 | #... 50 | end 51 | ``` 52 | 53 | 如果我想定義一個小貓類別,順便在裡面先定義好一些方法,就可以這樣寫: 54 | 55 | ```ruby 56 | class Cat 57 | def eat(food) 58 | puts "#{food} 好好吃!!" 59 | end 60 | end 61 | ``` 62 | 63 | 其中,類別的名字規定必須是常數,也就是必須是大寫英文字母開頭。有了 `Cat` 類別之後,就可以用這個類別的 `new` 方法來產生實體: 64 | 65 | ```ruby 66 | kitty = Cat.new 67 | kitty.eat "鮪魚罐頭" #=> 印出「鮪魚罐頭 好好吃!!」 68 | 69 | nancy = Cat.new 70 | nancy.eat "小魚餅干" #=> 印出「小魚餅干 好好吃!!」 71 | ``` 72 | 73 | 在這裡我用 Cat 類別做了兩個不同的實體,分別叫做 `kitty` 跟 `nancy`,這兩個物件因為都是用 `Cat` 類別做出來的,所以都有 `eat` 方法。 74 | 75 | ### 初始化 76 | 77 | 一樣形狀的烤盤,放入不同的原料就可以做出不同口味的雞蛋糕。一樣的概念,在使用 `new` 方法製作實體的時候,也可以順便傳參數進去。 78 | 79 | ```ruby 80 | class Cat 81 | def initialize(name, gender) 82 | @name = name 83 | @gender = gender 84 | end 85 | 86 | def say_hello 87 | puts "hello, my name is #{@name}" 88 | end 89 | end 90 | 91 | kitty = Cat.new("kitty", "female") 92 | kitty.say_hello # => hello, my name is kitty 93 | ``` 94 | 95 | 如果要透過 `new` 方法傳參數進來,在類別裡面必須有個名為 `initialize` 的方法來接收傳進來的參數。在 `initialize` 方法裡,常見的手法是會把參數傳進來給內部的實體變數(instance variable)。 96 | 97 | ### 實體變數(instance variable) 98 | 99 | 在 Ruby 裡的實體變數是有一個 `@` 開頭的變數,顧名思義,是活在每個實體裡的變數,而且每個實體之間互不相影響。 100 | 101 | 以前面這段為例,`@name` 跟 `@gender` 就是實體變數。 102 | 103 | 在 Rails 專案中,實體變數常用的地方是 Controller 與 View 之間的溝通,例如以下這個例子,這是一個很常見的 Controller 的例子: 104 | 105 | ```ruby 106 | class PostsController < ApplicationController 107 | def index 108 | @posts = Post.all # 取得所有的 Post 資料 109 | end 110 | end 111 | ``` 112 | 113 | 更多細詳內容待後面的 MVC(Model, View, Controler)章節再說明。 114 | 115 | ### 取用實體變數 116 | 117 | Ruby 的實體變數沒辦法直接從外部取用,像這樣直接取用會發生錯誤訊息: 118 | 119 | ```ruby 120 | kitty = Cat.new("kitty", "female") 121 | kitty.name = "nancy" # 這會發生錯誤 122 | puts kitty.name # 這也會發生錯誤 123 | ``` 124 | 125 | Ruby 並沒有「屬性」(property/attribute)這樣的東西,要取用實體變數,需要另外定義的方法才行: 126 | 127 | ```ruby 128 | class Cat 129 | def initialize(name, gender) 130 | @name = name 131 | @gender = gender 132 | end 133 | 134 | def say_hello 135 | puts "hello, my name is #{@name}" 136 | end 137 | 138 | def name 139 | @name 140 | end 141 | 142 | def name=(new_name) 143 | @name = new_name 144 | end 145 | end 146 | 147 | kitty = Cat.new("kitty", "female") 148 | kitty.name = "nancy" 149 | puts kitty.name # => nancy 150 | ``` 151 | 152 | 這裡定義的 `name` 以及 `name=` 方法(是的,你沒看錯,等號 `=` 也是方法的一部份)就是負責回傳及設定 `@name` 這個實體變數的。 153 | 154 | 每次要這樣取用或設定都要這麼麻煩嗎?還好,怕麻煩的工程師有另外定義了三個方法來解決這件事,分別是 `attr_reader`、`attr_writer` 以及 `attr_accessor`。這三個方法分別會做出「讀取」、「設定」以及「讀取+設定」的方法出來,所以原來的有點囉嗦的寫法就可改成這樣: 155 | 156 | ```ruby 157 | class Cat 158 | attr_accessor :name 159 | 160 | def initialize(name, gender) 161 | @name = name 162 | @gender = gender 163 | end 164 | 165 | def say_hello 166 | puts "hello, my name is #{@name}" 167 | end 168 | end 169 | ``` 170 | 171 | ### 實體方法與類別方法 172 | 173 | 依據方法作用的對像不同,有分實體方法(instance method)及類別方法(class method),舉個例子來說: 174 | 175 | ```ruby 176 | kitty = Cat.new("kitty", "female") 177 | kitty.say_hello 178 | ``` 179 | 180 | 這個 `say_hello` 是作用在 `kitty` 這個實體,所以稱這個 `say_hello` 為實體方法。如果是這樣: 181 | 182 | ```ruby 183 | class PostsController < ApplicationController 184 | def index 185 | @posts = Post.all # 取得所有的 Post 資料 186 | end 187 | end 188 | ``` 189 | 190 | 這裡的 `all` 方法是直接作用在 `Post` 這個類別上,故稱之類別方法。在 Ruby 要定義類別方法有幾種寫法,其中一種比較簡單的,就是在前面加上 `self`: 191 | 192 | ```ruby 193 | class Cat 194 | def self.all 195 | # ... 196 | end 197 | end 198 | ``` 199 | 200 | 這樣就可以直接用 `Cat.all` 的方式呼叫了。 201 | 202 | ### 繼承 (Inheritance) 203 | 204 | 到目前為止的範例都是只有單一類別,但在真實的世界裡其實是更複雜的,像是如果想要再加入一個小狗類別: 205 | 206 | ```ruby 207 | class Cat 208 | def eat(food) 209 | puts "#{food} 好好吃!!" 210 | end 211 | end 212 | 213 | class Dog 214 | def eat(food) 215 | puts "#{food} 好好吃!!" 216 | end 217 | end 218 | ``` 219 | 220 | 不管是 Cat 或 Dog 類別都有定義了一樣功能的 `eat` 方法,在物件導向的概念裡,通常會把相同功能的方法移到上一層的類別裡,然後再去繼承它: 221 | 222 | ``` 223 | class Animal 224 | def eat(food) 225 | puts "#{food} 好好吃!!" 226 | end 227 | end 228 | 229 | class Cat < Animal 230 | end 231 | 232 | class Dog < Animal 233 | end 234 | ``` 235 | 236 | 在這裡我定義了一個 Animal 類別,然後讓 Cat 跟 Dog 都去繼承它,那個小於符號 `<` 就是繼承的意思。這樣一來,就算 Cat 跟 Dog 類別空空的什麼都沒寫,也一樣都可以執行 `eat` 方法。雖然 Cat 跟 Dog 是不同的類別,但我們可以說「Cat 是一種 Animal,Dog 也是一種 Animal」,利用這樣的設計,可以把程式碼整理得更漂亮,不會寫出一堆重複的程式碼。 237 | 238 | ### 開放類別(Open Class) 239 | 240 | 大家請先看一下這段程式碼: 241 | 242 | ```ruby 243 | class Cat 244 | def abc 245 | # ... 246 | end 247 | end 248 | 249 | class Cat 250 | def xyz 251 | # ... 252 | end 253 | end 254 | 255 | kitty = Cat.new 256 | kitty.abc # => 會發生什麼事? 257 | kitty.xyz # => 會發生什麼事? 258 | ``` 259 | 260 | 一個不小心,定義了兩個 `Cat` 類別,所以你可能會猜,後面寫的類別會蓋掉前面先寫的類別,所以 `kitty.xyz` 可正常運作,但 `kitty.abc` 會出錯。 261 | 262 | 在 Ruby 裡,如果遇到兩個一樣名字的類別,其實並不會「覆蓋」,而是會進行「融合」,上面這兩個類別最後會變成: 263 | 264 | ```ruby 265 | class Cat 266 | def abc 267 | # ... 268 | end 269 | 270 | def xyz 271 | # ... 272 | end 273 | end 274 | ``` 275 | 276 | 然後 `abc` 跟 `xyz` 兩個方法都可以正常執行。利用這個特性,可以做出有趣的效果: 277 | 278 | ```ruby 279 | class String 280 | def say_hello 281 | "hi, I am #{self}" 282 | end 283 | end 284 | 285 | puts "eddie".say_hello # => hi, I am eddie 286 | puts "kitty".say_hello # => hi, I am kitty 287 | ``` 288 | 289 | 在這裡定義了一個 `say_hello` 方法,在那之後所有的字串就都有 `say_hello` 方法可以用了。等等,那個 `String` 類別不是內建的類別嗎?是的,你沒看錯,在 Ruby 即使是內建的類別,也是可以幫它「加料」的,這個技巧稱之開放類別(Open Class)。 290 | 291 | 這是我個人很喜歡的功能,雖然有些人會認為這樣感覺很恐怖,竟然連內建的類別都可以修改,但我想大家都是大人了,應該不會沒事亂 open 然後去惡搞自己或自己的同事吧。事實上,Rails 本身也正是利用這個特性,讓程式碼的可讀性變得更好,例如: 292 | 293 | ```ruby 294 | puts 3.days.ago # => Wed, 21 Dec 2016 12:06:13 UTC +00:00 295 | puts 10.megabyte # => 10485760 296 | ``` 297 | 298 | 這樣不是很酷嗎 :) 299 | 300 | ## 模組(Module) 301 | 302 | 如果我有一隻小貓類別,我想要這個小貓類別有飛行功能,應該怎麼做?也許你會想到用「繼承」的做法: 303 | 304 | > 我只要讓小貓類別去繼承小鳥類別就好啦,反正小鳥會飛,所以繼承之後的小貓就會飛了! 305 | 306 | 1. 直接寫一個有飛行功能的小鳥類別,然後再叫小貓類別去繼承它? 307 | 308 | 2. 直接把飛行功能寫在小貓類別裡? 309 | 310 | 第 1 種做法的設計有點怪怪的,好好的貓不當,為什麼要去當鳥?為了想要有飛行功能就去當別人家的小孩... 311 | 312 | 第 2 種做法看來似乎可行,但如果之後又有個「我希望我的這個小狗類別也會飛!」的需求,那這樣又得在小狗類別裡寫一段飛行功能,程式碼沒辦法共用。 313 | 314 | 這時候,模組就可以派上用場了。 315 | 316 | ### 飛行模組 317 | 318 | 在 Ruby 定義模組,使用的是 `module` 這個關鍵字: 319 | 320 | ```ruby 321 | module Flyable 322 | def fly 323 | puts "I can fly!" 324 | end 325 | end 326 | ``` 327 | 328 | 寫起來的手感跟類別一樣,連模組名字的規定也跟類別一樣,必須是常數(也就是大字英文字母開頭)。定義好了之後,如果要把它拿來用,只要用 `include` 這個方法: 329 | 330 | ```ruby 331 | class Cat 332 | include Flyable 333 | end 334 | 335 | kitty = Cat.new 336 | kitty.fly # => I can fly! 337 | ``` 338 | 339 | 就可以把這個飛行模組掛上去,然後小貓就會飛了!如果之後小狗類別也想要會飛的話,只要這樣: 340 | 341 | ```ruby 342 | class Dog 343 | include Flyable 344 | end 345 | ``` 346 | 347 | 小狗也會飛了。 348 | 349 | ### 要用繼承還是要用模組? 350 | 351 | 基本上,如果你發現你要做的這個功能,它可能在很多不同體系的類別裡都會用得到,那你可以考慮把功能包在模組裡,然後在必要的時候再 include 進來即可。但如果你還是不知道到底類別跟模組有什麼差別,我再舉二個例子。 352 | 353 | 不知道大家有沒看過[火影忍者](http://zh.wikipedia.org/wiki/%E7%81%AB%E5%BD%B1%E5%BF%8D%E8%80%85)這部漫畫,漫畫裡的主人公之一,宇智波佐助,因為他們家族血統的關係,他寫輪眼這個功能是天生就有的,這個功能是從他的家族「繼承」來的。而佐助的老師,旗木卡卡西,他雖然也有寫輪眼功能,但他的寫輪眼並非繼承來的,事實上是他在年輕時候 include 了某個寫輪眼模組,所以才有這個效果。 354 | 355 | 另一個例子,[海賊王](http://zh.wikipedia.org/wiki/ONE_PIECE)漫畫裡,魯夫本來是普通人,但在偶然的機會下,他 include 了橡膠果實之後,他就有了橡膠人的能力了,並不是因為他老爸是橡膠人所以他才是橡膠人。 356 | 357 | ### 在 Rails 專案中,模組用在哪些地方? 358 | 359 | 在 Rails 專案中其實還不少地方有用到模組,主要有用在 View 的 Helper 以及 Model 跟 Controller 的 Concern,這待到後面的 Rails 章節再做詳細的介紹。 360 | 361 | -------------------------------------------------------------------------------- /markdown/chapter09-using-gems.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | title: 使用套件(gem)讓開發更有效率 4 | comments: true 5 | permalink: /chapters/09-using-gems.html 6 | 7 | --- 8 | 9 | # 使用套件(gem)讓開發更有效率 10 | 11 | - [安裝套件](#install-gem) 12 | - [使用 gem 來加速開發](#using-gem) 13 | - [小結](#note) 14 | 15 | 在開放原始碼的世界,有非常多厲害開發者願意無私的貢獻程式碼,而這些程式碼大多會打包成好用的套件,在 Ruby 的世界,我們稱它為 gem。所有 gem 的詳細資訊,都可在 [RubyGems](https://rubygems.org/) 網站上找得到: 16 | 17 | ![image](/images/chapter09/rubygems.png) 18 | 19 | ## 安裝套件 20 | 21 | 在 Ruby 要安裝套件超簡單的,只要 `gem install` 指令加上套件的名字,敲完按下 Enter 鍵,就自動會連上網路、下載套件、安裝套件,一氣呵成。例如我想安裝一個名為 `takami` 的套件: 22 | 23 | $ gem install takami 24 | Fetching: takami-0.0.1.gem (100%) 25 | Successfully installed takami-0.0.1 26 | Parsing documentation for takami-0.0.1 27 | Installing ri documentation for takami-0.0.1 28 | Done installing documentation for takami after 0 seconds 29 | 1 gem installed 30 | 31 | 如果該套件又有需要其它套件,它也會一併順便一起下載、安裝。這個 `takami` 是我自己寫的 gem,裡面沒有任何功能,僅是上課時教同學們怎麼把程式碼打包成 gem 的範例,所以可安心安裝!((咦?!) 32 | 33 | ### 所以我說那個套件呢? 34 | 35 | 安裝 gem 很簡單,但安裝好了之的那些檔案放哪去了?執行 `gem env` 可列出目前在這台電腦的設定: 36 | 37 | $ gem env 38 | RubyGems Environment: 39 | - RUBYGEMS VERSION: 2.6.8 40 | - RUBY VERSION: 2.4.0 (2016-12-24 patchlevel 0) [x86_64-darwin15] 41 | - INSTALLATION DIRECTORY: /Users/user/.rvm/gems/ruby-2.4.0 42 | - USER INSTALLATION DIRECTORY: /Users/user/.gem/ruby/2.4.0 43 | - RUBY EXECUTABLE: /Users/user/.rvm/rubies/ruby-2.4.0/bin/ruby 44 | - EXECUTABLE DIRECTORY: /Users/user/.rvm/gems/ruby-2.4.0/bin 45 | - SPEC CACHE DIRECTORY: /Users/user/.gem/specs 46 | - SYSTEM CONFIGURATION DIRECTORY: /Users/user/.rvm/rubies/ruby-2.4.0/etc 47 | - RUBYGEMS PLATFORMS: 48 | - ruby 49 | - x86_64-darwin-15 50 | ... 略 ... 51 | 52 | 那個 `INSTALLATION DIRECTORY` 就是 gem 安裝的地方,裡面翻一下應該就可以找得到剛剛安裝的 `takami` 套件了。因為我是使用 [RVM](https://rvm.io/),所以 gem 的安裝路徑會在 .rvm 目錄裡。 53 | 54 | ### 使用 gem 55 | 56 | gem 裝好了要怎麼使用呢?剛好趁這個機會介紹一個我很喜歡的 gem:[Faker](https://github.com/stympy/faker)。這個套件可以快速的產生很多種的看起來像真的「假資料」。 57 | 58 | 安裝一下套件: 59 | 60 | gem install faker 61 | 62 | 安裝完成之後,開 Ruby 內附的互動小工具 `irb` 來試玩一下: 63 | 64 | $ irb 65 | # 先 require 這個套件 66 | >> require 'faker' 67 | => true 68 | 69 | # 產生假的 Email 70 | >> Faker::Internet.email 71 | => "lynn.raynor@grahamcartwright.net" 72 | 73 | >> Faker::Internet.email 74 | => "guiseppe@jones.net" 75 | 76 | # 連權利遊戲的假資料都有 77 | >> Faker::GameOfThrones.character 78 | => "Ned Stark" 79 | 80 | >> Faker::GameOfThrones.character 81 | => "Stannis Baratheon" 82 | 83 | 做測試的時候用這個 gem 來產生假資料相當方便! 84 | 85 | ## 在 Rails 專案裡使用 gem 86 | 87 | 如果要在 Rails 專案中使用 gem 的話,需要把要使用的 gem 標註在專案目錄下的 `Gemfile`。打開 `Gemfile`,大概會長得像這樣: 88 | 89 | ```ruby 90 | source 'https://rubygems.org' 91 | 92 | git_source(:github) do |repo_name| 93 | repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") 94 | "https://github.com/#{repo_name}.git" 95 | end 96 | 97 | gem 'rails', '~> 5.0.1' 98 | gem 'sqlite3' 99 | gem 'puma', '~> 3.0' 100 | gem 'sass-rails', '~> 5.0' 101 | gem 'uglifier', '>= 1.3.0' 102 | gem 'coffee-rails', '~> 4.2' 103 | 104 | gem 'jquery-rails' 105 | gem 'turbolinks', '~> 5' 106 | gem 'jbuilder', '~> 2.5' 107 | 108 | group :development, :test do 109 | gem 'byebug', platform: :mri 110 | end 111 | 112 | group :development do 113 | gem 'web-console', '>= 3.3.0' 114 | gem 'listen', '~> 3.0.5' 115 | gem 'spring' 116 | gem 'spring-watcher-listen', '~> 2.0.0' 117 | end 118 | 119 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 120 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] 121 | ``` 122 | 123 | 在這個檔案裡,你可以看到有些 gem 的後面有加註版本號碼,有的沒有,這分別代表不同的意思: 124 | 125 | ### 沒加註版號 126 | 127 | 先從最簡單的來看。當後面沒有加註版本號碼的時候,像這樣: 128 | 129 | ```ruby 130 | gem 'sqlite3' 131 | gem 'jquery-rails' 132 | ``` 133 | 134 | 這樣的寫法將會在安裝的時候選用「最新的穩定(stable)版本」,要注意這裡的重點是「穩定」而不是「最新」。以 Rails 來說,假設最新的版本是 5.0.2 beta 4,但最新的「穩定」版本是 5.0.1 版,當沒有加註版本號的時候,它會選擇安裝 5.0.1 版本。 135 | 136 | ### 加註明確版號 137 | 138 | 例如像這樣: 139 | 140 | ```ruby 141 | gem "rails", "5.0.1" 142 | ``` 143 | 144 | 這相當明顯了,這就是說「我要安裝 rails 5.0.1 版」,應該不需要特別解釋。 145 | 146 | ### 大於、小於版號 147 | 148 | ```ruby 149 | gem 'uglifier', '>= 1.3.0' 150 | ``` 151 | 152 | 我想這個光用看的就猜得出來,就是要選用大於或等於 1.3.0 版本。如果是這樣: 153 | 154 | ```ruby 155 | gem 'rails', '>= 5.0.0.beta4', '< 5.1' 156 | ``` 157 | 158 | 則是會選用在 5.0.0.beta4 跟 5.1 之間的版本。 159 | 160 | ### 差不多... 161 | 162 | ```ruby 163 | gem 'coffee-rails', '~> 4.1.0' 164 | ``` 165 | 166 | 這是指會選用 4.1.0 以上,但 4.2 以下(不含括 4.2)的最新版本。 167 | 168 | 為什麼這麼麻煩?舉個例子來說,例如版本號 `4.2.6`,`4`、`2`、`6` 三個數字分別代表主要版號(Major)、次要版號(Minor)以及修訂版號(Patch),分別表示: 169 | 170 | * 主要版號:功能大改,公開的 API 做了不少修正,通常無法向下相容 171 | * 次要版號:加了某些新功能,但不影響其它功能,向下相容 172 | * 修訂版號:對現有的功能做了小幅度的修正,可向下相容 173 | 174 | 這是個不成文的規定(語義化版本),雖然沒有強制,但幾乎大部份的 gem 作者都會依照這個規範。這個 `~>` 「差不多」的寫法,可以確保不會因為套件昇級而把原本正常運作的系統弄壞了。 175 | 176 | ## 使用 gem 來加速開發 177 | 178 | 介紹完了 Gemfile 裡的內容,接下讓我們利用現有的 gem 來加速開發,舉個例子來說: 179 | 180 | ![image](/images/chapter09/paging-01.png) 181 | 182 | 這個頁面的資料太多了,如果我只想呈現每頁 5 筆資料,通常得自己算每頁幾筆、現在是第幾頁、總共有幾頁這些數字(我數學不好,很不擅長算這種)。有位好心又很厲害的大大做了一個專門計算分頁的套件稱為 [Kaminari](https://github.com/amatsuda/kaminari),可以很輕鬆的完成這件事: 183 | 184 | ### Step 1: 安裝套件 185 | 186 | 打開 `Gemfile`,加上這行: 187 | 188 | ``` 189 | gem 'kaminari' 190 | ``` 191 | 192 | > 重要:更新 Gemfile 檔案內容後,別忘了要到該專案目錄底下執行 `bundle install` 指令,確保所有套件都有正常安裝。 193 | 194 | ### Step 2: 修改程式碼 195 | 196 | 打開專案的 `app/controllers/posts_controller.rb` 檔案,把原來在 `index` 方法的 `Post.all` 做一些調整: 197 | 198 | ```ruby 199 | class PostsController < ApplicationController 200 | before_action :set_post, only: [:show, :edit, :update, :destroy] 201 | 202 | # GET /posts 203 | # GET /posts.json 204 | def index 205 | @posts = Post.page(params[:page]).per(5) 206 | end 207 | 208 | ... [略] ... 209 | end 210 | ``` 211 | 212 | 那個 `page` 方法,是 Kaminari 這個套件專門拿來做分頁的方法,後面的 `per(5)` 就是「每頁有 5 筆資料」的意思。重新整理一下瀏覽器,應該會看到只剩 5 筆了: 213 | 214 | ![image](/images/chapter09/paging-02.png) 215 | 216 | (如果發生 page 方法找不到之類的錯誤訊息,可能重新啟動 Rails Server 之後就正常了) 217 | 218 | 但這樣還不夠,在畫面上還少了「上一頁」、「下一頁」的功能啊!沒關係,這個套件也幫你做好了。打開檔案 `app/views/posts/index.html.erb`,找一個你想要放分頁器的地方: 219 | 220 | ```erb 221 |

<%= notice %>

222 | 223 |

Posts

224 | 225 | 226 | 227 | ...[略]... 228 | 229 |
230 |
231 | 232 | <%= paginate @posts %> 233 | 234 |
235 | <%= link_to 'New Post', new_post_path %> 236 | ``` 237 | 238 | 那行 `<%= paginate @posts %>` 會幫你把分頁器做出來。重新整理一下畫面: 239 | 240 | ![image](/images/chapter09/paging-03.png) 241 | 242 | 就這樣,寫沒幾行程式碼就把分頁功能做完了! 243 | 244 | ## 小結 245 | 246 | 善用現有的套件可以大幅的縮短開發時程。這些 gem 的作者通常很愛現(稱讚意味),他們大多會在說明文件裡詳細介紹這個套件怎麼用(怕你不會用),所以,使用套件前應詳閱公開說明書(README),如果有任何問題,也都歡迎留 issue 給作者們,通常很快就會被解答。 247 | 248 | 另外,有兩個網站推薦給大家參考: 249 | 250 | - [RailsCasts](http://railscasts.com/) 251 | - [GoRails](https://gorails.com/) 252 | 253 | 這兩個網站的影片都有介紹怎麼使用 gem,雖然 RailsCasts 網站已停止更新,但網站上的內容仍非常有參考價值。 254 | 255 | -------------------------------------------------------------------------------- /markdown/chapter10-mvc.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | title: Model、View、Controller 三分天下 4 | comments: true 5 | permalink: /chapters/10-mvc.html 6 | 7 | --- 8 | 9 | # Model、View、Controller 三分天下 10 | 11 | - [為什麼要這麼麻煩?](#why-mvc) 12 | - [圖解 MVC](#mvc-flow) 13 | - [目錄結構](#project-folders) 14 | 15 | Rails 的專案是採用 Model、View、Controller(簡稱 MVC)的方式設計的。當年我在開發 PHP 專案的時候都沒有這麼麻煩,就是在一個 .php 檔案就處理完查詢資料庫、展示資料等工作(那時候沒有使用其它框架)。所以在我剛開始學習 Rails 的時候一直有這個疑問,就是「為什麼 Rails 要搞這麼複雜?檔案分得這麼細要一直切換實在很麻煩耶!」 16 | 17 | ## 為什麼要這麼麻煩? 18 | 19 | ### 分工容易 20 | 21 | 拆解成 MVC 結構之後,雖然檔案變多、變分散了,但也因此更容易進行分工,當團隊人數增加,每個人可以在各自負責的部份進行開發,較不易互相衝突、干擾。 22 | 23 | ### 開發慣例 24 | 25 | 另一個好處,就是因為整個 Rails 專案都是遵循 MVC 的結構,所以即使是不同程度的開發者寫出來的 Rails 專案,Controller 通常會放在 `app/controllers` 目錄裡,Model 應該也會放在 `app/models` 裡,不會有太大的差別。 26 | 27 | ## 圖解 MVC 28 | 29 | 我們用一張圖來說明 Rails 裡的 MVC 是怎麼運作的: 30 | 31 | ![image](/images/chapter10/mvc.png) 32 | 33 | 1. 當有使用者輸入網址,連到你的網站的時候,第一關會遇到的是路徑對照表(Route,檔案 `config/routes.rb`)。 34 | 2. 在這個路徑對照表裡,記錄著這個網站對外開放的路徑對照表。Rails 會根據使用者輸入的網址及參數,比對這個路徑對照表的資料,然後告訴你應該去找哪個 Controller 上的哪個 Action;或是在對照表裡查不到相關資料,然後就會告訴你 `HTTP 404` 找不到頁面。 35 | 3. 在 Controller 上通常會有好幾個 Action,其實這些 Action 說穿了就是一般的方法而已。透過路徑對照表,找到了對應的 Action,這個 Action 會決定要做什麼事。 36 | 4. 舉例個子來說,在這個 Action 可能會需要查閱「目前所有的商品列表」,接著它就會去請 Model 幫忙要資料。 37 | 5. 雖然 Model 本身並不是資料庫,但它可以幫你把你跟 Model 說的「人話」轉成資料庫看得懂的資料庫查詢語言(SQL)。 38 | 6. 透過資料庫查詢語言,Model 從資料庫那邊取得你想要的資料。 39 | 7. Model 把這包資料交回 Controller/Action 手上。 40 | 8. 雖然 Controller/Action 拿到資料了,但目前這包東西還沒美化、整理過,還不適合給使用者看,所以 Controller/Action 需要跟 View 借一下畫面,讓資料更適合閱讀。 41 | 9. Controller/Action 把資料跟 View 的畫面組合,最後呈現給使用者看。 42 | 43 | 最後第 8、第 9 步,大概就是「I have a data, I have a template, um!... 秀出查詢結果」之類的概念吧。 44 | 45 | ## 目錄結構 46 | 47 | 針對 Rails 的 Route + MVC 的組合,介紹一下這些角色在專案裡對應的目錄結構及慣例。 48 | 49 | ### Route 50 | 51 | 跟 MVC 相比,Route 相對的較為單純,全部的路徑設定都放在 `config/routes.rb` 這個檔案裡: 52 | 53 | ![image](/images/chapter10/folder-config.png) 54 | 55 | `config` 目錄裡面除了 `routes.rb` 之外,基本上跟整個專案設定有關的幾乎都是放在這裡,例如 `database.yml` 就是專門用來設定資料庫連線資訊的地方。關於 Route 的使用會在下一篇做更詳細的介紹。 56 | 57 | ### Controller 58 | 59 | Controller 就是放在專案的 `app/controllers` 目錄裡: 60 | 61 | ![image](/images/chapter10/folder-controller.png) 62 | 63 | 通常每個 Controller 會有自己獨立的檔案,而且檔案的名字跟類別的名字是對得起來的。規則很簡單,就是「大寫字元改成底線加小寫」。舉個例子來說,如果類別的名稱如果叫做 `PostsController` 的話,那這個檔案的名字就是會是 `posts_controller.rb`。 64 | 65 | 如果有興趣,你也可以進到 `rails console` 裡使用字串類別的 `underscore` 以及 `camelcase` 兩個方法來玩看看: 66 | 67 | $ rails console 68 | Running via Spring preloader in process 38922 69 | Loading development environment (Rails 5.0.1) 70 | >> "PostsController".underscore 71 | => "posts_controller" 72 | 73 | >> "UsersController".underscore 74 | => "users_controller" 75 | 76 | >> "posts_controller".camelcase 77 | => "PostsController" 78 | 79 | 另外,如果你打開每個 Controller 的內容,會發現預設都是繼承自 `ApplicationController` 這個類別。根據前面的規則,這個檔案的檔名自然就是 `application_controller.rb` 了 80 | 81 | ### Model 82 | 83 | 跟 Model 相關的檔案都放在 `app/models` 目錄裡: 84 | 85 | ![image](/images/chapter10/folder-model.png) 86 | 87 | 它的類別與檔名規則跟 Controller 是一樣的,例如 `Post` Model,它的檔名是 `post.rb`;如果是 `UserStory` 的話,則是 `user_story.rb`,以此類推。 88 | 89 | 另外,如果資料庫中有 Model 相對應的資料表(Table)的話,資料表的命名慣例是「小寫 + 複數」。簡單整理如下: 90 | 91 | | Model 類別名稱 | 檔案名稱 | 資料表名稱 | 92 | |----------------|--------------------|---------------| 93 | | User | user.rb | users | 94 | | Post | post.rb | posts | 95 | | ProductItem | product_item.rb | product_items | 96 | 97 | 當然資料表的命名慣例是可以修改的,但沒必要的話通常不會特別去改它,儘量維持 Rails 的「慣例優於設定」(CoC, Convention Over Configuration)的原則。 98 | 99 | ### View 100 | 101 | View 的工作主要是負責畫面輸出,通常是一群 HTML 之類的檔案,就放在 `app/views/` 目錄底下,而且預設會隨著 Controller 的名字而集中在某個資料夾: 102 | 103 | ![image](/images/chapter10/folder-view.png) 104 | 105 | 舉例來說,跟 `PostsController` 相關的 View,就會放在 `app/views/posts` 目錄裡。如果執行的是 `PostsController` 的 `index` Action,沒特別聲明 render 方法的話,預設會去找 `app/views/posts/index.html.erb` 這個檔案。 106 | 107 | 而這個 `index.html.erb` 的附檔名本身也是有特別意義的: 108 | 109 | 1. `erb` 是 Embedded Ruby 的縮寫,表示這個檔案會由 Ruby 標準函式庫中的 [ERB](http://ruby-doc.org/stdlib/libdoc/erb/rdoc/ERB.html) 樣版引擎進行解讀。你可以在這個檔案裡寫一些 Ruby 語法,例如陣列的 `each` 或 `map` 方法以及像是產生超連結的 `link_to` 方法。 110 | 2. `html` 表示這個檔案在被 ERB 樣版引擎處理後會被輸出成 HTML。 111 | 112 | 大概了解 Route 跟 MVC 的運作方式,以及每個角色的所在目錄後,下個章節就可以準備來寫一些簡單的程式碼,熟悉一下 Rails 開發的手感。 113 | 114 | -------------------------------------------------------------------------------- /markdown/chapter12-controllers.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | title: Controller 4 | comments: true 5 | permalink: /chapters/12-controllers.html 6 | 7 | --- 8 | 9 | # Controller 10 | 11 | - [向你的用戶說聲哈囉](#say-hello-world) 12 | - [Params 變數](#params) 13 | - [實作練習:BMI 計算器](#bmi-calculator) 14 | 15 | ## 向你的用戶說聲哈囉 16 | 17 | 接續前一章,Route 解讀網址之後,會把工作轉往指定的 Controller 及 Action。在這個小節我們會試著在畫面上跟使用者說聲哈囉,熟悉一下 Route、Controller 以及 View 是怎麼運作的。 18 | 19 | ### Controller 是幹嘛的 20 | 21 | Controller 中文可翻譯成「控制器」,顧名思義,就是用來控制流程用的。它可能需要跟 Model 要資料,可能需要跟 View 要 HTML template 來玩填空遊戲,或是可能需要存取外部服務(例如金流串接)等,這大多是 Controller 要做的工作。 22 | 23 | ### 命名慣例 24 | 25 | 在 Rails 的慣例中,Controller 的命名會根據 Route 是使用複數的 `resources` 還是單數 `resource` 方法而定。如果在 Route 是使用複數型態,例如: 26 | 27 | ```ruby 28 | Rails.application.routes.draw do 29 | resources :posts 30 | resources :users 31 | end 32 | ``` 33 | 34 | 在沒有特別指定 Resources 的 `controller` 參數的情況下,預設會對到的 Controller 就會是 `PostsController` 或是 `UsersController` 這樣的複數型態;反之,如果使用的是單數 `resource`,對到的就會是單數命名的 Controller。 35 | 36 | ### 第 0 步 - 新增 Controller 37 | 38 | 在開始之前,讓我們使用 Rails 內建的產生器做一個全新的 Controller: 39 | 40 | $ rails g controller pages 41 | Running via Spring preloader in process 16503 42 | create app/controllers/pages_controller.rb 43 | invoke erb 44 | create app/views/pages 45 | invoke test_unit 46 | create test/controllers/pages_controller_test.rb 47 | invoke helper 48 | create app/helpers/pages_helper.rb 49 | invoke test_unit 50 | invoke assets 51 | invoke coffee 52 | create app/assets/javascripts/pages.coffee 53 | invoke scss 54 | create app/assets/stylesheets/pages.scss 55 | 56 | 上面這行指令會幫你做出一個 `PagesController`,以及一些其它對應的檔案、目錄。Controller 的內容如下: 57 | 58 | ```ruby 59 | class PagesController < ApplicationController 60 | end 61 | ``` 62 | 63 | 這個 Controller 裡什麼內容都沒有,就只有繼承自 `ApplicationController` 而已。所以如果上手之後,也不一定要用產生器來幫你產生 Controller,直接自己手動新增也行。 64 | 65 | ### 第 1 步 - 新增 Route 66 | 67 | 別忘了,使用者想要看到你網站上的內容,第一步是要問過 Route,所以我們先在 Route 上簡單的加上一條: 68 | 69 | ```ruby 70 | Rails.application.routes.draw do 71 | get "/hello_world", to: "pages#hello" 72 | 73 | resources :posts 74 | resources :users 75 | end 76 | ``` 77 | 78 | 當使用者輸入 `/hello_world` 網址的時候,會交給 `PagesController` 的 `hello` 方法處理。(是的,其實網址跟 Controller 上的 Action 不一定要同名) 79 | 80 | ### 第 2 步 - 把文字印出來吧! 81 | 82 | 有了 Route 之後,接下來回到 Controller 把 `hello` 這個 Action 加上去: 83 | 84 | ```ruby 85 | class PagesController < ApplicationController 86 | def hello 87 | render plain: "

你好,世界!

" 88 | end 89 | end 90 | ``` 91 | 92 | 在 `hello` 方法裡要把文字輸出到瀏覽器上,不是使用 `return` 也不是使用 `puts`,而是使用 `render` 方法,後面的 `plain` 參數是指要輸出一個一般的文字內容到畫面上。 93 | 94 | 有些剛開始學 Rails 的新朋友可能會想這樣做: 95 | 96 | ```ruby 97 | class PagesController < ApplicationController 98 | def hello 99 | render plain: "

你好,世界!

" 100 | puts "---- 你好 ----" 101 | end 102 | end 103 | ``` 104 | 105 | 使用 `puts` 方法把資料直接輸出在畫面上,看起來很直覺,但這樣不會有效果。事實上並不是 `puts` 方法不能用,它的確可以把東西印出來,只是不是印在瀏覽器上給你看到,而是印在 Rails 的 log 裡,仔細看一下正在執行 `rails server` 的那個畫面是不是有這樣的東西: 106 | 107 | ![image](/images/chapter12/puts-to-console.png) 108 | 109 | 你就可以發現有這樣的畫面: 110 | 111 | ![image](/images/chapter12/render-hello-world.png) 112 | 113 | ### 第 3 步 - 把工作交給 View 吧 114 | 115 | 雖然在第 2 步這樣可以直接在 Action 裡透過 `render` 方法把資料輸出在畫面上沒錯,但如果遇到比較複雜的 HTML 通常就不會用這個方式了。在 Controller 裡的 Action,如果沒有特別指定 `render` 方法或參數的話,它會到 `app/views/` 的目錄找「 Controller 名字」目錄裡的 Action 同名檔案。以這個例子來說,它會去找 `app/views/pages/hello.html.erb`。 116 | 117 | ![image](/images/chapter12/controller-view-mapping.png) 118 | 119 | 如果這個 `hello.html.erb` 不存在,就自己手動建一個吧。即然輸出的事情交給 View,原來 `hello` 這個 Action 的 `render` 方法就可以拿掉: 120 | 121 | ```ruby 122 | class PagesController < ApplicationController 123 | def hello 124 | end 125 | end 126 | ``` 127 | 128 | 就這樣空空的,然後編輯 `app/views/pages/hello.html.erb` 129 | 130 | ```erb 131 |

你好,世界

132 |

我是設計師也看得懂的檔案喔

133 | ``` 134 | 135 | 重新整理,應該就會看到跟剛才的差別: 136 | 137 | ![image](/images/chapter12/render-hello-world-with-view.png) 138 | 139 | 這樣的好處是不用把 HTML 都寫在 Controller 裡(事實上也很少人會這麼做),再來就是要跟設計師合作的時候也比較方便。 140 | 141 | ## Params 參數 142 | 143 | 接下來我們看看怎麼傳參數給 Controller。當使用者輸入網址這樣的網址: 144 | 145 | /hello_world?name=5xruby&price=100&staff=20 146 | 147 | 畫面的輸出雖然沒變,但後面跟的那串東西會被當做參數傳進一個特別的變數叫做 `params`。這是 Rails 預先幫我們定義好的,它可以捕捉到這個頁面的資訊。讓我們在剛剛的 `hello` Action 裡加一些料: 148 | 149 | 150 | ```ruby 151 | class PagesController < ApplicationController 152 | def hello 153 | render json: params 154 | end 155 | end 156 | ``` 157 | 158 | 使用 `render` 方法,把 `params` 這個變數用 `JSON` 的方式印出來,可以看到這個結果: 159 | 160 | ![image](/images/chapter12/render-params-1.png) 161 | 162 | Rails 會把剛剛後面那串東西,整理成一個類似 Hash 的東西,例如我只想要 `name` 參數的話: 163 | 164 | ```ruby 165 | class PagesController < ApplicationController 166 | def hello 167 | render plain: params["name"] 168 | end 169 | end 170 | ``` 171 | 172 | ![image](/images/chapter12/render-params-2.png) 173 | 174 | 不管是 GET 或是 POST 方式傳過來的參數,都會被收集到這個 `params` 裡。 175 | 176 | ## 實作練習:BMI 計算器 177 | 178 | 大概知道 Route、Controller、View 以及 Params 的使用方法後,接下來我們來做一個可以計算 BMI(Body Mass Index,身體質量指數)的計算機。 179 | 180 | ### 第 0 步 - 新增 Controller 及 Route 181 | 182 | 先用產生器把 Controller 做出來: 183 | 184 | $ rails g controller bmi index  21:53:32 185 | Running via Spring preloader in process 18198 186 | create app/controllers/bmi_controller.rb 187 | route get 'bmi/index' 188 | invoke erb 189 | create app/views/bmi 190 | create app/views/bmi/index.html.erb 191 | invoke test_unit 192 | create test/controllers/bmi_controller_test.rb 193 | invoke helper 194 | create app/helpers/bmi_helper.rb 195 | invoke test_unit 196 | invoke assets 197 | invoke coffee 198 | create app/assets/javascripts/bmi.coffee 199 | invoke scss 200 | create app/assets/stylesheets/bmi.scss 201 | 202 | 203 | 跟前面稍微有點不一樣的是在 Controller 後面多加了 `index` 這個參數,這樣會自動幫你做幾件事: 204 | 205 | #### 1 - 幫你加上 Route 206 | 207 | ```ruby 208 | Rails.application.routes.draw do 209 | get 'bmi/index' 210 | 211 | get "hello_world", to: "pages#hello" 212 | resources :posts 213 | resources :users 214 | end 215 | ``` 216 | 217 | 多加了 `get 'bmi/index` 條路徑。但我不是很喜歡這樣的路徑,所以請把它改成: 218 | 219 | ```ruby 220 | Rails.application.routes.draw do 221 | get "bmi", to: "bmi#index" 222 | 223 | get "hello_world", to: "pages#hello" 224 | resources :posts 225 | resources :users 226 | end 227 | ``` 228 | 229 | 這時候輸入路徑 `/bmi` 應該可以看到這個畫面: 230 | 231 | ![image](/images/chapter12/bmi-1.png) 232 | 233 | #### 2 - 自動幫 Controller 加上 `index` Action: 234 | 235 | ```ruby 236 | class BmiController < ApplicationController 237 | def index 238 | end 239 | end 240 | ``` 241 | 242 | #### 3 - 自動幫你產生 `app/views/bmi/index.html.erb` 檔案 243 | 244 | 內容如下: 245 | 246 | ```erb 247 |

Bmi#index

248 |

Find me in app/views/bmi/index.html.erb

249 | ``` 250 | 251 | ### 第 1 步 - 建立表單 252 | 253 | 編輯 `app/views/bmi/index.html.erb` 如下: 254 | 255 | ```erb 256 |

BMI 計算機

257 | 258 | <%= form_tag '/bmi/result' do %> 259 | 身高:<%= text_field_tag 'body_height' %> 公分
260 | 體重:<%= text_field_tag 'body_weight' %> 公斤
261 | <%= submit_tag "開始計算" %> 262 | <% end %> 263 | ``` 264 | 265 | 這裡有幾個需要稍做說明的地方: 266 | 1. `form_tag` 會被轉換成 HTML 的 `
` 標籤 267 | 2. `text_field_tag` 會被轉換成 HTML 的 `` 標籤。 268 | 3. `submit_tag` 會被轉換成 HTML 的 `` 標籤。 269 | 270 | 以上這些方法都統稱為 `View Helper`,更多相關的使用方法參考 [Form Helper](http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html) 的 API 手冊。這時候的畫面會長得像這樣: 271 | 272 | ![image](/images/chapter12/bmi-2.png) 273 | 274 | 在繼續之前,先讓我們檢視一下這一頁的原始碼,仔細看一下跟表單有關的部份,稍做整理如下: 275 | 276 | ```html 277 | 278 | 279 | 280 | 身高: 公分
281 | 體重: 公斤
282 | 283 |
284 | ``` 285 | 286 | 這邊有一段名字叫做 `authenticity_token` 的隱藏 input 標籤,不只內容看起來像是亂碼,而且每次重新整理又會得到不一樣的值,這個是做什麼用的呢? 287 | 288 | 我在一開始接觸網路的時候做的工作是網路行銷,因為工作的關係,常常需要撰寫讓網友們票選或是填寫資料抽獎之類的程式。稍微有點技術底子的參加者,只要檢視網頁的原始碼,就可以看得出來這個表單要用什麼方式(GET 或 POST)、要送到什麼地方,以及要送的資料欄位名稱。有心人士只要寫一個簡單的小程式,仿照原頁面送資料到指定的地方,就可能可以造成灌票或是大量留言、灌水的情況,影響活動的公平性。若因此而再加一些驗證規則,反而又提高了一般參加者的的門檻。 289 | 290 | 如果這個活動網站是用 Rails 開發的,Rails 預設在處理表單的時候會檢查這個 `authenticity_token` 是不是由本站所產生的,如果沒有這個欄位,或是這個欄位的值經 Rails 核對後發現並不是本身所產生,就會出現這個錯誤訊息: 291 | 292 | ![image](/images/chapter12/invalid-authenticity-token-error.png) 293 | 294 | 不管是 `form_tag` 或是下個章節才介紹的 `form_for`,在產生 `
` 標籤的時候都會自動幫你加上並產生 `authenticity_token` 的欄位,確保比較不會太容易被有心人士所破壞。 295 | 296 | ### 第 2 步 - 新增 Route 297 | 298 | 這時候當我們按下送出的時候會得到 `Routing Error` 的錯誤訊息,那是因為我們還沒有這個路徑,所以現在來補做一下。在 Route 裡加上一行: 299 | 300 | ```ruby 301 | Rails.application.routes.draw do 302 | get "bmi", to: "bmi#index" 303 | post "bmi/result", to: "bmi#result" 304 | 305 | get "hello_world", to: "pages#hello" 306 | resources :posts 307 | resources :users 308 | end 309 | ``` 310 | 311 | 這樣可讓 Route 可以接到 `POST` 並轉往 `BmiController` 的 `result` Action。 312 | 313 | ### 第 3 步 - 計算 314 | 315 | Route 有了,接下來就是把 `result` Action 的內容補上去: 316 | 317 | ```ruby 318 | class BmiController < ApplicationController 319 | def index 320 | end 321 | 322 | def result 323 | height = params[:body_height].to_f / 100 # 把單位換算成公尺 324 | weight = params[:body_weight].to_f 325 | 326 | # BMI 計算公式: BMI = 體重(單位:公斤) / 身高平方(單位:公尺). 327 | @bmi = (weight / (height * height)).round(2) 328 | end 329 | end 330 | ``` 331 | 332 | BMI 的計算公式還滿單純的,不過要注意的是: 333 | 334 | 1. 透過 `params` 取得的資料預設型態是字串,所以需要使用 `.to_i` 或 `.to_f` 轉換成數字。這裡因為需要使用除法計算到小數點以下所以使用 `.to_f` 方法進行轉換。 335 | 2. 計算完的結果存成實體變數 `@bmi`,以便讓 View 可以取用。 336 | 337 | ### 第 4 步 - 呈現結果 338 | 339 | 最後一步,把結果印出來。編輯檔案 `app/views/bmi/result.html.erb` (如果檔案不存在請直接手動建立): 340 | 341 | ```erb 342 |

您的 BMI 值為:<%= @bmi %>

343 | ``` 344 | 345 | 搞定!試玩一下,我輸入身高 178 公分、體重 80 公斤: 346 | 347 | ![image](/images/chapter12/bmi-3.png) 348 | 349 | 按下送出即可得到計算結果: 350 | 351 | ![image](/images/chapter12/bmi-4.png) 352 | 353 | ## 小結 354 | 355 | 雖然這個計算機的功能相當陽春,而且也很多地方需要改善,例如防呆機制,或是根據計算結果嘲諷一下 BMI 值過高的胖子。但如果你能理解這個例子裡 Route、Controller、View 之間的基本運作原理,當下回遇到更複雜的應用程式開發相信也是可以迎刃而解。 356 | 357 | > 以上實作完整程式碼可在[我的 GitHub 帳號](https://github.com/kaochenlong/hello_rails)取得。 358 | 359 | -------------------------------------------------------------------------------- /markdown/chapter14-layout-render-and-view-helper.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | title: Layout, Render 與 View Helper 4 | comments: true 5 | permalink: /chapters/14-layout-render-and-view-helper.html 6 | 7 | --- 8 | 9 | # Layout, Render 與 View Helper 10 | 11 | - [版型(Layout)](#layout) 12 | - [局部渲染(Partial Render)](#partial-render) 13 | - [View Helper](#view-helper) 14 | 15 | 在上個章節介紹了 CRUD 的分解動作,接下來這個章節要介紹的是在 Rails 專案 MVC 架構的 V。 16 | 17 | ## 版型 Layout 18 | 19 | 隨便打開一個在 `app/views` 目錄裡的檔案,例如上個章節的候選人列表頁面: 20 | 21 | ```erb 22 |

候選人列表

23 | 24 | <%= link_to "新增候選人", new_candidate_path %> 25 | 26 | 27 | 28 | 29 | 30 | ...[略]... 31 | 35 | 36 | <% end %> 37 | 38 |
投票 32 | <%= link_to "編輯", edit_candidate_path(candidate) %> 33 | <%= link_to "刪除", candidate_path(candidate), method: "delete", data: { confirm: "確認刪除" } %> 34 |
39 | ``` 40 | 41 | 在這個檔案裡,看不到任何 ``、`` 或 `<body>` 之類的 HTML 標籤,但檢視實際網頁的原始碼又都有,這是怎麼回事呢? 42 | 43 | ### yield 44 | 45 | 以這個例子來說,Controller 在處理 View 的時候,並不只是單純的只取用 `index.html.erb`,而是會先取用 Layout 檔案的內容(預設是 `app/views/layouts/application.html.erb`),然後把 `index.html.erb` 的內容填到 `<%= yield %>` 裡。 46 | 47 | ![image](/images/chapter14/layout.png) 48 | 49 | 版型的好處,就是不需要重複的寫一堆長得一樣的 HTML 標籤,例如頁面的頁首跟頁尾通常不會有什麼變化,這種就是版型適用的地方。 50 | 51 | 讓我們看一下 `app/views/layouts/application.html.erb` 的內容: 52 | 53 | ```erb 54 | <!DOCTYPE html> 55 | <html> 56 | <head> 57 | <title>MyCandidates 58 | <%= csrf_meta_tags %> 59 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> 60 | <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> 61 | 62 | 63 | 64 |
65 | <%= yield %> 66 |
67 | 68 | 69 | ``` 70 | 71 | 這裡有幾行需要說明一下: 72 | 73 | 1. `csrf_meta_tags` 方法會在頁面上產生 `` 跟 `` 兩個 `` 標籤,用途主要是確保網站較不容易受到 CSRF(Cross-site request forgery)攻擊。 74 | 2. `stylesheet_link_tag` 方法會轉換成 CSS 的 `` 標籤。 75 | 3. `javascript_include_tag` 方法會轉換成 JavaScript 的 ` 75 | 82 | ``` 83 | 84 | 說明: 85 | 86 | 1. Form 裡面必須有一個 id 為 `payment-form` 的元素,好讓底下的 JavaScript 可以對到這個名字。不一定要用 `payment-form` 這個名字,但如果換的話,底下的 `braintree.setup` 那段範例裡的名字也要跟著換。 87 | 2. 文件的範例只是一般的 HTML form,但在 Rails 傳送表單的時候會順便檢查 CSRF Token,所以通常會用 `form_for` 或 `form_tag` 來產生 HTML form。 88 | 3. 接續 2,為了讓 `form_for` 裡的 `checkout_product_path` 可以正常運作,我在 `config/routes.rb` 加了一些修改: 89 | 90 | ```ruby 91 | Rails.application.routes.draw do 92 | resources :products do 93 | member do 94 | post :checkout 95 | end 96 | end 97 | end 98 | ``` 99 | 100 | 重新整理頁面,這時候的畫面會變成這樣: 101 | 102 | ![image](/images/chapter28/payment-form.png) 103 | 104 | ### 測試卡號 105 | 106 | Braintree 有提供一組測試用的卡號: 107 | 108 | 卡號:4111-1111-1111-1111 (第一個 4,剩下按 1 按到底) 109 | 日期:只要超過今天的日期就行了 110 | 111 | 填完卡號以及有效期限按下送出後,沒意外的話應該會看到這個錯誤畫面: 112 | 113 | ![image](/images/chapter28/post-error.png) 114 | 115 | 那是因為我們還沒有寫這個 `checkout` Action,所以有這個錯誤訊息是正常的。到這裡,前端頁面的設定算是完成一部份了。為什麼說一部份?因為那個 `clientToken` 目前還是寫死的,它應該由我們的伺服器來傳給它才對,這也就是我們下一步要做的事。 116 | 117 | ## 使用 Braintree - 後端 118 | 119 | 後端要做幾件事情: 120 | 121 | 1. 安裝 `braintree` gem 122 | 2. 設定金鑰 123 | 3. 產生 `clientToken` 124 | 4. 接收到前端頁面 POST 過來的資訊,準備進行刷卡 125 | 126 | ### 1. 安裝 `braintree` gem 127 | 128 | 這個步驟還滿簡單的,只要在 Gemfile 裡加上 `gem 'braintree'` 就行了,存檔後記得執行 `bundle install` 指令確定有正確安裝完成。 129 | 130 | ### 2. 設定金鑰 131 | 132 | 接下來要設定一些金鑰資訊以便讓我們產生下一步所需的 Client Token。以我們在開發環境為例,請打開 `config/environments/development.rb` 檔案,在最下方加上這幾行: 133 | 134 | ```ruby 135 | Rails.application.configure do 136 | #...[略]... 137 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker 138 | end 139 | 140 | Braintree::Configuration.environment = :sandbox 141 | Braintree::Configuration.merchant_id = "use_your_merchant_id" 142 | Braintree::Configuration.public_key = "use_your_public_key" 143 | Braintree::Configuration.private_key = "use_your_private_key" 144 | ``` 145 | 146 | 其中 `use_your_merchant_id`、`use_your_public_key` 以及 `use_your_private_key` 這三個資訊,請到 Braintree 的上方選單「Account」→「My User」頁面,下方有一個「API Keys, Tokenization Keys, Encryption Keys」段落,裡面可以新增或取得所需的資訊: 147 | 148 | ![image](/images/chapter28/api-keys.png) 149 | 150 | > 注意:修改過 `config` 目錄下的檔案,通常都需要重新啟動 `rails server` 才會生效。 151 | 152 | ### 3. 產生 `clientToken` 153 | 154 | 回到 ProductsController 的 show Action,加上這行: 155 | 156 | ```ruby 157 | class ProductsController < ApplicationController 158 | #...[略]... 159 | 160 | def show 161 | @client_token = Braintree::ClientToken.generate 162 | end 163 | 164 | #...[略]... 165 | end 166 | ``` 167 | 168 | 產生一個 `@client_token` 的實體變數,準備給 View 使用。然後回到 `show.html.erb` 檔案,把剛剛很長而且寫死的那個 `clientToken` 換成我們剛剛產生的資訊: 169 | 170 | ```erb 171 |

<%= notice %>

172 | 173 |

174 | Title: 175 | <%= @product.title %> 176 |

177 | 178 |

179 | Description: 180 | <%= @product.description %> 181 |

182 | 183 |

184 | Price: 185 | <%= @product.price %> 186 |

187 | 188 | <%= link_to 'Back', products_path, class:'btn btn-default' %> 189 |
190 | 191 | <%= form_for(@product, url: checkout_product_path(@product), method: :post) do |f| %> 192 |
193 | <%= f.submit "確認付款", class:"btn btn-default btn-danger" %> 194 | <% end %> 195 | 196 | 197 | 204 | ``` 205 | 206 | 這樣就可以準備來刷卡了! 207 | 208 | ### 4. 接收到前端頁面 POST 過來的資訊,準備進行刷卡 209 | 210 | 按下送出,這時候它會去找 `checkout` Action,所以我們現在來完成這段功能。當按下送出之後,`checkout` Action 會收到的 `params` 的內容如下: 211 | 212 | ```ruby 213 | Parameters: {"utf8"=>"✓", "authenticity_token"=>"PdmlFcBf6AmjyNg9bM6nh3wppzdC3xPZGBRmv7NR58DKcYh4DsoV804YKI9pyfU+FyrzzRbh0iq6Tg9K/BL9ZA==", "payment_method_nonce"=>"47571afc-8316-0b1d-1619-24f29754a320", "id"=>"1"} 214 | ``` 215 | 216 | 這段資訊裡面我們會需要的是 `payment_method_nonce` 以及 `id`,所以只要截取這兩個資訊出來就行了: 217 | 218 | > 你有發現這串 params 裡面沒有「信用卡卡號」嗎? 219 | 220 | ```ruby 221 | class ProductsController < ApplicationController 222 | before_action :set_product, only: [:show, :edit, :update, :destroy, :checkout] 223 | #...[略]... 224 | 225 | def checkout 226 | if @product 227 | nonce = params[:payment_method_nonce] 228 | 229 | result = Braintree::Transaction.sale( 230 | amount: @product.price, 231 | payment_method_nonce: nonce 232 | ) 233 | 234 | if result 235 | redirect_to products_path, notice: "刷卡成功" 236 | else 237 | # 錯誤處理 238 | end 239 | else 240 | # 錯誤處理 241 | end 242 | end 243 | 244 | #...[略]... 245 | end 246 | ``` 247 | 248 | 說明: 249 | 250 | 1. 因為 `checkout` Action 也需要先把要刷卡的那項商品挑出來,所以在 `before_action` 也把它掛上去 251 | 2. `Braintree::Transaction.sale` 方法需傳入「金額」以及 Client 頁面傳過來的那個隨機數(nonce) 252 | 3. 如果刷卡錯誤需適時的提醒使用者哪裡發生錯誤 253 | 254 | 如果一切順利,應該就會轉往商品列表頁面了。 255 | 256 | 這時候回到 Braintree 的後台主頁面,可以看到我們的確刷了 100 塊錢: 257 | 258 | ![image](/images/chapter28/paid.png) 259 | 260 | ## 小結 261 | 262 | Braintree 算是相當容易串接的服務,不過在上面的例子我們僅傳了消費金額給 Braintree,事實上在實務上還需要傳更多資訊過去,例如訂單編號等,這樣店家才會知道到底賣了什麼、賣給誰。更多詳細使用方法,請參閱 Braintree 的 API 手冊。 263 | 264 | --------------------------------------------------------------------------------