ruby-trello 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -15,6 +15,11 @@ Seriously, [check it out](http://www.trello.com/).
15
15
 
16
16
  Full Disclosure: This library is not complete, but it does function enough to be useful.
17
17
 
18
+ ## Special thanks
19
+
20
+ A special thanks goes out to [Ben Biddington](https://github.com/ben-biddington) who has contributed a significant amount
21
+ of refactoring and functionality to be deserving of a beer and this special thanks.
22
+
18
23
  ## Contributing
19
24
 
20
25
  Pick up an editor, fix a test! (If you want to, of course.) Send a pull
@@ -24,4 +29,4 @@ request, and I'll look at it. I only ask a few things:
24
29
  2. Adding or refactoring existing features, ensure there are tests.
25
30
 
26
31
  Also, don't be afraid to send a pull request if your changes aren't done. Just
27
- let me know.
32
+ let me know.
data/lib/trello.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'oauth'
2
2
  require 'json'
3
+ require 'logger'
3
4
 
4
5
  # Ruby wrapper around the Trello[http://trello.com] API
5
6
  #
@@ -28,6 +29,7 @@ module Trello
28
29
  autoload :BasicData, 'trello/basic_data'
29
30
  autoload :Board, 'trello/board'
30
31
  autoload :Card, 'trello/card'
32
+ autoload :Checklist, 'trello/checklist'
31
33
  autoload :Client, 'trello/client'
32
34
  autoload :Item, 'trello/item'
33
35
  autoload :ItemState, 'trello/item_state'
@@ -35,10 +37,28 @@ module Trello
35
37
  autoload :Member, 'trello/member'
36
38
  autoload :Notification, 'trello/notification'
37
39
  autoload :Organization, 'trello/organization'
40
+ autoload :Request, 'trello/net'
41
+ autoload :TInternet, 'trello/net'
38
42
 
39
43
  # Version of the Trello API that we use by default.
40
44
  API_VERSION = 1
41
45
 
42
- # Raise this when we can't find a record.
43
- class RecordNotFound < StandardError; end
44
- end
46
+ # Raise this when we hit a Trello error.
47
+ class Error < StandardError; end
48
+
49
+ def self.logger
50
+ @@logger || Logger.new(STDOUT)
51
+ end
52
+
53
+ def self.logger=(logger)
54
+ @@logger = logger
55
+ end
56
+ end
57
+
58
+ module Trello
59
+ module Authorization
60
+ autoload :AuthPolicy, 'trello/authorization'
61
+ autoload :BasicAuthPolicy, 'trello/authorization'
62
+ autoload :OAuthPolicy, 'trello/authorization'
63
+ end
64
+ end
data/lib/trello/action.rb CHANGED
@@ -19,6 +19,7 @@ module Trello
19
19
  @type = fields['type']
20
20
  @data = fields['data']
21
21
  @member_creator_id = fields['idMemberCreator']
22
+ self
22
23
  end
23
24
 
24
25
  # Returns the board this action occurred on.
@@ -0,0 +1,77 @@
1
+ require 'securerandom'
2
+
3
+ module Trello
4
+ module Authorization
5
+
6
+ AuthPolicy = Class.new
7
+
8
+ class BasicAuthPolicy
9
+ class << self
10
+ attr_accessor :developer_public_key, :member_token
11
+
12
+ def authorize(request)
13
+ the_uri = Addressable::URI.parse(request.uri)
14
+ existing_values = the_uri.query_values.nil? ? {} : the_uri.query_values
15
+ new_values = { :key => @developer_public_key, :token => @member_token }
16
+ the_uri.query_values = new_values.merge existing_values
17
+
18
+ Request.new request.verb, the_uri, request.headers
19
+ end
20
+ end
21
+ end
22
+
23
+ class Clock
24
+ def self.timestamp; Time.now.to_i; end
25
+ end
26
+
27
+ class Nonce
28
+ def self.next
29
+ SecureRandom.hex()
30
+ end
31
+ end
32
+
33
+ OAuthCredential = Struct.new "OAuthCredential", :key, :secret
34
+
35
+ class OAuthPolicy
36
+ class << self
37
+ attr_accessor :consumer_credential, :token
38
+
39
+ def authorize(request)
40
+ fail "The consumer_credential has not been supplied." unless consumer_credential
41
+
42
+ request.headers = {"Authorization" => get_auth_header(request.uri, :get)}
43
+ request
44
+ end
45
+
46
+ private
47
+
48
+ def get_auth_header(url, verb)
49
+ require "oauth"
50
+
51
+ self.token ||= OAuthCredential.new
52
+
53
+ consumer = OAuth::Consumer.new(
54
+ consumer_credential.key,
55
+ consumer_credential.secret,
56
+ {
57
+ :scheme => :header,
58
+ :scope => 'read,write,account',
59
+ :http_method => verb
60
+ }
61
+ )
62
+
63
+ request = Net::HTTP::Get.new Addressable::URI.parse(url).to_s
64
+
65
+ consumer.options[:signature_method] = 'HMAC-SHA1'
66
+ consumer.options[:nonce] = Nonce.next
67
+ consumer.options[:timestamp] = Clock.timestamp
68
+ consumer.options[:uri] = url
69
+
70
+ consumer.sign!(request, OAuth::Token.new(token.key, token.secret))
71
+
72
+ request['authorization']
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -5,7 +5,6 @@ module Trello
5
5
  attr_reader :id
6
6
 
7
7
  class << self
8
- # Perform a query to retrieve some information at a specific path for a specific id.
9
8
  def find(path, id)
10
9
  Client.get("/#{path}/#{id}").json_into(self)
11
10
  end
@@ -29,4 +28,4 @@ module Trello
29
28
  id == other.id
30
29
  end
31
30
  end
32
- end
31
+ end
data/lib/trello/board.rb CHANGED
@@ -1,31 +1,44 @@
1
1
  module Trello
2
- # A board is a container which holds lists. This is where everything lives.
2
+
3
3
  class Board < BasicData
4
- attr_reader :id, :name, :description, :closed, :url, :organization_id
4
+ attr_reader :id, :name, :description, :url, :organization_id
5
5
 
6
6
  class << self
7
- # Locate a board given a specific id.
7
+
8
8
  def find(id)
9
9
  super(:boards, id)
10
10
  end
11
+
12
+ def create(attributes)
13
+ Client.post("/boards/", attributes).json_into Board
14
+ end
15
+ end
16
+
17
+ def save!
18
+ fail "Cannot save new instance." unless self.id
19
+
20
+ Client.put("/boards/#{self.id}/", {
21
+ :name => @name,
22
+ :description => @description,
23
+ :closed => @closed,
24
+ :url => @url,
25
+ :organisation_id => @organisation_id
26
+ }).json_into(self)
11
27
  end
12
28
 
13
- # Update the fields of a board.
14
- #
15
- # Supply a hash of string keyed data retrieved from the Trello API representing
16
- # a board.
17
29
  def update_fields(fields)
18
- @id = fields['id']
19
- @name = fields['name']
20
- @description = fields['desc']
21
- @closed = fields['closed']
22
- @url = fields['url']
23
- @organization_id = fields['idOrganization']
30
+ @id = fields['id'] if fields['id']
31
+ @name = fields['name'] if fields['name']
32
+ @description = fields['desc'] if fields['desc']
33
+ @closed = fields['closed'] if fields.has_key?('closed')
34
+ @url = fields['url'] if fields['url']
35
+ @organization_id = fields['idOrganization'] if fields['idOrganization']
36
+
37
+ self
24
38
  end
25
39
 
26
- # Check if the board is active.
27
40
  def closed?
28
- closed
41
+ @closed
29
42
  end
30
43
 
31
44
  # Return a timeline of actions related to this board.
@@ -44,6 +57,10 @@ module Trello
44
57
  @cards = Client.get("/boards/#{id}/cards").json_into(Card)
45
58
  end
46
59
 
60
+ def has_lists?
61
+ lists.size > 0
62
+ end
63
+
47
64
  # Returns all the lists on this board.
48
65
  #
49
66
  # The options hash may have a filter key which can have its value set as any
@@ -61,7 +78,7 @@ module Trello
61
78
  # :filter => [ :none, :normal, :owners, :all ] # default :all
62
79
  def members(options = { :filter => :all })
63
80
  return @members if @members
64
- @members = Client.get("/boards/#{id}/members").json_into(Member)
81
+ @members = Client.get("/boards/#{id}/members", options).json_into(Member)
65
82
  end
66
83
 
67
84
  # Returns a reference to the organization this board belongs to.
data/lib/trello/card.rb CHANGED
@@ -14,7 +14,7 @@ module Trello
14
14
  def create(options)
15
15
  new('name' => options[:name],
16
16
  'idList' => options[:list_id],
17
- 'desc' => options[:description]).save!
17
+ 'desc' => options[:description]).save!
18
18
  end
19
19
  end
20
20
 
@@ -31,6 +31,7 @@ module Trello
31
31
  @board_id = fields['idBoard']
32
32
  @member_ids = fields['idMembers']
33
33
  @list_id = fields['idList']
34
+ self
34
35
  end
35
36
 
36
37
  # Returns a list of the actions associated with this card.
@@ -69,15 +70,35 @@ module Trello
69
70
  end
70
71
  end
71
72
 
73
+ # Change the name of the card
74
+ def name=(val)
75
+ Client.put("/card/#{id}/name", :value => val)
76
+ @name = val
77
+ end
78
+
79
+ # Change the description of the card
80
+ def description=(val)
81
+ Client.put("/card/#{id}/desc", :value => val)
82
+ @description = val
83
+ end
84
+
85
+ # Change the list this card is a part of
86
+ def list=(other)
87
+ Client.put("/card/#{id}/idList", :value => other.id)
88
+ @list_id = other.id
89
+ other
90
+ end
91
+
72
92
  # Saves a record.
73
93
  def save!
94
+ # If we have an id, just update our fields.
74
95
  return update! if id
75
96
 
76
97
  Client.post("/cards", {
77
98
  :name => @name,
78
99
  :desc => @description,
79
100
  :idList => @list_id
80
- })
101
+ }).json_into(self)
81
102
  end
82
103
 
83
104
  # Update an existing record.
@@ -93,7 +114,7 @@ module Trello
93
114
 
94
115
  # Add a comment with the supplied text.
95
116
  def add_comment(text)
96
- Client.put("/cards/#{id}/actions/comments", :text => text)
117
+ Client.post("/cards/#{id}/actions/comments", :text => text)
97
118
  end
98
119
  end
99
120
  end
@@ -23,6 +23,7 @@ module Trello
23
23
  @check_items = fields['checkItems']
24
24
  @board_id = fields['idBoard']
25
25
  @member_ids = fields['idMembers']
26
+ self
26
27
  end
27
28
 
28
29
  # Check if the checklist is currently active.
data/lib/trello/client.rb CHANGED
@@ -1,66 +1,51 @@
1
1
  require 'addressable/uri'
2
2
 
3
3
  module Trello
4
- # Client is used to handle the OAuth connection to Trello as well as send requests over that authenticated socket.
5
4
  class Client
6
- class EnterYourPublicKey < StandardError; end
7
- class EnterYourSecret < StandardError; end
5
+ extend Authorization
8
6
 
9
7
  class << self
10
- attr_writer :public_key, :secret
11
-
12
- # call-seq:
13
- # get(path, params)
14
- # post(path, params)
15
- # put(path, params)
16
- # delete(path, params)
17
- # query(api_version, path, options)
18
- #
19
- # Makes a query to a specific path via one of the four HTTP methods, optionally
20
- # with a hash specifying parameters to pass to Trello.
21
- #
22
- # You should use one of _.get_, _.post_, _.put_ or _.delete_ instead of this method.
23
- def query(api_version, path, options = { :method => :get, :params => {} })
8
+ def get(path, params = {})
9
+ api_version = 1
10
+
24
11
  uri = Addressable::URI.parse("https://api.trello.com/#{api_version}#{path}")
25
- uri.query_values = options[:params]
26
-
27
- access_token.send(options[:method], uri.to_s)
28
- rescue OAuth::Problem => e
29
- headers = []
30
- e.request.each_header do |k,v|
31
- headers << [k, v]
32
- end
33
-
34
- logger.error("[#{@access_token}] Disposing of access token.\n#{e.inspect}")
35
- logger.info("[#{@access_token}] Headers: #{headers.inspect}\nRequest Body: #{e.request.body}")
36
- @access_token = nil
37
- end
12
+ uri.query_values = params unless params.empty?
13
+
14
+ request = Request.new :get, uri, {}
15
+
16
+ response = TInternet.execute AuthPolicy.authorize(request)
38
17
 
39
- %w{get post put delete}.each do |http_method|
40
- send(:define_method, http_method) do |*args|
41
- path = args[0]
42
- params = args[1] || {}
43
- query(API_VERSION, path, :method => http_method, :params => params).read_body
44
- end
18
+ raise Error, response.body unless response.code.to_i == 200
19
+
20
+ response.body
45
21
  end
46
22
 
47
- protected
23
+ def post(path, body = {})
24
+ api_version = 1
25
+
26
+ uri = Addressable::URI.parse("https://api.trello.com/#{api_version}#{path}")
27
+
28
+ request = Request.new :post, uri, {}, body
29
+
30
+ response = TInternet.execute AuthPolicy.authorize(request)
48
31
 
49
- def consumer
50
- raise EnterYourPublicKey if @public_key.to_s.empty?
51
- raise EnterYourSecret if @secret.to_s.empty?
32
+ raise Error, response.body unless response.code.to_i == 200
52
33
 
53
- OAuth::Consumer.new(@public_key, @secret, :site => 'https://trello.com',
54
- :request_token_path => '/1/OAuthGetRequestToken',
55
- :authorize_path => '/1/OAuthAuthorizeToken',
56
- :access_token_path => '/1/OAuthGetAccessToken',
57
- :http_method => :get)
34
+ response.body
58
35
  end
59
36
 
60
- def access_token
61
- return @access_token if @access_token
37
+ def put(path, body = {})
38
+ api_version = 1
39
+
40
+ uri = Addressable::URI.parse("https://api.trello.com/#{api_version}#{path}")
41
+
42
+ request = Request.new :put, uri, {}, body
43
+
44
+ response = TInternet.execute AuthPolicy.authorize(request)
45
+
46
+ raise Error, response.body unless response.code.to_i == 200
62
47
 
63
- @access_token = OAuth::AccessToken.new(consumer)
48
+ response.body
64
49
  end
65
50
  end
66
51
  end
data/lib/trello/item.rb CHANGED
@@ -11,6 +11,7 @@ module Trello
11
11
  @id = fields['id']
12
12
  @name = fields['name']
13
13
  @type = fields['type']
14
+ self
14
15
  end
15
16
  end
16
17
  end
@@ -11,6 +11,7 @@ module Trello
11
11
  @id = fields['id']
12
12
  @state = fields['state']
13
13
  @item_id = fields['idItem']
14
+ self
14
15
  end
15
16
 
16
17
  # Return the item this state belongs to.
data/lib/trello/list.rb CHANGED
@@ -8,6 +8,11 @@ module Trello
8
8
  def find(id)
9
9
  super(:lists, id)
10
10
  end
11
+
12
+ def create(options)
13
+ new('name' => options[:name],
14
+ 'idBoard' => options[:board_id]).save!
15
+ end
11
16
  end
12
17
 
13
18
  # Updates the fields of a list.
@@ -19,6 +24,20 @@ module Trello
19
24
  @name = fields['name']
20
25
  @closed = fields['closed']
21
26
  @board_id = fields['idBoard']
27
+ self
28
+ end
29
+
30
+ def save!
31
+ return update! if id
32
+
33
+ Client.post("/lists", {
34
+ :name => @name,
35
+ :closed => @closed || false,
36
+ :idBoard => @board_id
37
+ }).json_into(self)
38
+ end
39
+
40
+ def update!
22
41
  end
23
42
 
24
43
  # Check if the list is not active anymore.