instapaper 0.3.0 → 1.0.0.pre2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.md +1 -1
  3. data/README.md +62 -38
  4. data/instapaper.gemspec +21 -30
  5. data/lib/instapaper.rb +0 -25
  6. data/lib/instapaper/api.rb +15 -0
  7. data/lib/instapaper/{client/account.rb → api/accounts.rb} +5 -5
  8. data/lib/instapaper/api/bookmarks.rb +77 -0
  9. data/lib/instapaper/{client/folder.rb → api/folders.rb} +12 -11
  10. data/lib/instapaper/api/highlights.rb +33 -0
  11. data/lib/instapaper/api/oauth.rb +17 -0
  12. data/lib/instapaper/bookmark.rb +21 -0
  13. data/lib/instapaper/bookmark_list.rb +20 -0
  14. data/lib/instapaper/client.rb +40 -28
  15. data/lib/instapaper/credentials.rb +12 -0
  16. data/lib/instapaper/error.rb +78 -0
  17. data/lib/instapaper/folder.rb +17 -0
  18. data/lib/instapaper/highlight.rb +16 -0
  19. data/lib/instapaper/http/headers.rb +45 -0
  20. data/lib/instapaper/http/qline_parser.rb +9 -0
  21. data/lib/instapaper/http/request.rb +84 -0
  22. data/lib/instapaper/http/utils.rb +67 -0
  23. data/lib/instapaper/user.rb +14 -0
  24. data/lib/instapaper/version.rb +1 -1
  25. metadata +63 -209
  26. data/.gemtest +0 -0
  27. data/.gitignore +0 -11
  28. data/.rspec +0 -3
  29. data/.travis.yml +0 -8
  30. data/.yardopts +0 -3
  31. data/Gemfile +0 -7
  32. data/Rakefile +0 -13
  33. data/lib/faraday/response/raise_http_1xxx.rb +0 -65
  34. data/lib/instapaper/authentication.rb +0 -32
  35. data/lib/instapaper/client/bookmark.rb +0 -81
  36. data/lib/instapaper/client/user.rb +0 -19
  37. data/lib/instapaper/configuration.rb +0 -88
  38. data/lib/instapaper/connection.rb +0 -35
  39. data/lib/instapaper/request.rb +0 -22
  40. data/spec/faraday/response_spec.rb +0 -22
  41. data/spec/fixtures/access_token.qline +0 -1
  42. data/spec/fixtures/bookmarks_add.json +0 -1
  43. data/spec/fixtures/bookmarks_archive.json +0 -1
  44. data/spec/fixtures/bookmarks_get_text.txt +0 -299
  45. data/spec/fixtures/bookmarks_list.json +0 -5
  46. data/spec/fixtures/bookmarks_move.json +0 -1
  47. data/spec/fixtures/bookmarks_star.json +0 -1
  48. data/spec/fixtures/bookmarks_unarchive.json +0 -1
  49. data/spec/fixtures/bookmarks_unstar.json +0 -1
  50. data/spec/fixtures/bookmarks_update_read_progress.json +0 -1
  51. data/spec/fixtures/folders_add.json +0 -1
  52. data/spec/fixtures/folders_delete.json +0 -1
  53. data/spec/fixtures/folders_list.json +0 -1
  54. data/spec/fixtures/folders_set_order.json +0 -1
  55. data/spec/fixtures/invalid_credentials.qline +0 -1
  56. data/spec/fixtures/verify_credentials.json +0 -1
  57. data/spec/instapaper/client/account_spec.rb +0 -27
  58. data/spec/instapaper/client/bookmark_spec.rb +0 -234
  59. data/spec/instapaper/client/folder_spec.rb +0 -89
  60. data/spec/instapaper/client/user_spec.rb +0 -36
  61. data/spec/instapaper/client_spec.rb +0 -65
  62. data/spec/instapaper_spec.rb +0 -85
  63. data/spec/spec_helper.rb +0 -52
data/.gitignore DELETED
@@ -1,11 +0,0 @@
1
- *.gem
2
- *.rbc
3
- .DS_Store
4
- .bundle
5
- .rvmrc
6
- .yardoc
7
- Gemfile.lock
8
- coverage/*
9
- doc/*
10
- log/*
11
- pkg/*
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --color
2
- --format=nested
3
- --backtrace
@@ -1,8 +0,0 @@
1
- rvm:
2
- - 1.8.7
3
- - 1.9.2
4
- - 1.9.3
5
- - jruby
6
- - rbx
7
- - ree
8
- - ruby-head
data/.yardopts DELETED
@@ -1,3 +0,0 @@
1
- --markup markdown
2
- -
3
- LICENSE.md
data/Gemfile DELETED
@@ -1,7 +0,0 @@
1
- source :rubygems
2
-
3
- platforms :jruby do
4
- gem 'jruby-openssl', '~> 0.7'
5
- end
6
-
7
- gemspec
data/Rakefile DELETED
@@ -1,13 +0,0 @@
1
- #!/usr/bin/env rake
2
-
3
- require 'bundler'
4
- Bundler::GemHelper.install_tasks
5
-
6
- require 'rspec/core/rake_task'
7
- RSpec::Core::RakeTask.new(:spec)
8
-
9
- task :test => :spec
10
- task :default => :spec
11
-
12
- require 'yard'
13
- YARD::Rake::YardocTask.new
@@ -1,65 +0,0 @@
1
- require 'faraday'
2
-
3
- # @private
4
- module Faraday
5
- # @private
6
- class Response::RaiseHttp1xxx < Response::Middleware
7
- def on_complete(env)
8
- case env[:status].to_i
9
-
10
- # general errors
11
-
12
- when 1040
13
- raise Instapaper::Error.new(error_message(env, "Rate-limit exceeded."))
14
- when 1041
15
- raise Instapaper::Error.new(error_message(env, "Subscription account required."))
16
- when 1042
17
- raise Instapaper::Error.new(error_message(env, "Application is suspended."))
18
-
19
- # bookmark errors
20
-
21
- when 1220
22
- raise Instapaper::Error.new(error_message(env, "Domain requires full content to be supplied."))
23
- when 1221
24
- raise Instapaper::Error.new(error_message(env, "Domain has opted out of Instapaper compatibility."))
25
- when 1240
26
- raise Instapaper::Error.new(error_message(env, "Invalid URL specified."))
27
- when 1241
28
- raise Instapaper::Error.new(error_message(env, "Invalid or missing bookmark_id."))
29
- when 1242
30
- raise Instapaper::Error.new(error_message(env, "Invalid or missing folder_id."))
31
- when 1243
32
- raise Instapaper::Error.new(error_message(env, "Invalid or missing progress."))
33
- when 1244
34
- raise Instapaper::Error.new(error_message(env, "Invalid or missing progress_timestamp."))
35
- when 1245
36
- raise Instapaper::Error.new(error_message(env, "Private bookmarks require supplied content."))
37
- when 1250
38
- raise Instapaper::Error.new(error_message(env, "Unexpected error when saving bookmark."))
39
-
40
- # folder errors
41
-
42
- when 1250
43
- raise Instapaper::Error.new(error_message(env, "Invalid or missing title."))
44
- when 1251
45
- raise Instapaper::Error.new(error_message(env, "User already has a folder with this title."))
46
- when 1252
47
- raise Instapaper::Error.new(error_message(env, "Cannot add bookmarks to this folder."))
48
-
49
- # operational errors
50
-
51
- when 1500
52
- raise Instapaper::Error.new(error_message(env, "Unexpected service error."))
53
- when 1550
54
- raise Instapaper::Error.new(error_message(env, "Error generating text version of this URL."))
55
-
56
- end
57
- end
58
-
59
- private
60
-
61
- def error_message(env, body=nil)
62
- "#{env[:method].to_s.upcase} #{env[:url].to_s}: #{[env[:status].to_s + ':', body].compact.join(' ')}."
63
- end
64
- end
65
- end
@@ -1,32 +0,0 @@
1
- module Instapaper
2
- # @private
3
- module Authentication
4
- private
5
-
6
- # Authentication hash
7
- #
8
- # @return [Hash]
9
- def authentication
10
- {
11
- :consumer_key => consumer_key,
12
- :consumer_secret => consumer_secret,
13
- :token => oauth_token,
14
- :token_secret => oauth_token_secret
15
- }
16
- end
17
-
18
- def consumer_tokens
19
- {
20
- :consumer_key => consumer_key,
21
- :consumer_secret => consumer_secret
22
- }
23
- end
24
-
25
- # Check whether user is authenticated
26
- #
27
- # @return [Boolean]
28
- def authenticated?
29
- authentication.values.all?
30
- end
31
- end
32
- end
@@ -1,81 +0,0 @@
1
- module Instapaper
2
- class Client
3
- # Defines methods related to bookmarks
4
- module Bookmark
5
-
6
- # Lists the user’s unread bookmarks, and can also synchronize reading positions.
7
- # @option limit: Optional. A number between 1 and 500, default 25.
8
- # @option folder_id: Optional. Possible values are unread (default), starred, archive, or a folder_id value from /api/1/folders/list.
9
- # @option have: Optional. A concatenation of bookmark_id values that the client already has from the specified folder. See below.
10
- def bookmarks(options={})
11
- post('bookmarks/list', options)[2..-1]
12
- end
13
-
14
- # Updates the user’s reading progress on a single article.
15
- # @param bookmark_id [String] The id of the bookmark to update.
16
- # @param progress [Float] The user’s progress, as a floating-point number between 0.0 and 1.0, defined as the top edge of the user’s current viewport, expressed as a percentage of the article’s total length.
17
- # @param progress_timestamp [Integer] The Unix timestamp value of the time that the progress was recorded.
18
- def update_read_progress(bookmark_id, progress, progress_timestamp=Time.now)
19
- post('bookmarks/update_read_progress', :bookmark_id => bookmark_id, :progress => progress, :progress_timestamp => progress_timestamp.to_i).first
20
- end
21
-
22
- # Adds a new unread bookmark to the user’s account.
23
- # @param url [String] The url of the bookmark.
24
- def add_bookmark(url, options={})
25
- post('bookmarks/add', options.merge(:url => url)).first
26
- end
27
-
28
- # Permanently deletes the specified bookmark.
29
- # This is NOT the same as Archive. Please be clear to users if you’re going to do this.
30
- # @param bookmark_id [String] The id of the bookmark.
31
- def delete_bookmark(bookmark_id)
32
- post('bookmarks/delete', :bookmark_id => bookmark_id)
33
- end
34
-
35
- # Stars the specified bookmark.
36
- # @param bookmark_id [String] The id of the bookmark.
37
- def star(bookmark_id)
38
- post('bookmarks/star', :bookmark_id => bookmark_id).first
39
- end
40
- alias :star_bookmark :star
41
-
42
- # Un-stars the specified bookmark.
43
- # @param bookmark_id [String] The id of the bookmark.
44
- def unstar(bookmark_id)
45
- post('bookmarks/unstar', :bookmark_id => bookmark_id).first
46
- end
47
- alias :unstar_bookmark :unstar
48
-
49
- # Moves the specified bookmark to the Archive.
50
- # @param bookmark_id [String] The id of the bookmark.
51
- def archive(bookmark_id)
52
- post('bookmarks/archive', :bookmark_id => bookmark_id).first
53
- end
54
- alias :archive_bookmark :archive
55
-
56
- # Moves the specified bookmark to the top of the Unread folder.
57
- # @param bookmark_id [String] The id of the bookmark.
58
- def unarchive(bookmark_id)
59
- post('bookmarks/unarchive', :bookmark_id => bookmark_id).first
60
- end
61
- alias :unarchive_bookmark :unarchive
62
-
63
- # Moves the specified bookmark to a user-created folder.
64
- # @param bookmark_id [String] The id of the bookmark.
65
- # @param folder_id [String] The id of the folder to move the bookmark to.
66
- def move(bookmark_id, folder_id)
67
- post('bookmarks/move', :bookmark_id => bookmark_id, :folder_id => folder_id).first
68
- end
69
- alias :move_bookmark :move
70
-
71
- # Returns the specified bookmark’s processed text-view HTML, which is
72
- # always text/html encoded as UTF-8.
73
- # @param bookmark_id [String] The id of the bookmark.
74
- def text(bookmark_id)
75
- post('bookmarks/get_text', { :bookmark_id => bookmark_id }, true).body
76
- end
77
- alias :get_text :text
78
-
79
- end
80
- end
81
- end
@@ -1,19 +0,0 @@
1
- module Instapaper
2
- class Client
3
- # Defines methods related to users
4
- module User
5
-
6
- # Gets an OAuth access token for a user.
7
- def access_token(username, password)
8
- response = post('oauth/access_token', { :x_auth_username => username, :x_auth_password => password, :x_auth_mode => "client_auth"}, true)
9
- params = response.body.split("&")
10
- values = params.map {|part| part.split("=") }.flatten
11
- if values.length == 1
12
- values.unshift('error')
13
- end
14
- Hash[*values]
15
- end
16
-
17
- end
18
- end
19
- end
@@ -1,88 +0,0 @@
1
- require 'instapaper/version'
2
-
3
- module Instapaper
4
- module Configuration
5
- # An array of valid keys in the options hash when configuring a {Instapaper::API}
6
- VALID_OPTIONS_KEYS = [
7
- :adapter,
8
- :consumer_key,
9
- :consumer_secret,
10
- :endpoint,
11
- :oauth_token,
12
- :oauth_token_secret,
13
- :proxy,
14
- :version,
15
- :path_prefix,
16
- :user_agent,
17
- :connection_options].freeze
18
-
19
- # The adapter that will be used to connect if none is set
20
- #
21
- # @note The default faraday adapter is Net::HTTP.
22
- DEFAULT_ADAPTER = :net_http
23
-
24
- # By default, don't set an application key
25
- DEFAULT_CONSUMER_KEY = nil
26
-
27
- # By default, don't set an application secret
28
- DEFAULT_CONSUMER_SECRET = nil
29
-
30
- # The endpoint that will be used to connect if none is set
31
- DEFAULT_ENDPOINT = 'https://www.instapaper.com/'.freeze
32
-
33
- # The version of the API.
34
- DEFAULT_VERSION = '1'
35
-
36
- DEFAULT_PATH_PREFIX = 'api/' + DEFAULT_VERSION + '/'
37
-
38
- # By default, don't set a user oauth token
39
- DEFAULT_OAUTH_TOKEN = nil
40
-
41
- # By default, don't set a user oauth secret
42
- DEFAULT_OAUTH_TOKEN_SECRET = nil
43
-
44
- # By default, don't use a proxy server
45
- DEFAULT_PROXY = nil
46
-
47
- # The user agent that will be sent to the API endpoint if none is set
48
- DEFAULT_USER_AGENT = "Instapaper Ruby Gem #{Instapaper::VERSION}".freeze
49
-
50
- DEFAULT_CONNECTION_OPTIONS = {}
51
-
52
- # @private
53
- attr_accessor *VALID_OPTIONS_KEYS
54
-
55
- # When this module is extended, set all configuration options to their default values
56
- def self.extended(base)
57
- base.reset
58
- end
59
-
60
- # Convenience method to allow configuration options to be set in a block
61
- def configure
62
- yield self
63
- end
64
-
65
- # Create a hash of options and their values
66
- def options
67
- options = {}
68
- VALID_OPTIONS_KEYS.each{|k| options[k] = send(k) }
69
- options
70
- end
71
-
72
- # Reset all configuration options to defaults
73
- def reset
74
- self.adapter = DEFAULT_ADAPTER
75
- self.consumer_key = DEFAULT_CONSUMER_KEY
76
- self.consumer_secret = DEFAULT_CONSUMER_SECRET
77
- self.endpoint = DEFAULT_ENDPOINT
78
- self.oauth_token = DEFAULT_OAUTH_TOKEN
79
- self.oauth_token_secret = DEFAULT_OAUTH_TOKEN_SECRET
80
- self.proxy = DEFAULT_PROXY
81
- self.user_agent = DEFAULT_USER_AGENT
82
- self.version = DEFAULT_VERSION
83
- self.path_prefix = DEFAULT_PATH_PREFIX
84
- self.connection_options = DEFAULT_CONNECTION_OPTIONS
85
- self
86
- end
87
- end
88
- end
@@ -1,35 +0,0 @@
1
- require 'faraday_middleware'
2
- require 'faraday/response/raise_http_1xxx'
3
-
4
- module Instapaper
5
- # @private
6
- module Connection
7
- private
8
-
9
- def connection(raw=false)
10
- merged_options = connection_options.merge({
11
- :headers => {
12
- 'Accept' => "application/json",
13
- 'User-Agent' => user_agent
14
- },
15
- :proxy => proxy,
16
- :ssl => {:verify => false},
17
- :url => api_endpoint
18
- })
19
-
20
- Faraday.new(merged_options) do |builder|
21
- if authenticated?
22
- builder.use Faraday::Request::OAuth, authentication
23
- else
24
- builder.use Faraday::Request::OAuth, consumer_tokens
25
- end
26
- builder.use Faraday::Request::Multipart
27
- builder.use Faraday::Request::UrlEncoded
28
- builder.use Faraday::Response::Rashify unless raw
29
- builder.use Faraday::Response::ParseJson unless raw
30
- builder.use Faraday::Response::RaiseHttp1xxx
31
- builder.adapter(adapter)
32
- end
33
- end
34
- end
35
- end
@@ -1,22 +0,0 @@
1
- module Instapaper
2
- # Defines HTTP request methods
3
- module Request
4
-
5
- # Perform an HTTP POST request
6
- def post(path, options={}, raw=false)
7
- request(:post, path, options, raw)
8
- end
9
-
10
- private
11
-
12
- # Perform an HTTP request
13
- def request(method, path, options, raw=false)
14
- response = connection(raw).send(method) do |request|
15
- request.path = path_prefix + path
16
- request.body = options unless options.empty?
17
- end
18
- raw ? response : response.body
19
- end
20
-
21
- end
22
- end
@@ -1,22 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Faraday::Response do
4
- before do
5
- @client = Instapaper::Client.new
6
- end
7
-
8
- [1040, 1041, 1042, 1220, 1221, 1240, 1241, 1242, 1243, 1244, 1245, 1250,
9
- 1251, 1252, 1500, 1550].each do |status|
10
- context "when HTTP status is #{status}" do
11
- before do
12
- stub_post('folders/list').to_return(:status => status)
13
- end
14
-
15
- it "should raise Instapaper::Error error" do
16
- lambda do
17
- @client.folders
18
- end.should raise_error(Instapaper::Error)
19
- end
20
- end
21
- end
22
- end
@@ -1 +0,0 @@
1
- oauth_token=aabbccdd&oauth_token_secret=efgh1234
@@ -1 +0,0 @@
1
- [{"type":"bookmark","bookmark_id":169529989,"url":"http:\/\/www.fastcodesign.com\/1662169\/ideos-axioms-for-starting-disruptive-new-businesses","title":"Ideo's Axioms for Starting Disruptive New Businesses | Co.Design","description":"www.fastcodesign.com","time":1307586766,"starred":"0","private_source":"","hash":"9GZzaC8U","progress":"0","progress_timestamp":1307585389}]
@@ -1 +0,0 @@
1
- [{"type":"bookmark","bookmark_id":169529989,"url":"http:\/\/www.fastcodesign.com\/1662169\/ideos-axioms-for-starting-disruptive-new-businesses","title":"Ideo's Axioms for Starting Disruptive New Businesses | Co.Design","description":"www.fastcodesign.com","time":1306963988,"starred":"0","private_source":"","hash":"v27qHZc2","progress":"0","progress_timestamp":1307145892}]
@@ -1,299 +0,0 @@
1
-
2
- <html>
3
- <head>
4
- <title>Ideo&#039;s Axioms for Starting Disruptive New Businesses</title>
5
- <meta name="viewport" content="width=device-width; initial-scale=1.0; user-scalable=no; minimum-scale=1.0; maximum-scale=1.0;" />
6
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
7
- <meta name="robots" content="noindex"/>
8
- <link rel="icon" href="/images/favicon.png"/>
9
- <!-- IP:TITLE
10
- Ideo's Axioms for Starting Disruptive New Businesses
11
- /IP:TITLE -->
12
- <!-- IP:IMAGES
13
-
14
- /IP:IMAGES -->
15
- <style type="text/css">
16
- body {
17
- font-family: Georgia;
18
- font-size: 16px;
19
- margin: 0px auto 0px auto;
20
- width: 500px word-wrap: break-word;
21
- }
22
-
23
- h1 { font-size: 1.3em; }
24
- h2 { font-size: 1.15em; }
25
- h3, h4, h5, h6, h7 { font-size: 1.0em; }
26
-
27
- img { border: 0; display: block; margin: 0.5em 0; }
28
- pre, code { overflow: scroll; }
29
- #story {
30
- clear: both; padding: 0 10px; overflow: hidden; margin-bottom: 40px;
31
- }
32
-
33
- .bar {
34
- color: #555;
35
- font-family: 'Helvetica';
36
- font-size: 11pt;
37
- margin: 0 -20px;
38
- padding: 10px 0;
39
- }
40
- .top { border-bottom: 2px solid #000; }
41
-
42
- .top a {
43
- display: block;
44
- float: right;
45
- text-decoration: none;
46
- font-size: 11px;
47
- background-color: #eee;
48
- -webkit-border-radius: 8px;
49
- -moz-border-radius: 8px;
50
- padding: 2px 15px;
51
- }
52
-
53
- #story div {
54
- margin: 1em 0;
55
- }
56
-
57
- .bottom {
58
- border-top: 2px solid #000;
59
- color: #555;
60
- }
61
-
62
- .bar a { color: #444; }
63
-
64
- blockquote {
65
- border-top: 1px solid #bbb;
66
- border-bottom: 1px solid #bbb;
67
- margin: 1.5em 0;
68
- padding: 0.5em 0;
69
- }
70
- blockquote.short { font-style: italic; }
71
-
72
- pre {
73
- white-space: pre-wrap;
74
- }
75
-
76
- ul.bodytext, ol.bodytext {
77
- list-style: none;
78
- margin-left: 0;
79
- padding-left: 0em;
80
- }
81
-
82
- </style>
83
- </head>
84
- <body onload="loadFont();">
85
- <div class="bar top">
86
- <a href="http://www.fastcodesign.com/1662169/ideos-axioms-for-starting-disruptive-new-businesses">View original</a>
87
- <div class="sm">fastcodesign.com</div>
88
- </div>
89
-
90
- <div id="editing_controls" style="float: right; padding-top: 2px;">
91
- </div>
92
-
93
- <div id="story">
94
-
95
-
96
- <div>
97
- <div>
98
- <div><a href="http://www.fastcodesign.com/1662169/ideos-axioms-for-starting-disruptive-new-businesses"><span>Back</span> to Fast Company</a></div>
99
- </div>
100
-
101
-
102
-
103
-
104
- <div>
105
-
106
-
107
- <div><a href="http://www.fastcodesign.com/">Fast Company</a></div>
108
- <div>
109
-
110
-
111
- <div><a href="http://www.fastcodesign.com/12072010-tue-0" title="Issue Flag"><strong>Dec 04,
112
- 2010</strong></a></div>
113
- <img src="http://www.fastcodesign.com/multisite_files/codesign/imagecache/article-feature/business-beta-frontofhouseB.jpg" title="" alt="Ideo's Axioms for Starting Disruptive New Businesses" />
114
- <div>
115
-
116
- <div>
117
- <h1><span>Ideo&#8217;s Axioms for Starting Disruptive New
118
- Businesses</span></h1>
119
-
120
- <div>
121
- <div>Don&#8217;t wait for perfection: Launch and
122
- learn.</div>
123
- <p><em>This is the first piece in our PATTERNS
124
- series by IDEO. Read more about the series <a href="http://www.fastcodesign.com/1662168/introducing-ideos-new-column-patterns-affecting-business-and-design-today">
125
- here</a>.</em></p>
126
- <p>How do you build a business in an unproven market? How do you
127
- figure out what customers need when you’re delivering an
128
- experience they’ve never seen before? You begin where service
129
- and software companies have begun, by conducting fast, cheap
130
- experiments that help you understand your customers. You build on
131
- what you learn. In short, you prototype.</p>
132
- <p>With ever-increasing competition, innovative businesses are
133
- finding that in order to stay competitive their offerings need to
134
- constantly evolve. And that to improve their offerings is to
135
- encourage consumer participation. This helps them build a
136
- competitive advantage by constantly revisiting what they deliver
137
- and how they deliver it. They know that traditional market testing
138
- will only validate their past successes. To understand the next big
139
- thing, companies have to engage with customers and react to their
140
- needs.</p>
141
-
142
- <h2><strong>TAKE ACTION: Designing for Life&#8217;s Changes</strong></h2>
143
- <p><br /><br /><strong>1. Go early, go often</strong><br />
144
- Building experimentation into your business is harder than you
145
- think. Start small and stay focused. Try everything, but
146
- don’t try it all in one prototype.<br /><strong><br />
147
- 2. Learning by doing</strong><br />
148
- Build value for the business as you prototype. If you fail, what
149
- will you have learned? What will you salvage?</p>
150
- <p><strong>3. Inspiration through constraint</strong><br />
151
- Don’t exhaust yourself searching for money and resources. The
152
- tighter your constraints, the more creative your prototypes will
153
- be.</p>
154
- <p><strong>4. Open to opportunity</strong><br />
155
- Look for unanticipated ways customers are using your offering.
156
- Their improvisations may be the future of your business.</p>
157
- <h2><strong>THE EVIDENCE: Stories from Around the
158
- Globe</strong></h2>
159
- <img src="http://images.fastcompany.com/upload/business-beta-platform.jpg" alt="" style="border: 0px;" />
160
- <strong>Platform for Change</strong><br />
161
- Companies like Apple and Facebook have learned to harness the
162
- energy of outside developers to create new applications. By
163
- allowing thousands of new applications to run on their platforms,
164
- they create a Darwinian environment where only the fittest survive.
165
- <p>Jeff, a developer, noticed that Facebook lacked reminders around
166
- birthdays, so he created Birthday Alert. Customers quickly made it
167
- one of the hottest apps on the platform. Instead of trying to guess
168
- what type of functionality users wanted, Facebook just tapped into
169
- smart developers like Jeff who built it for them.</p>
170
- <p>Through this process, Facebook learned it needed a mechanism to
171
- foster developers who could improve the overall ecosystem. Now the
172
- Facebook Fund supports enterprising developers who are eager to
173
- build their ideas.<br /><em><br />
174
- How can you engage your customers and partners to help you
175
- prototype new offerings?<br /></em></p>
176
- <p><strong>Front-of-house Flexibility</strong><br />
177
- The secret behind the unique feel of Whole Foods and Trader
178
- Joe’s is how employees are empowered to cocreate the customer
179
- experience. Each store establishes teams to figure out the best way
180
- to serve customers, from the products they offer to the way
181
- sections are organized. Each week, employees can see the results of
182
- their experiments in the aisles.</p>
183
- <p>Jesse recently joined the cheese department at Whole Foods and
184
- one of his favorite jobs is to select the cheeses that customers
185
- sample. He feels it helps set the mood of the entire store, and
186
- when he nails the selection, the store usually sells the entire
187
- stock. Giving teams the tools to constantly improve the business
188
- creates an engaging and successful environment.<br /><em><br />
189
- What control should you give up so your team is empowered?</em></p>
190
- <img src="http://images.fastcompany.com/upload/business-beta-grassroots.jpg" alt="image" />
191
- <br /><strong>Grassroots Growth</strong><br />
192
- Ayr is a former scientist with an MBA from Harvard. After several
193
- years with McKinsey, he decided to follow his dream to create a
194
- chain of fast and friendly vegetarian restaurants.
195
- <p>He could have hired a chef and tested his menu with focus
196
- groups, but instead he decided that it would be better to run a lot
197
- of experiments at low cost. So he launched his restaurant from a
198
- food truck parked outside the MIT campus, updating his customers
199
- about daily specials through text messages and blog posts.</p>
200
- <p>After six months, the results have been phenomenal. By starting
201
- small and prototyping, Ayr is learning while he shapes his
202
- business. He’s adding additional trucks, developing permanent
203
- spaces, and has begun to cater special events. Each experiment
204
- brings him closer to his ultimate goal.</p>
205
- <p><em>How can you intentionally limit your resources to create a
206
- more inspired offering?</em></p>
207
- <p><strong>Making Lemonade</strong><br />
208
- Like many fashion houses, Gucci and Ann Taylor were hit hard during
209
- the recent recession. The nation’s sudden shopping withdrawal
210
- left many designers with too few retail orders to manufacture their
211
- line. In similar circumstances, manufacturers will order the
212
- additional garments and offer excess inventory in outlet malls and
213
- discount retailers. This time things were different, demand was
214
- much lower so the fashion houses got creative. Taking advantage of
215
- empty retail space many designers negotiated short, temporary
216
- leases in high-traffic areas. In this short stay space they opened
217
- pop-up shops to connect with customers. The recession could have
218
- distanced these designers from their customers but quick, nimble
219
- moves created new opportunities to engage.<br /><em><br />
220
- How can you turn your biggest challenge into an opportunity to try
221
- something new?</em></p>
222
- <p><strong>Real-time Results</strong><br />
223
- Internet companies routinely use their constant connections with
224
- customers to prototype new offerings. Companies like Google and
225
- Amazon routinely select pools of users and change the functionality
226
- in their products (e.g., you may be looking at a different Gmail
227
- interface than your friends). Depending on specific behavioral
228
- metrics, Google may change a product without ever directly asking
229
- the customer. Smart and nimble businesses know that always-on and
230
- always-accessible allows them to learn and evolve.<br /><em><br />
231
- How can you experiment on the fly and learn without compromising
232
- experience?</em></p>
233
- <img src="http://images.fastcompany.com/upload/business-beta-frontofhouse.jpg" alt="image" />
234
- <br /><strong>Ongoing Experimentation</strong><br />
235
- McDonald’s has built prototyping into its organization. Since
236
- the company does not want every employee in every store deviating
237
- from service patterns, it has set up test restaurants to try new
238
- menu items, new pricing strategies, and new food preparation
239
- methods. This flexibility has paid off. McDonald&#8217;s has been able to
240
- roll out worldwide menu expansions in just a few months&#8212;quite a
241
- feat for a company that serves 47 million customers a day.
242
- <p><em>How can you build experimentation into the culture of your
243
- organization?</em></p>
244
- <p><strong>Name my Book</strong><br />
245
- Tim Ferriss loved the playful working title of his first book, Drug
246
- Dealing for Fun and Profit, but it was too racy for Walmart and
247
- other retailers. With the success of the book hinging on this
248
- decision, Tim decided to prototype. He drafted a shortlist of
249
- titles and bought Google AdWords. Each online click equaled one
250
- vote. Within a week, he had his title, and <em>The 4-Hour Work
251
- Week</em> was finally finished.</p>
252
- <p><em>How much information do you need to make decisions? Can
253
- prototyping help you get there faster?</em></p>
254
- <h2><strong>Be a Pattern Spotter</strong></h2>
255
- <p><br /><br />
256
- Now that you&#8217;ve been exposed to a few different examples,
257
- don’t be surprised if you start seeing Life&#8217;s Changes
258
- patterns all around. Keep your eyes open and let us know what you
259
- find, especially if it&#8217;s the next new pattern.</p>
260
- <p><em>PATTERNS are a collection of shared thoughts, insights, and
261
- observations gathered by IDEO through their work and the world
262
- around them. Read more pieces from the series <a href="http://www.fastcodesign.com/tag/ideo-patterns">here</a>.</em></p>
263
- <p><em><a href="http://www.fastcodesign.com/users/craney"><strong>Colin
264
- Raney</strong></a> leads the Business Design Community within IDEO.
265
- He specializes in designing new ventures for clients based on new
266
- technologies or unique insights. He believes that good business is
267
- good design&#8212;successful businesses require offerings, brands,
268
- services, and strategies that complement each other.</em></p>
269
-
270
- <div>
271
- <a href="http://www.fastcodesign.com/users/kclark" title="IDEO"><img src="http://www.fastcodesign.com/multisite_files/codesign/imagecache/124x124/IDEO_square_bio.jpg" alt="IDEO" title="IDEO" /></a>
272
- <div>
273
- <h2><a href="http://www.fastcodesign.com/users/kclark" title="IDEO">IDEO</a></h2>
274
- <p>IDEO is an award-winning global design firm that takes a
275
- human-centered approach to helping organizations in the public and
276
- private sectors innovate and grow. We identify &#8230; <a href="http://www.fastcodesign.com/users/kclark" title="Read more by IDEO">Read more</a></p>
277
- <p>• <a href="http://www.twitter.com/IDEO" title="IDEO's twitter profile">Twitter</a></p>
278
- </div>
279
- </div>
280
-
281
- </div>
282
-
283
- </div>
284
-
285
- <div><img src="http://www.fastcodesign.com/sites/fastcodesign.com/themes/co/images/blank.png" alt="image" /></div>
286
- <div>
287
- </div>
288
-
289
- </div>
290
- </div>
291
- </div>
292
- </div>
293
-
294
- </div>
295
-
296
- <div class="bar bottom">
297
- </div>
298
- </body>
299
- </html>