ruby-trello-wgibbs 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/README.md +37 -0
  2. data/lib/trello/action.rb +46 -0
  3. data/lib/trello/association.rb +11 -0
  4. data/lib/trello/association_proxy.rb +42 -0
  5. data/lib/trello/authorization.rb +114 -0
  6. data/lib/trello/basic_data.rb +84 -0
  7. data/lib/trello/board.rb +95 -0
  8. data/lib/trello/card.rb +162 -0
  9. data/lib/trello/checklist.rb +82 -0
  10. data/lib/trello/client.rb +49 -0
  11. data/lib/trello/has_actions.rb +9 -0
  12. data/lib/trello/item.rb +18 -0
  13. data/lib/trello/item_state.rb +23 -0
  14. data/lib/trello/label.rb +19 -0
  15. data/lib/trello/list.rb +71 -0
  16. data/lib/trello/member.rb +93 -0
  17. data/lib/trello/multi_association.rb +10 -0
  18. data/lib/trello/net.rb +37 -0
  19. data/lib/trello/notification.rb +48 -0
  20. data/lib/trello/organization.rb +47 -0
  21. data/lib/trello/string.rb +36 -0
  22. data/lib/trello/token.rb +24 -0
  23. data/lib/trello.rb +83 -0
  24. data/spec/action_spec.rb +71 -0
  25. data/spec/basic_auth_policy_spec.rb +56 -0
  26. data/spec/board_spec.rb +196 -0
  27. data/spec/card_spec.rb +213 -0
  28. data/spec/checklist_spec.rb +50 -0
  29. data/spec/client_spec.rb +131 -0
  30. data/spec/integration/how_to_authorize_spec.rb +53 -0
  31. data/spec/integration/how_to_use_boards_spec.rb +48 -0
  32. data/spec/integration/integration_test.rb +40 -0
  33. data/spec/item_spec.rb +27 -0
  34. data/spec/item_state_spec.rb +0 -0
  35. data/spec/list_spec.rb +50 -0
  36. data/spec/member_spec.rb +92 -0
  37. data/spec/notification_spec.rb +83 -0
  38. data/spec/oauth_policy_spec.rb +93 -0
  39. data/spec/organization_spec.rb +26 -0
  40. data/spec/spec_helper.rb +244 -0
  41. data/spec/string_spec.rb +50 -0
  42. data/spec/token_spec.rb +33 -0
  43. metadata +220 -0
@@ -0,0 +1,49 @@
1
+ require 'addressable/uri'
2
+
3
+ module Trello
4
+ class Client
5
+ extend Authorization
6
+
7
+ class << self
8
+ def get(path, params = {})
9
+ uri = Addressable::URI.parse("https://api.trello.com/#{API_VERSION}#{path}")
10
+ uri.query_values = params unless params.empty?
11
+ invoke_verb(:get, uri)
12
+ end
13
+
14
+ def post(path, body = {})
15
+ uri = Addressable::URI.parse("https://api.trello.com/#{API_VERSION}#{path}")
16
+ invoke_verb(:post, uri, body)
17
+ end
18
+
19
+ def put(path, body = {})
20
+ uri = Addressable::URI.parse("https://api.trello.com/#{API_VERSION}#{path}")
21
+ invoke_verb(:put, uri, body)
22
+ end
23
+
24
+ def delete(path)
25
+ uri = Addressable::URI.parse("https://api.trello.com/#{API_VERSION}#{path}")
26
+ invoke_verb(:delete, uri)
27
+ end
28
+
29
+ def invoke_verb(name, uri, body = nil)
30
+ request = Request.new name, uri, {}, body
31
+ response = TInternet.execute AuthPolicy.authorize(request)
32
+
33
+ return '' unless response
34
+
35
+ if response.code.to_i == 401 && response.body =~ /expired token/
36
+ Trello.logger.error("[401 #{name.to_s.upcase} #{uri}]: Your access token has expired.")
37
+ raise InvalidAccessToken, response.body
38
+ end
39
+
40
+ unless [200, 201].include? response.code
41
+ Trello.logger.error("[#{response.code} #{name.to_s.upcase} #{uri}]: #{response.body}")
42
+ raise Error, response.body
43
+ end
44
+
45
+ response.body
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,9 @@
1
+ module Trello
2
+ module HasActions
3
+ # Returns a list of the actions associated with this object.
4
+ def actions(options = {})
5
+ actions = Client.get("#{request_prefix}/actions", { :filter => :all }.merge(options)).json_into(Action)
6
+ MultiAssociation.new(self, actions).proxy
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ module Trello
2
+ # An Item is a basic task that can be checked off and marked as completed.
3
+ class Item < BasicData
4
+ register_attributes :id, :name, :type, :readonly => [ :id, :name, :type ]
5
+ validates_presence_of :id, :type
6
+
7
+ # Updates the fields of an item.
8
+ #
9
+ # Supply a hash of string keyed data retrieved from the Trello API representing
10
+ # an item.
11
+ def update_fields(fields)
12
+ attributes[:id] = fields['id']
13
+ attributes[:name] = fields['name']
14
+ attributes[:type] = fields['type']
15
+ self
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,23 @@
1
+ module Trello
2
+ # Represents the state of an item.
3
+ class ItemState < BasicData
4
+ register_attributes :id, :state, :item_id, :readonly => [ :id, :state, :item_id ]
5
+ validates_presence_of :id, :item_id
6
+
7
+ # Update the fields of an item state.
8
+ #
9
+ # Supply a hash of string keyed data retrieved from the Trello API representing
10
+ # an item state.
11
+ def update_fields(fields)
12
+ attributes[:id] = fields['id']
13
+ attributes[:state] = fields['state']
14
+ attributes[:item_id] = fields['idItem']
15
+ self
16
+ end
17
+
18
+ # Return the item this state belongs to.
19
+ def item
20
+ Item.find(item_id)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ module Trello
2
+
3
+ # A colored Label attached to a card
4
+ class Label < BasicData
5
+ register_attributes :name, :color
6
+
7
+ # Update the fields of a label.
8
+ #
9
+ # Supply a hash of stringkeyed data retrieved from the Trello API representing
10
+ # a label.
11
+ def update_fields(fields)
12
+ attributes[:name] = fields['name']
13
+ attributes[:color] = fields['color']
14
+ self
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -0,0 +1,71 @@
1
+ module Trello
2
+ # A List is a container which holds cards. Lists are items on a board.
3
+ class List < BasicData
4
+ register_attributes :id, :name, :closed, :board_id, :readonly => [ :id, :board_id ]
5
+ validates_presence_of :id, :name, :board_id
6
+ validates_length_of :name, :in => 1..16384
7
+
8
+ include HasActions
9
+
10
+ class << self
11
+ # Finds a specific list, given an id.
12
+ def find(id)
13
+ super(:lists, id)
14
+ end
15
+
16
+ def create(options)
17
+ new('name' => options[:name],
18
+ 'idBoard' => options[:board_id]).save
19
+ end
20
+ end
21
+
22
+ # Updates the fields of a list.
23
+ #
24
+ # Supply a hash of string keyed data retrieved from the Trello API representing
25
+ # a List.
26
+ def update_fields(fields)
27
+ attributes[:id] = fields['id']
28
+ attributes[:name] = fields['name']
29
+ attributes[:closed] = fields['closed']
30
+ attributes[:board_id] = fields['idBoard']
31
+ self
32
+ end
33
+
34
+ def save
35
+ return update! if id
36
+
37
+ Client.post("/lists", {
38
+ :name => name,
39
+ :closed => closed || false,
40
+ :idBoard => board_id
41
+ }).json_into(self)
42
+ end
43
+
44
+ def update!
45
+ Client.put("/lists", {
46
+ :name => name,
47
+ :closed => closed
48
+ }).json_into(self)
49
+ end
50
+
51
+ # Check if the list is not active anymore.
52
+ def closed?
53
+ closed
54
+ end
55
+
56
+ # Return the board the list is connected to.
57
+ one :board, :using => :board_id
58
+
59
+ # Returns all the cards on this list.
60
+ #
61
+ # The options hash may have a filter key which can have its value set as any
62
+ # of the following values:
63
+ # :filter => [ :none, :open, :closed, :all ] # default :open
64
+ many :cards, :filter => :open
65
+
66
+ # :nodoc:
67
+ def request_prefix
68
+ "/lists/#{id}"
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,93 @@
1
+ module Trello
2
+ # A Member is a user of the Trello service.
3
+ class Member < BasicData
4
+ register_attributes :id, :username, :full_name, :avatar_id, :bio, :url, :readonly => [ :id, :username, :avatar_id, :url ]
5
+ validates_presence_of :id, :username
6
+ validates_length_of :full_name, :minimum => 4
7
+ validates_length_of :bio, :maximum => 16384
8
+
9
+ include HasActions
10
+
11
+ class << self
12
+ # Finds a user
13
+ #
14
+ # The argument may be specified as either an _id_ or a _username_.
15
+ def find(id_or_username)
16
+ super(:members, id_or_username)
17
+ end
18
+ end
19
+
20
+ # Update the fields of a member.
21
+ #
22
+ # Supply a hash of string keyed data retrieved from the Trello API representing
23
+ # an Member.
24
+ def update_fields(fields)
25
+ attributes[:id] = fields['id']
26
+ attributes[:full_name] = fields['fullName']
27
+ attributes[:username] = fields['username']
28
+ attributes[:avatar_id] = fields['avatarHash']
29
+ attributes[:bio] = fields['bio']
30
+ attributes[:url] = fields['url']
31
+ self
32
+ end
33
+
34
+ # Retrieve a URL to the avatar.
35
+ #
36
+ # Valid values for options are:
37
+ # :large (170x170)
38
+ # :small (30x30)
39
+ def avatar_url(options = { :size => :large })
40
+ size = options[:size] == :small ? 30 : 170
41
+ "https://trello-avatars.s3.amazonaws.com/#{avatar_id}/#{size}.png"
42
+ end
43
+
44
+ # Returns a list of the boards a member is a part of.
45
+ #
46
+ # This method, when called, can take a hash table with a filter key containing any
47
+ # of the following values:
48
+ # :filter => [ :none, :members, :organization, :public, :open, :closed, :all ] # default: :all
49
+ # i.e.,
50
+ # me.boards(:filter => :closed) # retrieves all closed boards
51
+ many :boards, :filter => :all
52
+
53
+ # Returns a list of cards the member is assigned to.
54
+ #
55
+ # This method, when called, can take a hash table with a filter key containing any
56
+ # of the following values:
57
+ # :filter => [ :none, :open, :closed, :all ] # default :open
58
+ # i.e.,
59
+ # me.cards(:filter => :closed) # retrieves all closed cards
60
+ many :cards, :filter => :open
61
+
62
+ # Returns a list of the organizations this member is a part of.
63
+ #
64
+ # This method, when called, can take a hash table with a filter key containing any
65
+ # of the following values:
66
+ # :filter => [ :none, :members, :public, :all ] # default: all
67
+ # i.e.,
68
+ # me.organizations(:filter => :public) # retrieves all public organizations
69
+ many :organizations, :filter => :all
70
+
71
+ # Returns a list of notifications for the user
72
+ many :notifications
73
+
74
+ def save
75
+ @previously_changed = changes
76
+ @changed_attributes.clear
77
+
78
+ return update! if id
79
+ end
80
+
81
+ def update!
82
+ Client.put(request_prefix, {
83
+ :fullName => full_name,
84
+ :bio => bio
85
+ }).json_into(self)
86
+ end
87
+
88
+ # :nodoc:
89
+ def request_prefix
90
+ "/members/#{id}"
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,10 @@
1
+ module Trello
2
+ class MultiAssociation < Association
3
+ delegate :count, :to => :target
4
+
5
+ def initialize(owner, target = [])
6
+ super
7
+ @proxy = AssociationProxy.new(self)
8
+ end
9
+ end
10
+ end
data/lib/trello/net.rb ADDED
@@ -0,0 +1,37 @@
1
+ module Trello
2
+ Request = Struct.new "Request", :verb, :uri, :headers, :body
3
+ Response = Struct.new "Response", :code, :headers, :body
4
+
5
+ class TInternet
6
+ class << self
7
+ def execute(request)
8
+ try_execute request
9
+ end
10
+
11
+ private
12
+
13
+ def try_execute(request)
14
+ begin
15
+ if request
16
+ result = execute_core request
17
+ Response.new(200, {}, result)
18
+ end
19
+ rescue Exception => e
20
+ Response.new(e.http_code, {}, e.http_body)
21
+ end
22
+ end
23
+
24
+ def execute_core(request)
25
+ require "rest_client"
26
+
27
+ RestClient::Request.execute(
28
+ :method => request.verb,
29
+ :url => request.uri.to_s,
30
+ :headers => request.headers,
31
+ :payload => request.body,
32
+ :timeout => 10
33
+ )
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,48 @@
1
+ module Trello
2
+ class Notification < BasicData
3
+ register_attributes :id, :unread, :type, :date, :data, :member_creator_id,
4
+ :read_only => [ :id, :unread, :type, :date, :member_creator_id ]
5
+ validates_presence_of :id, :type, :date, :member_creator_id
6
+
7
+ class << self
8
+ # Locate a notification by its id
9
+ def find(id)
10
+ super(:notifications, id)
11
+ end
12
+ end
13
+
14
+ def update_fields(fields)
15
+ attributes[:id] = fields['id']
16
+ attributes[:unread] = fields['unread']
17
+ attributes[:type] = fields['type']
18
+ attributes[:date] = fields['date']
19
+ attributes[:data] = fields['data']
20
+ attributes[:member_creator_id] = fields['idMemberCreator']
21
+ self
22
+ end
23
+
24
+ alias :unread? :unread
25
+
26
+ one :member_creator, :via => Member, :using => :member_creator_id
27
+
28
+ def board
29
+ Client.get("/notifications/#{id}/board").json_into(Board)
30
+ end
31
+
32
+ def list
33
+ Client.get("/notifications/#{id}/list").json_into(List)
34
+ end
35
+
36
+ def card
37
+ Client.get("/notifications/#{id}/card").json_into(Card)
38
+ end
39
+
40
+ def member
41
+ Client.get("/notifications/#{id}/member").json_into(Member)
42
+ end
43
+
44
+ def organization
45
+ Client.get("/notifications/#{id}/organization").json_into(Organization)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,47 @@
1
+ module Trello
2
+ # Organizations are useful for linking members together.
3
+ class Organization < BasicData
4
+ register_attributes :id, :name, :display_name, :description, :url,
5
+ :readonly => [ :id, :name, :display_name, :description, :url ]
6
+ validates_presence_of :id, :name
7
+
8
+ include HasActions
9
+
10
+ class << self
11
+ # Find an organization by its id.
12
+ def find(id)
13
+ super(:organizations, id)
14
+ end
15
+ end
16
+
17
+ # Update the fields of an organization.
18
+ #
19
+ # Supply a hash of string keyed data retrieved from the Trello API representing
20
+ # an Organization.
21
+ def update_fields(fields)
22
+ attributes[:id] = fields['id']
23
+ attributes[:name] = fields['name']
24
+ attributes[:display_name] = fields['displayName']
25
+ attributes[:description] = fields['description']
26
+ attributes[:url] = fields['url']
27
+ self
28
+ end
29
+
30
+ # Returns a list of boards under this organization.
31
+ def boards
32
+ boards = Client.get("/organizations/#{id}/boards/all").json_into(Board)
33
+ MultiAssociation.new(self, boards).proxy
34
+ end
35
+
36
+ # Returns an array of members associated with the organization.
37
+ def members
38
+ members = Client.get("/organizations/#{id}/members/all").json_into(Member)
39
+ MultiAssociation.new(self, members).proxy
40
+ end
41
+
42
+ # :nodoc:
43
+ def request_prefix
44
+ "/organizations/#{id}"
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,36 @@
1
+ class String
2
+ # Decodes some JSON text in the receiver, and marshals it into a class specified
3
+ # in _obj_. If _obj_ is not a class, then we marshall the data into that instance
4
+ # via _update_fields_.
5
+ #
6
+ # For instance:
7
+ #
8
+ # class Stuff
9
+ # attr_reader :a, :b
10
+ # def initialize(values)
11
+ # @a = values['a']
12
+ # @b = values['b']
13
+ # end
14
+ # end
15
+ # thing = '{"a":42,"b":"foo"}'.json_into(Stuff)
16
+ # thing.a == 42
17
+ # thing.b == "foo"
18
+ def json_into(obj)
19
+ data = JSON.parse(self)
20
+ action = obj.kind_of?(Class) ? :new : :update_fields
21
+ if data.kind_of? Hash
22
+ obj.send(action, JSON.parse(self))
23
+ else
24
+ data.map { |element| obj.send(action, element) }
25
+ end
26
+ rescue JSON::ParserError => json_error
27
+ if json_error.message =~ /model not found/
28
+ Trello.logger.error "Could not find that record."
29
+ raise Trello::Error, "Request could not be found."
30
+ elsif json_error.message =~ /A JSON text must at least contain two octets/
31
+ else
32
+ Trello.logger.error "Unknown error."
33
+ raise
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,24 @@
1
+ module Trello
2
+ class Token < BasicData
3
+ register_attributes :id, :member_id, :created_at, :permissions,
4
+ :readonly => [ :id, :member_id, :created_at, :permissions ]
5
+
6
+ class << self
7
+ # Finds a token
8
+ def find(token)
9
+ super(:tokens, token)
10
+ end
11
+ end
12
+
13
+ # :nodoc:
14
+ def update_fields(fields)
15
+ attributes[:id] = fields['id']
16
+ attributes[:member_id] = fields['idMember']
17
+ attributes[:created_at] = Time.iso8601(fields['dateCreated'])
18
+ attributes[:permissions] = fields['permissions'] || {}
19
+ end
20
+
21
+ # Returns a reference to the user who authorized the token.
22
+ one :member, :using => :member_id
23
+ end
24
+ end
data/lib/trello.rb ADDED
@@ -0,0 +1,83 @@
1
+ require 'oauth'
2
+ require 'json'
3
+ require 'logger'
4
+ require 'active_model'
5
+
6
+ # Ruby wrapper around the Trello[http://trello.com] API
7
+ #
8
+ # First, set up your key information. You can get this information by {clicking here}[https://trello.com/1/appKey/generate].
9
+ #
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
+ # You can get the key by going to this url in your browser:
18
+ # https://trello.com/1/connect?key=PUBLIC_KEY_FROM_ABOVE&name=MyApp&response_type=token&scope=read,write,account&expiration=never
19
+ # 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
+ # 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
+ # url above:
22
+ #
23
+ # OAuthPolicy.token = OAuthCredential.new 'KEY', nil
24
+ #
25
+ # All the calls this library make to Trello require authentication using these keys. Be sure to protect them.
26
+ #
27
+ # So lets say you want to get information about the user *bobtester*. We can do something like this:
28
+ #
29
+ # bob = Member.find("bobtester")
30
+ # # Print out his name
31
+ # puts bob.full_name # "Bob Tester"
32
+ # # Print his bio
33
+ # puts bob.bio # A wonderfully delightful test user
34
+ # # How about a list of his boards?
35
+ # bob.boards
36
+ #
37
+ # And so much more. Consult the rest of the documentation for more information.
38
+ #
39
+ # Feel free to {peruse and participate in our Trello board}[https://trello.com/board/ruby-trello/4f092b2ee23cb6fe6d1aaabd]. It's completely open to the public.
40
+ module Trello
41
+ autoload :Action, 'trello/action'
42
+ autoload :Association, 'trello/association'
43
+ autoload :AssociationProxy, 'trello/association_proxy'
44
+ autoload :BasicData, 'trello/basic_data'
45
+ autoload :Board, 'trello/board'
46
+ autoload :Card, 'trello/card'
47
+ autoload :Checklist, 'trello/checklist'
48
+ autoload :Client, 'trello/client'
49
+ autoload :HasActions, 'trello/has_actions'
50
+ autoload :Item, 'trello/item'
51
+ autoload :ItemState, 'trello/item_state'
52
+ autoload :Label, 'trello/label'
53
+ autoload :List, 'trello/list'
54
+ autoload :Member, 'trello/member'
55
+ autoload :MultiAssociation, 'trello/multi_association'
56
+ autoload :Notification, 'trello/notification'
57
+ autoload :Organization, 'trello/organization'
58
+ autoload :Request, 'trello/net'
59
+ autoload :TInternet, 'trello/net'
60
+ autoload :Token, 'trello/token'
61
+
62
+ module Authorization
63
+ autoload :AuthPolicy, 'trello/authorization'
64
+ autoload :BasicAuthPolicy, 'trello/authorization'
65
+ autoload :OAuthPolicy, 'trello/authorization'
66
+ end
67
+
68
+ # Version of the Trello API that we use by default.
69
+ API_VERSION = 1
70
+
71
+ # Raise this when we hit a Trello error.
72
+ class Error < StandardError; end
73
+ # This specific error is thrown when your access token is invalid. You should get a new one.
74
+ class InvalidAccessToken < StandardError; end
75
+
76
+ def self.logger
77
+ @logger ||= Logger.new(STDOUT)
78
+ end
79
+
80
+ def self.logger=(logger)
81
+ @logger = logger
82
+ end
83
+ end
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+
3
+ module Trello
4
+ describe Action do
5
+ include Helpers
6
+
7
+ before(:each) do
8
+ Client.stub(:get).with("/actions/4ee2482134a81a757a08af47").
9
+ and_return JSON.generate(actions_details.first)
10
+
11
+ @action = Action.find('4ee2482134a81a757a08af47')
12
+ end
13
+
14
+ context "fields" do
15
+ before(:all) do
16
+ @detail = actions_details.first
17
+ end
18
+
19
+ it "gets its id" do
20
+ @action.id.should == @detail['id']
21
+ end
22
+
23
+ it "gets its type" do
24
+ @action.type.should == @detail['type']
25
+ end
26
+
27
+ it "has the same data" do
28
+ @action.data.should == @detail['data']
29
+ end
30
+
31
+ it "gets the date" do
32
+ @action.date.utc.iso8601.should == @detail['date']
33
+ end
34
+ end
35
+
36
+ context "boards" do
37
+ it "has a board" do
38
+ Client.stub(:get).with("/actions/4ee2482134a81a757a08af47/board").
39
+ and_return JSON.generate(boards_details.first)
40
+
41
+ @action.board.should_not be_nil
42
+ end
43
+ end
44
+
45
+ context "card" do
46
+ it "has a card" do
47
+ Client.stub(:get).with("/actions/4ee2482134a81a757a08af47/card").
48
+ and_return JSON.generate(cards_details.first)
49
+
50
+ @action.card.should_not be_nil
51
+ end
52
+ end
53
+
54
+ context "list" do
55
+ it "has a list of lists" do
56
+ Client.stub(:get).with("/actions/4ee2482134a81a757a08af47/list").
57
+ and_return JSON.generate(lists_details.first)
58
+
59
+ @action.list.should_not be_nil
60
+ end
61
+ end
62
+
63
+ context "member creator" do
64
+ it "knows its member creator" do
65
+ Client.stub(:get).with("/members/abcdef123456789123456789").and_return user_payload
66
+
67
+ @action.member_creator.should_not be_nil
68
+ end
69
+ end
70
+ end
71
+ end