ruby-trello 0.4.4.3 → 0.5.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/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
|