ruby-trello-wgibbs 0.4.3

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.
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