ruby-trello 0.4.4.3 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +81 -8
- data/lib/trello.rb +25 -9
- data/lib/trello/action.rb +5 -5
- data/lib/trello/association_proxy.rb +3 -3
- data/lib/trello/authorization.rb +105 -49
- data/lib/trello/basic_data.rb +48 -4
- data/lib/trello/board.rb +10 -9
- data/lib/trello/card.rb +38 -35
- data/lib/trello/checklist.rb +10 -8
- data/lib/trello/client.rb +98 -29
- data/lib/trello/configuration.rb +68 -0
- data/lib/trello/has_actions.rb +1 -1
- data/lib/trello/list.rb +9 -7
- data/lib/trello/member.rb +3 -3
- data/lib/trello/multi_association.rb +4 -2
- data/lib/trello/notification.rb +8 -8
- data/lib/trello/organization.rb +3 -3
- data/lib/trello/token.rb +4 -4
- data/spec/action_spec.rb +30 -18
- data/spec/basic_auth_policy_spec.rb +1 -0
- data/spec/board_spec.rb +141 -120
- data/spec/card_spec.rb +104 -82
- data/spec/checklist_spec.rb +35 -6
- data/spec/client_spec.rb +56 -19
- data/spec/configuration_spec.rb +114 -0
- data/spec/integration/how_to_authorize_spec.rb +1 -1
- data/spec/list_spec.rb +74 -20
- data/spec/member_spec.rb +37 -23
- data/spec/notification_spec.rb +28 -24
- data/spec/oauth_policy_spec.rb +55 -9
- data/spec/organization_spec.rb +18 -7
- data/spec/spec_helper.rb +5 -0
- data/spec/token_spec.rb +25 -8
- data/spec/trello_spec.rb +73 -0
- metadata +10 -6
data/README.md
CHANGED
@@ -16,6 +16,86 @@ Seriously, [check it out](http://www.trello.com/).
|
|
16
16
|
Full Disclosure: This library is mostly complete, if you do find anything missing or not functioning as you expect it
|
17
17
|
to, please [let us know](https://trello.com/card/spot-a-bug-report-it/4f092b2ee23cb6fe6d1aaabd/17).
|
18
18
|
|
19
|
+
## Configuration
|
20
|
+
|
21
|
+
Basic authorization
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
Trello.configure do |config|
|
25
|
+
config.developer_public_key = TRELLO_DEVELOPER_PUBLIC_KEY
|
26
|
+
config.member_token = TRELLO_MEMBER_TOKEN
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
2-legged OAuth authorization
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
Trello.configure do |config|
|
34
|
+
config.consumer_key = TRELLO_CONSUMER_KEY
|
35
|
+
config.consumer_secret = TRELLO_CONSUMER_SECRET
|
36
|
+
config.oauth_token = TRELLO_OAUTH_TOKEN
|
37
|
+
config.oauth_token_secret = TRELLO_OAUTH_TOKEN_SECRET
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
3-legged OAuth authorization
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
Trello.configure do |config|
|
45
|
+
config.consumer_key = TRELLO_CONSUMER_KEY
|
46
|
+
config.consumer_secret = TRELLO_CONSUMER_SECRET
|
47
|
+
config.return_url = "http://your.site.com/path/to/receive/post"
|
48
|
+
config.callback = lambda { |request_token| DB.save(request_token.key, request_token.secret) }
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
All the calls this library make to Trello require authentication using these keys. Be sure to protect them.
|
53
|
+
|
54
|
+
So lets say you want to get information about the user *bobtester*. We can do something like this:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
bob = Trello::Member.find("bobtester")
|
58
|
+
# Print out his name
|
59
|
+
puts bob.full_name # "Bob Tester"
|
60
|
+
# Print his bio
|
61
|
+
puts bob.bio # A wonderfully delightful test user
|
62
|
+
# How about a list of his boards?
|
63
|
+
bob.boards
|
64
|
+
```
|
65
|
+
|
66
|
+
#### Multiple Users
|
67
|
+
|
68
|
+
Applications that make requests on behalf of multiple Trello users have an alternative to global configuration. For each user's access token/secret pair, instantiate a `Trello::Client`:
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
@client_bob = Trello::Client.new(
|
72
|
+
:consumer_key => YOUR_CONSUMER_KEY,
|
73
|
+
:consumer_secret => YOUR_CONSUMER_SECRET,
|
74
|
+
:oauth_token => "Bob's access token",
|
75
|
+
:oauth_token_secret => "Bob's access secret"
|
76
|
+
)
|
77
|
+
|
78
|
+
@client_alice = Trello::Client.new(
|
79
|
+
:consumer_key => YOUR_CONSUMER_KEY,
|
80
|
+
:consumer_secret => YOUR_CONSUMER_SECRET,
|
81
|
+
:oauth_token => "Alice's access token",
|
82
|
+
:oauth_token_secret => "Alice's access secret"
|
83
|
+
)
|
84
|
+
```
|
85
|
+
|
86
|
+
You can now make threadsafe requests as the authenticated user:
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
Thread.new do
|
90
|
+
@client_bob.find(:members, "bobtester")
|
91
|
+
@client_bob.find(:boards, "bobs_board_id")
|
92
|
+
end
|
93
|
+
Thread.new do
|
94
|
+
@client_alice.find(:members, "alicetester")
|
95
|
+
@client_alice.find(:boards, "alices_board_id")
|
96
|
+
end
|
97
|
+
```
|
98
|
+
|
19
99
|
## Special thanks
|
20
100
|
|
21
101
|
A special thanks goes out to [Ben Biddington](https://github.com/ben-biddington) who has contributed a significant amount
|
@@ -27,11 +107,4 @@ Several ways you can contribute. Documentation, code, tests, feature requests, b
|
|
27
107
|
|
28
108
|
We develop ruby-trello using [Trello itself](https://trello.com/board/ruby-trello/4f092b2ee23cb6fe6d1aaabd).
|
29
109
|
|
30
|
-
|
31
|
-
request, and I'll look at it. I only ask a few things:
|
32
|
-
|
33
|
-
1. Feature branches please!
|
34
|
-
2. Adding or refactoring existing features, ensure there are tests.
|
35
|
-
|
36
|
-
Also, don't be afraid to send a pull request if your changes aren't done. Just
|
37
|
-
let me know.
|
110
|
+
Please see the `CONTRIBUTING.md` file for more information.
|
data/lib/trello.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
|
1
2
|
require 'oauth'
|
2
3
|
require 'json'
|
3
4
|
require 'logger'
|
@@ -7,20 +8,18 @@ require 'active_model'
|
|
7
8
|
#
|
8
9
|
# First, set up your key information. You can get this information by {clicking here}[https://trello.com/1/appKey/generate].
|
9
10
|
#
|
10
|
-
# include Trello
|
11
|
-
# include Trello::Authorization
|
12
|
-
#
|
13
|
-
# Trello::Authorization.const_set :AuthPolicy, OAuthPolicy
|
14
|
-
#
|
15
|
-
# OAuthPolicy.consumer_credential = OAuthCredential.new 'PUBLIC_KEY', 'SECRET'
|
16
|
-
#
|
17
11
|
# You can get the key by going to this url in your browser:
|
18
|
-
# https://trello.com/1/authorize?key=
|
12
|
+
# https://trello.com/1/authorize?key=TRELLO_CONSUMER_KEY_FROM_ABOVE&name=MyApp&response_type=token&scope=read,write,account&expiration=never
|
19
13
|
# Only request the permissions you need; i.e., scope=read if you only need read, or scope=write if you only need write. Comma separate scopes you need.
|
20
14
|
# If you want your token to expire after 30 days, drop the &expiration=never. Then run the following code, where KEY denotes the key returned from the
|
21
15
|
# url above:
|
22
16
|
#
|
23
|
-
#
|
17
|
+
# Trello.configure do |config|
|
18
|
+
# config.consumer_key = TRELLO_CONSUMER_KEY
|
19
|
+
# config.consumer_secret = TRELLO_CONSUMER_SECRET
|
20
|
+
# config.oauth_token = TRELLO_OAUTH_TOKEN
|
21
|
+
# config.oauth_token_secret = TRELLO_OAUTH_TOKEN_SECRET
|
22
|
+
# end
|
24
23
|
#
|
25
24
|
# All the calls this library make to Trello require authentication using these keys. Be sure to protect them.
|
26
25
|
#
|
@@ -47,6 +46,7 @@ module Trello
|
|
47
46
|
autoload :Card, 'trello/card'
|
48
47
|
autoload :Checklist, 'trello/checklist'
|
49
48
|
autoload :Client, 'trello/client'
|
49
|
+
autoload :Configuration, 'trello/configuration'
|
50
50
|
autoload :HasActions, 'trello/has_actions'
|
51
51
|
autoload :Item, 'trello/item'
|
52
52
|
autoload :CheckItemState, 'trello/item_state'
|
@@ -81,4 +81,20 @@ module Trello
|
|
81
81
|
def self.logger=(logger)
|
82
82
|
@logger = logger
|
83
83
|
end
|
84
|
+
|
85
|
+
def self.client
|
86
|
+
@client ||= Client.new
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.configure(&block)
|
90
|
+
reset!
|
91
|
+
client.configure(&block)
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.reset!
|
95
|
+
@client = nil
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.auth_policy; client.auth_policy; end
|
99
|
+
def self.configuration; client.configuration; end
|
84
100
|
end
|
data/lib/trello/action.rb
CHANGED
@@ -8,7 +8,7 @@ module Trello
|
|
8
8
|
class << self
|
9
9
|
# Locate a specific action and return a new Action object.
|
10
10
|
def find(id)
|
11
|
-
|
11
|
+
client.find(:action, id)
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
@@ -27,20 +27,20 @@ module Trello
|
|
27
27
|
|
28
28
|
# Returns the board this action occurred on.
|
29
29
|
def board
|
30
|
-
|
30
|
+
client.get("/actions/#{id}/board").json_into(Board)
|
31
31
|
end
|
32
32
|
|
33
33
|
# Returns the card the action occurred on.
|
34
34
|
def card
|
35
|
-
|
35
|
+
client.get("/actions/#{id}/card").json_into(Card)
|
36
36
|
end
|
37
37
|
|
38
38
|
# Returns the list the action occurred on.
|
39
39
|
def list
|
40
|
-
|
40
|
+
client.get("/actions/#{id}/list").json_into(List)
|
41
41
|
end
|
42
42
|
|
43
43
|
# Returns the member who created the action.
|
44
|
-
one :member_creator, :via => Member, :using => :member_creator_id
|
44
|
+
one :member_creator, :via => Member, :path => :members, :using => :member_creator_id
|
45
45
|
end
|
46
46
|
end
|
@@ -2,12 +2,12 @@ require 'active_support/core_ext'
|
|
2
2
|
|
3
3
|
module Trello
|
4
4
|
class AssociationProxy
|
5
|
+
extend Forwardable
|
5
6
|
alias :proxy_extend :extend
|
6
7
|
|
7
8
|
instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|^respond_to|proxy_/ }
|
8
9
|
|
9
|
-
|
10
|
-
delegate :count, :to => :@association
|
10
|
+
def_delegators :@association, :owner, :target, :count
|
11
11
|
|
12
12
|
def initialize(association)
|
13
13
|
@association = association
|
@@ -39,4 +39,4 @@ module Trello
|
|
39
39
|
proxy_assocation.concat(records) && self
|
40
40
|
end
|
41
41
|
end
|
42
|
-
end
|
42
|
+
end
|
data/lib/trello/authorization.rb
CHANGED
@@ -4,21 +4,34 @@ require "oauth"
|
|
4
4
|
module Trello
|
5
5
|
module Authorization
|
6
6
|
|
7
|
-
AuthPolicy = Class.new
|
7
|
+
AuthPolicy = Class.new do
|
8
|
+
def initialize(attrs = {}); end
|
9
|
+
end
|
8
10
|
|
9
11
|
class BasicAuthPolicy
|
10
12
|
class << self
|
11
13
|
attr_accessor :developer_public_key, :member_token
|
12
14
|
|
13
15
|
def authorize(request)
|
14
|
-
|
15
|
-
existing_values = the_uri.query_values.nil? ? {} : the_uri.query_values
|
16
|
-
new_values = { :key => @developer_public_key, :token => @member_token }
|
17
|
-
the_uri.query_values = new_values.merge existing_values
|
18
|
-
|
19
|
-
Request.new request.verb, the_uri, request.headers, request.body
|
16
|
+
new.authorize(request)
|
20
17
|
end
|
21
18
|
end
|
19
|
+
|
20
|
+
attr_accessor :developer_public_key, :member_token
|
21
|
+
|
22
|
+
def initialize(attrs = {})
|
23
|
+
@developer_public_key = attrs[:developer_public_key] || self.class.developer_public_key
|
24
|
+
@member_token = attrs[:member_token] || self.class.member_token
|
25
|
+
end
|
26
|
+
|
27
|
+
def authorize(request)
|
28
|
+
the_uri = Addressable::URI.parse(request.uri)
|
29
|
+
existing_values = the_uri.query_values.nil? ? {} : the_uri.query_values
|
30
|
+
new_values = { :key => @developer_public_key, :token => @member_token }
|
31
|
+
the_uri.query_values = new_values.merge existing_values
|
32
|
+
|
33
|
+
Request.new request.verb, the_uri, request.headers, request.body
|
34
|
+
end
|
22
35
|
end
|
23
36
|
|
24
37
|
class Clock
|
@@ -57,60 +70,103 @@ module Trello
|
|
57
70
|
attr_accessor :consumer_credential, :token, :return_url, :callback
|
58
71
|
|
59
72
|
def authorize(request)
|
60
|
-
|
61
|
-
Trello.logger.error "The consumer_credential has not been supplied."
|
62
|
-
fail "The consumer_credential has not been supplied."
|
63
|
-
end
|
64
|
-
|
65
|
-
if token
|
66
|
-
request.headers = {"Authorization" => get_auth_header(request.uri, :get)}
|
67
|
-
request
|
68
|
-
else
|
69
|
-
consumer(:return_url => return_url, :callback_method => :postMessage)
|
70
|
-
request_token = consumer.get_request_token
|
71
|
-
callback.call request_token
|
72
|
-
return nil
|
73
|
-
end
|
73
|
+
new.authorize(request)
|
74
74
|
end
|
75
|
+
end
|
75
76
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
77
|
+
attr_accessor :attributes
|
78
|
+
attr_accessor :consumer_credential, :token, :return_url, :callback
|
79
|
+
|
80
|
+
def initialize(attrs = {})
|
81
|
+
@consumer_key = attrs[:consumer_key]
|
82
|
+
@consumer_secret = attrs[:consumer_secret]
|
83
|
+
@oauth_token = attrs[:oauth_token]
|
84
|
+
@oauth_token_secret = attrs[:oauth_token_secret]
|
85
|
+
@return_url = attrs[:return_url] || self.class.return_url
|
86
|
+
@callback = attrs[:callback] || self.class.callback
|
87
|
+
end
|
88
|
+
|
89
|
+
def authorize(request)
|
90
|
+
unless consumer_credential
|
91
|
+
Trello.logger.error "The consumer_credential has not been supplied."
|
92
|
+
fail "The consumer_credential has not been supplied."
|
87
93
|
end
|
88
94
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
+
if token
|
96
|
+
request.headers = {"Authorization" => get_auth_header(request.uri, :get)}
|
97
|
+
request
|
98
|
+
else
|
99
|
+
consumer(:return_url => return_url, :callback_method => :postMessage)
|
100
|
+
request_token = consumer.get_request_token
|
101
|
+
callback.call request_token
|
102
|
+
return nil
|
95
103
|
end
|
104
|
+
end
|
96
105
|
|
97
|
-
|
98
|
-
|
106
|
+
def consumer_credential
|
107
|
+
@consumer_credential ||= build_consumer_credential
|
108
|
+
end
|
99
109
|
|
100
|
-
|
110
|
+
def token
|
111
|
+
@token ||= build_token
|
112
|
+
end
|
113
|
+
|
114
|
+
def consumer_key; consumer_credential.key; end
|
115
|
+
def consumer_secret; consumer_credential.secret; end
|
116
|
+
def oauth_token; token.key; end
|
117
|
+
def oauth_token_secret; token.secret; end
|
101
118
|
|
102
|
-
|
103
|
-
consumer.options[:nonce] = Nonce.next
|
104
|
-
consumer.options[:timestamp] = Clock.timestamp
|
105
|
-
consumer.options[:uri] = url
|
106
|
-
consumer.key = consumer_credential.key
|
107
|
-
consumer.secret = consumer_credential.secret
|
119
|
+
private
|
108
120
|
|
109
|
-
|
121
|
+
def build_consumer_credential
|
122
|
+
if @consumer_key && @consumer_secret
|
123
|
+
OAuthCredential.new @consumer_key, @consumer_secret
|
124
|
+
else
|
125
|
+
self.class.consumer_credential
|
126
|
+
end
|
127
|
+
end
|
110
128
|
|
111
|
-
|
129
|
+
def build_token
|
130
|
+
if @oauth_token
|
131
|
+
OAuthCredential.new @oauth_token, @oauth_token_secret
|
132
|
+
else
|
133
|
+
self.class.token
|
112
134
|
end
|
113
135
|
end
|
136
|
+
|
137
|
+
def consumer_params(params = {})
|
138
|
+
{
|
139
|
+
:scheme => :header,
|
140
|
+
:scope => 'read,write,account',
|
141
|
+
:http_method => :get,
|
142
|
+
:request_token_path => "https://trello.com/1/OAuthGetRequestToken",
|
143
|
+
:authorize_path => "https://trello.com/1/OAuthAuthorizeToken",
|
144
|
+
:access_token_path => "https://trello.com/1/OAuthGetAccessToken"
|
145
|
+
}.merge!(params)
|
146
|
+
end
|
147
|
+
|
148
|
+
def consumer(options = {})
|
149
|
+
@consumer ||= OAuth::Consumer.new(
|
150
|
+
consumer_credential.key,
|
151
|
+
consumer_credential.secret,
|
152
|
+
consumer_params(options)
|
153
|
+
)
|
154
|
+
end
|
155
|
+
|
156
|
+
def get_auth_header(url, verb, options = {})
|
157
|
+
request = Net::HTTP::Get.new Addressable::URI.parse(url).to_s
|
158
|
+
|
159
|
+
consumer.options[:signature_method] = 'HMAC-SHA1'
|
160
|
+
consumer.options[:nonce] = Nonce.next
|
161
|
+
consumer.options[:timestamp] = Clock.timestamp
|
162
|
+
consumer.options[:uri] = url
|
163
|
+
consumer.key = consumer_credential.key
|
164
|
+
consumer.secret = consumer_credential.secret
|
165
|
+
|
166
|
+
consumer.sign!(request, OAuth::Token.new(token.key, token.secret))
|
167
|
+
|
168
|
+
request['authorization']
|
169
|
+
end
|
114
170
|
end
|
115
171
|
end
|
116
172
|
end
|
data/lib/trello/basic_data.rb
CHANGED
@@ -7,8 +7,36 @@ module Trello
|
|
7
7
|
include ActiveModel::Serializers::JSON
|
8
8
|
|
9
9
|
class << self
|
10
|
-
def
|
11
|
-
|
10
|
+
def path_name
|
11
|
+
name.split("::").last.underscore
|
12
|
+
end
|
13
|
+
|
14
|
+
def find(id)
|
15
|
+
client.find(path_name, id)
|
16
|
+
end
|
17
|
+
|
18
|
+
def create(options)
|
19
|
+
client.create(path_name, options)
|
20
|
+
end
|
21
|
+
|
22
|
+
def save(options)
|
23
|
+
new(options).tap do |basic_data|
|
24
|
+
yield basic_data if block_given?
|
25
|
+
end.save
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse(response)
|
29
|
+
response.json_into(self).tap do |basic_data|
|
30
|
+
yield basic_data if block_given?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def parse_many(response)
|
35
|
+
response.json_into(self).map do |data|
|
36
|
+
data.tap do |d|
|
37
|
+
yield d if block_given?
|
38
|
+
end
|
39
|
+
end
|
12
40
|
end
|
13
41
|
end
|
14
42
|
|
@@ -43,7 +71,13 @@ module Trello
|
|
43
71
|
options = opts.dup
|
44
72
|
klass = options.delete(:via) || Trello.const_get(name.to_s.camelize)
|
45
73
|
ident = options.delete(:using) || :id
|
46
|
-
|
74
|
+
path = options.delete(:path)
|
75
|
+
|
76
|
+
if path
|
77
|
+
client.find(path, self.send(ident))
|
78
|
+
else
|
79
|
+
klass.find(self.send(ident))
|
80
|
+
end
|
47
81
|
end
|
48
82
|
end
|
49
83
|
end
|
@@ -55,14 +89,20 @@ module Trello
|
|
55
89
|
resource = options.delete(:in) || self.class.to_s.split("::").last.downcase.pluralize
|
56
90
|
klass = options.delete(:via) || Trello.const_get(name.to_s.singularize.camelize)
|
57
91
|
params = options.merge(args[0] || {})
|
58
|
-
resources =
|
92
|
+
resources = client.find_many(klass, "/#{resource}/#{id}/#{name}", params)
|
59
93
|
MultiAssociation.new(self, resources).proxy
|
60
94
|
end
|
61
95
|
end
|
62
96
|
end
|
63
97
|
|
98
|
+
def self.client
|
99
|
+
Trello.client
|
100
|
+
end
|
101
|
+
|
64
102
|
register_attributes :id, :readonly => [ :id ]
|
65
103
|
|
104
|
+
attr_writer :client
|
105
|
+
|
66
106
|
def initialize(fields = {})
|
67
107
|
update_fields(fields)
|
68
108
|
end
|
@@ -80,5 +120,9 @@ module Trello
|
|
80
120
|
def ==(other)
|
81
121
|
id == other.id
|
82
122
|
end
|
123
|
+
|
124
|
+
def client
|
125
|
+
@client ||= self.class.client
|
126
|
+
end
|
83
127
|
end
|
84
128
|
end
|