Your SlideShare is downloading. ×
0
RAILS SOJOURN:
ONE MAN’S
JOURNEY
Mike Desjardins

Monday, October 14, 13
HI, I’M MIKE!
I’M FROM
PORTLAND
Monday, October 14, 13
NO, NOT THAT PORTLAND
Monday, October 14, 13
THIS PORTLAND
Monday, October 14, 13
CONFESSION

Monday, October 14, 13
OK MIKE, WE GET IT.
YOU HAVE SELF
ESTEEM ISSUES.
So what is this
presentation about?
Monday, October 14, 13
So I’ve worked on some
decently “big” Rails apps

Monday, October 14, 13
FAT MODELS
Monday, October 14, 13
MASSIVE CONTROLLERS
Monday, October 14, 13
VIEWS
WITH
(CONFUSING)
LOGIC
IN THEM

Monday, October 14, 13
STUFF HAPPENS

Monday, October 14, 13
GOOD NEWS:
This presentation
isn’t a complainfest about Rails

Monday, October 14, 13
I work here:

(we’re kinda awesome)
Monday, October 14, 13
But I used to work here:

Monday, October 14, 13
(NEED A BULLPEN)

Monday, October 14, 13
INCUBATION
MONTH

Monday, October 14, 13
LIKE CHRISTMAS MORNING!

Monday, October 14, 13
WE SHOULD
USE
THIS TIME TO
EXPLORE!
Monday, October 14, 13
LET’S BUILD AN APP!
It’ll be one of those
new-fangled
single page whizzbang Javascript MVC
thingies!
Monday, October 14, 1...
WHAT TO DO?

Monday, October 14, 13
WHAT TO DO?

Monday, October 14, 13
WHAT TO DO?

Monday, October 14, 13
BACK END CRITERIA
• Had to be Ruby
• Building RESTful API
• Had to be easily deployed
• Want “lightweight”
• Had to be act...
Monday, October 14, 13
FRONT END CRITERIA

• Active Development
• Strategic choice for company
• Well documented
• Stable API
(Not gonna talk a w...
FRONTEND

(Not gonna talk a whole lot about these)
Monday, October 14, 13
ARCHITECTURE
Notifications Worker
Worker
Process
Process
Amazon SQS
(Queueing Service)

Collector
Collector
Client Gem
User...
FOCUS ON
Collector
Collector

API Server
API Server

THE “RUBY” PARTS
Monday, October 14, 13
HMM...
require	
  'sinatra'
get	
  '/hi'	
  do
	
  	
  "Hello	
  World!"
end
$	
  ruby	
  hi.rb
Monday, October 14, 13
HMM...
$	
  ls
app.rb
$

Monday, October 14, 13
Monday, October 14, 13
GOOD NEWS:
This presentation
isn’t a How-To
session on
Sinatra

Monday, October 14, 13
SETTLED ON THIS
Gemfile
Gemfile.lock
Procfile
README.md
Rakefile
app.rb
config/
config.ru
log/
middlewares/
routes/
servic...
INIT.RB PATTERN

Monday, October 14, 13
CONFIG AND
INITIALIZERS

Monday, October 14, 13
THIS SERVED US
PRETTY WELL.

Monday, October 14, 13
ASIDE: MONK
Learned about this project
after the fact.
Website down?
http://monkrb.com
Monday, October 14, 13
WAIT, WHERE ARE
THE MODELS?

Monday, October 14, 13
ORM
Monday, October 14, 13
ORM
We tried three different ones.
Models were in their own gem.

Monday, October 14, 13
DATAMAPPER
Ran into issues.
1.x kind of a ghost town.

Monday, October 14, 13
Monday, October 14, 13
OPEN SOURCE

Monday, October 14, 13
OPEN SOURCE
You can, you know, actually
read the source when you get stuck.

Monday, October 14, 13
ONE OF THE
BIGGEST THINGS
I LEARNED!
•I always had been intimidated by
complexity Rails’ source.

•Intimidated by size and...
bundle	
  open	
  <gem	
  name>

Monday, October 14, 13
LESSON #1

DON’T BE
AFRAID.
Monday, October 14, 13
SEQUEL
Weird (concurrency?) problem we just couldn’t nail down.
Probably wasn’t Sequel, probably was us.
Running out of ti...
ACTIVERECORD
We swallowed our pride.

Monday, October 14, 13
PUTTING THE
MODELS INTO
THEIR OWN GEM
SAVED OUR
BACON.
Monday, October 14, 13
LESSON #2

ISOLATE YOUR
APP FROM YOUR
LIBRARY
CHOICES.
Monday, October 14, 13
WHAT IS RACK?
Monday, October 14, 13
“A Rack application is an Ruby object (not a
class) that responds to call. It takes exactly one
argument, the environment ...
“A Rack application is an Ruby object (not a
class) that responds to call. It takes exactly one
argument, the environment ...
MIDDLEWARE
Monday, October 14, 13
SECURITY
Monday, October 14, 13
• Rack::Attack - middleware to do
•

•

whitelisting, blacklisting, throttling.
Rack::Protection - middleware that
handles...
SERIALIZATION

Monday, October 14, 13
SERIALIZATION
• achiu/rack-parser - I was surprised to
•

learn that converting JSON docs to
Rack parameters wasn’t automa...
SESSION COOKIES
Rack::Session::Cookie

Monday, October 14, 13
WE EVEN WROTE
OUR OWN
MIDDLEWARE!
HTTP status handling
for common errors
Monday, October 14, 13
class	
  ErrorMiddleware
	
  	
  def	
  initialize(app,	
  options	
  =	
  {})
	
  	
  	
  	
  @app	
  =	
  app
	
  	
  en...
INITIALIZER
ExceptionHandler.register(
	
  	
  "CloudshineData::UniqueConstraintViolation"	
  =>	
  {
	
  	
  	
  	
  body...
class	
  ExceptionHandler
	
  	
  HANDLERS	
  =	
  {}
	
  	
  attr_accessor	
  :exception
	
  	
  def	
  self.register(han...
LESSON #3

MIDDLEWARE
IS EASY!
Monday, October 14, 13
TOOLS
Monday, October 14, 13
ARCHITECTURE
Notifications Worker
Worker
Process
Process

SQL
DB

API Server
API Server

Amazon SQS
(Queueing Service)

Sta...
CODE IS SPREAD
OVER EIGHT
REPOSITORIES
Minimum of four separate
applications just to run the system.

Monday, October 14, ...
VAGRANT
Monday, October 14, 13
VAGRANT

Still doesn’t help w/ the nuisance of
having to commit code into multiple
places to make one feature change.
Mond...
VAGRANT

If I had to do it over again, I’d
probably keep one repo/app for the
server-side components and deploy
it differe...
OTHER HANDY
TOOLS

Monday, October 14, 13
RAILS CONSOLE
Monday, October 14, 13
RAILS CONSOLE
Missed it for a while.

Monday, October 14, 13
RAILS CONSOLE
Missed it for a while.
Then I realized I could just make a silly setup script.

Monday, October 14, 13
RAILS CONSOLE
Missed it for a while.
Then I realized I could just make a silly setup script.
It worked just as well and wa...
SILLY SETUP SCRIPT
require	
  'bundler'
require	
  'active_record'
require	
  'activeuuid'
Bundler.require(:default,:devel...
Monday, October 14, 13

RERUN
RERUN
Restarts your server for you when
files change in your app’s directory.
Wouldn’t work with pokey old Rails
but works ...
CORS Issues
Cross Origin
Resource Sharing

Monday, October 14, 13
User’s Browser

Web Server

HTTP	
  OPTIONS	
  http://api.myapp.com/resource
Origin:	
  
http://www.myapp.com
Monday, Octo...
User’s Browser

Web Server

200	
  OK
Access-­‐Control-­‐Allow-­‐Origin:	
  
http://www.myapp.com
Monday, October 14, 13
User’s Browser

Web Server

HTTP	
  GET	
  http://api.myapp.com/resource
Origin:	
  
http://www.myapp.com
Monday, October ...
CORS Support
Firefox: 3.5+
Chrome: 3+
Safari: 4+
Opera: 12+
IE: 10+
Monday, October 14, 13
CORS Issues
Can’t serve your single
page app from file://
during development
Can’t go across ports
without explicit
permiss...
CORS Issues
Did all of this with (you
guessed it) middleware!
github.com/cyu/rack-cors

Monday, October 14, 13
SUMMARY
•Don’t be afraid to look at the source code

of your libraries.
•Isolate your app from libraries
•Middleware is ea...
SPENDING TIME AWAY
FROM RAILS DE-MYSTIFIED
IT FOR ME AND MADE ME
MORE COMFORTABLE WITH
RUBY.

Monday, October 14, 13
SPENDING TIME AWAY
FROM RAILS DE-MYSTIFIED
IT FOR ME AND MADE ME
MORE COMFORTABLE WITH
RUBY.
It was totally worth it.
Mond...
HOWEVER...
If I were starting all over again,
I probably would just use
Rails API.

Monday, October 14, 13
Monday, October 14, 13
@mdesjardins

github.com/mdesjardins

md@openbay.com

Monday, October 14, 13
FIN.
Monday, October 14, 13
PHOTO CREDITS

Lobster on Slide 2: Creative Commons © Man Pikin (http://www.flickr.com/photos/musicamang/)
Priest on Slide ...
Upcoming SlideShare
Loading in...5
×

Rails Sojourn: One Man's Journey - Wicked Good Ruby Conference 2013

2,269

Published on

With several spawling, monolithic Rails apps under my belt, I had the opportunity to go a different route. Bulging models, obtuse controllers, and views chock full of logic were my world. When I came up for air, all the cool kids were writing thick clients with svelte backends. Perhaps Sinatra and some hip Javascript framework were the way? Here's what I learned...

Published in: Technology
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
2,269
On Slideshare
0
From Embeds
0
Number of Embeds
6
Actions
Shares
0
Downloads
3
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

Transcript of "Rails Sojourn: One Man's Journey - Wicked Good Ruby Conference 2013"

  1. 1. RAILS SOJOURN: ONE MAN’S JOURNEY Mike Desjardins Monday, October 14, 13
  2. 2. HI, I’M MIKE! I’M FROM PORTLAND Monday, October 14, 13
  3. 3. NO, NOT THAT PORTLAND Monday, October 14, 13
  4. 4. THIS PORTLAND Monday, October 14, 13
  5. 5. CONFESSION Monday, October 14, 13
  6. 6. OK MIKE, WE GET IT. YOU HAVE SELF ESTEEM ISSUES. So what is this presentation about? Monday, October 14, 13
  7. 7. So I’ve worked on some decently “big” Rails apps Monday, October 14, 13
  8. 8. FAT MODELS Monday, October 14, 13
  9. 9. MASSIVE CONTROLLERS Monday, October 14, 13
  10. 10. VIEWS WITH (CONFUSING) LOGIC IN THEM Monday, October 14, 13
  11. 11. STUFF HAPPENS Monday, October 14, 13
  12. 12. GOOD NEWS: This presentation isn’t a complainfest about Rails Monday, October 14, 13
  13. 13. I work here: (we’re kinda awesome) Monday, October 14, 13
  14. 14. But I used to work here: Monday, October 14, 13
  15. 15. (NEED A BULLPEN) Monday, October 14, 13
  16. 16. INCUBATION MONTH Monday, October 14, 13
  17. 17. LIKE CHRISTMAS MORNING! Monday, October 14, 13
  18. 18. WE SHOULD USE THIS TIME TO EXPLORE! Monday, October 14, 13
  19. 19. LET’S BUILD AN APP! It’ll be one of those new-fangled single page whizzbang Javascript MVC thingies! Monday, October 14, 13
  20. 20. WHAT TO DO? Monday, October 14, 13
  21. 21. WHAT TO DO? Monday, October 14, 13
  22. 22. WHAT TO DO? Monday, October 14, 13
  23. 23. BACK END CRITERIA • Had to be Ruby • Building RESTful API • Had to be easily deployed • Want “lightweight” • Had to be actively developed • Couldn’t be Rails. Just ‘cuz. Monday, October 14, 13
  24. 24. Monday, October 14, 13
  25. 25. FRONT END CRITERIA • Active Development • Strategic choice for company • Well documented • Stable API (Not gonna talk a whole lot about these) Monday, October 14, 13
  26. 26. FRONTEND (Not gonna talk a whole lot about these) Monday, October 14, 13
  27. 27. ARCHITECTURE Notifications Worker Worker Process Process Amazon SQS (Queueing Service) Collector Collector Client Gem User Application Monday, October 14, 13 SQL DB API Server API Server Static Single-Page Javascript Front End Happy User
  28. 28. FOCUS ON Collector Collector API Server API Server THE “RUBY” PARTS Monday, October 14, 13
  29. 29. HMM... require  'sinatra' get  '/hi'  do    "Hello  World!" end $  ruby  hi.rb Monday, October 14, 13
  30. 30. HMM... $  ls app.rb $ Monday, October 14, 13
  31. 31. Monday, October 14, 13
  32. 32. GOOD NEWS: This presentation isn’t a How-To session on Sinatra Monday, October 14, 13
  33. 33. SETTLED ON THIS Gemfile Gemfile.lock Procfile README.md Rakefile app.rb config/ config.ru log/ middlewares/ routes/ services/ spec/ templates/ Monday, October 14, 13
  34. 34. INIT.RB PATTERN Monday, October 14, 13
  35. 35. CONFIG AND INITIALIZERS Monday, October 14, 13
  36. 36. THIS SERVED US PRETTY WELL. Monday, October 14, 13
  37. 37. ASIDE: MONK Learned about this project after the fact. Website down? http://monkrb.com Monday, October 14, 13
  38. 38. WAIT, WHERE ARE THE MODELS? Monday, October 14, 13
  39. 39. ORM Monday, October 14, 13
  40. 40. ORM We tried three different ones. Models were in their own gem. Monday, October 14, 13
  41. 41. DATAMAPPER Ran into issues. 1.x kind of a ghost town. Monday, October 14, 13
  42. 42. Monday, October 14, 13
  43. 43. OPEN SOURCE Monday, October 14, 13
  44. 44. OPEN SOURCE You can, you know, actually read the source when you get stuck. Monday, October 14, 13
  45. 45. ONE OF THE BIGGEST THINGS I LEARNED! •I always had been intimidated by complexity Rails’ source. •Intimidated by size and talent of community. •Hadn’t run into trouble with Rails, no need to look at the source. Monday, October 14, 13
  46. 46. bundle  open  <gem  name> Monday, October 14, 13
  47. 47. LESSON #1 DON’T BE AFRAID. Monday, October 14, 13
  48. 48. SEQUEL Weird (concurrency?) problem we just couldn’t nail down. Probably wasn’t Sequel, probably was us. Running out of time so... Monday, October 14, 13
  49. 49. ACTIVERECORD We swallowed our pride. Monday, October 14, 13
  50. 50. PUTTING THE MODELS INTO THEIR OWN GEM SAVED OUR BACON. Monday, October 14, 13
  51. 51. LESSON #2 ISOLATE YOUR APP FROM YOUR LIBRARY CHOICES. Monday, October 14, 13
  52. 52. WHAT IS RACK? Monday, October 14, 13
  53. 53. “A Rack application is an Ruby object (not a class) that responds to call. It takes exactly one argument, the environment and returns an Array of exactly three values: The status, the headers, and the body.” - Rack API docs Monday, October 14, 13
  54. 54. “A Rack application is an Ruby object (not a class) that responds to call. It takes exactly one argument, the environment and returns an Array of exactly three values: The status, the headers, and the body.” - Rack API docs require  'rubygems' require  'rack' class  HelloWorld    def  call(env)        [200,  {"Content-­‐Type"  =>  "text/html"},  "Hello  Rack!"]    end end Rack::Handler::Mongrel.run  HelloWorld.new,  :port  =>  9292 Monday, October 14, 13
  55. 55. MIDDLEWARE Monday, October 14, 13
  56. 56. SECURITY Monday, October 14, 13
  57. 57. • Rack::Attack - middleware to do • • whitelisting, blacklisting, throttling. Rack::Protection - middleware that handles a bunch of attack vectors (IP Spoofing, Path Traversal, Session Hijacking, others). Omniauth::Identity - User authentication Monday, October 14, 13
  58. 58. SERIALIZATION Monday, October 14, 13
  59. 59. SERIALIZATION • achiu/rack-parser - I was surprised to • learn that converting JSON docs to Rack parameters wasn’t automatic. HashWithIndifferentAccess - Don’t expect your parameter names to be symbolized for you. Monday, October 14, 13
  60. 60. SESSION COOKIES Rack::Session::Cookie Monday, October 14, 13
  61. 61. WE EVEN WROTE OUR OWN MIDDLEWARE! HTTP status handling for common errors Monday, October 14, 13
  62. 62. class  ErrorMiddleware    def  initialize(app,  options  =  {})        @app  =  app    end        def  call(env)        dup._call(env)    end    def  _call(env)        @app.call  env    rescue  Exception  =>  e        logger.error  "-­‐-­‐-­‐  rescued  #{e.inspect}  at  #{Time.now}  -­‐-­‐-­‐"        e.backtrace.each  do  |line|            logger.error  line        end        handler  =  ExceptionHandler.new(e)        Rack::Response.new(handler.body,  handler.status,  {}).finish    end end Monday, October 14, 13
  63. 63. INITIALIZER ExceptionHandler.register(    "CloudshineData::UniqueConstraintViolation"  =>  {        body:  {errors:  ["Already  taken"]}.to_json,        status:  422    } ) ExceptionHandler.register(    "CloudshineData::Unauthorized"  =>  {status:  401,  body:  "Unauthorized"} ) ExceptionHandler.register(    "CloudshineData::RecordNotFound"  =>  {        body:  {errors:  ["Resource  not  found."]}.to_json,        status:  404    } ) ExceptionHandler.register("CloudshineData::ValidationFailed"  =>  HandlerWithMessage) ExceptionHandler.register("Cloudshine::MissingParameter"  =>  HandlerWithMessage) Monday, October 14, 13
  64. 64. class  ExceptionHandler    HANDLERS  =  {}    attr_accessor  :exception    def  self.register(handler={})        HANDLERS.merge!(handler)    end    def  initialize(exception)        @exception  =  exception        @handler_map  =  HANDLERS        @handler_map.default  =  {body:  {errors:  ["Server  error"]}.to_json,  status:  500}    end    def  handler        return  @handler  if  @handler        logger.info  "using  #{handler_map[@exception.class.to_s]}  to  handle”        logger.info  "  a  #{@exception.class.to_s}  exception."        handler  =  handler_map[@exception.class.to_s]        @handler  =  handler.is_a?(Class)  ?  handler.new(@exception)  :  OpenStruct.new(handler)    end    def  status;  handler.status;  end    def  body;  handler.body;  end    private    attr_accessor  :handler_map end Monday, October 14, 13
  65. 65. LESSON #3 MIDDLEWARE IS EASY! Monday, October 14, 13
  66. 66. TOOLS Monday, October 14, 13
  67. 67. ARCHITECTURE Notifications Worker Worker Process Process SQL DB API Server API Server Amazon SQS (Queueing Service) Static Single-Page Javascript Front End Collector Collector Happy User Client Gem User Application Monday, October 14, 13
  68. 68. CODE IS SPREAD OVER EIGHT REPOSITORIES Minimum of four separate applications just to run the system. Monday, October 14, 13
  69. 69. VAGRANT Monday, October 14, 13
  70. 70. VAGRANT Still doesn’t help w/ the nuisance of having to commit code into multiple places to make one feature change. Monday, October 14, 13
  71. 71. VAGRANT If I had to do it over again, I’d probably keep one repo/app for the server-side components and deploy it differently by task. Monday, October 14, 13
  72. 72. OTHER HANDY TOOLS Monday, October 14, 13
  73. 73. RAILS CONSOLE Monday, October 14, 13
  74. 74. RAILS CONSOLE Missed it for a while. Monday, October 14, 13
  75. 75. RAILS CONSOLE Missed it for a while. Then I realized I could just make a silly setup script. Monday, October 14, 13
  76. 76. RAILS CONSOLE Missed it for a while. Then I realized I could just make a silly setup script. It worked just as well and was a lot faster. Monday, October 14, 13
  77. 77. SILLY SETUP SCRIPT require  'bundler' require  'active_record' require  'activeuuid' Bundler.require(:default,:development) ActiveRecord::Base.establish_connection(    :adapter    =>  'mysql2',    :database  =>  'cloudshine_dev',    :username  =>  'root',    :host          =>  'localhost') ActiveUUID::Patches.apply! Monday, October 14, 13
  78. 78. Monday, October 14, 13 RERUN
  79. 79. RERUN Restarts your server for you when files change in your app’s directory. Wouldn’t work with pokey old Rails but works great with Sinatra. Monday, October 14, 13
  80. 80. CORS Issues Cross Origin Resource Sharing Monday, October 14, 13
  81. 81. User’s Browser Web Server HTTP  OPTIONS  http://api.myapp.com/resource Origin:   http://www.myapp.com Monday, October 14, 13 PR -F E IG L T H
  82. 82. User’s Browser Web Server 200  OK Access-­‐Control-­‐Allow-­‐Origin:   http://www.myapp.com Monday, October 14, 13
  83. 83. User’s Browser Web Server HTTP  GET  http://api.myapp.com/resource Origin:   http://www.myapp.com Monday, October 14, 13
  84. 84. CORS Support Firefox: 3.5+ Chrome: 3+ Safari: 4+ Opera: 12+ IE: 10+ Monday, October 14, 13
  85. 85. CORS Issues Can’t serve your single page app from file:// during development Can’t go across ports without explicit permission Monday, October 14, 13
  86. 86. CORS Issues Did all of this with (you guessed it) middleware! github.com/cyu/rack-cors Monday, October 14, 13
  87. 87. SUMMARY •Don’t be afraid to look at the source code of your libraries. •Isolate your app from libraries •Middleware is easy! •There are lots of cool tools out there. Learn about them. •Figure out your strategy for dealing w/ cross origin requests right away. Monday, October 14, 13
  88. 88. SPENDING TIME AWAY FROM RAILS DE-MYSTIFIED IT FOR ME AND MADE ME MORE COMFORTABLE WITH RUBY. Monday, October 14, 13
  89. 89. SPENDING TIME AWAY FROM RAILS DE-MYSTIFIED IT FOR ME AND MADE ME MORE COMFORTABLE WITH RUBY. It was totally worth it. Monday, October 14, 13
  90. 90. HOWEVER... If I were starting all over again, I probably would just use Rails API. Monday, October 14, 13
  91. 91. Monday, October 14, 13
  92. 92. @mdesjardins github.com/mdesjardins md@openbay.com Monday, October 14, 13
  93. 93. FIN. Monday, October 14, 13
  94. 94. PHOTO CREDITS Lobster on Slide 2: Creative Commons © Man Pikin (http://www.flickr.com/photos/musicamang/) Priest on Slide 3: © Anyka, Licensed from Fotolia.com, All Rights Reserved Giant NES Controller on Slide 8: Creative Commons © Pop Culture Geek (http://www.flickr.com/photos/popculturegeek/) Boston Construction Road Sign on Slide 9: Creative Commons © NNECAPA (http://www.flickr.com/photos/nnecapa/) Bird Poop on Slide 10: Creative Commons © Chris Smith Ronnie Shumate “My Favorite Pet Sitter” (http://www.flickr.com/photos/myfavoritepetsitter/) Fenway Bullpen on Slide 15: Creative Commons © BuzzFarmers “Red Sox Game at Fenway Park.” (http://www.flickr.com/photos/buzzfarmers/) Alien Incubator on Slide 16: Creative Commons © Steve Jurvetson “alien incubator” (http://www.flickr.com/photos/jurvetson/) Christmas Morning on Slide 17: Creative Commons © John Lemieux “Nikon Christmas” (http://www.flickr.com/photos/newdimensionfilms/) Segway on Slide 42: Creative Commons © Wesley Fryer “Darth Vader on a segway in the Edmond 4th of July Parade” (http://www.flickr.com/photos/wfryer) Bacon on Slide 49: Creative Commons © Shawn Zamecheck “Bacon” (http://www.flickr.com/photos/shawnzam/) Rack of Lamb on Slide 51: Creative Commons © Waferboard “rack of lamb presentation II” (http://www.flickr.com/photos/waferboard/) Rainbow Cake on Slide 54: Creative Commons © Janine and Jim Eden “Rainbow Cake” (http://www.flickr.com/photos/edenpictures/) Security Cameras on Slide 55: Creative Commons © Jake Setlak “Security” (http://www.flickr.com/photos/pylbug/) Cookies on Slide 59: Creative Commons © Jeramey Jannene “Cookies” (http://www.flickr.com/photos/compujeramey/) Tool Chest on Slide 66: Creative Commons © El Cajon Yacht Club “tool chest” (http://www.flickr.com/photos/el_cajon_yacht_club/) Console on Slide 73: Creative Commons © Cliff “Console, Countdown and Monitor“ (http://www.flickr.com/photos/nostri-imago/) Apple Cores Slides 80-85: Creative Commons © Roger Karlsson “red apple core two days” (http://www.flickr.com/photos/free-photos/) Mosaic 3.5” Floppy Slides 81-83: Creative Commons © Bill Rawlinson “plug-n-play-mosaic” (http://www.flickr.com/photos/thefinalcut/) Mainframe Slides 81-83: Creative Commons © Don DeBold “NEC 2203 Mainframe Computer” (http://www.flickr.com/photos/ddebold/) Creative Commons images licensed under the CC 2.0 Attribution License. Monday, October 14, 13
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×