From the Library of Shirong Chen
The Merb Way
From the Library of Shirong Chen
Addison-Wesley Professional Ruby Series Obie Fernandez, Series Editor
The Addison-Wesley Professional Ruby Series provides readers with practical, people-oriented, and in-depth information about applying the Ruby platform to create dynamic technology solutions. The series is based on the premise that the need for expert reference books, written by experienced practitioners, will never be satisfied solely by blogs and the Internet.
informit.com/ruby
From the Library of Shirong Chen
The Merb Way
Foy Savas
Upper Saddle River, NJ • Boston • Indianapolis • San Francisco New York • Toronto • Montreal • London • Munich • Paris • Madrid Capetown • Sydney • Tokyo • Singapore • Mexico City
From the Library of Shirong Chen
Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in this book, and the publisher was aware of a trademark claim, the designations have been printed with initial capital letters or in all capitals. The author and publisher have taken care in the preparation of this book, but make no expressed or implied warranty of any kind and assume no responsibility for errors or omissions. No liability is assumed for incidental or consequential damages in connection with or arising out of the use of the information or programs contained herein. The publisher offers excellent discounts on this book when ordered in quantity for bulk purchases or special sales, which may include electronic versions and/or custom covers and content particular to your business, training goals, marketing focus, and branding interests. For more information, please contact:
Acquisitions Editor Debra Williams Cauley Development Editor Michael Thurston Managing Editor John Fuller Project Editor Anna Popick
U.S. Corporate and Government Sales (800) 382-3419
[email protected]
Copy Editor Barbara Wood
For sales outside the United States please contact:
Indexer Richard Evans
International Sales
[email protected]
Proofreader Barbara Wood
Visit us on the Web: informit.com/aw Library of Congress Cataloging-in-Publication Data Savas, Foy. The Merb way / Foy Savas. p. cm. ISBN 978-0-321-60638-9 (pbk. : alk. paper) 1. Web site development. 2. Merb (Electronic resource) I. Title. TK5105.888.S2775 2009 006.7’6—dc22
Editor-in-Chief Mark Taub
Technical Reviewers Matthew Knox Jen Lindner Editorial Assistant Kim Boedigheimer Cover Designer Chuti Prasertsith
2009013263
c 2009 Pearson Education, Inc. Copyright All rights reserved. Printed in the United States of America. This publication is protected by copyright, and permission must be obtained from the publisher prior to any prohibited reproduction, storage in a retrieval system, or transmission in any form or by any means, electronic, mechanical, photocopying, recording, or likewise. For information regarding permissions, write to:
Compositor ITC
Pearson Education, Inc. Rights and Contracts Department 501 Boylston Street, Suite 900 Boston, MA 02116 Fax: (617) 671-3447 The code in this book may be distributed only subject to the terms and conditions set forth in the MIT License. The MIT License reads: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation to the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. ISBN-13: 978-0-321-60638-9 ISBN-10: 0-321-60638-8 Text printed in the United States on recycled paper at Donnelley in Crawfordsville, Indiana. First printing, June 2009
From the Library of Shirong Chen
To Sophia —I think we can afford to eat this month
From the Library of Shirong Chen
This page intentionally left blank
From the Library of Shirong Chen
Contents
Foreword xix Acknowledgments xxi Merb pastie xxiii Introduction xxvii About the author xxxi
Chapter 1 1.1 1.2
1.3
1.4
Fundamentals 1
Generating a new application 1 The layout of a Merb application 2 1.2.1 Very flat layout 3 1.2.2 Flat layout 6 1.2.3 Standard layout 8 Interactive Merb 9 1.3.1 Console methods 10 1.3.2 Sandboxing 11 Merb configuration 12 1.4.1 Init script 12 1.4.1.1 Gems and load path 12 1.4.1.2 Dependencies 13 after_app_loads 14 1.4.1.3 1.4.1.4 Template engine 14 1.4.1.5 Basic configuration 14
vii From the Library of Shirong Chen
viii
Contents
1.5
1.6
1.4.1.6 ORM options 14 1.4.1.7 Testing options 15 1.4.1.8 Inflector 15 1.4.2 Environments 16 1.4.3 Router 17 1.4.4 Logging 17 1.4.4.1 Using logging 18 1.4.4.2 Viewing logs 18 1.4.5 Database 19 Understanding the Merb stack 20 1.5.1 Extlib 20 1.5.1.1 ObjectSpace 20 Class 20 1.5.1.2 Object 21 1.5.1.3 1.5.1.4 String 21 Time 22 1.5.1.5 DateTime 22 1.5.1.6 1.5.1.7 Pathname 22 1.5.1.8 blank? 22 Logger 22 1.5.1.9 1.5.1.10 Mash 22 1.5.1.11 SimpleSet 23 1.5.1.12 VirtualFile 23 1.5.1.13 LazyArray 23 1.5.1.14 Hook 23 1.5.1.15 Pooling 23 1.5.2 Rack 23 1.5.2.1 Adapter options 23 1.5.2.2 How Rack works 24 1.5.3 ORMs 26 1.5.4 Plugins 26 An overview of Merb internals 27 1.6.1 Boot loaders 27 1.6.1.1 BootLoader::Logger 27 1.6.1.2 BootLoader::DropPidFile 28 BootLoader::Defaults 28 1.6.1.3 BootLoader::BuildFramework 28 1.6.1.4
From the Library of Shirong Chen
Contents
1.7
ix
1.6.1.5 BootLoader::Dependencies 29 1.6.1.6 BootLoader::MixinSession 29 BootLoader::BeforeAppHooks 29 1.6.1.7 BootLoader::LoadClasses 29 1.6.1.8 BootLoader::Templates 29 1.6.1.9 1.6.1.10 BootLoader::MimeTypes 30 1.6.1.11 BootLoader::Cookies 30 1.6.1.12 BootLoader::SetupSession 30 1.6.1.13 BootLoader::AfterAppLoads 30 1.6.1.14 BootLoader::SetupStubClasses 31 1.6.1.15 BootLoader::ChooseAdapter 31 1.6.1.16 BootLoader::StartWorkerThread 31 1.6.1.17 BootLoader::RackUpApplication 31 1.6.1.18 BootLoader::ReloadClasses 32 1.6.1.19 BootLoader::ReloadTemplates 32 1.6.2 Server 32 1.6.3 Requests 33 1.6.4 Router 33 1.6.5 Dispatcher 33 1.6.6 Controllers 34 1.6.7 Sessions 34 1.6.8 Worker 34 Conclusion 35
Chapter 2 2.1
2.2
Routing 37
How Merb routing works 38 2.1.1 Route conditions 39 2.1.1.1 Route method 39 2.1.1.2 Route path 40 2.1.1.3 Other conditions 41 2.1.2 Route parameters 42 Router configuration 43 2.2.1 The router file 43 2.2.2 The prepare block 43 2.2.3 Route order 44 2.2.4 Adding routes later on 44
From the Library of Shirong Chen
x
Contents
2.3
2.4
2.5
2.6
2.7
Checking routes 45 2.3.1 Listing routes 45 2.3.1.1 Using interactive Merb 45 2.3.1.2 Using the audit routes rake task 48 2.3.2 Trying the router 49 2.3.2.1 For URL recognition 49 2.3.2.2 For URL generation 50 Match rules 50 2.4.1 Literal matching 50 2.4.2 Symbolic matches 51 2.4.2.1 Automatic parameters 51 2.4.2.2 Flexible segmentation 52 2.4.2.3 Segment-specific regular expressions 52 2.4.3 Optional matches 52 2.4.4 Full regular expressions 53 2.4.5 Deferred routes 53 Registering routes 54 2.5.1 Using to 54 2.5.2 Using with 55 2.5.3 Using register 55 2.5.4 Redirects 56 2.5.5 Using symbols 56 2.5.6 Using other captures 56 Other route settings 57 2.6.1 Setting defaults 57 2.6.2 Named routes 57 2.6.3 Setting prefixes 58 2.6.3.1 Name prefix 58 2.6.3.2 Controller prefix 59 2.6.3.3 Namespaces 59 2.6.4 Fixatable routes 59 Resource routes 60 2.7.1 Standard resources routing 60 2.7.1.1 Index 61 2.7.1.2 New 62 2.7.1.3 Create 62 2.7.1.4 Edit 62
From the Library of Shirong Chen
Contents
2.8
xi
2.7.1.5 Update 62 2.7.1.6 Destroy 62 2.7.1.7 Show 62 2.7.2 Singular resource routing 2.7.3 Using identify 63 Conclusion 64
Chapter 3 3.1
3.2
3.3
3.4
62
Controllers 65
From request to controller 65 3.1.1 A simple application 66 3.1.2 How requests get dispatched 66 3.1.3 The controller’s perspective 67 The controller classes 68 3.2.1 The abstract controller 69 3.2.1.1 Class methods 69 3.2.1.2 Instance methods 70 3.2.2 The Merb controller 70 3.2.2.1 Class methods 70 3.2.2.2 Instance methods 72 3.2.3 The application controller 72 3.2.4 The exceptions controller 74 3.2.5 Other controllers 74 Custom controller classes 75 3.3.1 Controller location 75 3.3.2 Naming controllers 76 3.3.3 Organizing controller methods 76 3.3.3.1 Sharing a nonaction method 77 3.3.3.2 Making methods available to subclasses 3.3.3.3 Increasing readability of an action 78 3.3.4 Setting callable actions 79 3.3.4.1 Hiding an action 79 3.3.4.2 Showing an action 79 Filters 79 3.4.1 Before filters 80 3.4.2 After filters 81 3.4.3 Filter options 82
77
From the Library of Shirong Chen
xii
Contents
3.5
3.6
3.7
3.8 3.9
3.10
3.4.4 Skipping filters 83 Redirects 84 3.5.1 A redirect caveat 84 3.5.2 Redirects after POST requests 85 3.5.3 Redirecting in before filters 86 Exceptions 87 3.6.1 Raising an exception 87 3.6.2 Controller exceptions 87 3.6.3 The exceptions controller 88 Rendering templates 89 3.7.1 How templates are compiled 89 3.7.2 Basic rendering 91 3.7.3 Template 91 3.7.4 Formats 92 3.7.5 Status 94 3.7.6 Layout 94 display 95 3.7.7 3.7.8 render_chunked 96 3.7.9 render_deferred 96 3.7.10 render_then_call 97 run_later 97 Sending and streaming 97 3.9.1 Sending files 98 3.9.2 Streaming files 98 Conclusion 99
Chapter 4 4.1
4.2
Views 101
ERB 101 4.1.1 Basic delimiters 102 4.1.2 Removing whitespace 102 4.1.3 Comments 103 4.1.4 Merb’s block-aware enhancer Haml 105 4.2.1 Tags 105 4.2.2 Indentation 106 4.2.3 IDs and classes 106
103
From the Library of Shirong Chen
Contents
4.3 4.4 4.5
xiii
4.2.4 Attributes 107 4.2.5 Interpreting lines 107 4.2.6 Outputting lines 107 4.2.7 Outputting string lines 108 4.2.8 Sanitized lines 108 4.2.9 Preserving whitespace 108 4.2.10 Filters 109 Merb view templates 109 Partials 111 Conclusion 112
Chapter 5 5.1 5.2 5.3
5.4
5.5
Models 113
Configuration 113 Model classes 115 Properties 118 5.3.1 Database storage 120 5.3.1.1 Automigrating the DB schema 121 5.3.2 Defining properties 127 5.3.2.1 Property types 127 5.3.2.2 Option hash 131 Associations 132 5.4.1 Belongs to 133 5.4.2 Has 135 5.4.3 Has through 138 CRUD basics 140 5.5.1 Creating records 140 5.5.2 Retrieving records 142 5.5.2.1 Special query parameters 145 5.5.2.2 Lazy loading of collections 146 5.5.2.3 Lazy loading of properties 148 5.5.2.4 Strategic eager loading 150 5.5.3 Updating records 152 5.5.3.1 Using update_attributes 152 5.5.3.2 Original and dirty attribute values 153 5.5.4 Destroying records 154
From the Library of Shirong Chen
xiv
Contents
5.6 5.7
5.8
Hooks Plugins 5.7.1 5.7.2 5.7.3 5.7.4
154 155 Extra property types 155 Timestamps 156 Aggregates 158 Validations 162 5.7.4.1 Conditions 163 5.7.4.2 Contexts 164 5.7.4.3 Errors 164 Conclusion 165
Chapter 6 6.1 6.2
6.3
6.4 6.5 6.6
6.7
Truncate helper 167 Numeric helpers 168 6.2.1 Two digits 168 6.2.2 Minutes to hours 169 6.2.3 Currency strings 169 Date and time helpers 171 6.3.1 Formats 171 6.3.2 Ordinals 173 6.3.3 Time DSL 174 6.3.4 Relative time 175 Cycle helper 176 Tag helpers 177 Form helpers 179 6.6.1 Builders 179 6.6.2 Helpers 187 Conclusion 192
Chapter 7 7.1
7.2 7.3
Helpers 167
Slices 193
Slice development 193 7.1.1 Generating slices 193 7.1.2 Running slices 196 7.1.3 Building slices 199 7.1.4 Slice controllers 200 Slice usage 201 Conclusion 203
From the Library of Shirong Chen
Contents
xv
Chapter 8 8.1 8.2 8.3
8.4 8.5 8.6
How sessions work 205 Configuration 206 Storing sessions 207 8.3.1 Session containers 207 8.3.2 Session store containers 208 8.3.3 Session storage mechanisms 211 8.3.3.1 Cookie sessions 211 8.3.3.2 Memory sessions 214 8.3.3.3 Memcached sessions 216 8.3.3.4 DataMapper sessions 217 Request access 219 Controller access 220 Conclusion 220
Chapter 9 9.1
9.2
9.3
Sessions 205
Authentication 221
Auth core 221 9.1.1 Authentication 223 9.1.2 Strategy 227 9.1.3 Sessions 231 9.1.4 Errors 232 9.1.5 Responses 233 9.1.6 Helpers 234 9.1.7 Router helper 235 9.1.8 Customizations 236 9.1.9 Callbacks 236 Auth more 237 9.2.1 Strategies 238 9.2.1.1 Basic 239 9.2.1.2 Password form 241 9.2.1.3 OpenID 242 9.2.2 Models 242 9.2.3 Controller 245 Auth password slices 245 9.3.1 Lib file 246
From the Library of Shirong Chen
xvi
Contents
9.4
9.3.2 Controller 247 9.3.3 Views 249 Conclusion 251
Chapter 10 10.1
10.2 10.3
10.4 10.5 10.6
Configuration 253 10.1.1 SMTP 253 10.1.2 Sendmail 254 10.1.3 Test send 254 10.1.4 Custom delivery methods Using mailers directly 255 Mail controllers 257 10.3.1 Invoking actions 258 10.3.2 Parameters 259 10.3.3 Attaching files 260 10.3.4 Templates 261 Testing 261 Generation 261 Conclusion 262
Chapter 11 11.1 11.2 11.3 11.4
Mailers 253
254
Parts 263
Parts controllers 263 Invoking actions 266 Generation 267 Conclusion 267
Chapter 12
Caching 269
12.1
Configuration 269 12.1.1 Fundamental stores 269 12.1.2 Strategy stores 270 12.2 Caching basics 271 12.2.1 Writing 271 12.2.2 Reading 272 12.2.3 Fetching 273 12.2.4 Deleting 273 12.3 Caching helpers 274
From the Library of Shirong Chen
Contents
xvii
12.3.1 Action caching 274 12.3.2 Eager caching 275 12.3.3 Fragment caching 277 12.3.4 Partial caching 277 12.4 Conclusion 278
Chapter 13 13.1 13.2 13.3 13.4 13.5 13.6 13.7 13.8 13.9
Testing 279
Rake tasks 279 Spec files 282 Model specs 283 Request specs 285 Request helper 289 Request matchers 290 RSpec extensions 292 Miscellaneous extensions Conclusion 295
Afterword Index
294
297
299
From the Library of Shirong Chen
This page intentionally left blank
From the Library of Shirong Chen
Foreword
I viewed the Merb project with a big dollop of suspicion almost from the very start. First of all, it started out as a really simple concept, but got a lot more complicated as additional contributors got involved. And whether my suspicion was misplaced or not, I couldn’t shake the feeling that the Merb people were insisting on reinventing Ruby on Rails simply to be difficult. Merb isn’t that different from Rails, nor is it a major improvement as far as I was concerned. It was only slightly better (in some regards), and only slightly different from Ruby on Rails. Sure there were some benchmarks showing big performance gains for using Merb, but I was not convinced enough to switch. And I couldn’t bear to use Merb and Rails concurrently. You see, they were very similar, yet different in subtle ways. The last thing I want to do when I’m programming on hard deadlines is to slow down to remember the differences between Merb’s render method and the one in Rails, etc. Life continued, and while I was very happy and making a ton of money working with and writing about Rails, I still occasionally looked at Merb, if only to see what they were up to. Sure enough, by 2008 my perception of Merb had started to change. I met Foy Savas and he gave me the gist of why it would make sense to write The Merb Way. I concurred and Foy got busy writing. As Merb approached a 1.0 release, it appeared that Yehuda Katz and his band of merry discontents were actually achieving a viable platform, one that would attain permanent status as an alternative to Rails. I started hearing about significant production projects running on Merb, such as yellowpages.com. My appreciation for Merb grew, as I got familiar with the project goals and noticed how the competition was acting as a healthy stimulus for Rails to not rest on its laurels. I wasn’t ready to switch to Merb myself, since my key criterion is maturity, which Rails has in spades, but no longer was I suspicious. xix From the Library of Shirong Chen
xx
Foreword
In November of 2008 I was extremely happy about Foy being almost finished writing the first manuscript of this book, knowing that it would be a good complement to the Professional Ruby Series and a good companion for The Rails Way. Foy delivered an awesome conference presentation in Boston where he covered some of the key concepts from the book in an engaging and entertaining manner—the crowd ate it up and I knew The Merb Way would be a winner. It wasn’t too many weeks later that the unthinkable happened. After an eruption of feuding between members of the Rails and Merb teams, some secret meetings occurred and shocking news was unveiled right before Christmas: DHH and Yehuda happily proclaimed that the core teams of both frameworks were merging and that within a year, the Merb codebase would be merged with Rails in order to produce Rails Version 3, after which development of Merb would discontinue. The million-dollar question (okay, not quite that much) for Foy and myself as the Series Editor was whether to continue with the publication of The Merb Way. After all, if the Merb framework was going away, then what was the point? After letting the matter settle for a while, we arrived at our answer, which you already know since you are holding this book. It turns out that learning about Merb is valuable in a number of important ways. First, Merb is still used rather widely and we suspect that its lifetime will exceed what the current core team has in mind. Second, Foy is a gifted writer and his descriptions of the philosophies that impacted the design and implementation of Merb are definitely useful to everyone who will be affected by the changes that will happen in Rails 3. It’s with great pleasure that I welcome the incomparable Foy Savas and publish The Merb Way as a full-fledged and proud installment of the Professional Ruby Series. I sincerely hope you get as much out of this book as I have. Obie Fernandez March 31, 2009
From the Library of Shirong Chen
Acknowledgments
First off, thanks go to all the Merb and DataMapper contributors. Obviously, no Merb would mean no Merb Way, but that’s certainly not all I want to thank them for. The elegance of both Merb and DataMapper, especially within their implementations, profoundly sets off my passion for holding code to an even higher standard of clarity. Consequently, throughout this book I have highlighted and included extensive amounts of Merb and DataMapper source code, truly making the contributors to both projects contributors to this book as well. I would like to thank those individuals with whom I have personally interacted the most: Matt Aimonetti, Ben Burkert, Dirkjan Bussink, Lori Holden, Michael D. Ivey, Michael Klishin, Dan Kubb, Carl Lerche, Daniel Neighman, Sam Smoot, Ezra Zygmuntowics, and especially Yehuda Katz. Completing this book against a backdrop of changing technology demanded the help of numerous friends and editors. Without their advice, analysis, and sharp eyes, I don’t know how it would ever have gotten done. To Sophia Chou, Andrew Guay, Kieran Huggins, Matt Knox, Jennifer Lindner, Bryan Ray, and Michael Thurston—thank you for each and every note and correction you made. A huge thank you goes to both Obie Fernandez and Debra Williams Cauley. You two are truly the driving force behind the Addison-Wesley Professional Ruby Series and the reason why it produces such high-quality books. Obie, had you not spotted the importance of Merb at such an early stage, inspired me with your own book on Rails, and pushed me to write one on Merb even after the frameworks merged, who knows if such a topic would have received the treatment it deserved? And Debra, simply wow. I know coordinating this book was no easy job, but you did so with such a seemingly paradoxical mix of patience and drive that I will remain thankful forever.
xxi From the Library of Shirong Chen
xxii
Acknowledgments
Finally, my warmest thanks and love go to all the members of my family who undeniably play a strong part in all that I do. Without your support, encouragement, perspective, and insight, I don’t know where, or more important who, I would be—thank you, all.
From the Library of Shirong Chen
Merb pastie
Anything that starts off with pure code is good in my book. Pun intended, here is Ezra Zygmuntowicz’s original Merb pastie, as found at http://pastie.org/14416, here formatted for the pages of this book. #!/usr/local/bin/ruby require 'rubygems' require 'mongrel' # Add the request handler directory to the load path. # Files in the 'app/controllers' dir will be mapped # against the first segment of a URL $LOAD_PATH.unshift( File.join( File.dirname( _ _FILE_ _ ) , 'app/controllers' ) ) PORT = 4000 # If true, controller source files are 'load'ed # rather than 'require'd so you can tweak code and # reload a page. ALLOW_RELOADING = true class String def import ALLOW_RELOADING ? load( self + '.rb' ) : require( end
self )
def controller_class_name self.capitalize
xxiii From the Library of Shirong Chen
xxiv
Merb pastie
end end class MerberHandler < Mongrel::HttpHandler def instantiate_controller(controller_name) controller_name.import begin return Object.const_get( controller_name.controller_class_name ).new rescue Exception # If life is sad, then print the error and # re-raise the exception: warn "Error getting instance of "+ "'#{controller_name.controller_class_name}':"+ "#{$!}" raise $! end end # Grab the request URL and break it up to get the # parts that map to the code request. There's a # simple assumption that the first part defines a # class holding the desired code. def handle(request) path = request.params["PATH_INFO"] puts request.inspect puts '='*50 # Might want to consider returning a # default object if we have a bare URL. return [nil, nil, nil ] if path =˜ /ˆ\/$/ c, m, args = path.to_s.gsub( /ˆ\//, '' ).split( '/' , 3) args = args.to_s. strip.empty? ? nil : args.split( '/' ) # Return an array with our object instance, # the method name, and any args. [ instantiate_controller(c), m, args ] end def process(request, response) response.start(200) do |head,out| head["Content-Type"] = "text/html" begin # Looks at the URL and breaks it up into # chunks that map to a class, a method call, # and arguments.
From the Library of Shirong Chen
Merb pastie
xxv
# Basically, # /foo/bar/baz/baz # ends up becoming # Foo.new.bar( baz, baz ) controller, method, args = handle(request) if controller # No allowance for default methods. # Worth considering, maybe default # to 'index' or 'to_s' out << ( args ? controller.send( method, *args ) : controller.send( method ) ) else out << "Error: no merb "+ "controller found for this url."+ "" end rescue Exception out << "Error! #{$!}" end end end end h = Mongrel::HttpServer.new("0.0.0.0", PORT) h.register("/", MerberHandler.new) h.register("/", Mongrel::DirHandler.new("assets")) h.run.join - -merb.rb- - require 'erb' class Merb # Define a class variable to track the default # location of the template files. @@template_dir = File.expand_path( ( File.dirname( _ _FILE_ _ ) + "/../views/merb" ) )
def hello(*names) # Assign the parameter to an instance variable @name = names.join(', ') template = ERB.new( IO.read( @@template_dir + '/hello.rhtml' ) ) template.result( binding )
From the Library of Shirong Chen
xxvi
Merb pastie
end end --hello.rhtml-
Hello, <%= @name %> Hello, <%= @name %>!
From the Library of Shirong Chen
Introduction
ナ ൴ ナ 㒋 ፆ ナ ඇ ൴ ඇ 㒋 ፆ ඇ — ❨ᆎ A way that can be taken rarely stays the way. A name that can be given rarely stays the name. —Laozi (translated by your author)
The first two lines of the Dao De Jing capture truths so fundamental that we find them everywhere—including, without exception, in this book. Originally entitled the The Merb Companion, it was intended to be the advanced practitioner’s bible. But the little framework named Merb grew quickly, so much so that the Rails world took notice, and before long, the core teams came together and decided to merge the two. Yet despite Merb’s fundamentals being seen as the basis for the features of the future, Rails conventions needed to be maintained long-term. Incidentally, this book’s content was left in an awkward limbo. Should we wait for the completion of the merge or perhaps publish immediately? Should we preserve the chapters we had written so far or produce them anew after the merge? Soon, however, an epiphany came. Though the book had material highly relevant to the Merb developers of the day and was equally suitable for bringing foresight to the Rails developers of tomorrow, its greatest and lasting potential was in shedding light on how Merb, the framework that even Rails envied, had been designed. Thus, we arrive here, at the introduction of The Merb Way.
xxvii From the Library of Shirong Chen
xxviii
Introduction
Born a pastie Zoom back to September 9, 2006. If you were using Rails, it was version 1.1. As for Git, it was still just something the Linux kernel used. So naturally, when Ezra Zygmuntowicz needed to publicly distribute the then only 120-line source code for Merb, a pastie made the most sense. Turn back a few pages and you’ll find that pastie, prepended to this book in all of its glory. Look it over once and you’ll realize Ezra had an itch to scratch: fast Ruby template rendering with the smallest possible memory footprint. To do this he used only two gems, Mongrel and ERB, which, for those keen on etymology, also serve as the roots of the name Merb itself. But like any project that starts off both small and practical, it grew. A few months later a gem was put up on RubyForge. For a while it came with an example app that touted Merb’s ability to handle file uploads while not locking out other requests. This became the most popular use of Merb, often appearing coupled with Rails applications in the wild.
Created by rebels But Merb has moved far beyond the stage when its application generator could produce the uploader application. This may have been because it quickly became a breeding ground in which Rails concepts could be rethought. Eventually, this led to a shared vision among developers that Merb would be Rails done right. Thankfully, the openly opinionated nature of Rails actually kept them away from simply adopting alternative opinions. Instead, they chose as one of their maxims agnosticism, in the form of opt-in modularity. This allowed Merb application developers to work with whatever tools they needed for their projects. Merb’s versatility meant that it would become the underlying framework for numerous customized stacks that otherwise did not easily fit within the opinions of Rails. Its developers, consequently, have often been thought of as rebels from the Rails mentality, since many of them maintain strong opposing views on particular Rails opinions. However, that’s not all there is to the Merb development story. The crowd of developers also had an arguably more scientific bent, aiming to make the best selection of the methods they employed through microbenchmarks. Ruby is often cited as one of the world’s slowest programming languages from a computational perspective, but to the developers of Merb, this didn’t mean that microseconds didn’t matter. After all, web development frameworks are often tested in terms of responses per second. So when it came to Merb internals, whether it was in route matching or filter chaining, only the fastest Ruby constructs were used. The end result was Merb blazing past Rails
From the Library of Shirong Chen
Introduction
xxix
in response time benchmarks. More profoundly, Merb raised the bar for Ruby web framework performance in general, finally allowing Ruby to win when pitted against other frameworks from different languages. Interestingly enough, this combination of performance and modularity is also indicative of another Merb development character trait: its desire to solve higher-level problems. Whereas Rails features have nearly consistently been born of necessity, Merb’s focus has been on abstract goals like versatility and design. This has pushed the limits of what can be done with Merb, resulting in advancements like the abstract controller class, code slices, and arbitrary application layout. In sum, Merb in many ways conceptually broke the “frame” in framework by thinking of itself less as a way to produce Model-ViewController (MVC) web apps and more as a highly modular platform upon which nearly all Ruby web development, no matter how unusual, can occur.
The future of Rails Put all this goodness together, and you’re bound to attract the attention of most developers. Not surprisingly, teams large and small began using Merb as a full application framework in production environments for their most demanding applications. This finally put Merb to the test, and the results were completely in line with its benchmarks: more responses per second with a solidly smaller memory footprint. All of this occurred at the cusp of its reaching its 1.0 release on November 7, 2008. Quickly the Merb development team oriented itself toward larger goals through what would have been a 2.0 release. Objectives included, among other things, an abstracted Object Relational Mapper (ORM) that could be used to create an agnostic admin panel and convenient direct routing to templates. But with the stable release already out, developers at large began taking Merb much more seriously, and soon online comment wars fanned flames between the Rails and Merb core teams. Thus prompted to confront each other, the core development teams began to talk it over. However, soon they began earnestly asking themselves whether the large amount of duplication between the two frameworks was warranted. At the end they concluded two things: first, that each side had aspects that would be beneficial to the other, and second, that yes, they could work together. Announced officially on December 23, 2008, the Rails and Merb development effort would be merged, with a promise of a smooth transition from both Merb 1 and Rails 2 to the combined Rails 3. So what can we expect from this merger? Well, above all will be the fusion of attitudes and objectives from both sides. The Rails of the future will undoubtedly appeal to developers in both pragmatic and idealistic ways. Merb will bring speed, performance,
From the Library of Shirong Chen
xxx
Introduction
and modularity along with a tempering of the preestablished Rails opinions. We will see a flowering of custom Rails stacks, some of which may eventually push the limits of the Rails framework itself. Toward what—who knows? But with the possibility of such a greatly expanded domain, two things are certain. The first is that anyone in software who needs to build an agile and sophisticated web application upon a highperformance framework should definitely get on this train. The second, which applies more to developers themselves, is that a firm understanding of the design decisions behind Merb’s development will not only prepare them for the future of Rails but can also open up their own talents through the elegance of its code. This book is thus focused on giving developers the deepest possible understanding of Merb itself. While you may use it as a reference guide in the development of Merb applications (which we strongly recommend without hesitance until the release of Rails 3), you may also appreciate its guided exploration of the Merb source. That said, for those afraid of code, it may perhaps be a good time to put this book back on the shelf. Everyone else, be warned; we’re going to cut deep into the framework itself, revealing not only what was done, but what can be learned from it. As you move forward, do not lose focus. After all, they say that one should always aim to learn from the best, and through Merb, the framework that without exaggeration brought the Rails monoculture to its knees, you are doing just that.
From the Library of Shirong Chen
About the author
Foy Savas runs Assembly, a Boston-based web consultancy focused on innovative technology. In the past, he has acted as CTO of a large health and fitness site and as a lead consultant for several white-labeled web applications. Foy is also one of the earliest adopters of Merb and over the past year has taken several Merb applications, both small and large, into production. Actively involved in the Merb open-source ecosystem, he has also been a core contributor to both Merb and DataMapper.
xxxi From the Library of Shirong Chen
This page intentionally left blank
From the Library of Shirong Chen
C HAPTER 1
Fundamentals
The true power of Merb arises from the framework’s emphasis on hackability. Though misinformed readers may misunderstand this term, thinking Merb means that their site is “pwnd” upon launch, that’s not the hack we’re talking about. Instead, when we say hack, we mean the act of extending the functionality of something beyond its original scope. Merb does this well, and in this chapter we’ll discover how Merb enables this kind of unbounded development by examining the hackability within Merb’s own fundamentals. To do this, we’ll explore the Merb stack, discuss configuration options, and pull apart internals. If you’re a newcomer to Merb, you’ll pick up the practical know-how to get started using the framework. Experienced developers shouldn’t overlook what’s here, though, because while we’ve designed this chapter as a walk-through of the basics, it’s in no way an installation guide. The digging we do into the internals may reveal aspects of Merb that you have not yet found through routine development. In any case and no matter your background, by the end of this chapter you should be able to concretely identify the design decisions that have made Merb so agile and robust.
1.1 Generating a new application The easiest way to set up a new Merb application is to use merb-gen. This command is standardly available as part of the Merb bundle. As a developer, you’ll be using it regularly to quickly generate common files used by Merb. One of its uses is the creation of a skeleton for the overall layout of new applications. Below we make our first app, by specifying the option app followed by the name of the application directory. $ merb-gen app first_app
This creates a new directory named first_app as well as a number of subdirectories and files. Merb uses all of these subdirectories and files to define the application’s desired 1 From the Library of Shirong Chen
2
Chapter 1: Fundamentals
behavior. To run our first app, we first need to change the directory into its root and fire up Merb using the command merb: $ cd first_app $ merb Loading init file from /home/foysavas/src/first_app/config/init.rb Loading /home/foysavas/src/first_app/config/environments/ development.rb ˜ Connecting to database... ˜ Loaded slice 'MerbAuthSlicePassword' ... ˜ Parent pid: 5917 ˜ Compiling routes... ˜ Activating slice 'MerbAuthSlicePassword' ... merb : worker (port 4000) ˜ Starting Mongrel at port 4000 merb : worker (port 4000) ˜ Successfully bound to port 4000
Our Merb app is now running and bound to port 4000. Consequently, if we visit http://localhost:4000, we’ll see a welcome page. We can shut down the Merb
server by using Ctrl-C. Note that this may not happen instantaneously as Merb looks to gracefully shut down each of the worker processes. Merb’s master process Merb uses a master process to manage clusters of server processes. This provides a number of benefits over its lone process alternative, including
• Graceful boot-up and port binding that enable seamless deployment • Huge memory saving for clusters when using Ruby Enterprise Edition • Blazing-fast worker process reload for live development
1.2 The layout of a Merb application Now that we’ve generated our first application, it’s a good time to bring up the fact that the directory structure and file arrangement we just saw are not set in stone. Merb out-of-the-box is far more versatile than that. Though the layout we just generated is the standard Merb layout, merb-gen itself can produce a few alternative common layouts. These are best used for simpler applications and are known as the flat and very flat layouts. Table 1.1 describes the various layouts available to you as a Merb application developer. In order to gain an understanding of what code is absolutely necessary to make
From the Library of Shirong Chen
1.2
The layout of a Merb application
3
Table 1.1 Merb layouts Layout
merb-gen Argument
Best Use
Standard
app
The standard layout provides the most comfort for most developers through a highly conventional and intelligent structure. We recommend it for nearly every application you develop.
Core
core
Like the standard layout but without the numerous dependencies the Merb stack makes. This is the best starting point for the Merb developer who does not intend to use the full default Merb stack.
Flat
flat
The flat layout is best used when you’re going to design an application that crafts simple responses but that still uses view templates. It’s also fairly easy to grow a flat layout into a standard one.
Very flat
very_flat
Very flat Merb applications are the way to go when keeping everything in one file provides some special advantage. We’ll use these numerous times in demonstration applications, and you may find them useful for micro-applications as well as proof-ofconcept work.
Custom
N/A
Custom layouts are possible when you define the location of the numerous types of files Merb uses. We definitely don’t advise taking this route, but if your circumstances are unique, Merb has the flexibility to handle your needs.
a Merb application tick, let’s take a detailed look at what’s inside both of these flatter layouts before we move on to studying the standard full layout.
1.2.1 Very flat layout Using merb-gen, you can easily create a single-file Merb application in the style of Camping or Sinatra. To do this, use the argument very_flat with merb-gen: $ merb-gen very_flat my_very_flat_app Generating with very_flat generator: [ADDED] my_very_flat_app.rb
From the Library of Shirong Chen
4
Chapter 1: Fundamentals [ADDED] [ADDED] [ADDED] [ADDED]
Rakefile spec .gitignore merb.thor
This produces a directory my_very_flat_app containing a single Ruby file along with some other useful files with content similar to the following snippet: # run very flat apps with merb -I
. Merb::Config.use { |c| c[:framework] = { :public => [Merb.root / "public", nil] } c[:session_store] = 'none' c[:exception_details] = true c[:log_level] = :debug # or error, warn, info or fatal c[:log_stream] = STDOUT # or use file for logging: # c[:log_file] = Merb.root / "log" / "merb.log" c[:reload_classes] = true c[:reload_templates] = true } Merb::Router.prepare do match('/').to( :controller => 'my_very_flat_app', :action =>'index' ) end class MyVeryFlatApp < Merb::Controller def index "hi" end end
Stepping into its directory is not enough to run a very flat application. The comment on the first line, however, points us in the right direction. Using the -I flag, we can specify the init file for our Merb application: merb -I my_very_flat_app.rb
Now if you visit http://localhost:4000, you should see the message returned by the value of the index action.
From the Library of Shirong Chen
1.2
The layout of a Merb application
5
Design decision: the Principle of Least Surprise This simple example exhibits one of Merb’s founding principles, that of ‘‘least surprise.’’ Here, in order to give us the least surprise, any request handled by this method receives a response containing the returned string. This may seem like an obvious design decision, but other frameworks do not behave this way; among other things they presuppose that the purpose of your controller is nearly always to render a template. Consequently, they attempt to render that template without any such explicit method call. The Merb developers chose against so brashly deciding what your controllers would be used for, knowing that such a decision would come at the cost of hackability. They also realized that a great litmus test to make sure that the line between clear, hackable behavior and scope-narrowing magic remains uncrossed was the veritable Principle of Least Surprise, also known as the Principle of Least Astonishment. Anyway, don’t be surprised (pun intended) if you see signs of this principle at work in other aspects of Merb.
Getting back to the code, there are three sections: one for configuration, another for routing, and finally a controller. Each of these sections is fundamental in Merb application development, and that’s why they’re there. Strictly speaking, though, the configuration section wasn’t necessary in order to send “hi” to your browser. If it wasn’t there, Merb would use its default values instead. Design decision: Convention over Configuration This, too, is another example of a principle to which Merb adheres, Convention over Configuration. Ruby on Rails notably popularized this paradigm, and Merb continues the tradition. We’ll find, however, that Merb has tempered the concept of Convention over Configuration through the primacy of hackability. To be more concrete, if a default configuration lessens the amount of code needed without reducing the hackability of your application, then Merb has provided it for you. This demonstrates how Merb has striven to keep opinions at bay and give you the maximum power to get the job done, no matter what the job.
Let’s take a look at the specifics of the config block and see which of the defaults have been overridden. The first of these is the location of public static assets. If you create a directory public/ in the same directory as the application and then add an image, say merb.jpg, you’ll be able to view it at http://localhost:4000/merb.jpg. The second line relating to sessions doesn’t actually override any default but has been included for you to change at will. The third line in the config block switches exception
From the Library of Shirong Chen
6
Chapter 1: Fundamentals
details to true. This allows you to see full exception details on error pages when they occur. For an example, take a look at http://localhost:4000/mybad. With exception details set to true, you will be able to view a stacktrace right in the browser. Set the config value instead to false, and after restarting Merb, you’ll no longer have access to the stacktrace via the browser.
1.2.2 Flat layout If you want to use view templates, but still prefer a single file for the bulk of your code, a flat layout may be just what you’re looking for. The flat layout is optimal for simple Merb applications that do not use models but still have templates. Using merb-gen once again, you can create a flat app using the --flat option: $ merb-gen flat my_flat_app Generating with flat generator: [ADDED] Rakefile [ADDED] application.rb [ADDED] config/framework.rb [ADDED] config/init.rb [ADDED] views/foo.html.erb [ADDED] spec [ADDED] gems [ADDED] merb.thor [ADDED] .gitignore [ADDED] README.txt
This creates a directory structure as follows: . |-|-|-|-| | |-|-|-| ‘-|
README.txt Rakefile application.rb config |-- framework.rb ‘-- init.rb gems merb.thor spec ‘-- spec_helper.rb views ‘-- foo.html.erb
In order to run a flat layout, all we have to do is step into its directory and run merb. You’ll notice that we no longer need to specify a file to include. This is because by default config/init.rb is automatically included.
From the Library of Shirong Chen
1.2
The layout of a Merb application
7
Incidentally, one of the largest benefits of jumping from the very flat to the flat layout is that the presence of the init file allows us to avoid restarting Merb every time we make changes to application code. Instead, in development mode, Merb automatically checks to see if application files have been updated and reloads the appropriate classes for us. You can try this by running Merb and altering the application.rb index method to say "Something Different" instead of "Hello...". If you check via your browser, the response will have changed without the need to restart Merb. Other environments Note that by convention, class reloading applies only to Merb applications running in development mode. This is the default mode for a Merb application, and it’s what we’ve used in every example so far. There are two other standard environments for running Merb applications---production and testing---and we’ll take a look at those later on.
The principal difference here from the Merb standard layout is that all controller code (and possibly model code) is found in application.rb. Another difference is the inclusion of config/framework.rb. This file is actually what prevents Merb from thinking the application is of the default form by defining the flat layout. You’ll notice that config/framework.rb does this by just defining a hash: Merb::Config[:framework] = { :application => Merb.root / "application.rb", :config => [Merb.root / "config", nil], :public => [Merb.root / "public", nil], :view => Merb.root / "views" }
If you change these values, Merb will on boot expect to find particular code in different places. Let’s say you start with a flat application, but soon decide it needs to grow to the standard layout. You won’t have much trouble accomplishing the transition. Here is a series of terminal commands to help you on your way: $ $ $ $ $
mkdir app mv views app/ mkdir app/controllers mv application.rb app/controllers/ rm config/framework.rb
From the Library of Shirong Chen
8
Chapter 1: Fundamentals
As exhibited above, it’s the removal of the config/framework.rb file that defaults us to the standard layout. Finally, though this will run as an application in standard layout, there may be a few more items of cleanup you’d like to attend to. Here’s a list to keep in mind: • Pull out the router in config/init.rb and put it in config/router.rb. • Separate app/controllers/application.rb into as many controllers as needed. • Alter your controllers to subclass from Application instead of Merb:: Controller.
1.2.3 Standard layout Now let’s take a look at the standard layout. Returning to the directory of the first application that we created with merb-gen, we find the following directory tree: . |-|-| | | | |-|-| | | | | | |-|-|-| | |-| | | | | | |-‘--
Rakefile app |-- controllers |-- helpers |-- models ‘-- views autotest config |-- database.yml |-- dependencies.rb |-- environments |-- init.rb |-- rack.rb ‘-- router.rb doc gems merb |-- merb-auth ‘-- session public |-- favicon.ico |-- images |-- javascripts |-- merb.fcgi |-- robots.txt ‘-- stylesheets spec tasks
From the Library of Shirong Chen
1.3
Interactive Merb
9
We haven’t included all the files the standard layout creates (there are a lot of them), but just enough to understand the structure. The directories spec/ and autotest/ serve to facilitate the testing of your application. The Rakefile and tasks directories provide common tasks for use in your application. For now, though, what matters most are these three directories: app/, config/, and public/. The first of these stores all of your application code. To keep everything organized in an MVC way, app/ also has a few subdirectories of its own. By default these include controllers/, helpers/, models/, and views/. Next up, config/ is a directory we saw in the flat layout. It holds init.rb and a number of other environment configuration files. Unlike the flat layout, however, it has a dependencies.rb file which is referenced in init.rb and by default includes the dependencies making up the Merb stack. Another file in the config directory is router.rb. This defines the routes for your application, mapping requests to specific controller actions. Another file definitely worth noticing is database.yml, which defines database access on a per-environment level. Finally, the directory public/, which we also saw in the flatter layouts, is meant to hold assets for your application to serve. These may include images, stylesheets, javascripts, and other static files.
1.3 Interactive Merb Often you’ll want to be able to dig around in a running Merb application. Interactive Merb allows you to do this by giving you an IRB session on a running Merb application. There are two ways to get this kind of console access. The first and simpler way is to add the -i flag to the command merb: $ merb -i Loading init file from /home/foysavas/src/first_app/config/init.rb Loading /home/foysavas/src/first_app/config/environments/ development.rb ˜ Connecting to database... ˜ Loaded slice 'MerbAuthSlicePassword' ... ˜ Parent pid: 25514 ˜ Compiling routes... ˜ Activating slice 'MerbAuthSlicePassword' ... irb: warn: can't alias context from irb_context. irb(main):001:0>
This creates an exclusive interactive instance of your Merb application not bound to any port. If, however, you wanted a Merb application that could respond to web requests as well, then using the -C flag provides a console trap on an otherwise typical
From the Library of Shirong Chen
10
Chapter 1: Fundamentals
Merb instance. To get to this console you have to hit Ctrl-C once Merb is booted. We do this below and then immediately return to server-only mode with the command exit. foysavas@navi:˜/src/first_app$ merb -C Loading init file from /home/foysavas/src/first_app/config/init.rb Loading /home/foysavas/src/first_app/config/environments/ development.rb ˜ Connecting to database... ˜ Loaded slice 'MerbAuthSlicePassword' ... ˜ Parent pid: 26366 ˜ Compiling routes... ˜ Activating slice 'MerbAuthSlicePassword' ... merb : worker (port 4000) ˜ Starting Mongrel at port 4000 merb : worker (port 4000) ˜ Successfully bound to port 4000 ˆCmerb : worker (port 4000) ˜ Interrupt a second time to quit. irb(main):001:0> exit merb : worker (port 4000) ˜ Exiting from IRB mode back into server mode.
Using interactive Merb during development can often speed up the process since you’ll be able to directly interact with the code you’re writing. Some words of caution, though. First, don’t let this lead to writing complex methods you can’t comprehend, just because they produce the right results. Second, direct interaction can’t take the place of proper behavior testing, so don’t treat it that way. Making either of these mistakes will only put you behind later on, especially when you need to rework or extend what you have. Another practical situation when interactive Merb is helpful is after you’ve deployed a production application and need to determine why something has seemingly gone wrong and correct it. Whether the bug is the result of a subtle misstep in deployment or an incidental result of rare production data, you’ll be glad you weren’t without console interaction.
1.3.1 Console methods Interactive Merb also comes with a number of console methods, all of which are accessible through the object merb. These let you do things such as conveniently faking requests within interactive Merb. Below we use a helper method to retrieve a response body from a URL using the HTTP GET method on a fresh very flat application. > merb.get('/').body ˜ Params: {"action"=>"index", "controller"=>"my_very_flat_app"} ˜ {:after_filters_time=>1.6e-05, :before_filters_time=>2.0e-05, :action_time=>0.000229} => "hi"
Table 1.2 lists the methods available on the merb object along with their use.
From the Library of Shirong Chen
1.3
Interactive Merb
11
Table 1.2 merb console methods Method
Use
check_request_for_route
Returns the parameters of what route a request matches
close_sandbox!
Ends a sandboxed session
delete
Uses HTTP DELETE on a URL with optional parameters
dispatch_request
Dispatches a request mimicking its handling and the return of a controller
dispatch_to
Dispatches to a specific controller action, with optional parameters
fake_request
Returns a request composed of defaults and merged-in parameters
get
Uses HTTP GET to retrieve a URL
open_sandbox!
Starts a sandboxed session
post
Uses HTTP POST on a URL with optional parameters
put
Uses HTTP PUT on a URL with optional parameters
reload!
Reloads your Merb application code
request
Builds a request from a URL and the request environment
show_routes
Lists all compiled routes in a tabular fashion
trace_log!
Explicitly sets logger output to be flushed
url
Generates a URL given parameters
1.3.2 Sandboxing Interactive Merb is capable of sandboxing your activity, allowing you to mess around with database resources only to have all changes reverted when you exit the sandbox. This is extremely useful when you have fixture data (and for those who live dangerously, even production data) that you need to work with. Because we haven’t seen
From the Library of Shirong Chen
12
Chapter 1: Fundamentals
anything that involves databases yet, we’ll leave the following example for you to revisit at will: > User.count ˜ SELECT COUNT(*) FROM ‘users‘ => 1 > merb.open_sandbox! Loading development environment in sandbox (Merb 1.0.0) Any modifications you make will be rolled back on exit => [Merb::Orms::DataMapper] > User.all.destroy! ˜ DELETE FROM ‘users‘ => true > User.count ˜ SELECT COUNT(*) FROM ‘users‘ => 0 > merb.close_sandbox! Modifications have been rolled back => nil > User.count ˜ SELECT COUNT(*) FROM ‘users‘ => 1
1.4 Merb configuration The config/ directory found in the root of a Merb application holds all the configuration files for your application. Some of these files, like init.rb, are always there. The others depend upon the demands of your application. Let’s take a look at the most typical configuration files that Merb files use and what’s inside each of them.
1.4.1 Init script The init script is the first thing that Merb runs. As we saw when moving up from the very flat to the flat layout, it provided us with a jump in agility during development through class reloads. The init script itself, though, serves many other purposes, including basic configuration, inclusion of dependencies, and the selection of a testing environment.
1.4.1.1 Gems and load path In the Merb world, gems are the preferred form of distributing plugins and libraries. Merb developers did not want to create yet another vendor lock-in-inducing plugin system. Instead, RubyGems was chosen since it’s already the standard for distributed packets of Ruby functionality and could work well with the Merb plugin infrastructure.
From the Library of Shirong Chen
1.4
Merb configuration
13
Doing things the Ruby way Merb developers strive to get things done the Ruby way, that is, in the way most idiomatically compatible with the Ruby language and community. The use of RubyGems to distribute plugins is just one example of this. Others, like the refusal to use Symbol#to_proc or Module#alias_method_chain, not only keep Merb understandable but give it performance advantages over Rails and other frameworks.
Additionally, the convenience of the gem-based system allows us to install specific gems within an application, in the directory gems/ in the application’s root directory. When the gems are loaded as dependencies, they are given preference over system-wide gems. The benefit of being able to install version-specific gems in your application is huge, allowing you to • Share your application code with other developers without worrying about having
different versions • Deploy your application without any need to install all the gem dependencies on
the production server If at some point you want to freeze a specific gem inside your Merb application, you can do so using the install directory -i flag with gem. Here we do this from inside a directory with a gem by issuing the following command: $ gem -i ˜/src/your_app/gems dm-geokit-1.0.0.gem
Note that gem also installs the gem’s listed dependencies into your application for you. You may want to pass in the flag --no-rdoc to save on the size of your application’s gem/ directory.
1.4.1.2 Dependencies Including gems as dependencies instead of directly requiring has been done primarily so that we can load application-installed gems if they exist. The dependency method also defers loading until after logging has been initiated. With this in mind, you should be able to use the method dependency just as if you were using require. If the string you pass in doesn’t refer to a gem, dependency will attempt to load it as a file via require. dependency 'dm-geokit' dependency 'geokit-core', '>= 0.9.0' dependency 'lib/support/string'
From the Library of Shirong Chen
14
Chapter 1: Fundamentals
You can also use the method dependencies to require a few dependencies at once instead of one per line. By passing in hashes, you specify gem version. dependencies "RedCloth", "merb_helpers" dependencies "RedCloth" => "3.0"
1.4.1.3 after_app_loads Inside init.rb, you’ll find a method taking a block called Merb::BootLoader.after_ app_loads. The boot loader evaluates this passed-in block after most of the application is loaded. This is useful if you need certain classes loaded before including a dependency or defining a constant within a module: dependency 'merb_admin' GeoKit::default_units = :miles
1.4.1.4 Template engine You have your choice of template engines when using Merb. In fact, you can use multiple template engines by embedding templates inside one another. Still, when it comes to the template merb-gen, the templates are generated for ERB. If you prefer that they be in Haml when possible, you can change the following lines in init.rb so that the method use_template_engine has the :haml parameter: use_template_engine :erb # use_template_engine :haml
1.4.1.5 Basic configuration Merb stores configuration settings inside the class Merb::Config. You can do this in two different ways, first by explicitly using the class methods Merb::Config.[] and Merb::Config.[]=: Merb::Config[:session_store] = 'memory' if Merb::Config[:verbose] Merb.logger.info "Above all be terse." end
or instead by passing a block into the method Merb::Config.use: Merb::Config.use do |c| c[:session_store] = 'cookie' end
1.4.1.6 ORM options Inside the init script we see the lines for the three most popular ORMs, or Object Relations Mapping libraries, that work with Merb. Enabling any one of them is as easy as uncommenting a line. Below we’ve enabled the DataMapper ORM.
From the Library of Shirong Chen
1.4
Merb configuration
15
# Uncomment for DataMapper ORM use_orm :datamapper # Uncomment for ActiveRecord ORM # use_orm :activerecord # Uncomment for Sequel ORM # use_orm :sequel
For more information on ORMs, please see Chapter 5 on models, which uses DataMapper and briefly covers the use of both ActiveRecord and Sequel.
1.4.1.7 Testing options Another thing the init.rb file handles is your choice of testing suite. By default, Merb uses RSpec, and we’ll go over how to use it later on. If, however, you want to use Test::Unit instead, that’s as easy as commenting out one line and uncommenting the other: # use_test :test_unit use_test :rspec
Note that to complete this switch you may also have to install the merb_test_unit gem if it hasn’t been installed already.
1.4.1.8 Inflector At the bottom of the Merb init script you’ll find details about how to customize the inflector. The inflector is used to take English words (really just nouns) and switch between their singular and plural forms. This may seem odd to include in an MVC framework, but its existence allows us a lot of ease in abstracting URLs and working with RESTful resources (REST stands for Representational State Transfer). That said, you may not end up using the inflector, but in case you do, it’s always there. You can test it in interactive Merb, simply by tacking the methods singular or plural onto strings of nouns: > "users".singular => "user" > "forum".plural => "forums"
If, however, you run into a word that is not being pluralized as you need it to be (let’s say, for example, you wanted the plural of forum to be fora), you would be able to use the init.rb file to assure this behavior by adding a line like this: Extlib::Inflection.word "forum", "fora"
From the Library of Shirong Chen
16
Chapter 1: Fundamentals
1.4.2 Environments Merb can standardly run in three different environments: development, testing, and production. Up until now we’ve run Merb only under development mode. You can run Merb in these other modes by specifying the environment after the -e flag with the merb command: $ merb -e production
Each of these modes runs slightly differently. These differences are defined in the files inside config/environments. For instance, inside config/environment/ development.rb, we find Merb.logger.info("Loaded DEVELOPMENT Environment...") Merb::Config.use { |c| c[:exception_details] = true c[:reload_templates] = true c[:reload_classes] = true c[:reload_time] = 0.5 c[:ignore_tampered_cookies] = true c[:log_auto_flush ] = true c[:log_level] = :debug c[:log_stream] = STDOUT c[:log_file] = nil # Or redirect logging into a file: # c[:log_file] = Merb.root / "log" / "development.log" }
This file uses the method Merb::Config.use, just as init.rb does, to set environmentspecific settings. You can also set other constants or whatever environment-specific variables you need in here. We’ve seen some of these configuration settings before. The settings reload_classes and reload_time define what a development environment is by setting application classes to reload periodically. The low log level as well as log auto-flushing also mean that in development mode your application will log everything and write it out as it comes. Let’s take a look at production.rb to make a comparison: Merb.logger.info("Loaded PRODUCTION Environment...") Merb::Config.use { |c| c[:exception_details] = false c[:reload_classes] = false c[:log_level] = :error
From the Library of Shirong Chen
1.4
Merb configuration
17
c[:log_file] = Merb.root / "log" / "production.log" # or redirect logger using IO handle # c[:log_stream] = STDOUT }
Note that exception details are off, and users of the site will not see backtraces if they run into an error. The reloading of classes is also off to save on performance. Finally, the log level has been pushed up to error, because we don’t want to be dealing with huge logs. The log file has been explicitly set, and by omission, log auto-flushing is off. The third environment configuration file, test.rb, is used only when running tests. Its environment configuration is decently empty, setting a configuration key :testing to true. Note that you are able to create your own environments as well as configuration files. To do so, just pass in the same name to the -e flag as the filename of your custom configuration. Here we check via interactive Merb that we’re running in the “staging” environment: $ merb -i -e staging > Merb.environment => "staging"
1.4.3 Router The Merb file config/router.rb defines the routes used by your application. We’ll cover this in detail in the next chapter, but if you need only the most basic setup, you can rely on the default routes created by the line default_routes
1.4.4 Logging By default, all Merb logs are stored in log/ in the Merb root directory. If you’re running Merb under a testing environment, then the log file will be named merb_test.log. Otherwise, it’ll be labeled based on the port number under which it’s running. Typically this means the log filename will be merb.4000.log. You can manually change the location of the log file by setting Merb::Config[:log_file]. This is typically done per the environment and by default results in the log production.log for a Merb application running in production mode. The other log settings that can also be altered as part of Merb::Config are log_level and log_auto_flush. Log level determines
From the Library of Shirong Chen
18
Chapter 1: Fundamentals
the minimal level for a log message to be recorded. Starting from the lowest, these levels are • Debug—intended for development-only logging used for debugging • Info—typically used for logging uncommon events so you know when they hap-
pened • Warn—best used to warn developers that a near-miss happened though no error
occurred • Error—occurs when a user experiences an error • Fatal—logs an error that has killed the server
You can set Merb::Config[:log_level] by using a symbol for any one of these. log_auto_flush, on the other hand, can be set to true or false and will determine whether the log buffer is automatically flushed out to the log file or not.
1.4.4.1 Using logging Throughout your Merb application you will have access to logging via the Merb.logger. When the logger receives a call to any of the log levels, it logs the string it is given as a parameter. Here are some examples of logger usage: Merb.logger.info "Insides of @current_user:" Merb.logger.info @current_user.inspect
If auto-flushing is off, you can manually flush the logs by either using the method flush or using the bang method variant of a log level: Merb.logger.warn! "Need this in the log file now!" Merb.logger.warn "User creation has failed." Merb.logger.flush
1.4.4.2 Viewing logs Unless your Merb application is running as a background process, the first place you see your logs is in the terminal you run it under. If, however, you’re in production mode, you could use the UNIX command tail to actively watch your logs. Here we stream log changes to the terminal by using the flag -f: $ tail -f log/merb.4000.log
If more than one server is running, you can use a wildcard to stream all the logs. Below we also use the v flag to identify which file has been modified. $ tail -vf log/*.log
From the Library of Shirong Chen
1.4
Merb configuration
19
1.4.5 Database When you use an ORM with Merb, it has to know how to access the database or databases you use in the different environments. We’ll go over ORMs more in depth later on, but for reference let’s take a look at how the file config/database.yml helps DataMapper connect. As the YAML files are broken down into three sections, one for each environment, and since each of these is virtually the same, we’ll look at only one of them: development: \&defaults # These are the settings for repository :default adapter: postgres database: sample_development username: the_user password: secrets host: localhost # Add more repositories # repositories: # repo1: # adapter: postgres # database: sample_development # username: the_user # password: secrets # host: localhost # repo2: # ...
Here, adapter specifies the type of database program you’re running (and incidentally the adapter DataMapper will have to use to communicate with it). The rest of the lines are pretty self-explanatory. However, if you access your development database without a password (don’t ever do such a thing on a production server!), you may wonder how to do it. It’s simple enough; just leave whitespace after password: and before the next line. Two-space indents YAML files are whitespace-sensitive so you’ll have to make sure that there are two spaces for every nested line. Among Ruby developers, this is also the standard way of indenting code. Though it’s not necessary with Ruby code, please, for everyone’s sake, abide by this rule.
The extra repository options commented after the host line are a powerful option. DataMapper allows you to access alternative databases through either more explicit
From the Library of Shirong Chen
20
Chapter 1: Fundamentals
method calls or by specifying when they apply in model files. You can add these extra repositories under repositories by starting each with a name by which to reference it. Last, on the first line you may have noticed the inclusion of &defaults. This allows us to use these settings as defaults in the other environments. To do this, we’ll use << along with a YAML dereferencer: production: <<: *defaults database: sample_production
Notice that any one of the settings can be overriden by defining it again after the inclusion of the default.
1.5 Understanding the Merb stack Merb is a modular web framework. This means that to use it you’ll have to be comfortable with the various gems that it pulls together. We’ll take a glance at each of these and cover fundamentals as they come up.
1.5.1 Extlib One of the most powerful aspects of programming in Ruby is the ability to open up classes and add in new behavior. In the wrong hands, though, this could lead to many disastrous effects, including the polluting of the Ruby core classes. Consequently, the developers of Merb (and DataMapper) have chosen to limit the number of so-called support classes and maintain them as a shared side project called Extlib. As an application developer, you’ll be glad they did this, because nobody wants the libraries or frameworks they use to crowd up core classes. Anyway, because there aren’t many additions, let’s take a look at Extlib and the classes it extends.
1.5.1.1 ObjectSpace • ObjectSpace.classes—returns a list of all defined classes
1.5.1.2 Class • cattr_reader, cattr_writer, cattr_accessor—create a unified class and in-
stance variable in either reader, writer, or accessor form • class_inheritable_reader, class_inheritable_writer, class_inheritable_accessor—create a class-level variable that is inherited by
subclasses in either reader, writer, or accessor form
From the Library of Shirong Chen
1.5
Understanding the Merb stack
21
1.5.1.3 Object • in?—returns true if the object is in the array (or the array formed by multiple parameters): 1.in?([1,2,3]) #=> true 1.in?(1,2,3) #=> true
• try_dup—returns a duplicated object if possible. This method is overridden in
non-dupe-able classes to return self. • meta_class—gives you easy access to an object’s singleton class to make metapro-
gramming painless
1.5.1.4 String • escape_regex and unescape_regex—allow you to escape or unescape regex
special characters in strings: "*?{}.".escape_regexp #=> "\\*\\?\\{\\}\\." "\\*\\?\\{\\}\\.".unescape_regexp #=> "*?{}."
• snake_case and camel_case—return a string in either snake or camel case. These
are often used to convert class names to filenames and back again: "FooBar".snake_case #=> "foo_bar" "foo_bar".camel_case #=> "FooBar"
• to_const_string and to_const_path—used to convert a constant name to a
standard path and back again: "merb/core_ext/string".to_const_string #=> "Merb::CoreExt:: String" "FooBar::Baz".to_const_path # => "foo_bar/baz"
• /—used to join strings as part of a file path (uses File.join): "merb"/"core_ext" #=> "merb/core_ext"
• String.translate, String.translations, and t—enable phrase translations
with replacements: spanish_translations = { "hello %s" => "hola %s", "world" => "mundo" } spanish_translations.each_pair{ |x,y| String. translations[x] = y } String.translate("world") #=> "mundo" "hello %s".t("merb") #=> "hola merb" "hello %s".t("world") #=> "hola mundo"
From the Library of Shirong Chen
22
Chapter 1: Fundamentals
1.5.1.5 Time • to_json—returns an ISO-8601-compatible rendering of the Time object’s properties • to_datetime—returns the DateTime equivalent
1.5.1.6 DateTime • to_time—returns the Time equivalent 1.5.1.7 Pathname • /—returns its receiver plus parameter as path fully expanded 1.5.1.8 blank? blank? is a method that has been added to numerous classes. In each of these, the blank? method may vary slightly. Here’s a list of the classes to which it’s been applied and what it tests: • Object—tests if the object is nil; if it is not, it tests first if it responds to empty?
and then whether it is empty or not • Numeric—always responds false • NilClass—always responds true • TrueClass—always responds false • FalseClass—always responds true • String—asks if the string less whitespace is empty?
1.5.1.9 Logger Extlib::Logger is the logger used by both Merb and DataMapper. The Merb log is specifically created in a boot loader we’ll see later on, but creating another logger for any of your Ruby applications is easy enough: logger = Extlib::Logger.new('/tmp/extliblog') logger.info "Log!!"
1.5.1.10 Mash Mash is a subclass of Hash designed specifically to allow you to interchangeably use strings or symbols as keys. With Merb, the real impetus for the creation of Mash was being able to refer to request parameters by both the string key they come with and the more Ruby-programmer-friendly symbolic key.
From the Library of Shirong Chen
1.5
Understanding the Merb stack
23
1.5.1.11 SimpleSet SimpleSet is a simulation of a set where each object is a key in a Hash. This class is used to manage callable actions in Merb controllers. 1.5.1.12 VirtualFile VirtualFile is really just StringIO with a path accessor. This allows Merb to use strings as inline templates. 1.5.1.13 LazyArray LazyArray is an Enumerable used by DataMapper as the parent class of DataMapper::Collection. Notably, LazyArray is designed to limit the loading of data sets to only when they are needed. As an application developer, you won’t need to know any of its methods directly, but knowing of their existence is definitely useful in understanding DataMapper’s beauty. 1.5.1.14 Hook Extlib::Hook is a module used in DataMapper that allows models to associate methods before or after the execution of other methods. It’s similar in use and style to the before and after filters we’ll find in Merb controllers but is built quite differently. 1.5.1.15 Pooling Extlib::Pooling is a module that allows for the sharing of similar resources with the same purpose. It is used by DataMapper to pool database connections.
1.5.2 Rack Rack is a Ruby web server interface that shuttles responses from web applications to actual web servers. The simplicity, modularity, and adaptability of Rack’s implementation are truly beautiful. Consequently, it is used not only by Merb but by numerous other Ruby web frameworks. These frameworks not only have the convenience of allowing programming to an interface but have also enjoyed the benefit of sharing the code that finally pushes responses to different web servers.
1.5.2.1 Adapter options Rack leaves the final decision of which web server to use up to the application developer. The default web server is Mongrel, but you can change this option by passing in an option to Merb on the command line after the -a or --adapter flag or by setting the
From the Library of Shirong Chen
24
Chapter 1: Fundamentals
config variable Merb::Config[:adapter]. Table 1.3 is a list of the adapters available through Rack and the Merb core. If you feel overwhelmed by the number of web server options, don’t worry: The default Mongrel server will most likely serve all your needs, at least at first.
1.5.2.2 How Rack works Rack works by passing around a Ruby object that responds to the call method. This call method needs to take one block parameter known as the environment and return an array of three values: status, headers, and body. This design choice makes Rack applications capable of being as small as lambda expressions, which intrinsically accept a call method. Here’s a “Hello World” example using just Rack and Mongrel: require 'rack' app = lamdba{[ 200, {"Content-Type" => "text/plain"}, ["Hello World"] ]} Rack::Handler::Mongrel.run(app, {:Host => "127.0.0.1", :Port => 4000})
Merb, however, uses Rack in a more complex way than this. Here is the abbreviated source from the Merb core that defines the Merb Rack application: class Merb::Rack::Application def call(env) begin controller = ::Merb::Dispatcher.handle(Merb::Request.new(env)) rescue Object => e return [500, {Merb::Const::CONTENT_TYPE => "text/html"}, e.message + "
" + e.backtrace.join("
")] end Merb.logger.info "\n\n" Merb.logger.flush controller.rack_response end end
You can see how the call method uses the passed-in Rack environment, which is just the incoming request from the web server, and then ultimately returns a response
From the Library of Shirong Chen
1.5
Understanding the Merb stack
25
Table 1.3 Available adapters Adapter
Key
Description
Mongrel
(default)
Mongrel is a web server created by Zed Shaw that boasts high performance.
Evented Mongrel
emongrel
Evented Mongrel is an enhancement of the Mongrel web server that handles concurrent requests with more consistent output by using EventMachine, a current IO library.
Swiftiplied Mongrel
swift
Swiftiplied Mongrel further enhances Evented Mongrel with an unconventional clustering proxy that eliminates significant overhead performance costs in setting up and tearing down connections.
Thin
thin
Thin is a web server that glues together the Mongrel server, EventMachine, and a builtin Rack interface.
Ebb
ebb
Ebb is a Ruby web server that uses the lightweight HTTP server library libebb that boasts performance similar to or better than that of Evented servers.
FastCGI
fcgi
FastCGI is another web interface that will plug into other web servers, including Abyss, Apache, Cherokee, LiteSpeed, Microsoft IIS, nginx, and the Sun Java System Web Server.
WEBrick
webrick
WEBrick is an older Ruby web server often used before Mongrel.
IRB
irb
Not actually a web server, the IRB adapter enables you to interactively run a Merb application.
Runner
runner
Also not an actual web server, but the Runner adapter is used to run tests.
From the Library of Shirong Chen
26
Chapter 1: Fundamentals
that Rack passes up to the web server for client distribution. Here are the steps it takes: 1. Create a request object using the Rack environment which contains everything from the incoming request. 2. Use the dispatcher to handle the newly created request object, resulting in a controller object. 3. Rescue any unhandled errors, returning a backtrace if needed. 4. Flush the log. 5. Return the Rack response crafted by the controller.
1.5.3 ORMs If your applicaton needs to store, retrieve, and manipulate data in complex ways, using a database is a no-brainer. But having to litter your code with SQL queries is painful. Fortunately, a number of ORMs enable you to interact with a database almost without ever having to write a SQL statement. In this book we’ll mostly focus on the DataMapper ORM, which is part of the standard Merb stack. Still, because there are options, the Merb core stays agnostic, and when it comes down to picking the ORM, it’s all up to you. Here are the three officially supported ORMs: • ActiveRecord is the Rails ORM. If you’re porting a Rails application to Merb, using
the ActiveRecord ORM will save you the trouble of having to modify your models. • DataMapper is a next-generation ActiveRecord-like ORM that innovates past its
predecessor. Some of its enhancements include identity maps, lazy loading, strategic eager loading, dirty property tracking, connection pooling, and a Ruby syntax that minimizes or eliminates the need to ever write SQL statements. • Sequel is a robust ORM offering thread safety, connection pooling, and a very
natural DSL for constructing database queries and table schemas. It has numerous well-supported adapters as well as a sophisticated feature set, including prepared statements, bound variables, master/slave configuration, and database sharding.
1.5.4 Plugins The Merb core stays away from dumping in everything you aren’t going to use just in case you’ll need it in another project. Instead, Merb emphasizes plugins as integral parts of the Merb stack. This keeps your application footprint small, but it also demands knowledge of what plugins exist. The most often used plugins are maintained by the
From the Library of Shirong Chen
1.6
An overview of Merb internals
27
Merb developers themselves and are included in the standard Merb stack. Less often used plugins are distributed by third parties.
1.6 An overview of Merb internals You don’t need to understand Merb internals for the work you’ll do as an application developer, but taking a look under the hood to understand how it all came together will help you use them better.
1.6.1 Boot loaders When Merb starts up, the first significant thing it does is run its boot loader. This makes it the best place to start understanding Merb internals. More important, casual knowledge of the startup routine of the Merb boot loader can change your perception of Merb from that of a black box that runs your application code to a framework you can pull apart and extend. Merb actually uses subclasses of the principal boot loader to define subroutines needed at boot-up. The class Merb::BootLoader thus serves two major functions. First, it is the parent class of all boot loaders. Second, at startup it goes through each of its subclasses and calls a class method named run. With this in mind, let’s take a look at each of the subclassed boot loaders in the order in which they are loaded. We’ll also include excerpts from their run methods. There’s no need to be overwhelmed by the code included (especially since externally defined variables appear). Instead, just let it simmer, and appreciate the fact that you can concretely trace things back to their source.
1.6.1.1 BootLoader::Logger The first boot loader to be loaded is the logger. We need it to come first so that we can log any issues that may occur during boot-up. Here’s the code from inside the run method of the logger boot loader: Merb.logger = Merb::Logger.new(Merb.log_file, Merb:: Config[:log_level], Merb::Config[:log_delimiter], Merb::Config[:log_auto_flush])
Note how the Merb::Config variables we saw earlier are used to initiate the logger. Merb.log_file may seem unfamiliar, but it’s really only a method that references Merb::Config['log_file'] or otherwise constructs a default log filename.
From the Library of Shirong Chen
28
Chapter 1: Fundamentals
1.6.1.2 BootLoader::DropPidFile When our application is run either as a daemon or as one server among a cluster, we’ll need to store its process ID, so that we can, among other things, kill it later using merb -k. The DropPidFile boot loader does this for us by storing our server’s process ID number in a file found in log/: Merb::Server.store_pid(Merb::Config[:port]) if Merb:: Config[:daemonize] || Merb::Config[:cluster]
This run method doesn’t reveal much of what goes on, but by default the pidfile is named based on the number of the port on which the server is running. So, for example, the standard pidfile can be found at log/merb.4000.pid. You can change this default by setting Merb::Config[:pid_file] directly within either your config file or an environment file.
1.6.1.3 BootLoader::Defaults Within the Merb default boot loader, some critical overrides are defined to make up for the inability of a number of browsers to use all the HTTP methods, particularly PUT and DELETE. This is particularly useful in building RESTful routes for resources. Note how the default boot loader overrides some requests to imitate the client being capable of all the HTTP methods: Merb::Request.http_method_overrides.concat([ proc { |c| c.params[:_method] }, proc { |c| c.env['HTTP_X_HTTP_METHOD_OVERRIDE'] } ])
1.6.1.4 BootLoader::BuildFramework As we saw in the config section, Merb allows us to designate alternative layouts for our applications. The BuildFramework boot loader gets this working: if File.exists?(Merb.root / "config" / "framework.rb") require Merb.root / "config" / "framework" elsif File.exists?(Merb.root / "framework.rb") require Merb.root / "framework" else Merb::BootLoader.default_framework end (Merb::Config[:framework] || {}).each do |name, path| path = Array(path) Merb.push_path(name, path.first, path.length == 2 ? path[1] : "**/*.rb") end
From the Library of Shirong Chen
1.6
An overview of Merb internals
29
Note how, in the final lines, the inclusion of a second parameter alters the default of '**/*.rb'. This string is known as a glob and is used to recursively search for Ruby files. Changing the glob affects what files are included. This may come in handy in framework definition, where we want to include only one file (set the glob to nil) or only files named in a particular fashion (for example, '*_controller.rb').
1.6.1.5 BootLoader::Dependencies This boot loader actually does a bit more than load dependencies. It loads the init file, loads the applicable environment configuration file, loads other dependencies you’ve included in the init or environment file, and then finally updates the logger with any changes you may have made. 1.6.1.6 BootLoader::MixinSession This adds in session functionality by including relevant mixins in both Merb:: Controller and Merb::Request. It’s critical that this happens so early because custom subclasses of SessionContainer and SessionStoreContainer may be created through a before_app_loads block: def self.run require 'merb-core/dispatch/session' Merb::Controller.send(:include, ::Merb::SessionMixin) Merb::Request.send(:include, ::Merb::SessionMixin::RequestMixin) end
1.6.1.7 BootLoader::BeforeAppHooks This boot loader can be used to set or define whatever you need before your application code gets loaded. Plugins often use this hook to include code. The boot loader works by calling all the before load callbacks created by before_app_loads blocks: Merb::BootLoader.before_load_callbacks.each { |x| x.call }
1.6.1.8 BootLoader::LoadClasses The LoadClasses boot loader loads all the nontemplate application code. It also records modification times for each file so that they can be used by the ReloadClasses boot loader. Since the code gets fairly complex, we’ll leave it to you to check the source yourself. 1.6.1.9 BootLoader::Templates This boot loader does nearly the same thing as the LoadClasses boot loader but for templates. Pulling from a template path, it inlines each of the templates for later use by the controllers:
From the Library of Shirong Chen
30
Chapter 1: Fundamentals
template_paths.each do |path| Merb::Template.inline_template(File.open(path)) end
1.6.1.10 BootLoader::MimeTypes Merb is capable of rendering responses with different MIME types. The MimeTypes boot loader registers the default MIME types and serves as a good example of how you can add your own MIME types in an after_app_loads block: Merb.add_mime_type(:all, nil, %w[*/*]) Merb.add_mime_type(:yaml, :to_yaml, %w[application/x-yaml text/yaml], :charset => "utf-8") Merb.add_mime_type(:text, :to_text, %w[text/plain], : charset => "utf-8") Merb.add_mime_type(:html, :to_html, %w[text/html application/xhtml+xml application/html], :charset => " utf-8") Merb.add_mime_type(:xml, :to_xml, %w[application/xml text/xml application/x-xml], {:charset => "utf-8"}, 0.9998) Merb.add_mime_type(:js, :to_json, %w[text/javascript application/javascript application/x-javascript], : charset => "utf-8") Merb.add_mime_type(:json, :to_json, %w[application/json text/x-json], :charset => "utf-8")
Note that the addition of each MIME type takes an identifying symbol, a transformation method or nil, a list of Accept header values to associate with, and the response header to be sent out.
1.6.1.11 BootLoader::Cookies This boot loader mixes in cookie functionality in both controllers and requests: require 'merb-core/dispatch/cookies' Merb::Controller.send(:include, Merb::CookiesMixin) Merb::Request.send(:include, Merb::CookiesMixin::RequestMixin)
1.6.1.12 BootLoader::SetupSession This steps up the defined session containers and default values. Once again, here it’s best to take a look into the source code yourself if your interest is piqued. 1.6.1.13 BootLoader::AfterAppLoads Analogous to the before_app_loads boot loader, the after_app_loads boot loader calls all the callbacks collected from after_app_loads blocks: Merb::BootLoader.after_load_callbacks.each {|x| x.call }
From the Library of Shirong Chen
1.6
An overview of Merb internals
31
1.6.1.14 BootLoader::SetupStubClasses This boot loader creates stubbed classes for Merb::Application and Merb:: Exceptions. This is important in making sure exception controllers are available even in very flat applications: unless defined?(Exceptions) Object.class_eval <<-RUBY class Application < Merb::Controller end class Exceptions < Application end RUBY end
1.6.1.15 BootLoader::ChooseAdapter This sets the Merb Rack adapter to be used. By this time, if a value was passed in on the command line, it has been used to set Merb::Config[:adapter]: Merb.adapter = Merb::Rack::Adapter.get(Merb::Config[:adapter])
1.6.1.16 BootLoader::StartWorkerThread This starts a worker thread that will run deferred blocks as your controllers demand: Merb::Worker.new
1.6.1.17 BootLoader::RackUpApplication Here’s the boot loader that sets up Rack. You may want to take your time with it. require 'rack' if File.exists?(Merb.dir_for(:config) / "rack.rb") Merb::Config[:rackup] ||= Merb.dir_for(:config) / "rack.rb" end if Merb::Config[:rackup] rackup_code = File.read(Merb::Config[:rackup]) Merb::Config[:app] = eval("::Rack::Builder.new {( #{rackup_code} \n )}.to_app", TOPLEVEL_BINDING, Merb::Config[:rackup]) else Merb::Config[:app] = ::Rack::Builder.new { if prefix = ::Merb::Config[:path_prefix] use Merb::Rack::PathPrefix, prefix end
From the Library of Shirong Chen
32
Chapter 1: Fundamentals
use Merb::Rack::Static, Merb.dir_for(:public) run Merb::Rack::Application.new }.to_app end
Note that it looks for a file named rack.rb, which we saw when we covered configuration. It then uses the code from that file as part of an eval statement that initializes Rack::Builder. If there is no rack.rb, the boot loader uses the defaults we saw earlier.
1.6.1.18 BootLoader::ReloadClasses The ReloadClasses boot loader sets up a timed executor to reload classes as needed. You can change the reload time or turn it off completely in your configuration files. The standardly generated environment files set Merb::Config[:reload_classes] to true only in the development environment. Here’s the source from the boot loader that makes use of these configuration variables: return unless Merb::Config[:reload_classes] TimedExecutor.every(Merb::Config[:reload_time] || 0.5) do reload end
1.6.1.19 BootLoader::ReloadTemplates Templates are also set to be reloaded by the final boot loader. However, as the code below indicates, you turn this off either by setting Merb::Config[:reload_templates] or by not using the development environment. unless Merb::Config.key?(:reload_templates) Merb::Config[:reload_templates] = (Merb.environment == "development") end
1.6.2 Server A Merb server is a system process that responds to requests on a given port defined in configuration. You can also start clusters of Merb servers, but each runs on its own port and as its own process. The Merb::Server class offers a number of class methods to manage running servers, but the application developer typically accesses these via the merb command. For example, the following command kills a Merb server running on port 4000: $ merb -k 4000
From the Library of Shirong Chen
1.6
An overview of Merb internals
33
Using other Merb::Server class methods under the hood, we can start a cluster of ten daemonized Merb servers and then kill each one of them: $ merb -d -c 10 $ merb -k all
To see exactly how Merb makes these commands work, you can check out the source code for the files lib/merb-core.rb and lib/merb-core/server.rb inside the Merb core.
1.6.3 Requests The class Merb::Request describes the requests a Merb server receives. Each of these requests will exist for the duration of the creation of a response. We’ll see plenty of these in the next couple of chapters.
1.6.4 Router The Merb router stores routes that map requests to controller actions. We’ll go over this extensively in Chapter 2.
1.6.5 Dispatcher The Merb dispatcher is responsible for dispatching incoming requests from a work queue it maintains. Merb is thread-safe, meaning it’s capable of better performance through multithreading, but if the libraries you use make thread safety an issue, the dispatcher is capable of locking requests using mutual exclusion. You can do this in your configuration file as follows: Merb::Config[:use_mutex]
Alternatively, you can pass in “on” or “off” to the -X flag with the merb command: $ merb -X on
Most of the methods within lib/merb-core/dispatch/dispatcher.rb, notably handle and redirect, are actually part of Merb::Request but serve to dispatch requests from the work queue. As an application developer, you’ll use only the redirect method; however, in the chapter on controllers (Chapter 3) we’ll demonstrate the path by which a request comes to be handled by using Merb::Request.handle.
From the Library of Shirong Chen
34
Chapter 1: Fundamentals
1.6.6 Controllers Merb controllers are the classes that do the actual handling of requests and produce the responses to send out. Since we talk extensively about them in Chapter 3, we’ll defer our discussion until then.
1.6.7 Sessions Sessions allow for the stringing of otherwise independent requests into a user-specific interaction. We’ll use them heavily in crafting responses as well as in filtering access to controller actions. As we previously saw, Merb is capable of storing sessions in a number of different ways; thus much of the source is dedicated just to this. Fundamentally, though, session functionality is abstracted away from its means of storage and then mixed into both the controller and request classes.
1.6.8 Worker The Merb worker allows us to run code outside the standard request-to-response cycle. This is perfect for time-intensive tasks that would otherwise delay the sending of a response. We’ll go over the use of workers in more depth in Chapter 3, but the following excerpt from the merb-core source demystifies workers as containers for threads that maintain work queues: module Merb class Worker attr_accessor :thread def initialize @thread = Thread.new { loop { process_queue } } end def process_queue begin while blk = Merb::Dispatcher.work_queue.pop Thread.pass blk.call end # ... end end end
From the Library of Shirong Chen
1.7
Conclusion
35
1.7 Conclusion Merb’s architecture emphasizes modularity, both internally and through its plugin interface. Throughout this book we will encounter aspects of this design decision while examining how it has shaped the tools around it. Application developers should be pleased by Merb’s versatility, as it can provide them with as little or as much as they need. Along with the prospect of custom stacks, a plethora of possibilities are opened, making Merb suitable for whatever a web application may demand.
From the Library of Shirong Chen
This page intentionally left blank
From the Library of Shirong Chen
C HAPTER 2
Routing
If you’re new to MVC web frameworks in general, you may at first expect that each request path would correlate with a particular file of code. This would be similar to the way static assets and preprocessed scripts are most often served. However, organizing our application code by request path typically leads to a sprawl of files and immense challenges to sensible refactoring. Merb refuses to let this happen by adhering to the MVC paradigm and separating code by its abstract purpose. The only hitch, of course, is that we now need an application router to guide incoming requests to the code meant to respond to it. If this at first sounds a bit complicated, don’t worry. The Merb router is not only sophisticated enough to parse and direct requests based on complex conditions, it’s also easy to use and understand. In fact, if you’ve used the routers of other frameworks, chances are you’ll find Merb’s route definitions far cleaner and better organized than anything you’ve seen in the past. As an MVC framework, Merb routes each of its requests to a controller. More specifically, each request is routed to a particular controller method that in this context is called an action and is responsible for producing a response. Constructing Merb routes for use by the router is not an exercise of tediously mapping URLs to controller actions, however. Instead, we are able to match particular conditions in a nested fashion, consolidating the definition of related routes. Merb also sports the ability to parse conditions using regular expressions and is consequently incredibly versatile when you need it to be. Nonetheless, in many cases the best way of accomplishing routing is often the simplest, and as we’ll see with Merb’s resource routing, a single line in the router can set up all the routes you need to standardly interact with a resource. This routing is accomplished in adherence to REST standards (aka RESTfully) and makes it possible to sensibly double the use of your routes as a basis for web services. 37 From the Library of Shirong Chen
38
Chapter 2: Routing
Once defined, a list of routes plays a part not only in the recognition of request URLs, but also in their generation. This is because Merb can inversely map parameters to a URL, and thus it enables us to abstractly generate URLs needed for redirects, links, and elsewhere. This is important in both controllers and views as we’ll see in the chapters on those topics, but we’ll also briefly touch upon them at the end of this chapter. With all this is mind, let’s take a look at the internals of the Merb router to see how it works. You don’t need to remember all the classes we’ll describe from within the core, but again, familiarity with them will also bring substantial comfort, so a peek is well worth your time.
2.1 How Merb routing works Merb routing works by first matching conditions against a request’s method and path. It then looks to associate matches with a parameters hash, which specifies the relevant controller and action as well as additional query parameters that may be needed. Each of these pairings of conditions with parameters is called a route and is stored in an ordered list generated at boot-up. As we look at the classes that define routes, we find that each route is created as an instance of Merb::Router::Route and, if active, can be found as an element in the array Merb::Router.routes. If we start up interactive Merb outside any project, we’ll find the routes to be empty: > Merb::Router.routes => []
There is another class that serves as a precursor to routes, called a behavior. Router behaviors make it possible for us to specify routes in a nested manner that grow toward completeness through the layering of both route conditions and parameters. As an application developer, you do not need to know of the existence of route behaviors even though you’ll be interacting with them unknowingly. On the other hand, for those of you destined to become plugin developers or possibly core contributors, it’ll serve you well to know that route behaviors are instances of Merb::Router::Behavior and that each block inside a router configuration is evaluated within the context of a parent behavior. Nested blocks imply nested behaviors, and these are compiled down to a flat ordered list at boot-up in the previously mentioned array Merb::Router.routes. From either perspective, what matters most when specifying routes is an understanding of the two elements that make up each complete route. These are the route conditions and the route parameters. Let’s take a look at each before we put them to use.
From the Library of Shirong Chen
2.1
How Merb routing works
39
2.1.1 Route conditions Route conditions are what the Merb router matches incoming requests against. In general, there are two minimal conditions a route must have: a path and a method. Paths are specified using strings, whereas method matching may use strings, symbols, or arrays. Getting the most out of route regular expressions You can also use regular expressions when setting route conditions. Doing so, however, may make the router incapable of creating an inverse mapping of the route, which prevents it from using the route statement for URL generation. The more modest alternative that also allows for route generation is to use regular expressions against the symbolic path segments that we’ll talk about soon. Merb also translates these regular expression matches into additional constraints for your routes, so overall, using them can be the best option.
2.1.1.1 Route method Sometimes requests going to the same path have different intents. For example, a request for /users/1 may sometimes be used to show the profile of a user, but at other times the request may include data used to update the user’s details. However, instead of guessing what the intent of the request is, based on what data is received, it is standard to rely on the request’s HTTP method as the definitive indicator of intent. A number of HTTP methods exist, but four in particular are used with Merb. These are GET, POST, PUT, and DELETE. In the context of REST, these methods are often called verbs. RESTfully speaking, each one plays a particular role well defined by both the W3C HT TP standards and RESTful intent. However, if you feel the need to set up routes that deviate from these standards, you have the freedom to do so. Just be warned: Using HTTP methods and their RESTful interpretations is more and more becoming a shared best practice, and going against it may only serve to complicate your application for both internal developers as well as web service consumers. Here’s a list of the four most important HTTP methods: • GET—the most common HTTP verb, used to request data without explicitly af-
fecting it • POST—the default verb for form submission, used to push arbitrary data onto the
server • PUT—used when data sent is intended to replace the existing data corresponding
with a particular URL • DELETE—used to request the deletion of data related to a particular URL
From the Library of Shirong Chen
40
Chapter 2: Routing
The case of the missing HTTP methods Note that some browsers do not produce all four of these HTTP verbs and are limited to only GET and POST. To make up for this and imitate full HTTP method compatibility, Merb uses both X-HTTP_METHOD_OVERRIDE and a hidden query parameter to enable the translation of particular POSTs into PUTs or DELETEs. In Chapter 1 we saw the boot loader that enabled these overrides. The point is, you won’t have to worry about the details of method overriding as an application developer. Merb will take care of it for you.
In the conditions hash of a route, a route method is typically indicated by use of one of the symbols :get, :post, :put, or :delete. You may instead use strings or regular expressions, but symbols are preferred. In the following code, we begin to define a route by specifying a method: Merb::Router.prepare do match(:method => :get) do # ... end end
2.1.1.2 Route path The more recognizable part of an HTTP request is its path. This includes everything after the domain of the request and before the query string. For example, the path of the request URL http://merbdeveloper.com/users/1 is /users/1. What about the query string? An HTTP path does not include the query string, which is everything that follows the first question mark in a URL. In general, query strings are used to pass in request parameters that may be used by the application code. For example, a request to /users?first_name=Foy may pull up a list of users with that first name only. Strictly speaking, though, the request parameters found in a query string are used only to drill down to the specifics of the response desired and do not play an intrinsic role in route definition. Nonetheless, Merb routes do allow you to translate parts of a route path into request parameters. We’ll see this often with resources routes. For example, /users/foysavas may, in the process of getting routed, translate the second segment into the value of params[:username].
Route paths are segmented by Merb with the standard slash separator or, as needed, by the end of symbolic segments. The two types of segments are literal and symbolic.
From the Library of Shirong Chen
2.1
How Merb routing works
41
Table 2.1 Route paths Route Path
Matching Path
Extracted Parameters
/users
/users
/users/:id
/users/1
:id => 1
/posts/:year_:month
/posts/08/08
:year => 08, :month => 08
Literal segments match literally against themselves. For instance, /users/new matches the first segment of the route path '/users/:action', which is '/users'. Symbolic segments, on the other hand, match any string that does not include a slash separator. For example, in the last example, the new at the end is matched against :action. As a side note, since :action has a special meaning among symbols, this route path automatically associates itself with the controller action named new in practice. To whet your appetite, Table 2.1 shows a few more examples of route paths and their interpretations by the router. When it comes time to write out our route path conditions, they will appear either first in match statements or as values to the key path: Merb::Router.prepare do match("/users") do # ... end match(:path => "/session") do # ... end end
2.1.1.3 Other conditions There are a few other conditions we can match against in route conditions. Two of these are protocol and domain, and they allow us, for instance, to match requests coming from secure HTTP or requested from a particular subdomain: Merb::Router.prepare do match(:protocol => 'https', :domain => 'secure.merbdeveloper.com') do # ... end end
From the Library of Shirong Chen
42
Chapter 2: Routing
Other conditions aside from path, protocol, domain, and method are assumed to be for symbolic segments and can be used to add constraints to segments through regular expressions. Here, the ID of a path matches only if it’s a string of digits: Merb::Router.prepare do match("/users/:id", :id => %r{ˆ[0-9]+}) do # ... end end
2.1.2 Route parameters Route parameters specify where the route should go. They are described as hashes where a few keys are interpreted separately, and all the rest are considered part of the parameters of the request itself. Here’s a list of the special keys: • controller—the controller to which the request will be routed in snake case • action—the action to which the request will be routed; defaults to “index” • format—the format in which the response should be given
All other route parameters do not have special meaning and are passed on as parameters, appearing with or, if necessary, overriding any parameters from elsewhere in the request. Here’s an example that uses both the special and nonspecial parameters: Merb::Router.prepare do match('/:controller/:action/:id'). to(:controller => controller, :action => :action, :id => id) end
One thing to note about this example is that all of the route parameters are redundant, as the Merb router translates symbolic segments into route parameters automatically.
What’s with this word parameter? You may be feeling the pain from this wildly overloaded word parameter. To clarify, there are two types of parameters within the context of a request plus the more general use of the word in Ruby:
• Route parameters---parameters that define where a request should be directed, or are inversely used to abstractly generate a URL. For example, a line such as to(:controller => 'users', action => 'new') inside router.rb uses router parameters within the to.
From the Library of Shirong Chen
2.2
Router configuration
43
• Request parameters---the collective data parameters of a request. These include the parameters found in a query string or as part of post data, as well as the nonspecial route parameters Merb merges in. These are all accessible from either the controller or the view as part of the mash params.
• (Method) parameters---our veritable friends that serve as data passed into a method.
2.2 Router configuration Though we’ve seen some examples, up until now we’ve spoken only conceptually of routes, so it’s time to create our own configurations and learn about all the available methods. Routes are specified within a configuration block passed to the router for interpretation. Depending on the layout of your application, this configuration block may be in different locations, but it’s important that it gets loaded at boot-up so the routes can be compiled.
2.2.1 The router file In a standard Merb layout, routes are specified in the file config/router.rb. Single-file Merb applications include a router configuration near the top of the file. Flat layouts put it in application.rb. With either of these, router configuration code looks just the same and works through the passing of a block to the class method Merb::Router.prepare. Here’s the content from a standardly generated config/router.rb file less comments: Merb.logger.info("Compiling routes...") Merb::Router.prepare do default_routes end
The first line, of course, is just a log message and is not critical to our routes. The next class method is the one we previously referenced, and in the default case of a generated standard application it has only default_routes. We’ll get into what this does soon.
2.2.2 The prepare block The prepare block works by evaluating each line in the context of a route behavior. Remember, route behaviors are basically nestable and are not necessarily complete
From the Library of Shirong Chen
44
Chapter 2: Routing
precursors to routes. For instance, the root behavior for the prepare block is internally defined as Behavior.new.defaults(:action => "index")
All deeper route behaviors are based on the behavior created by the method before the nesting of a block. This is demonstrated by the following prepare block: Merb::Router.prepare do to(:controller => 'home') do match('/') match('/preview').to(:action => :preview) end end
The two match lines are evaluted within the context of a new child behavior where the following route parameters have been set: :controller => 'home', :action => 'index'
Note that the second match statement overrides the action of the parent behavior, whereas the first relies on its parent entirely to specify its route parameters.
2.2.3 Route order All behaviors are compiled down into a flat list on boot-up, and when the router is used to recognize or generate a URL, it’ll work with the first possible route it finds. This often trips up first-timers, especially via the line default_routes: Merb::Router.prepare do default_routes match('/users/:id').action(:action => 'show') end
In this example, the default_routes will always take precedence, and therefore the match on the second line will never work. Be sure to pay attention to the order of your routes to avoid this problem.
2.2.4 Adding routes later on Typically, as an application developer you won’t need to add routes outside the prepare block, but it is still possible. To do this, you can use the two class methods
From the Library of Shirong Chen
2.3
Checking routes
45
Merb::Router.append and Merb::Router.prepend. Both of these work exactly like
the prepare block but insert their routes before or after all the others: Merb::Router.append do match('/info').to(:controller => 'info') end
2.3 Checking routes Sometimes you’ll want to check the routes of your application to make sure requests are routed properly. There are a number of ways to do this. So that you can use these methods to test the various router configurations we describe later and the router configurations of your own applications, we’ll describe them here. For all of this section’s examples we’ll use a standard Merb application with the following routes defined in config/router.rb: Merb::Router.prepare do # RESTful routes resources :posts # This is the default route for /:controller/:action/:id # This is fine for most cases. If you're heavily using resource-based # routes, you may want to comment/remove this line to prevent # clients from calling your create or destroy actions with a GET default_routes # Change this for your home page to be available at / match('/').to(:controller => 'whatever', :action =>'index') end
2.3.1 Listing routes Getting a simple list of the compiled routes can do wonders in determining why a request has gone astray. We’ll do this first using interactive Merb and then using a rake task.
2.3.1.1 Using interactive Merb To list routes in interactive Merb, simply start up the application and issue the command Merb::Router.route. Here we pull up a list of the routes in interactive Merb using the previously defined routes: > pp Merb::Router.routes [/posts(/index)(.:format), /posts(.:format), /posts/new(.:format),
From the Library of Shirong Chen
46
Chapter 2: Routing
/posts/:id(.:format), /posts/:id/edit(.:format), /posts/:id/delete(.:format), /posts/:id(.:format), /posts/:id(.:format), /:controller(/:action(/:id))(.:format), /] => nil
The first eight come from the resources route and are perfect examples of how effective resources routing can be in producing the routes you need. These routes come first because resources :posts does, too. Following those eight routes is the default_routes route. Notice that it translates to only one route, but given its complexity and versatility it has kept its name. Finally, the last route, the single /, matches a request for the root or empty path. Each of these items in the routes array appears as the path string above. However, don’t let that deceive you, since they’re all full routes that you can play around with. Below, we interact with the conditions and parameters for the default_routes route. > Merb::Router.routes[8].conditions => {:path=>/ˆ\/([ˆ\/.,;?]+)(?:\/([ˆ\/.,;?]+) (?:\/([ˆ\/.,;?]+))?)?(?:\.([ˆ\/.,;?]+))?$/} > Merb::Router.routes[8].params => {:controller=>"(path1)", :format=>"(path4)", :action=> "(path2 || \"index\")", :id=>"(path3)"}
As we’ll learn shortly, routes also come with names. These are either selected by you or they default to the name “default.” Below we use interactive Merb to list the named routes for us. > pp Merb::Router.named_routes {:new_post=>/posts/new(.:format), :posts=>/posts(/index)(.:format), :default=>/:controller(/:action(/:id))(.:format), :edit_post=>/posts/:id/edit(.:format), :delete_post=>/posts/:id/delete(.:format), :post=>/posts/:id(.:format)} => nil
Nearly all of these are for the resources route that was specified. Note that the inflector has been used to create these names, thus allowing us to naturally generate URLs for the various RESTful interactions. Also of importance is the precedence of the default_routes route over the specified root route. The former has become the route attached to the default name.
From the Library of Shirong Chen
2.3
Checking routes
47
Alternatively we can use the convenience method merb.show_routes to display all the routes in a significantly more verbose yet less interactive way. Here we pull up the same routes using this method: > merb.show_routes ==== Named routes Helper : new_post HTTP method: GET Route : /posts/new(.:format) Params : {:controller=>"\"posts\"", :format=>"(path1)", :action=>"\"new\""} Helper : posts HTTP method: GET Route : /posts(/index)(.:format) Params : {:controller=>"\"posts\"", :format=>"(path1)", :action=>"\"index\""} Helper : default HTTP method: GET Route : /:controller(/:action(/:id))(.:format) Params : {:controller=>"(path1)", :format=>"(path4)", :action=>"(path2 || \"index\")", :id=>"(path3)"} Helper : edit_post HTTP method: GET Route : /posts/:id/edit(.:format) Params : {:controller=>"\"posts\"", :format=>"(path2)", :action=>"\"edit\"", :id=>"(path1)"} Helper : delete_post HTTP method: GET Route : /posts/:id/delete(.:format) Params : {:controller=>"\"posts\"", :format=>"(path2)", :action=>"\"delete\"", :id=>"(path1)"} Helper : post HTTP method: GET Route : /posts/:id(.:format) Params : {:controller=>"\"posts\"", :format=>"(path2)", :action=>"\"show\"", :id=>"(path1)"} ==== Anonymous routes HTTP method: POST Route : /posts(.:format) Params : {:controller=>"\"posts\"", :format=>"(path1)", :action=>"\"create\""}
From the Library of Shirong Chen
48
Chapter 2: Routing
HTTP method: PUT Route : /posts/:id(.:format) Params : {:controller=>"\"posts\"", :format=>"(path2)", :action=>"\"update\"", :id=>"(path1)"} HTTP method: DELETE Route : /posts/:id(.:format) Params : {:controller=>"\"posts\"", :format=>"(path2)", :action=>"\"destroy\"", :id=>"(path1)"} HTTP method: GET Route : / Params : {:controller=>"\"whatever\"", :action=>"\"index\""} => nil
In this output, Helper signifies the name that should be used when we look to generate the URL. If you take a look at the penultimate and antepenultimate routes, it’s clear why two routes have appeared previously with the exact same path: One route is for PUT requests, which go to the update action, and the other is for DELETE requests, which go to the destroy action.
2.3.1.2 Using the audit routes rake task Standard applications are generated with a rake task that lists all routes for us without our having to use interactive Merb. To do this we can step into the Merb application’s root directory and issue the following command: $ rake audit:routes
Given the routes that were previously defined, the following routes are displayed: Named Routes delete_post: /posts/:id/delete(.:format) posts: /posts(/index)(.:format) edit_post: /posts/:id/edit(.:format) default: /:controller(/:action(/:id))(.:format) post: /posts/:id(.:format) new_post: /posts/new(.:format) Anonymous Routes /posts(.:format) /posts/:id(.:format) /posts/:id(.:format) /
From the Library of Shirong Chen
2.3
Checking routes
49
2.3.2 Trying the router Sometimes the only way to figure out what’s going on is to ask the router directly. Here we use interactive Merb to check both URL recognition and generation.
2.3.2.1 For URL recognition Using the Merb convenience methods, we can first put together a fake request and then see where the route will take it. The method merb.fake_request makes it easy for us to put together a request by just overriding default values. Here are the default settings that make up a merb.fake_request: > pp Merb::Test::RequestHelper::FakeRequest::DEFAULT_ENV {"SERVER_NAME"=>"localhost", "PATH_INFO"=>"/", "HTTP_ACCEPT_ENCODING"=>"gzip,deflate", "HTTP_USER_AGENT"=> "Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.8.0.1) Gecko/20060214 Camino/1.0", "SCRIPT_NAME"=>"/", "SERVER_PROTOCOL"=>"HTTP/1.1", "HTTP_CACHE_CONTROL"=>"max-age=0", "HTTP_ACCEPT_LANGUAGE"=> "en,ja;q=0.9,fr;q=0.9,de;q=0.8,es;q=0.7,it;q=0.7,nl; q=0.6,sv;q=0.5,nb;q=0.5,da;q=0.4,fi;q=0.3,pt;q=0.3,zh -Hans;q=0.2,zh-Hant;q=0.1,ko;q=0.1", "HTTP_HOST"=>"localhost", "REMOTE_ADDR"=>"127.0.0.1", "SERVER_SOFTWARE"=>"Mongrel 1.1", "HTTP_KEEP_ALIVE"=>"300", "HTTP_REFERER"=>"http://localhost/", "HTTP_ACCEPT_CHARSET"=>"ISO-8859-1,utf-8;q=0.7,*;q=0.7", "HTTP_VERSION"=>"HTTP/1.1", "REQUEST_URI"=>"/", "SERVER_PORT"=>"80", "GATEWAY_INTERFACE"=>"CGI/1.2", "HTTP_ACCEPT"=> "text/xml,application/xml,application/xhtml+xml,text/ html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5", "HTTP_CONNECTION"=>"keep-alive", "REQUEST_METHOD"=>"GET"} => nil
We can override the settings by passing in parameters. With the following, we modify the default fake request so as to imitate the deletion of a post: > fr = merb.fake_request('REQUEST_PATH'=>'/posts/1',' REQUEST_METHOD'=>'DELETE')
From the Library of Shirong Chen
50
Chapter 2: Routing
With our fake request ready, we can now use another convenience method to check if it routes to where it should. This method is merb.check_request_for_route and takes a request (fake or not) as its sole parameter: > merb.check_request_for_route(fr) => {:controller=>"posts", :format=>nil, :action=>"destroy", :id=>"1"}
The hash returned says that the request does in fact route to the destroy action of the posts controllers, so everything is in order.
2.3.2.2 For URL generation We can also test the generation of routes using another convenience method, merb.url. It takes a route name plus route parameters. Leaving out the route name sets it to default. Below we test the generation of the URL for editing the first post and then for an anonymous default route. > merb.url(:edit_post,:id => 1) => "/posts/1/edit" > merb.url(:controller => 'home', :action => 'login') => "/home/login"
2.4 Match rules Match rules are the basis for defining all route definitions. Let’s explore the various possibilities.
2.4.1 Literal matching Literal matching is quite simple. Using the route behavior method match within the block given to Merb::Router.prepare, we can match against literal strings: Merb::Router.prepare do match('/examples/routes') end
You can also nest literal match statements by passing in blocks. The following example demonstrates the same match as before: Merb::Router.prepare do match('/examples') do match('/routes') end end
From the Library of Shirong Chen
2.4
Match rules
51
Literal matches occur only if the string matches the entirety of the request path. For example, if a request comes in with the path /examples/controllers', it will not match against '/examples'. The exception, of course, is if a nested match completes the literal path. Remember that the route behaviors listed in the route are compiled before use, so nested literal matches are concatenated in their final form. Finally, an uncommon alternative to simply passing in the literal string is to instead pass in a hash with a value on the key :path. We provide this example so that you may recognize such usage if you spot it: Merb::Router.prepare do match(:path => '/examples') end
2.4.2 Symbolic matches Symbolic matches use symbols to more abstractly match request paths and associate these matches with parameters. These symbols appear as substrings within match path strings and can have additional rules set upon them using regular expressions. By default, a symbol matches all alphanumeric characters, the dash, and the underscore in a nongreedy fashion. Below is a simple example of a match rule that automatically sets the controller and action. Note that the colon is not allowed in URL paths, so there’s no need to worry about ambiguity. Merb::Router.prepare do match('/:controller/:action') end
Overall, the use of symbolic segments effectively extends the matches in three ways. Let’s go over these individually.
2.4.2.1 Automatic parameters Using symbolic match, we can tersely specify both route conditions and parameters. This works by mapping each symbolic match to an equivalent route parameter. Below, we redundantly route a symbolic match to where it was going even without the to statement. Merb::Router.prepare do match('/:controller').to(:controller => controller) end
From the Library of Shirong Chen
52
Chapter 2: Routing
2.4.2.2 Flexible segmentation Symbolic matches allow us to segment routes without relying on slash separators. Below we pull out a year, month, and day separated by dashes. Merb::Router.prepare do match('/dates/:year-:month-:day').to(:controller => 'dates') end
This match rule matches and segments request paths that look like this: /dates/2008-10-11 /dates/sorry-try-harder
In both of these examples, the three symbolic segments are translated into route parameters under the names :year, :month, and :day.
2.4.2.3 Segment-specific regular expressions We just saw an example where the match rule ended up matching possibly unwanted segments and translated them into route parameters. We can set further limits on specific segments by using regular expressions, which are compatible with both route recognition and generation and should be used when possible. Here we limit the previous example so that only strings of digits are allowed: Merb::Router.prepare do match('/dates/:year-:month-:day').to( :controller => 'dates', :year => /\d+/, :month => /\d{1,2}/, :day => /\d{1,2}/ ) end
2.4.3 Optional matches Using parentheses, we can tell the router that certain parts of a match are optional. This can significantly lessen the number of rules we need to write. We’ve seen this already with the case of the so-called default_routes, which are actually defined with just one match but using a number of parentheses: match("/:controller(/:action(/:id))(.:format)")
Note that some of the parentheticals are embedded within other parentheticals. The following routes match the preceding match rule: /users /posts/delete
From the Library of Shirong Chen
2.4
Match rules
53
/projects/edit/3 /groups.xml /users/new.txt /posts/update/6
2.4.4 Full regular expressions Though it’s not advisable because it may leave you unable to generate URLs from route parameters, you can directly use a regular expression to match the full path of a request: Merb::Router.prepare do match(%r[/account/([a-z]{4,6})]).to(:controller => "account", :action => "show", :id => "[1]") end
Using interactive Merb on an application where this is the only route, we can confirm that though the route is recognized and mapped to parameters, it cannot inversely be generated by those parameters: > merb.check_request_for_route(merb.fake_request( 'REQUEST_PATH'=>'/account/abcd')) => {:controller=>"account", :action=>"show", :id=>"abcd"} > merb.url(:controller => 'account', :action => 'show', : id => 'abcd') Merb::Router::GenerationError: Named route not found: default
2.4.5 Deferred routes Deferred routes allow you to incorporate more complex logic into your routes. Again, though, this comes at the cost of being unable to generate those routes via route parameters. Below we route requests to an Ajax controller if they are XML HTTP requests (aka Ajax requests). defer_to do |request, params| params.merge! :controller => 'ajax' if request.xhr? end
Note the block parameters that defer_to receives. These are the requests from the client and the parameters from the behavior context in which they are being evaluated. This may suggest that deferred routing may also appear nested within other route statements: Merb::Router.prepare do match('/').to(:controller => 'home') do defer_to do |request, params| if Time.now.hour < 6
From the Library of Shirong Chen
54
Chapter 2: Routing
params[:action] = 'night' else params[:action] = 'day' end end end end
Although this example demonstrates the nesting of defer_to, we definitely don’t recommend using it for such a purpose. In fact, we recommend that you stay away from using deferred routing as much as possible, leaving such logic for inside your controllers.
2.5 Registering routes In Merb, the process of assigning route parameters to matches is known as the route registration. This is a necessary step in assuring that behaviors are compiled down and put into the routes array. Merb uses three equivalent methods to do this. Each varies only in name but thus provides a better semantic feel in different contexts.
2.5.1 Using to The to method is the standard way of both setting route parameters and registering them. We’ve seen the to method in past examples, and though its usage may seem obvious enough, we’ll spend some time on the details. The to method takes one optional parameter, a hash representing route parameters that are merged into existing contextual route parameters. For instance, here’s the second to method that adds in an additional route parameter to what was established by the first: Merb::Router.prepare do match('/users').to(:controller => 'users') do match('/:id').to(:action => 'show') end end
This example also displays that the to method accepts a block. This block works similarly to a block passed to the match method. More specifically, the block is evaluated in a new context where the parent behavior has been extended through the parameters that were given. Formalities aside, you can also rely on the descriptive nature of methods themselves to understand that the previous example compiles down to the following route: ==== Anonymous routes HTTP method: GET Route : /users/:id
From the Library of Shirong Chen
2.5
Registering routes
55
Params : {:controller=>"\"users\"", :action=>"\"show \"", :id=>"(path1)"}
2.5.2 Using with The with method, which is just an alias of to, is semantically used in forming blocks around routes without being chained to a specific behavior. Here we wrap with around a set of matches that are passed in a block: Merb::Router.prepare do with(:controller => "users") do match("/signup").to(:action => "signup") match("/login").to(:action => "login") match("/logout").to(:action => "logout") end end
These, of course, all compile down to routes set to be handled by the Users controller but by different actions: ==== Anonymous routes HTTP method: GET Route : /signup Params : {:controller=>"\"users\"", :action=>"\"signup\""} HTTP method: GET Route : /login Params : {:controller=>"\"users\"", :action=>"\"login\""} HTTP method: GET Route : /logout Params : {:controller=>"\"users\"", :action=>"\"logout\""}
Note that either to or register, which we’re about to discuss, could have been used in place of with, but we use with because it reads well aloud.
2.5.3 Using register The most common use of register, which is once again an alias of to, is to assure that a behavior is registered into a route even when no other route parameters need to be set. The reason this exists is because often the match method will automatically have set all your parameters for you and an empty .to would look awkward. Below we register such a behavior into a route.
From the Library of Shirong Chen
56
Chapter 2: Routing
Merb::Router.prepare do match("/:controller(/:action(/:id))(.:format)").register end
Note again, however, that register could have been to or even with and have had the same effect.
2.5.4 Redirects You can redirect routes from inside the Merb router. This is useful in maintaining compatibility after you’ve changed routes on a production application and want to assure that the bookmarks of users still work. To register a redirect, simply use the redirect method on a behavior. Note that you may also specify whether the redirect is permanent or not. If it is, the redirect will be sent with a status code of 301 instead of 302. Merb::Router.prepare do match('/stories').redirect('/articles', :permanent => true) end
2.5.5 Using symbols As we’ve seen previously, symbolic matching implicitly sets route parameters for us. However, we can also manually do so ourselves. Symbolic segment values are available as symbols within the strings used on values of the hash given to a to or any of its aliases. The following example should make this clearer: Merb::Router.prepare do match("/users/:last_name/:first_name").to( :controller => 'users', :full_name => "#{first_name} #{last_name}") end
To check that it works, we can fire up interactive Merb and send a fake request to the path /users/savas/foy: > merb.check_request_for_route(merb.fake_request( 'REQUEST_PATH'=>'/users/savas/foy')) => {:controller=>"users", :full_name=>"foy savas", :last_name=>"savas", :action=>"index", :first_name=>"foy"}
2.5.6 Using other captures We can also use other match captures in the value strings of the to hash. Bracketed numbers allow us to pull up either regex captures or symbolic segment values in the
From the Library of Shirong Chen
2.6
Other route settings
57
order in which they were matched, starting with 1. In the following example, we match the first segment as the language, then use default routes nested within: Merb::Router.prepare do match(%r{/?(en|es|fr|be|nl)?}).to(:language => "[1]") do default_routes end end
2.6 Other route settings You can do a few more standard things with routes, including setting defaults for route parameters and naming routes for references during generation. Let’s take a look at both of these.
2.6.1 Setting defaults Default values allow you to set defaults for route parameters that have not been set. To do this, we apply the default method on a behavior. For instance, below we set the controller match segment to optional but default it for Users. Note that we have to end the line with register, because default does not register the route itself. Merb::Router.prepare do match("(/:controller)/:id", :controller => /users|groups/). default(:controller => 'users').register end
We can test the results of this route registration using interactive Merb. Here it behaves as intended: > merb.check_request_for_route(merb.fake_request(' REQUEST_PATH'=>'/groups/robots')) => {:controller=>"groups", :action=>"index", :id=>"robots"} > merb.check_request_for_route(merb.fake_request(' REQUEST_PATH'=>'/robot')) => {:controller=>"users", :action=>"index", :id=>"robot"} > merb.check_request_for_route(merb.fake_request(' REQUEST_PATH'=>'/machines/robots')) Merb::ControllerExceptions::BadRequest: No routes match the request. Request uri: /machines/robots
2.6.2 Named routes Merb can identify routes by “name.” These names are convenient identifiers often used in the generation of URLs. Most of the routes we’ve used so far have gone unnamed.
From the Library of Shirong Chen
58
Chapter 2: Routing
However, we can easily add names by passing in a symbol, the name method: Merb::Router.prepare do match('/').to(:controller => 'home').name(:home) end
In interactive Merb, we can generate the root URL by passing the symbol :name to merb.url: > merb.url(:home) => "/"
Wasted typing? Of course, using a name to generate the root path may seem unnecessarily lengthy, but in practice this is not true. Named routes allow us to abstractly identify routes, whether short or long, and thus separate our need to determine what a route should look like from our need to use it in links, redirects, and elsewhere. Consequently, if at one point we decide to redefine the paths associated with particular routes, we will need to make changes only in the router. Now that’s time saved.
Remember that the url method is also mixed into Merb::Controller, so we’ll be using it heavily within both controllers and views to generate URLs for links and set the paths of redirects.
2.6.3 Setting prefixes We can prefix both names and controllers using routes. In the case of names, this means prepending a string plus an underscore to the name; this is most commonly used to semantically group associated routes. With controllers, this entails that the controller is found within a module. Let’s go through how to accomplish both of these types of prefixes.
2.6.3.1 Name prefix There are two ways to add a name prefix. The first is by passing in an extra symbol parameter to the name method: Merb::Router.prepare do match('/admin').to(:controller => 'admin').name(:admin, :home) end
From the Library of Shirong Chen
2.6
Other route settings
59
Alternatively we can use the options method: Merb::Router.prepare do match('/admin').to(:controller => 'admin').name(:home). options(:name_prefix => :admin) end
2.6.3.2 Controller prefix Merb allows you to nest controllers within modules and thus bundle sets of controllers with a similar purpose. For instance, we may nest all admin functionality within a module named Admin. In the following example, we make the router aware of such a setup: Merb::Router.prepare do match('/admin').option(:controller_prefix => :admin) do match('/:controller(/:action)').register end end
Here, paths like /admin/users route to the controller Admin::Users.
2.6.3.3 Namespaces You can achieve the effect of both name and controller prefixes more easily by using the method namespace. This is perfect for an admin module where you also want to be able to use named routes like admin_users to point to /admin/users. Below we use namespace to set both the name and the controller prefix. Merb::Router.prepare do namespace(:admin) do match('/:controller(/:action)').register end end
2.6.4 Fixatable routes Sometimes you need a route to be able to include a session ID. This is known as session fixation and is considered insecure for most purposes. The reason is that when you allow a session ID to be included in a URL, one user could trick another user into following a link with a fixated session. Once this second user logs in, the first user will be able to hijack the second user’s account. Merb therefore does not allow for fixated sessions by default, but does allow for them in certain cases so that they can pass on session-related responses to clients (like Adobe
From the Library of Shirong Chen
60
Chapter 2: Routing
Flash Player when it’s used to upload files) that are unable to handle cookies. Here’s an example of a fixatable route: Merb::Router.prepare do match('/file_uploads').to(:controller => 'files', : action => 'upload').fixatable end
Please remember to use fixatable routes only when needed, and to assure session integrity, regenerate the session ID after use.
2.7 Resource routes Resource routes are RESTful routes that provide a standard way of interacting with resources. Let’s explain how resources work in terms of the router.
2.7.1 Standard resources routing The method resources sets up various routes that allow you to access a particular resource. These routes are similar to the default routes but do not match exactly. For instance, with a default route you might use /users/show/1 to see the profile of a user. With a resources route, however, you would use /users/1. Resources routes also set up various defaults for the creation, modification, and deletion of resources. They also name each of your routes, making it easy to generate URLs. Let’s take a look at the routes generated by the following prepare block: Merb::Router.prepare do resources :turtles end
In interactive Merb we can pull up the following list: > merb.show_routes ==== Named routes Helper : delete_turtle HTTP method: GET Route : /turtles/:id/delete(.:format) Params : {:format=>"(path2)", :controller=>"\"turtles\"", :action=>"\"delete\"", :id=>"(path1)"} Helper : new_turtle HTTP method: GET Route : /turtles/new(.:format) Params : {:format=>"(path1)", :controller=>"\"turtles\"", :action=>"\"new\""}
From the Library of Shirong Chen
2.7
Resource routes
61
Helper : turtles HTTP method: GET Route : /turtles(/index)(.:format) Params : {:format=>"(path1)", :controller=>"\"turtles\"", :action=>"\"index\""} Helper : turtle HTTP method: GET Route : /turtles/:id(.:format) Params : {:format=>"(path2)", :controller=>"\"turtles\"", :action=>"\"show\"", :id=>"(path1)"} Helper : edit_turtle HTTP method: GET Route : /turtles/:id/edit(.:format) Params : {:format=>"(path2)", :controller=>"\"turtles\"", :action=>"\"edit\"", :id=>"(path1)"} ==== Anonymous routes HTTP method: POST Route : /turtles(.:format) Params : {:format=>"(path1)", :controller=>"\"turtles\"", :action=>"\"create\""} HTTP method: PUT Route : /turtles/:id(.:format) Params : {:format=>"(path2)", :controller=>"\"turtles\"", :action=>"\"update\"", :id=>"(path1)"} HTTP method: DELETE Route : /turtles/:id(.:format) Params : {:format=>"(path2)", :controller=>"\"turtles\"", :action=>"\"destroy\"", :id=>"(path1)"}
Note that all resources go to the same controller, but that there are seven standard actions: index, new, create, edit, update, destroy, and show. We’ll learn about these seven actions and their purpose in depth when we cover REST, but for now, it should suffice to know which routes go to which action. Here’s a list of examples with a brief explanation of intent:
2.7.1.1 Index • /users—retrieves a list of users • /turtles.xml—retrieves a list of turtles as XML
From the Library of Shirong Chen
62
Chapter 2: Routing
2.7.1.2 New • /users/new—requests the form needed to create a new user account
2.7.1.3 Create • /users with POST—creates a new user from POST data • /turtles.txt—creates a new turtle with POST data with confirmation in a text
format
2.7.1.4 Edit • /users/1/edit—requests the form needed to create a new user account
2.7.1.5 Update • /users/1 with PUT—updates the user with an ID equal to 1 with the POST data • /turtles/1.json with PUT—updates the turtle with an ID equal to 1 with the
POST data with confirmation in JSON
2.7.1.6 Destroy • /users/1 with DELETE—deletes the user with an ID of 1 • /turtle/1/delete with GET—deletes the turtle with an ID of 1
2.7.1.7 Show • /users/1—shows the profile of the user with an ID of 1 • /turtle/1.xml with GET—shows the details of the turtle with an ID of 1 in XML
2.7.2 Singular resource routing Sometimes a resource is all alone. In other cases the user’s interaction with that class of resources should be limited to only the one that belongs to that user. In both cases, a singular resource route is best. Here we use a singular resource route for our application’s overall admin settings (stored as one row in a table) and to set up routes for our user’s account, both times using the resource method: Merb::Router.prepare do namespace :admin do resource :settings end
From the Library of Shirong Chen
2.7
Resource routes
63
resource :account end
This creates routes similar to those created by resources but without IDs. The singularity of the resource also rules out the need for an index action. Thus the only actions routed to are show, new, create, edit, update, and destroy.
2.7.3 Using identify Sometimes a route like /users/1 is unpleasant. In fact, there are at least four reasons why such a URL may be unwanted: • You don’t want to have to remember ID numbers to see a resource’s page. • Search engines often optimize for keywords in URLs, so ID numbers are bad search
engine optimization. • ID numbers can give away how many of a resource you have. • ID numbers make scraping your data from the web easy to do.
To get around these issues, the Merb router has a method identify. By using it you can change how a resource is turned into a string in routes. Here we use identify to opt for the use of username instead of id: Merb::Router.prepare do identify User => :username do resources :users end end
Thus, routes generated with a user object use the username method for identification. url(:users, user) # => ex. '/users/foysavas'
We can also provide custom methods within our models to stringify our identifier. This method has to be defined in your model class. For those using the ActiveRecord ORM, the method to_params is supposed to serve this purpose. Consequently, the ActiveRecord plugin saves you from even having to include identify blocks in your router by automatically wrapping the router in an identify ActiveRecord::Base => :to_params.
From the Library of Shirong Chen
64
Chapter 2: Routing
2.8 Conclusion Routing is critical to an MVC web framework. Merb applications enjoy simple-to-use, semantically well-defined route definition that boosts comprehensive bidirectional route recognition and generation. We’ve also gotten a taste of what it means to be RESTful and why adherence to such standards can simplify both an application’s internals and its API. Finally, we even peered into the Merb routing internals to understand how the behavior class is used as an intermediate step toward route definition.
From the Library of Shirong Chen
C HAPTER 3
Controllers
In Merb, controllers are what tie all of your application code together. They are the C in MVC and have been built to assemble responses to the requests received from users. On occasion, a controller’s response is entirely produced by code in the controller itself. More often, though, responses are complex composites that incorporate the pulling of data through models and the rendering of views through templates. Controllers also handle the more subtle aspects of responses, including response format, MIME type, and status code. We’ll also craft within them behind-the-scenes logic that uses sessions, filters, and exceptions. When users request unavailable or impermissible data, controllers can redirect those users to more appropriate locations. Finally, Merb controllers come with the built-in ability to queue up background processes and run them outside the standard request-to-response cycle. In this chapter we will go over each of these topics as well as controller internals. While doing this, we’ll emphasize controllers themselves and defer examples involving models and views to later chapters. This may seem unconventional, but separating these concerns will, if nothing else, make this chapter an invaluable reference when all you need to do is cut to the chase. That said, there will be some blurring of focus near the end of this chapter as it weaves into the next and we discuss the rendering of views through controllers. If you’re completely new to MVC, controllers are definitely the most natural and best place to start, because they let you dig right in and produce responses from the get-go. To keep things simple, nearly all the examples in this chapter are going to be single-file Merb applications that can be run easily alongside a default set of routes.
3.1 From request to controller Since we previously went over the Merb router, it’s a good time to walk through how requests get to controllers and how controllers set up actions to handle those requests. 65 From the Library of Shirong Chen
66
Chapter 3: Controllers
3.1.1 A simple application While Merb controller actions are just standard methods of classes you can initialize and use, a Merb application itself does not call these actions. Instead, Merb initializes controllers based upon requests and then uses the controller method _dispatch to call the appropriate action. Notice that _dispatch starts with an underscore. As an application developer you don’t really need to know about it, but by using it you will avoid conflicts in the namespace of application-specific controller methods. However, we mention it here because of its central role in how controller instances deal with requests. That said, a basic understanding of both Merb::Request and _dispatch is desirable. So let’s fire up interactive Merb with the following application and imitate a request from the browser: class HelloFriend < Merb::Controller def index if params[:name] "hi #{params[:name]}" else "hi" end end end
3.1.2 How requests get dispatched When a Merb::Request is initialized, it requires at least two options to make sure it gets routed correctly: REQUEST_PATH and REQUEST_METHOD. True to their names, these two options represent the path and the method of the request. Let’s make a series of requests and observe their responses. To do this, we’ll use the Merb::Request#handle, which uses the router to first determine the appropriate controller and its action and then dispatch the request. Because we’re interested only in the body of the response, we’ll tack that onto our statement. $ merb -i -I home.rb > request = Merb::Request.new({'REQUEST_PATH'=>'/ hello_friend', 'REQUEST_METHOD'=>'GET'}) > request.handle.body => 'hi' > request = Merb::Request.new({'REQUEST_PATH'=>'/ hello_friend', 'REQUEST_METHOD'=>'GET', QUERY_STRING=> 'name=you'}) > request.handle.body => 'hi you'
From the Library of Shirong Chen
3.1
From request to controller
67
The second request we created used QUERY_STRING. Again, as an application developer, it’s not essential for you to know about the options that define a request, but QUERY_STRING, had it come from a browser, would be the text that follows the question mark in a URL such as http://localhost:4000/hello_friend?name=you. The end effect, with respect to the controller, is the setting of params[:name] equal to 'you'.
3.1.3 The controller’s perspective Let’s imitate another aspect of request handling by creating the controller ourselves instead of using Merb::Request#handle. We won’t need REQUEST_PATH and REQUEST_METHOD, since in this example we’ll be doing the job of the dispatcher and router ourselves when we choose and initialize the appropriate controller and then invoke the right action: > request = Merb::Request.new({'QUERY_STRING'=>'name=friend'}) > controller = HelloFriend.new(request) > controller.index => "hi friend"
Notice that this does not return a full response as in the previous examples but instead simply the body. This is because the job of each controller action is simply to return the body for the response. As we’ll discuss later on, the body can be more complex than a string and may use view template rendering through the method Merb::Controller#render. Aside from the body, the remainder of the response is typically set through various side effects of the controller code. To replicate the full response in interactive mode, we can more closely follow the path that the dispatcher takes and invoke the handling of a request with the Merb::AbstractController#_dispatch method we spoke of earlier: > request = Merb::Request.new({'QUERY_STRING'=>'name=there'}) > controller = HelloFriend.new(request) > controller._dispatch(:index).body => "hi there"
The second line initializes the HelloFriend controller. Notice how it requires a request as part of initialization. This is essential, as it allows the controller object to be aware of the request it is handling and interpret its parameters. Finally, we use _dispatch(:index) to indirectly perform the index action, and body to give us only the body of the return. There are two other major concerns that _dispatch takes care of, sessions and filters, both of which we will discuss later on.
From the Library of Shirong Chen
68
Chapter 3: Controllers
3.2 The controller classes All controllers in Merb inherit from the class Merb::AbstractController. This class was designed to allow the Merb core to be as extensible as possible. This is because, despite the fact that MVC and web frameworks now seemingly go hand in hand, the concept of the controller can apply well outside the web request controller. For example, sometimes you’ll want a controller class to manage the sending of emails. Other times, reminiscent of the original inspiration for the MVC design pattern, you’ll want a controller to determine the behavior of a particular widget embedded in the context of a response’s larger view template. Both of these are possible thanks to Merb’s abstract controller. At this point, if you feel a bit lost with regard to abstract controllers, don’t worry. What’s important to know right now is that all Merb controllers are actually subclasses of either Merb::AbstractController or one of its descendants. Within the Merb core, these descendants are Merb::Controller, Application, and Exception. Interestingly enough, these three classes, in the order in which they were just listed, form a descending chain of subclasses. In other words, Exception is a subclass of Application, which is a subclass of Merb::Controller, which itself is a subclass of Merb::AbstractController. We’ll go over each of these in this section and then describe how to use them in the rest of the chapter. In later chapters we’ll also see two more subclasses of Merb::AbstractController—MailController and PartController—both of which exemplify the ability of Merb::Abstract Controller to be subclassed in different ways for extremely practical purposes. However, before we get into these specific controller classes and their methods, it’s important to know that many of the methods you’ll find in the source of each of these classes begin with an underscore. This is because Merb controller classes seek to avoid populating themselves with method names you may otherwise want to use. Additionally, methods that start with underscores are understood to be outside the scope of the Merb public API and therefore don’t really need the attention of the application developer. With all this in mind, please note that we are simply enriching your understanding of the framework internals. Finally, you should know that some controller instance methods are called actions. This is because they are responsible for the act of responding to requests. In general, if you’re wondering whether a method is available as an action or not, you should first check if it is a public instance method. If it is, then it’s probably an action. There is, however, one exception, and that’s if the method has been added to the list of nonaction methods. This list is maintained so that you can prevent users of your application from sending requests directly to these methods. Not so surprisingly, most public instance methods in the core controller class appear in this list.
From the Library of Shirong Chen
3.2
The controller classes
69
Table 3.1 General methods Method
Use
controller_name
Returns the controller’s name in snake case with a / separating module namespaces
inherited
Adds a subclasses controller name to subclasses_list
subclasses_list
Holds a list of subclasses
3.2.1 The abstract controller Despite being the eldest parent class of all controllers, Merb::AbstractController makes very few specific demands on its descendants, leaving most of its methods as nothing more than stubs. Ultimately this means that Merb::AbstractController, true to its name, acts more as an ideal form of what a controller should look like than anything else. Let’s go over the prototypical methods that have been included in Merb:: AbstractController. First we’ll look at class methods and then instance methods.
3.2.1.1 Class methods There aren’t many API class methods in Merb::AbstractController, but those that do exist well characterize what a controller is all about. They also give you an idea of what sort of attributes and logic can be applied throughout a controller class and across actions. In Tables 3.1, 3.2, and 3.3, we’ve divided the class methods into general methods, filter methods, and render methods. Table 3.2 Filter methods Method
Use
add_filter
Adds a filter to a filter chain (typically not used directly)
after
Adds a filter to the after filter chain
before
Adds a filter to the before filter chain
skip_after
Skips a filter in the after filter chain
skip_before
Skips a filter in the before filter chain
skip_filter
Skips a filter previously added in a filter chain
normalize_filters!
Ensures that filter option values are arrays (typically not used directly)
From the Library of Shirong Chen
70
Chapter 3: Controllers
Table 3.3 Render methods Method
Use
default_layout
Resets layout options
layout
Sets the layout to be used or disables layouts when nil or false is given
render_options
Sets class-level layout options
template_roots
The root location of the templates the controller uses
template_roots=
Sets the root location of the templates the controller uses
3.2.1.2 Instance methods There are more API instance methods than class methods when it comes to Merb::AbstractController. Here we’ve divided them into general methods, URL methods, and view methods, as shown in Tables 3.4, 3.5, and 3.6. We’ve spoken before of using Merb outside web development, but the inclusion of URL methods in the abstract controller really pivots Merb use toward the web framework.
3.2.2 The Merb controller All Merb controllers inherit from the class Merb::Controller. This controller fully implements the web functionality for controllers. Therefore, it’s here that we’ll find the inclusion of request, parameter, and header methods. The API methods are broken down into class methods and instance methods; let’s go over these newly added methods.
3.2.2.1 Class methods The Merb::Controller class methods mainly serve to define what methods are available as actions and what formats are provided by those actions. Consequently, we’ve divided the methods introduced by the Merb::Controller into general and format methods, shown in Tables 3.7 and 3.8.
Table 3.4 General methods Method
Use
action_name
The action name associated with the controller instance
action_name=
Sets the action name associated with the controller instance
controller_name
Equivalent to the same class method
From the Library of Shirong Chen
3.2
The controller classes
71
Table 3.5 URL methods Method
Use
absolute_url
Returns the absolute URL of a route, including host and protocol
relative_url
Returns the relative URL of a route by route name or route parameters
url
Same as relative_url
Table 3.6 View methods Method
Use
body
Returns the body of the response being created
body=
Sets the body of the response being created
capture
Calls the capture method of the appropriate template engine
capture_erb
Captures an ERB template block, returning a string
catch_content
Returns the content “thrown” with a particular symbol
clear_content
Clears rendered content so another template can be rendered
concat
Calls the concat method on the appropriate template engine
concat_erb
Concats an ERB template block to the buffer
content_type
Gets the content type for the controller response
content_type=
Sets the content type for the controller response
partial
Used within templates for the embedding of templates
render
Renders a view with options, returning a full response with body
render_all
Calls render without any transformation
render_html
Calls render with the HTML MIME set
render_js
Calls render with the JS MIME set
render_json
Calls render with the JSON MIME set
render_text
Calls render with the text MIME set
render_xml
Calls render with the XML MIME set
render_yaml
Calls render with the YAML MIME set
template_roots
Equivalent to the class method with the same name
template_roots=
Equivalent to the class method with the same name
throw_content
Evaluates a template and stores it as a string for later use
thrown_content?
Checks whether content has been thrown
From the Library of Shirong Chen
72
Chapter 3: Controllers
Table 3.7 General methods Method
Use
callable_actions
Returns a set of all callable actions
hide_action
Hides an action, removing it from the set of callable actions
show_action
Adds an action to the set of callable actions
3.2.2.2 Instance methods The instance methods of Merb::Controller serve a number of purposes, most notably the need to make request and session data accessible to the action code. Other methods exist simply to aid in formatting. Tables 3.9 through 3.13 cover the most common API methods.
3.2.3 The application controller If you create a standard Merb application with merb-gen, you will find two files in app/controllers/. The first of these, Application, is meant to serve the parent class of all the application’s other controllers. This allows you to centralize shared code that would otherwise be found across your application’s controllers. For instance, here we have placed a filter that ensures that all web requests come from logged-in users: class Application < Merb::Controller before :ensure_authenticated end
Table 3.8 Format methods Method
Use
class_provided_formats
An array of formats the controller provides in responses
class_provided_formats=
Sets the array of formats the controller provides in responses
clear_provides
Clears the array of provided formats
does_not_provide
Asserts that an array is not provided
only_provides
Limits the array of provided formats to what is listed
provides
Appends an array of formats to the existing array of provided formats
reset_provides
Resets the array of provides
From the Library of Shirong Chen
3.2
The controller classes
73
Table 3.9 General methods Method
Use
headers
A hash containing the headers from the request
params
A mash containing the parameters from the request
status
The status code that will be sent
status=
Sets the status code for the response
request
The request object that got routed to the controller
rack_response
The rack response that will be delivered
redirect
Causes a redirect to a URL (note: will not exit from the action logic!)
message
The message that was included in the request
Table 3.10 Session methods Method
Use
cookies
The cookies from the request
delete_cookie
Deletes a cookie
set_cookie
Sets a cookie variable
session
Accesses the abstracted session store
Table 3.11 Format methods Method
Use
class_provided_formats
Proxy method for the class method
class_provided_formats=
Sets the proxy method for the class method
does_not_provide
Sets the formats not provided by an action
only_provides
Limits the formats provided by an action
provides
Describes the formats provided by an action
From the Library of Shirong Chen
74
Chapter 3: Controllers
Table 3.12 Escaping methods Method
Use
escape_xml
Escapes XML entities for use within XML
h
Escapes HTML entities for use within HTML
html_escape
Same as h
3.2.4 The exceptions controller The other controller generated as part of a Merb application is the exceptions controller. It is subclassed from Application and is used to craft error responses you may have already encountered during development. Note that it is common to extend or override the default responses. So, for instance, below we have created a new controller action that handles unauthenticated users. class Exceptions < Merb::Controller def unauthenticated render :format => :html end end
3.2.5 Other controllers As previously mentioned, Merb::Controller is not the only subclass of Merb:: AbstractController. In later chapters we’ll see the Merb::MailController and Table 3.13 Other methods Method
Use
nginx_send_file
Sends files through nginx
render_chunked
Renders the response in chunks
render_deferred
Mongrel-only method that defers rendering, allowing for other responses to be sent
render_then_call
Renders its parameter and then runs the block
run_later
Places a block in the worker queue
send_chunk
Sends a chunk
send_data
Sends binary data
send_file
Sends a file
stream_file
Streams a file
From the Library of Shirong Chen
3.3
Custom controller classes
75
Merb::PartController, each of which controls the flow of logic with actions without directly handling web requests.
3.3 Custom controller classes Now that we have described the various core controller classes as well as followed the pathway through which a request is handled, this is a good time to create our own controllers.
3.3.1 Controller location In the standard Merb application layout, all web controllers are located within the directory app/controllers/. We say all web controllers because the other controllers, MailController and PartController, tend to live elsewhere in other directories. However, if you’re using the flat layout, you’ll find that all of your controllers are kept in one file, application.rb. Also, in the case of the very flat layout, your controllers basically have nowhere else to live but as part of a single file. Note that both the flat and very flat layouts are generated with a custom controller subclassed directly from Merb::Controller instead of Application. This seems to be based on the assumption that you’ll be making an application small enough that it does not need a parent class for all controllers. However, don’t let that restrict you—if you’re using the flatter layouts and need Application or Exception, by all means, code them in. Even though it’s extremely rare to see someone deviate from the default locations, you can change the location of your controllers if you want. To do this, you can define a framework hash for Merb::Config inside the file config/framework.rb. This alters the default framework locations at Merb boot-up, but make sure you mention all locations you want to be set, because framework.rb doesn’t merge with the default settings. The following snippet shows, among other things, how to set the location of controllers to controllers/ as opposed to app/controllers: Merb::Config[:framework] = { :controller => Merb.root / "controllers", :view => Merb.root / "views", :model => Merb.root / "models", :lib => Merb.root / "lib", :public => [Merb.root / "public", nil], :router => [Merb.root / "config", "router.rb"] }
From the Library of Shirong Chen
76
Chapter 3: Controllers
To check that this has had an effect or just to find out where your application thinks controllers should be, you can always fire up interactive Merb and have it evaluate the following: $ merb -i > Merb.dir_for(:controller) => "/home/foysavas/src/the-merb-way/code/framework_changes/ controllers"
3.3.2 Naming controllers By convention, the filename of a controller file should be the snake case of the contained controller’s class name. For example, a controller named OrderTransactions would be contained in a file named order_transactions.rb. This isn’t strictly necessary, but it’s good practice and a convention that others can recognize. As long as your controllers are located within the controller directory or one of its subdirectories, they will be loaded at boot. Feel free to name each controller whatever you’d like, but keep in mind that the names of controllers could collide with other class names in the global namespace. This specifically becomes a problem with models. Consequently, the convention is to name controllers that have corresponding models in the plural form. For example, you’ll often find a controller named Users when there’s a model named User. If there is no corresponding model, don’t worry; just name your controller whatever makes the most sense. Welcome and Home are good examples of names for a controller that would handle the landing page of your application.
3.3.3 Organizing controller methods The organization of a controller’s methods is critical to how a controller responds to requests. In general, you should define one public instance method for each type of request a controller will handle. If you need other methods that won’t handle requests directly, you should leave them for near the end of your controller and mark them either private or protected. Both private and protected, in the context of Merb controllers, have the same effect, as it would be odd to have two Merb controller instances working together during a single request. Typically, you use private or protected methods in your controller classes because they allow you to • Share a nonaction method among controller actions • Make methods available to all controller subclasses • Increase the readability of code within your controller
From the Library of Shirong Chen
3.3
Custom controller classes
77
3.3.3.1 Sharing a nonaction method In this fabricated example we use a private method as a means of accessing data: class ColorTriad < Merb::Controller def index get_all_colors.join end def show get_all_colors[params[:id]] end private def get_all_colors ['red','blue','green'] end end
In our model-less world of controller-only applications this makes sense. However, more realistically, such a method would most likely encapsulate several lines of data, retrieving code that would otherwise appear in multiple controller actions. A perfect example of this is the case of the show and edit methods of a RESTful resource.
3.3.3.2 Making methods available to subclasses Sometimes you want to make a method available to more than one controller. The most typical way of doing this is to make it a private method within the Application class: class Application < Merb::Controller private def users_in_groups { 'me' => ['Merb Developers','Likes Durian'], 'you' => ['Merb Developers','Durian Stinks!'] } end end class Users < Application def index users_in_groups.map do |u, g| u + ' is in ' + g.join(',') end.join("\n") end end
From the Library of Shirong Chen
78
Chapter 3: Controllers
class Groups < Application def index groups = [] users_in_groups.each do |u, g| groups[:g] << u end groups.map do |g, u| g + ": " + u.join(',') end.join("\n") end end
This highly contrived example shows us using a private method in Application to retrieve a hash of information. After we go over more Merb functionality like models and filters, the practical use of parent private methods will become clearer. For now, recognize that it is a clean way to organize controller code.
3.3.3.3 Increasing readability of an action Often a controller action can get lengthy and hard to read. But by moving lines of code into private methods, we can increase readability with clear names. The following example shows how an action can be arranged in an understandable way even if all functionality is not in place: class Users < Merb::Application def show find_user hide_private_information render end private def find_user # ... end def hide_private_information # ... end end
From the Library of Shirong Chen
3.4
Filters
79
3.3.4 Setting callable actions Merb::Controller and its descendants were created to directly handle requests. Thus
by convention, all public instance methods are callable actions, but if you’re constructing your own variant of the Merb controller, you may need a public method that isn’t available to be called as an action. For example, the Merb::Controller class has the following public methods but has hidden them from being called as actions: request, headers, status, and rack_response. This is because all of these methods are used to manipulate a controller instance in the larger context of sending back a response and thus must be made public.
3.3.4.1 Hiding an action In case you too want to come up with a public method that’s best to hide, you can easily do so by using the hide_action class method within your controller class: class SpecialController < Merb::Controller hide_action :special def special # ... end end
3.3.4.2 Showing an action Likewise, you can show an action that was previously hidden using the show_action class method. This applies to subclasses of controllers with hidden actions. class SuperSpecialController < SpecialController show_action :special end
3.4 Filters Often you want to call a method before or after the performance of a controller action. Filters allow you to do so for multiple actions without having to explicitly evoke the relevant method in each action. Typical examples of filters include authentication, resource checking, and application auditing. Filters conventionally appear at the top of a controller and take either a method name or a Proc. An optional hash can specify to which actions the filter should apply. Multiple filters can apply to a single action and are executed as part of a filter chain. It’s good to know that filters are available to all
From the Library of Shirong Chen
80
Chapter 3: Controllers
controller classes descending from AbstractController, including MailController and PartController.
3.4.1 Before filters Before filters are called before any of the action code has been executed. They are created using the before class method, which is available to all descendants of AbstractController. The first parameter of before can be either a method name or a Proc. Method names may be in the form of strings but are typically symbols. Thus, all of the following filters are equivalent: before :authorize before "authorize" before Proc.new{ authorize }
Note, however, that a before filter specified by the same method name overrides previous before filters in the before filter chain. Consequently, if you actually used the code shown above in a controller, authorize would be called only twice. The method called by a before filter is typically a private or protected method of the controller, and you will often see filters appearing as follows: class Home < Merb::Controller before :set_favorites def index @favorite_framework end private def set_favorites @favorite_framework = 'Merb' end end
You should know that Merb filters are evaluated in the context of the controller object. Thus, when your before filter is in the form of a Proc, you are able to set instance variables to be used later on: class Home < Merb::Controller before Proc.new { @favorite_framework = 'Merb' } def index @favorite_framework end end
From the Library of Shirong Chen
3.4
Filters
81
Before filters are also able to catch :halt exceptions, which is useful for redirecting or diverting unauthorized or incorrect requests: class WhoseHome < Merb::Controller before :check_last_name, def index "welcome home" end private def check_last_name unless params[:last_name].downcase.in?(['savas','chou']) throw :halt, "sorry, this isn't your home" end end end
3.4.2 After filters An after filter can be created using the after class method of AbstractController. Similarly to before, it takes either a method name or a Proc, but it is evaluated after the performance of the action. The most typical use of an after filter is either to log the use of your application or to do cleanup or postperformance processing on resources: class LogVisits < Merb::Controller after :log_visit def index "hi" end def logout "bye" end private def log_visit case action_name when 'index' Merb.logger.info("Someone's here.") when 'logout' Merb.logger.info("Someone's leaving.") end end end
From the Library of Shirong Chen
82
Chapter 3: Controllers
You can make last-minute alterations to the response by accessing the @body attribute: class FrameworkSwitcher < Merb::Controller after Proc.new{ @body.gsub!("Rails","Merb") } def index "Rails Rocks!" end end
One thing to note with after filters is that the client does not receive a response until the after filter chain is complete. If you run the example below, you’ll end up waiting a random number of seconds for the actual response. If client response time is an issue, consider using Merb’s deferred instead, which is discussed near the end of this chapter. class SlowWakeUp < Merb::Controller before :alarm_and_time after :keep_sleeping def index "Good morning!" end private def alarm_and_time Merb.logger.info "*alarm clock sounds*" @start = Time.now end def keep_sleeping sleep(rand(7)) @body << " Woke up in #{Time.now - @start} seconds." end end
3.4.3 Filter options When you want a filter to be applied only to particular actions of a controller, you can do so by specifying them as an array of symbols keyed to :only in the option hash: before :read_file, :only => { :show, :edit }
From the Library of Shirong Chen
3.4
Filters
83
If instead you want a filter to not apply to particular actions of a controller, you can specify them as an array of symbols keyed to :exclude in the option hash: before :authorize, :exclude => { :login }
Sometimes you want to have filters applied only in the case of more complex conditions. The :if and :unless options allow you to do this with either a method name or Proc: class RoomService < Merb::Controller before :automate_response, :if => Proc.new { Time.now.hour < 6 } def index "Room service - may I help you?" end private def automate_response throw :halt, "Sorry, room service is unavailable at the moment." end end
You can also pass parameters to filters by using :with, but when doing so you’ll have to make sure that the arity of the filter matches the size of the :with array: before :has_role, :with => ['Admin']
3.4.4 Skipping filters On occasion you’ll want to skip a filter that was defined in a superclass. To do this, you can use the skip_before and skip_after class methods from AbstractController: class Application < Merb::Controller before :login_required private def login_required unless session[:user_id] throw :halt, Proc.new { |c| c.redirect url(:controller => 'sessions', : method => 'new') } end end
From the Library of Shirong Chen
84
Chapter 3: Controllers
end class Sessions < Application skip_before :login_required, :only => %w{ new create } end
3.5 Redirects There are many cases when a user has sent a request that should ultimately get redirected. You can make this happen by using the controller method redirect. The redirect method accepts a string for a URL as its first parameter and also takes an optional hash. This optional hash may include a Boolean setting of :permanent and a :message to pass on: redirect "/login" redirect "http://www.merbivore.com", :permanent => true redirect "/", :message => 'Please, sign up or log in to continue."
3.5.1 A redirect caveat One important point to remember when using redirects is that they do not stop the execution of the controller action, and whatever code is left to evaluate will run before the redirect is sent. The controller action, however, will not get the chance to return a value. The following example should make this clear: Merb::Router.prepare do to(:controller => 'recruiters_dilemma') do match('/').to(:action =>'index') match('/show_prize').to(:action => 'show_prize') match('/apologize').to(:action => 'apologize') end end class RecruitersDilemma < Merb::Controller def index if params[:people].to_i >= 10 redirect '/show_prize' else 'How many people have you recruited? Append it as a number after "?people=" to this URL.' end end
From the Library of Shirong Chen
3.5
Redirects
85
def show_prize redirect '/apologize', :permanent => true Merb.logger.warn "Here comes another winner!" "Your brand-new Ferrari F355!" end def apologize "Our apologies." end end
In the previous example, "Here comes another winner!" is logged, but because of the redirect, which is essentially a change in management for the final response, none of us gets our F355. This example is harmless enough for both user and developer, but you should be cautious when using redirects and make sure that unnecessary business logic does not run. Another thing to note in this example is the use of :permanent in the second redirect. This effectively changes the status code of the response from 301 to 302 and from the browser’s perspective assures that '/show_prize' should always point to '/apologize'.
3.5.2 Redirects after POST requests The most common use of redirects in practice is to redirect users after they have posted content. You may not be familiar enough with Merb to completely understand the following example, but here a user has posted what looks like a new entry of some sort, and if it saves without error, the user is redirected to the list of entries: class Entries < Application # ... def create @entry = params[:entry] if @entry.save redirect '/entries' else render :new end end # ... end
From the Library of Shirong Chen
86
Chapter 3: Controllers
3.5.3 Redirecting in before filters At other times, controllers redirect users before the action ever starts. This most likely occurs because a particular resource doesn’t exist or because a user has yet to log in. Before filters are a great place for this kind of logic because these cases tend to apply to multiple controller actions and are simplified logistically by gaining access to the throwing of halts. Let’s take a look at an example: Merb::Router.prepare do to(:controller => 'users_names') do match('/').to(:action =>'index') match('/:username').to(:action => 'show') end end DATA = { :users => { :foysavas => 'Foy Savas', :sophiachou => 'Sophia Chou' } }
class UsersNames < Merb::Controller before :get_by_username, :only => 'show' def index message || "Welcome" end def show "#{params[:username]} => #{@user}" end private def get_by_username if (@user = DATA[:users][params[:username].to_sym]).nil? throw :halt, Proc.new{ redirect '/', :message => "#{params[:username]} not found" } end end end
An important takeaway from this example is how the redirect was accomplished. Before filters are able to catch thrown halts that can also take Procs. These Procs are
From the Library of Shirong Chen
3.6
Exceptions
87
evaluated using instance_eval. By putting a redirect in this Proc, we are able to redirect the controller without having to worry about more of the controller code being evaluated. Though it may seem redundant to mention, throw :halt does not work anywhere but inside the before filter chain, so don’t try to use it anywhere else or you’ll just get an uncaught exception and an error that looks like this: ˜ uncaught throw ‘halt' in thread 0xd6bad0 - (ThreadError)
3.6 Exceptions If you’re looking for something similar to throw :halt that you can use outside before filters, raising exceptions may be your answer. By raising particular controller exceptions, you can halt controller execution midway and internally reroute the request to the Exceptions controller. There’s one important thing to note, though: Unlike redirects, using Exceptions means it’s the end of the line for your user and an error page will be displayed. Yes, you’ll be able to make this error page pleasing and include a cute mascot, but ultimately it is an error page, so if you’re going to use it, make sure that’s what you intend and not a more subtle redirect.
3.6.1 Raising an exception Many exceptions have already been defined for you within the Merb core. Let’s go through an example using one of the predefined exceptions to get a basic feel for how they are used: Merb::Router.prepare do match('/').to(:controller => 'exceptional', :action =>'index') end class Exceptional < Merb::Controller def index raise NotFound end end
Here, when we request the index, a Not Found error appears along with a 404 status code in the response.
3.6.2 Controller exceptions When you raise an exception somewhere in your controller code, it is rescued by the dispatcher and redispatched to be handled as a controller exception. These exceptions
From the Library of Shirong Chen
88
Chapter 3: Controllers
are defined in the module Merb::ControllerExceptions and offer an exception for each of the W3C standard HTTP status codes. If the error raised is not among these existing controller exceptions, Merb interprets it as an internal exception and uses the class InternalServerError to handle it. This is the error page you’ve seen in the past when something has gone wrong with your code.
3.6.3 The exceptions controller The exceptions controller is one of the core controller classes we talked about earlier. Its purpose is to translate an exception into a response. Merb handles the routing behind the scenes, but what’s important to know is that each exception class name is snake-cased and then called as an action method name in Exceptions. Below we see a custom controller exception that, with the help of the exceptions controller, responds with a string. Merb::Router.prepare do match('/').to(:controller => 'sad_hacking', :action =>'index') match('/root').to(:controller => 'sad_hacking', :action =>'root') end class SadHacking < Merb::Controller def index "hack me" end def root raise BadHacker end end class BadHacker < Merb::ControllerExceptions::Unauthorized; end class Application < Merb::Controller ; end class Exceptions < Application def bad_hacker "that was pathetic..." end end
If you request /root, the BadHacker exception will be handled in the same way as an unauthorized user. The only difference, of course, is the custom response found in the exceptions controller.
From the Library of Shirong Chen
3.7
Rendering templates
89
3.7 Rendering templates Rendering templates is one of the most common actions you’ll find at the end of a controller. Consequently, there are a number of methods found in the Merb::Controller class that deserve our consideration now before the chapter on views (Chapter 4). We’ll go over each of these methods, but before we do, we’ll need to get comfortable with how Merb brings together controllers and templates.
3.7.1 How templates are compiled Templates typically exist as files stored in app/views, all of which Merb opens up and inlines into a template method. These template methods are then accessible by original filename to any of the subclasses of Merb::AbstractController. Typically, a controller associates itself with a particular path, and then the action that calls render appends its name as a file followed by a response format extension and then a template format. For example, the action Users#index associates itself by default with the template at app/views/users/index.html.erb. The .html is there since HTML is the default format that Merb uses for responses. The .erb is for ERB, which is the default template format. ERB is a versatile template type that has the potential to render HTML, XML, JSON, or really any textual format. However, there are other template engines that may be more suitable for particular responses. These alternatives include Haml, which excels in structuring HTML and XML with extreme brevity. For now, though, since we’re currently considering templates only in the context of controller logic and not in that of the view, we’ll use only ERB. In fact, in order to keep with our desire to produce single-file Merb applications, we’re going to do something completely unconventional: We’re going to include our templates as virtual files. You will probably never do such a thing, but getting a good feel for how templates are compiled through imitation will definitely demystify what otherwise may seem like magic. Let’s start off with a simple example: Merb::Config.use { |c| c[:framework] ] }, c[:session_store] c[:exception_details] }
= { :public => [Merb.root / "public", nil = 'none', = true
Merb::Router.prepare do match('/').to(:controller => 'weird', :action =>'index') end
From the Library of Shirong Chen
90
Chapter 3: Controllers
Merb::BootLoader.after_app_loads do vf = VirtualFile.new("hi <%= @var %>",'app/views/weird/ index.html.erb') Merb::Template.inline_template(vf) end class Weird < Merb::Controller def index @var = 'friend' render end end
If you run the code above as a single-file application and check http://localhost: 4000, you’ll find a friendly 'hi'. You’ll recognize that pulling this off wasn’t as easy as it was in the past. The first thing we did was add a Merb::BootLoader. after_app_loads block. As we saw in Chapter 1, this block runs after our application code has been loaded. Here we use it to first create a fake template using a virtual file and then inline that virtual file using inline_template. Notice that the virtual file takes two parameters for initialization. The first is what would have been the actual content of the file. The second is a fake file path. Here we’ve matched it to what the Weird#index method would naturally associate itself with. Now let’s take a look at the content of the virtual file. You’ll notice some text, some odd bracketing, and a class method inside. We’ll go over these odd brackets in the next chapter, but suffice it to say that they designate sections of an ERB template that should be interpreted in Ruby, and in this case also appended to the template. But why a class variable? Remember that all templates are included as methods of the abstract controller. Consequently, class methods from the controller instance are also available to the template itself.
Speed through common sense or just contentious? Merb has always emphasized speed, but oddly enough it achieves this most often through common sense. We saw that Merb inlines its templates and includes them as methods. Not only is this fast, but given the nature of template variables, it also makes good sense. Not everyone is pleased, though. Many believe that Merb’s inlining of templates makes it only quasi-MVC because it effectively squeezes together the controllers and views. In this respect, Rails 3 will notably not stay with the Merb way of doing things.
From the Library of Shirong Chen
3.7
Rendering templates
91
3.7.2 Basic rendering In order to render a template, we use the controller method render. Note that render isn’t some magical method that takes control of your action. Instead it simply evaluates a template returning a string. Thus, it must be the final evaluation step on your controller action to have any effect. In the following modification of our first example, the rendered template is mistakenly not the final step of the action, and 'bye' ends up being the response instead: Merb::BootLoader.after_app_loads do vf = VirtualFile.new( "hi <%= @var %>", 'app/views/weird/index.html.erb') Merb::Template.inline_template(vf) end class Weird < Merb::Controller def index @var = 'friend' render 'bye' end end
The render method can take various parameters. The simplest of these is a string. When you render a string, the render method embeds the string within the layout (more on this later) or, if no layout is available, simply returns the string itself. Here is an example: class Example < Merb::Controller def index render "hello world" end end
3.7.3 Template The render method may alternatively take a symbol as one of its parameters. This switches the template to be rendered from the action name to a stringified version of the symbol. This is most commonly used to render a form when form submission has failed. We’ll see an example of this in Chapter 6 on helpers, but for now, here’s a simpler example based upon the first. Note how the template’s name has been changed. Merb::BootLoader.after_app_loads do vf = VirtualFile.new( "hi <%= @var %>",
From the Library of Shirong Chen
92
Chapter 3: Controllers
'app/views/weird/not_index.html.erb') Merb::Template.inline_template(vf) end class Weird < Merb::Controller def index @var = 'friend' render :not_index end end
Another way of specifying the template is to pass in an optional hash with :template with the template’s name as its value: class Weird < Merb::Controller def index @var = 'friend' render :template => :not_index end end
3.7.4 Formats We can also specify the format with which an action will be rendered. By default this format is HTML, but we can override this by using the hash key :format. Be aware that this requires that a template with that filename exists. Merb::BootLoader.after_app_loads do vf = VirtualFile.new( "{'response': 'hi <%= @var %>'}", 'app/views/weird/not_index.json.erb') Merb::Template.inline_template(vf) end class Weird < Merb::Controller def index @var = 'friend' render :format => :json end end
We can accomplish the same thing in two other ways. The first is setting the content_type, which may have been automatically set by the router from the value for
the route parameter format: class Weird < Merb::Controller def index
From the Library of Shirong Chen
3.7
Rendering templates
93
@var = 'friend' format_type = :json render end end
The second is using the method render_json. Merb adds a render_ prefixed method for each of the MIME types to which it can respond. Here we use it in our action: class Weird < Merb::Controller def index @var = 'friend' render_json end end
Occasionally there are several ways to do the same thing. Our recommendation is to stick with the simplest. Of course, deciding what that is, is a matter of opinion. We can also specify the formats provided by a controller when we want to be explicit. These can then be automatically specified as the formats in request parameters. The two methods that open up this possibility are provides and does_not_provide. Both of these are available as class and instance methods. The example below demonstrates both methods. Merb::Router.prepare do default_routes end Merb::BootLoader.after_app_loads do templates = [ VirtualFile.new( "{'response': 'hi <%= @var %>'}", 'app/views/weird/index.json.erb'), VirtualFile.new( "hi <%= @var %>", 'app/views/weird/index.html.erb'), VirtualFile.new( "Only in HTML", 'app/views/weird/extra.html.erb') ] templates.each do |t| Merb::Template.inline_template(t) end
From the Library of Shirong Chen
94
Chapter 3: Controllers
end class Weird < Merb::Controller provides :html, :json def index @var = 'friend' render end def extra does_not_provide :json render end end
3.7.5 Status You can also set the status code used in the response through render by setting :status to some integer. This, however, doesn’t actually affect the render itself but instead only incidentally sets the value of status used later by the Merb server in crafting the headers for the response. class Missing < Merb::Controller def index @var = 'friend - are you lost?' render :status => 404 end end
3.7.6 Layout A layout is a parent template in which a rendered template can be embedded. Layouts typically reside in app/views/layouts/. The default layout in a controller without a specified layout is app/views/layout/:controller.html.*, where :controller is the controller’s name in snake case. If that is not found, the controller looks for app/views/layouts/application.html.*. Either way, this also requires that we indicate where the render content should be placed. To do this we must use the method catch_content :for_layout. Below we put all of this together to produce the output "START hi friend FINISH": Merb::BootLoader.after_app_loads do vf = VirtualFile.new(
From the Library of Shirong Chen
3.7
Rendering templates
95
"START <%= catch_content :for_layout %> FINISH", 'app/views/layout/weird.html.erb') Merb::Template.inline_template(vf) vf = VirtualFile.new( "hi <%= @var %>", 'app/views/weird/index.html.erb') Merb::Template.inline_template(vf) end class Weird < Merb::Controller def index @var = 'friend' render end end
We can specify an alternative layout in various ways. We can do this controller-wide using the layout method. Alternatively, we can set it as the value of the :layout key in the render hash. Note that if we use the value nil, no layout will be used and the template will be rendered alone: class Example < Merb::Controller layout :elsewhere def index render :layout => nil end end
3.7.7 display An alternative way to render a template is to use the method display. In scenarios where there is an object that essentially forms the entirety of the response, this method may be a better fit than render, as it falls back on serialization of that object when an appropriate template isn’t found. We’ll use this effectively to handle multiple MIME templates, including XML and JSON, in Chapter 6. For now, the following contrived example shows the gist of the intent: Merb::BootLoader.after_app_loads do vf = VirtualFile.new( "hi", 'app/views/example/index.html.erb') Merb::Template.inline_template(vf) end
From the Library of Shirong Chen
96
Chapter 3: Controllers
class Example < Merb::Controller provides :html, :json, :xml def index obj = {:first => [1,2,3]} diplay obj end end
3.7.8 render_chunked Occasionally we may want to send data chunked to save on memory usage. This would make sense especially if we were going to send over a large log file that we were reading from a private location. In this example taken from the API, we pass the method render_chunked a block that contains looped calls to send_chunk: def stream prefix = '' suffix = "
\r\n" render_chunked do IO.popen("cat /tmp/test.log") do |io| done = false until done sleep 0.3 line = io.gets.chomp if line == 'EOF' done = true else send_chunk(prefix + line + suffix) end end end end
3.7.9 render_deferred If we are using the Mongrel web server, then another method available to us is render_deferred. When we use it in place of render, we can pass in a block that is executed before the final rendering. Nonetheless, the Merb thread is not locked during its evaluation of the block and is capable of handling other requests. class Example < Merb::Controller def index render_deferred do
From the Library of Shirong Chen
3.9 Sending and streaming
97
sleep 10 end end end
3.7.10 render_then_call Another method available to us is render_then_call. This method renders normally, but the block it is given is called after the response is sent. Thus, this block is evaluated outside the request-to-response cycle, making it a great place to put responseindependent, time-intensive instructions: class Example < Merb::Controller def index render_then_call "hi there" do sleep 10 end end end
We hope that sleeping on the job isn’t actually part of your application’s specifications.
3.8 run_later A better way of calling a block later on is to use the web-server-agnostic run_later method. This method places the block into the worker queue and has it run at some point after the response is handled. We can use this extensively once again for timeintensive instructions that are not necessary for the controller response. Also, because run_later is essentially just a means of pushing blocks into a queue, we can have as many of them as we like. class Example < Merb::Controller def index run_later do sleep 10 end "hi" end
3.9 Sending and streaming Sending and streaming allow us to programmatically deliver data from inside controller actions. This may seem odd at first when you could simply serve a static resource, but there are scenarios when you’ll find this helpful. For sending, this may be when you want to
From the Library of Shirong Chen
98
Chapter 3: Controllers
protect a file behind filters that among other things authorizes users. In the case of streaming, it may be because files are large and serving them a chunk at a time saves memory.
3.9.1 Sending files The method for sending a file is simply send_file. It requires only a single parameter, the path to the file, but you can explicitly set the filename, disposition, and file type through an options hash. Below we send a file only if the request comes from an authenticated user. class Files < Merb::Controller before :ensure_authenticated def book send_file('/data/books/the_merb_way.pdf', :filename => 'understanding_merb.pdf') end end
3.9.2 Streaming files Streaming is the process of sending information to the client in chunks, which saves on memory when we’re dealing with large files. The Merb method stream_file offers this benefit. In the example below, we stream data from Amazon S3 containing a user’s video intro stream_file. class Users < Merb::Controller before :find_user, :only => %w{ video_intro } def video_intro stream_file({ :filename => filename, :type => content_type, :content_length => content_length }) do |response| AWS::S3::S3Object.stream(@user.login, bucket_name) do |chunk| response.write chunk end end end # ... end
From the Library of Shirong Chen
3.10
Conclusion
99
3.10 Conclusion Controllers are an essential part of any Merb application. In this chapter we exhibited their central importance by almost exclusively coding all the examples using only controllers. Our tour of features such as filters, exceptions, and rendering should already enable many application developers to get a head start on building Merb applications. As we move on to the next chapter on views, remember that controllers and views are internally tightly coupled within the framework; the next chapter is more a discussion of Merb’s interaction with templating engines than anything else.
From the Library of Shirong Chen
This page intentionally left blank
From the Library of Shirong Chen
C HAPTER 4
Views
While web services may need to serialize data only when sending responses, users typically interpret websites visually through HTML. Consequently, in order to better separate the formatting of data from its composition, the MVC paradigm pushes us toward the convenience of views. Within a Merb application, these concretely translate to using templates from which application responses are built. Not all templates are formatted in the same way, though, and some are better suited than others for particular work. Consequently, Merb has since its beginnings been able to hook into different templating engines. Working off the Erubis and Haml engines, Merb enables the two most common template formats, ERB and Haml. We will cover both of these in this chapter as well as more specifically how to use them within Merb itself. As we’ll discover, Merb also enhances these templates in various ways, sometimes fundamentally as in the case of the BlockAwareEnhancer but most of the time more mildly through the inclusion of helpers and local variables. Ultimately, views are a critical part of nearly every web application, but the Merb way of building applications has always been to minimize the amount of logic within them. While it’s easy to start employing Ruby to its full extent within views, we strongly advise considerable restraint while crafting views. Try pushing off relevant logic to helpers, controllers, or, better yet, models.
4.1 ERB ERB, more properly known as eRuby, is the default templating system used by Merb. Because it does not make any requirements on the structure of the templates, ERB is amazingly versatile and can produce any textual format you may need for output, including, among other formats, HTML and XML. It is worthwhile to mention that nearly all the files produced by merb-gen are created using ERB templates. 101 From the Library of Shirong Chen
102
Chapter 4: Views
Merb compiles ERB using the Erubis templating engine, an extremely fast implementation of ERB. You can use Erubis by itself outside Merb, and in order to introduce the basics of ERB, we’ll do just that.
4.1.1 Basic delimiters The are two principal delimiters used within ERB: <% %> and <%= %>. If you have experience with JSP or ASP, you’ll find that their use is identical. Of the two, the first executes code, and the second executes and then inserts the result into the template ouput. The following template exhibits both: <% if @name %> <%= "Hello #{@name}!" %> <% else %> <%= "Hey you!" %> <% end %>
We can excute the file using the command erubis. Here’s the result: $ erubis hello.erb Hey you!
We are also able to pass in values for the variables from the command line using -c: $ erubis -c "@name = 'Sophia'" hello.erb Hello Sophia!
4.1.2 Removing whitespace Unlike other ERB implementations, Erubis automatically trims whitespace from its execution-only delimiters. Output delimiters, however, include both leading and trailing whitespace. If necessary you can eliminate trailing newlines using the modified delimiter <%= -%>. This may not matter for an HTML file, but if you’re using ERB to format plain text or code, you’ll definitely appreciate its existence. As an example, the following script outputs the numbers 1 through 10 all on the same line. Note that we’ve included a final line break to compensate for its final suppression. <% (1..10).each do |i| %> <%= i -%> <% end %>
From the Library of Shirong Chen
4.1
ERB
103
4.1.3 Comments If you need to quickly cancel the effect of a line of ERB in a noncommittal way, the pound sign will do the trick. Because ERB interprets text delimited by <% %> as Ruby, squeezing in a # effectively puts a stop to both execution-only and output-delimited code: <%#= "Foiled again!" %>
4.1.4 Merb’s block-aware enhancer Unfortunately, standard ERB lacks the ability to execute multiline Ruby code split among multiple delimiters. In other words, the following simply will not work: <%= "Hey " + %> <% "there" %>
This may not seem problematic at first (who would ever need to do this?), but template helper methods often take blocks that sometimes cannot reasonably fit in one delimiter. Here’s a somewhat less contrived example of a helper using a block just to get the point across. Note that it does not work in standard ERB. <% def quote(&block) ''+block.call.to_s+'
' end %> <%= quote do %> <% "HEY!" %> <% end %>
The work-around used by Rails (and most likely to be abandoned in the merge) has been to overload the use of execution-only delimiters through the inclusion of code in helper methods. This code first checks if the template being used is ERB or not and then forcefully outputs the result after interpreting the block. The Merb team turned away from this solution unsatisfied, and Yehuda Katz instead crafted an Erubis enhancer as an alternative solution. The BlockAwareEnhancer found in the Merb core does precisely what it says it does, enhancing ERB so that it can spot and use multiline blocks. This does come with one slight inconvenience, though: The ending line of a block must use a special tail character on its directive, <% =%>. With this in mind, let’s rewrite the previous nonworking example to make it suitable for use in Merb: <% def quote(&block) ''+block.call.to_s+'
'
From the Library of Shirong Chen
104
Chapter 4: Views
end %> <%= quote do %> <% "HEY!" %> <% end =%>
Note that no alteration of the helper method was needed. Let’s take a look at the source code behind the enhancer to get a better feel for how it was accomplished: module Erubis module BlockAwareEnhancer # :api: private def add_preamble(src) src << "_old_buf, @_erb_buf = @_erb_buf, ''; " src << "@_engine = 'erb'; " end # :api: private def add_postamble(src) src << "\n" unless src[-1] == ?\n src << "_ret = @_erb_buf; @_erb_buf = _old_buf; _ret.to_s;\n" end # ... # :api: private def add_stmt2(src, code, tailch) src << code src << " ).to_s; " if tailch == "=" src << ';' unless code[-1] == ?\n end # :api: private def add_expr_literal(src, code) if code =˜ /(do|\{)(\s*\|[ˆ|]*\|)?\s*\Z/ src << ' @_erb_buf.concat( ' << code << "; " else src << ' @_erb_buf.concat((' << code << ').to_s);' end end end class BlockAwareEruby < Eruby include BlockAwareEnhancer end end
From the Library of Shirong Chen
4.2
Haml
105
The final method, add_expr_literal, is where the hard work starts. Erubis calls this method while converting input when it finds a Ruby expression that should be outputted. In other words, lines of ERB surrounded with <%= ... %> are handled by this method. You’ll notice that the if statement in this method checks to see if the code ends with the beginning of a block. If it does, then it withholds the closing of the concat parameter. The add_stmt2 method is then used to recognize the added delimiter <% =%> and will close the concat parameter if it spots one. This method is unfortunately not a part of Erubis, and some monkey patching of the method convert_input can be found elsewhere in the Merb source.
4.2 Haml Haml is an alternative Ruby-infused templating language best suited for HTML and XML. Its focus is on terseness. If you are a web developer typically tortured with the verbosity of HTML and XML, this should come as a relief. Originally developed by Hampton Catlin, Haml is more recently maintained by Nathan Weizenbaum. A man is not always his work... As awesome as both Haml and Hampton Catlin are, if you run into Hampton, do not accidentally call him ‘‘Haml.” Others have done so, and he hates it.
As with ERB, you can create Haml files outside Merb and compile them into output. As we go through some examples, you may want to use haml from the command line to do just that.
4.2.1 Tags Tags in Haml are preceded by the percentage sign, %. You can use any of the standard HTML tags or dabble with your own creations as you please. Each Haml tag produces both open and close directives. For instance, the following: %div %span
produces the HTML output <span>
From the Library of Shirong Chen
106
Chapter 4: Views
The content of a Haml tag can appear directly after it on the same line, separated by some whitespace. Content not belonging to any tag can also appear simply by itself. Obviously this is not valid HTML, but Haml sets no limitation upon it. Below we demonstrate a first header tag with content inline and then some content on a second line belonging to no tag. %h1 Welcome to Z----Com! Anything is possible.
This compiles to Welcome to Z----Com!
Anything is possible.
4.2.2 Indentation Haml uses two-space indentation in order to indicate nesting. Any other form of indentation causes compilation errors, so watch out. Below we have nested a few common tags. Note that we can also choose to include tag content as if it were nested. %div %h1 AYB Are Belong to Us %span For Great Justice
This compiles to
AYB Are Belong to Us
<span>For Great Justice
4.2.3 IDs and classes Elements can also easily be given ID and class names. Prepend a # for IDs and a . for classes. Multiple classes can appear on a single element. Note that it is possible to use IDs and classes without explicitly specifying a tag, in which case a tag is used. The lines #header %h1#logo YourSite! %table.users.full
translate to
From the Library of Shirong Chen
4.2
Haml
107
4.2.4 Attributes If you need to add nonclass, non-ID attributes to tags, you can do so in Haml using curly braces immediately following the IDs and class names of an element: %img{ :src => '/logo.png' }
This compiles down to
4.2.5 Interpreting lines Lines that begin with - are interpreted as code. Their results are not outputted and are thus perfectly suited for the conditional appearance of markup or content. Because of the significance of indentation in Haml, the - is also used to represent the encapsulation of code. Consequently, no end keywords are needed or should be used. Below we set the content of an element based upon the time of day. .message - if Time.now.hour > 6 Good Evening - else Good Day
4.2.6 Outputting lines Haml outputs the results of executed Ruby code when a = is used in place of -. The following example outputs the sum of two numbers: .quiz .question What is 2+2? .answer= 2+2
From the Library of Shirong Chen
108
Chapter 4: Views
Note that it is possible to simply append the = at the end of the tag specification. This is unlike the usage of the execution-only -.
4.2.7 Outputting string lines Though the = automatically converts all objects to strings before outputting them to the template buffer, you’ll indubitably find yourself needing to format the content of some elements. The easiest way to do this is to use a string replacement like this: - name = 'Sophia' = "Hi #{name}"
For brevity, we can even drop the quotation marks and use a double equals sign, ==: - name = 'Sophia' == Hi #{name}
This also frees up the usage of both single and double quote marks, allowing us to use both without the need for escaping.
4.2.8 Sanitized lines Haml has built-in support for sanitizing lines containing HTML-sensitive characters. This can prevent malicious (or accidental) HTML injections by mapping < and > to < and >. To do so, prepend either = or == with a &: &= "<script>alert('FIRE!');" &== Don't shout fire in a <script> tag.
4.2.9 Preserving whitespace You can use the tilde, ~, in place of the equals sign to preserve whitespace within elements where it’s needed. In particular, this applies to <pre> and