instapaper 0.1.0 → 0.2.0
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.
- 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>
|