instapaper 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/{PostInstall.txt → .gemtest} +0 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.simplecov +1 -0
- data/.yardopts +3 -0
- data/Gemfile +2 -0
- data/LICENSE.md +20 -0
- data/README.md +152 -0
- data/Rakefile +13 -4
- data/instapaper.gemspec +33 -0
- data/lib/faraday/response/raise_http_1xxx.rb +65 -0
- data/lib/instapaper.rb +26 -10
- data/lib/instapaper/authentication.rb +32 -0
- data/lib/instapaper/client.rb +43 -0
- data/lib/instapaper/client/account.rb +13 -0
- data/lib/instapaper/client/bookmark.rb +81 -0
- data/lib/instapaper/client/folder.rb +34 -0
- data/lib/instapaper/client/user.rb +14 -0
- data/lib/instapaper/configuration.rb +88 -0
- data/lib/instapaper/connection.rb +35 -0
- data/lib/instapaper/request.rb +22 -9
- data/lib/instapaper/version.rb +1 -31
- data/spec/faraday/response_spec.rb +22 -0
- data/spec/fixtures/access_token.qline +1 -0
- data/spec/fixtures/bookmarks_add.json +1 -0
- data/spec/fixtures/bookmarks_archive.json +1 -0
- data/spec/fixtures/bookmarks_get_text.txt +299 -0
- data/spec/fixtures/bookmarks_list.json +5 -0
- data/spec/fixtures/bookmarks_move.json +1 -0
- data/spec/fixtures/bookmarks_star.json +1 -0
- data/spec/fixtures/bookmarks_unarchive.json +1 -0
- data/spec/fixtures/bookmarks_unstar.json +1 -0
- data/spec/fixtures/bookmarks_update_read_progress.json +1 -0
- data/spec/fixtures/folders_add.json +1 -0
- data/spec/fixtures/folders_delete.json +1 -0
- data/spec/fixtures/folders_list.json +1 -0
- data/spec/fixtures/folders_set_order.json +1 -0
- data/spec/fixtures/verify_credentials.json +1 -0
- data/spec/instapaper/client/account_spec.rb +27 -0
- data/spec/instapaper/client/bookmark_spec.rb +234 -0
- data/spec/instapaper/client/folder_spec.rb +89 -0
- data/spec/instapaper/client/user_spec.rb +28 -0
- data/spec/instapaper/client_spec.rb +65 -0
- data/spec/instapaper_spec.rb +85 -0
- data/spec/spec_helper.rb +44 -0
- metadata +230 -99
- data/History.txt +0 -3
- data/Manifest.txt +0 -43
- data/README.rdoc +0 -73
- data/config/hoe.rb +0 -73
- data/config/requirements.rb +0 -15
- data/lib/instapaper/base.rb +0 -113
- data/lib/instapaper/constants.rb +0 -37
- data/lib/instapaper/exceptions.rb +0 -41
- data/lib/instapaper/protocol.rb +0 -47
- data/lib/instapaper/request/abstract_request.rb +0 -104
- data/lib/instapaper/request/add_url_request.rb +0 -68
- data/lib/instapaper/request/authentication_request.rb +0 -60
- data/lib/instapaper/request/request_error.rb +0 -34
- data/lib/instapaper/request/request_errors.rb +0 -41
- data/lib/instapaper/request/request_validation.rb +0 -34
- data/lib/instapaper/response.rb +0 -10
- data/lib/instapaper/response/abstract_response.rb +0 -38
- data/lib/instapaper/response/add_url_success_response.rb +0 -41
- data/lib/instapaper/response/authentication_invalid_response.rb +0 -40
- data/lib/instapaper/response/authentication_success_response.rb +0 -40
- data/lib/instapaper/response/bad_request_response.rb +0 -40
- data/lib/instapaper/response/response_builder.rb +0 -61
- data/lib/instapaper/response/service_error_response.rb +0 -40
- data/script/console +0 -10
- data/script/destroy +0 -14
- data/script/generate +0 -14
- data/script/txt2html +0 -82
- data/setup.rb +0 -1585
- data/tasks/deployment.rake +0 -34
- data/tasks/environment.rake +0 -7
- data/tasks/website.rake +0 -17
- data/test/test_auth.rb +0 -62
- data/test/test_error.rb +0 -81
- data/test/test_helper.rb +0 -2
- data/test/test_instapaper.rb +0 -9
- data/test/test_request.rb +0 -45
- data/website/index.html +0 -99
- data/website/index.txt +0 -69
- data/website/javascripts/rounded_corners_lite.inc.js +0 -285
- data/website/stylesheets/screen.css +0 -138
- data/website/template.html.erb +0 -48
@@ -0,0 +1,81 @@
|
|
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
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Instapaper
|
2
|
+
class Client
|
3
|
+
# Defines methods related to folders
|
4
|
+
module Folder
|
5
|
+
|
6
|
+
# List the account’s user-created folders.
|
7
|
+
# @note This only includes organizational folders and does not include RSS-feed folders or starred-subscription folders
|
8
|
+
def folders
|
9
|
+
post('folders/list')
|
10
|
+
end
|
11
|
+
|
12
|
+
# Creates an organizational folder.
|
13
|
+
# @param title [String] The title of the folder to create
|
14
|
+
def add_folder(title)
|
15
|
+
post('folders/add', :title => title)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Deletes the folder and moves any articles in it to the Archive.
|
19
|
+
# @param folder_id [String] The id of the folder.
|
20
|
+
def delete_folder(folder_id)
|
21
|
+
post('folders/delete', :folder_id => folder_id)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Re-orders a user’s folders.
|
25
|
+
# @param order [Array] An array of folder_id:position pairs joined by commas.
|
26
|
+
# @example Ordering folder_ids 100, 200, and 300
|
27
|
+
# Instapaper.set_order(['100:1','200:2','300:3'])
|
28
|
+
def set_order(order=[])
|
29
|
+
post('folders/set_order', :order => order.join(','))
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,14 @@
|
|
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
|
+
Hash[*response.body.split("&").map {|part| part.split("=") }.flatten]
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,88 @@
|
|
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
|
@@ -0,0 +1,35 @@
|
|
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
|
data/lib/instapaper/request.rb
CHANGED
@@ -1,9 +1,22 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
data/lib/instapaper/version.rb
CHANGED
@@ -1,33 +1,3 @@
|
|
1
|
-
# Copyright (c) 2009 Douglas Willcocks
|
2
|
-
#
|
3
|
-
# Permission is hereby granted, free of charge, to any person
|
4
|
-
# obtaining a copy of this software and associated documentation
|
5
|
-
# files (the "Software"), to deal in the Software without
|
6
|
-
# restriction, including without limitation the rights to use,
|
7
|
-
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
-
# copies of the Software, and to permit persons to whom the
|
9
|
-
# Software is furnished to do so, subject to the following
|
10
|
-
# conditions:
|
11
|
-
#
|
12
|
-
# The above copyright notice and this permission notice shall be
|
13
|
-
# included in all copies or substantial portions of the Software.
|
14
|
-
#
|
15
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
-
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
-
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
-
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
-
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
-
# OTHER DEALINGS IN THE SOFTWARE.
|
23
|
-
|
24
1
|
module Instapaper
|
25
|
-
|
26
|
-
MAJOR = 0
|
27
|
-
MINOR = 1
|
28
|
-
TINY = 0
|
29
|
-
|
30
|
-
STRING = [MAJOR, MINOR, TINY].join('.')
|
31
|
-
self
|
32
|
-
end
|
2
|
+
VERSION = "0.2.0"
|
33
3
|
end
|
@@ -0,0 +1,22 @@
|
|
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
|
@@ -0,0 +1 @@
|
|
1
|
+
oauth_token=aabbccdd&oauth_token_secret=efgh1234
|
@@ -0,0 +1 @@
|
|
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}]
|
@@ -0,0 +1 @@
|
|
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}]
|
@@ -0,0 +1,299 @@
|
|
1
|
+
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Ideo'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’s Axioms for Starting Disruptive New
|
118
|
+
Businesses</span></h1>
|
119
|
+
|
120
|
+
<div>
|
121
|
+
<div>Don’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’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’s has been able to
|
240
|
+
roll out worldwide menu expansions in just a few months—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’ve been exposed to a few different examples,
|
257
|
+
don’t be surprised if you start seeing Life’s Changes
|
258
|
+
patterns all around. Keep your eyes open and let us know what you
|
259
|
+
find, especially if it’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—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 … <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>
|