ketchup 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,92 @@
1
+ # A collection of meeting for the current profile, which allows for the creation
2
+ # of new meetings. It's really just a slightly special subclass of Array.
3
+ #
4
+ class Ketchup::MeetingArray < Array
5
+ # Create a new array from a given api connection. This will make the request
6
+ # to the API to retrieve all meetings for the profile.
7
+ #
8
+ # This isn't something you need to call yourself - just call the meetings
9
+ # method on your profile object instead.
10
+ #
11
+ # @example
12
+ # meetings = Ketchup::MeetingArray.new api
13
+ #
14
+ # @param [Ketchup::API] api an API instance
15
+ # @see Ketchup::Profile#meetings
16
+ #
17
+ def initialize(api)
18
+ @api = api
19
+
20
+ replace @api.get('/meetings.json').collect { |hash|
21
+ Ketchup::Meeting.new(@api, hash)
22
+ }
23
+ end
24
+
25
+ # Create a new unsaved meeting object. The only parameters you really need to
26
+ # worry about are the title and date - the rest are optional, but mentioned in
27
+ # the notes for the Meeting class.
28
+ #
29
+ # Don't forget: the parameter keys need to be strings, not symbols.
30
+ #
31
+ # @example
32
+ # profile.meetings.build 'title' => 'Job Interview', 'date' => 'Tomorrow'
33
+ #
34
+ # @param [Hash] params The meeting's details
35
+ # @return [Ketchup::Meeting] An unsaved meeting
36
+ # @see Ketchup::Meeting
37
+ #
38
+ def build(params = {})
39
+ meeting = Ketchup::Meeting.new @api, params
40
+ push meeting
41
+ meeting
42
+ end
43
+
44
+ # Create a new (saved) meeting object. The only parameters you really need to
45
+ # worry about are the title and date - the rest are optional, but mentioned in
46
+ # the notes for the Meeting class.
47
+ #
48
+ # Don't forget: the parameter keys need to be strings, not symbols.
49
+ #
50
+ # @example
51
+ # profile.meetings.create 'title' => 'Job Interview', 'date' => 'Tomorrow'
52
+ #
53
+ # @param [Hash] params The meeting's details
54
+ # @return [Ketchup::Meeting] A saved meeting
55
+ # @see Ketchup::Meeting
56
+ #
57
+ def create(params = {})
58
+ meeting = build(params)
59
+ meeting.save
60
+ meeting
61
+ end
62
+
63
+ # Requests a set of meetings that happen on days after today from the API.
64
+ #
65
+ # @return [Array] An array of upcoming meetings
66
+ #
67
+ def upcoming
68
+ @upcoming ||= @api.get('/meetings/upcoming.json').collect { |hash|
69
+ Ketchup::Meeting.new(@api, hash)
70
+ }
71
+ end
72
+
73
+ # Requests a set of meetings that have already happened from the API.
74
+ #
75
+ # @return [Array] An array of previous meetings
76
+ #
77
+ def previous
78
+ @previous ||= @api.get('/meetings/previous.json').collect { |hash|
79
+ Ketchup::Meeting.new(@api, hash)
80
+ }
81
+ end
82
+
83
+ # Requests a set of meetings that will happen today from the API.
84
+ #
85
+ # @return [Array] An array of today's meetings
86
+ #
87
+ def today
88
+ @today ||= @api.get('/meetings/todays.json').collect { |hash|
89
+ Ketchup::Meeting.new(@api, hash)
90
+ }
91
+ end
92
+ end
@@ -0,0 +1,73 @@
1
+ # Represents a note for list/agenda item in a meeting. Only the content is
2
+ # editable.
3
+ #
4
+ # If you want to change the order of notes, you can do that using
5
+ # Ketchup::NoteArray (the item's note array).
6
+ #
7
+ class Ketchup::Note
8
+ attr_reader :shortcode_url, :updated_at, :created_at, :item_id, :position,
9
+ :item, :api
10
+ attr_accessor :content
11
+
12
+ # Create a new note for a given item, using an existing api connection. You
13
+ # generally won't want to call this yourself - the better interface to create
14
+ # new notes is via a item's note array.
15
+ #
16
+ # However, if you are doing some internal hacking, it's worth noting that all
17
+ # keys for parameters in the hash should be strings, not symbols.
18
+ #
19
+ # @example
20
+ # Ketchup::Note.new api, item, 'content' => "Motion passed."
21
+ #
22
+ # @param [Ketchup::API] api The API connection
23
+ # @param [Ketchup::Item] item The item the note will belong to
24
+ # @param [Hash] params Any other details for the note.
25
+ # @see Ketchup::NoteArray#build
26
+ # @see Ketchup::NoteArray#create
27
+ #
28
+ def initialize(api, item, params = {})
29
+ @api = api
30
+ @item = item
31
+
32
+ overwrite params
33
+ end
34
+
35
+ # Indicates whether the note exists on the server yet.
36
+ #
37
+ # @return [Boolean] True if the note does not exist on the server.
38
+ #
39
+ def new_record?
40
+ shortcode_url.nil?
41
+ end
42
+
43
+ # Saves the note to the server, whether it's a new note or an existing one.
44
+ #
45
+ def save
46
+ if new_record?
47
+ overwrite @api.post("/items/#{item.shortcode_url}/notes.json",
48
+ 'content' => content)
49
+ else
50
+ overwrite @api.put("/notes/#{shortcode_url}.json", 'content' => content)
51
+ end
52
+ end
53
+
54
+ # Deletes the note from the server. If the note has never been saved, this
55
+ # method will do nothing at all.
56
+ #
57
+ def destroy
58
+ return if new_record?
59
+
60
+ @api.delete "/notes/#{shortcode_url}.json"
61
+ end
62
+
63
+ private
64
+
65
+ def overwrite(attributes = {})
66
+ @shortcode_url = attributes['shortcode_url']
67
+ @created_at = attributes['created_at']
68
+ @updated_at = attributes['updated_at']
69
+ @item_id = attributes['item_id']
70
+ @position = attributes['position']
71
+ @content = attributes['content']
72
+ end
73
+ end
@@ -0,0 +1,85 @@
1
+ # A collection of notes for a specific item, and allows for the creation of
2
+ # new notes, and re-ordering of the full set of notes. It's really just a
3
+ # slightly special subclass of Array.
4
+ #
5
+ class Ketchup::NoteArray < Array
6
+ # Create a new NoteArray for a given api, item, and set of notes. The set
7
+ # of notes should be hashes taken from the API server, not actual note
8
+ # objects.
9
+ #
10
+ # Like many of the methods in this library, you really don't need to call this
11
+ # one yourself. An item object will create its own NoteArray without any
12
+ # help.
13
+ #
14
+ # @example
15
+ # Ketchup::NoteArray.new api, item, [{'content' => 'foo', #... }]
16
+ #
17
+ # @param [Ketchup::API] api A connected API instance
18
+ # @param [Ketchup::Item] item The item all notes are attached to.
19
+ # @param [Array] items An array of hashes that map to attributes of notes.
20
+ #
21
+ def initialize(api, item, notes = [])
22
+ @api = api
23
+ @item = item
24
+
25
+ replace notes.collect { |hash|
26
+ Ketchup::Note.new(@api, @item, hash)
27
+ }
28
+ end
29
+
30
+ # Create a new unsaved note object. The only parameter you really need to
31
+ # worry about is the content - the rest is all internal values, managed by the
32
+ # API server.
33
+ #
34
+ # @example
35
+ # item.notes.build 'content' => 'Targets are being met.'
36
+ #
37
+ # @param [Hash] params The note's details
38
+ # @return [Ketchup::Note] An unsaved note
39
+ #
40
+ def build(params = {})
41
+ note = Ketchup::Note.new @api, @item, params
42
+ push note
43
+ note
44
+ end
45
+
46
+ # Create a new (saved) note object. The only parameter you really need to
47
+ # worry about is the content - the rest is all internal values, managed by the
48
+ # API server.
49
+ #
50
+ # @example
51
+ # item.notes.create 'content' => 'Targets are being met.'
52
+ #
53
+ # @param [Hash] params The note's details
54
+ # @return [Ketchup::Note] A saved note
55
+ #
56
+ def create(params = {})
57
+ note = build(params)
58
+ note.save
59
+ note
60
+ end
61
+
62
+ # Re-order the notes in this array, by passing them through in the order you
63
+ # would prefer. This makes the change directly to the API server, as well as
64
+ # within this array.
65
+ #
66
+ # Make sure you're passing in all the notes that are in this array, and no
67
+ # others. This can't be used as a shortcut to add new notes.
68
+ #
69
+ # @example
70
+ # item.notes.reorder second_note, fourth_note, first_note, third_note
71
+ #
72
+ # @param [Ketchup::Note] notes The notes of the array, in a new order.
73
+ # @raise ArgumentError if you don't provide the exact same set of notes.
74
+ #
75
+ def reorder(*notes)
76
+ if notes.collect(&:shortcode_url).sort != collect(&:shortcode_url).sort
77
+ raise ArgumentError, 'cannot sort a different set of notes'
78
+ end
79
+
80
+ @api.put "/items/#{@item.shortcode_url}/sort_notes.json", {
81
+ 'notes' => notes.collect(&:shortcode_url)
82
+ }
83
+ replace notes
84
+ end
85
+ end
@@ -0,0 +1,125 @@
1
+ # A user profile, which provides an interface to the user's meetings and
2
+ # projects. You can either create it yourself, using an email and password, or
3
+ # through the Ketchup module's authenticate method - they're exactly the same.
4
+ #
5
+ # This is the central object for all of your operations through the Ketchup API.
6
+ # You don't need to create or manually request any other objects, but instead,
7
+ # access them all through the collections on this class.
8
+ #
9
+ # @example
10
+ # profile = Ketchup::Profile.load 'foo@bar.com', 'secret'
11
+ # profile.meetings.each do |meeting|
12
+ # puts meeting.title
13
+ # end
14
+ #
15
+ class Ketchup::Profile
16
+ attr_reader :api, :single_access_token
17
+ attr_accessor :name, :timezone, :email
18
+
19
+ # Set up a connection to an existing profile.
20
+ #
21
+ # @example
22
+ # profile = Ketchup::Profile.load 'foo@bar.com', 'secret'
23
+ #
24
+ # @param [String] email The user's email address
25
+ # @param [String] password The user's passwords
26
+ # @return [Ketchup::Profile] A new profile instance
27
+ # @raise [Ketchup::AccessDenied] when the email or password is incorrect
28
+ #
29
+ def self.load(email, password)
30
+ Ketchup::Profile.new Ketchup::API.new(email, password)
31
+ end
32
+
33
+ # Create a completely new user account. You (currently) don't need to have
34
+ # any existing authentication to the server to call this method - just specify
35
+ # the new user's email address and password.
36
+ #
37
+ # Other extra parameters are the name and timezone of the user account, but
38
+ # these are optional.
39
+ #
40
+ # Please note: This method doesn't return a new profile, just sets the account
41
+ # up.
42
+ #
43
+ # @example
44
+ # Ketchup::Profile.create 'bar@foo.com', 'swordfish', 'name' => 'Bruce'
45
+ #
46
+ # @param [String] email The email address of the new user.
47
+ # @param [String] password The chosen password of the new user.
48
+ # @param [Hash] options Other optional user details, such as the name and
49
+ # timezone.
50
+ #
51
+ def self.create(email, password, options = {})
52
+ Ketchup::API.post '/users.json', :body => options.merge(
53
+ 'email' => email,
54
+ 'password' => password
55
+ )
56
+ end
57
+
58
+ # Set up a new profile object with an active API connection.
59
+ #
60
+ # @param [Ketchup::API] api An authenticated API object
61
+ #
62
+ def initialize(api)
63
+ @api = api
64
+
65
+ overwrite @api.get('/profile.json')
66
+ end
67
+
68
+ # Reset the meeting and project objects of this profile, allowing them to
69
+ # be reloaded from the server when next requested.
70
+ #
71
+ def reload!
72
+ @meetings = nil
73
+ @projects = nil
74
+ end
75
+
76
+ # Get an array of meetings attached to this profile. Note that the returned
77
+ # object is actually an instance of Ketchup::MeetingArray, which has helper
78
+ # methods for creating and re-ordering meetings.
79
+ #
80
+ # @return [Ketchup::MeetingArray] The collection of meetings.
81
+ #
82
+ def meetings
83
+ @meetings ||= Ketchup::MeetingArray.new api
84
+ end
85
+
86
+ # Get an array of projects attached to this profile. This is not a special
87
+ # project array, as you can only edit projects, not create them. They are
88
+ # created explicitly from meetings.
89
+ #
90
+ # @return [Array] The collection of projects.
91
+ #
92
+ def projects
93
+ @projects ||= api.get('/projects.json').collect { |hash|
94
+ Ketchup::Project.new(api, hash)
95
+ }
96
+ end
97
+
98
+ # Saves any profile changes to the name, timezone and/or email. This does not
99
+ # save any changes made to underlying meetings and projects.
100
+ #
101
+ def save
102
+ overwrite @api.put('/profile.json',
103
+ 'name' => name,
104
+ 'timezone' => timezone,
105
+ 'email' => email
106
+ )
107
+ end
108
+
109
+ # Change the password for this profile account.
110
+ #
111
+ # @param [String] password The new password for the account.
112
+ #
113
+ def change_password(password)
114
+ @api.put '/profile.json', 'password' => password
115
+ end
116
+
117
+ private
118
+
119
+ def overwrite(attributes = {})
120
+ @single_access_token = attributes['single_access_token']
121
+ @name = attributes['name']
122
+ @timezone = attributes['timezone']
123
+ @email = attributes['email']
124
+ end
125
+ end
@@ -0,0 +1,51 @@
1
+ # Represents a project, which meetings can be grouped under.
2
+ #
3
+ # Projects cannot be created manually - just create meetings with project names,
4
+ # and the corresponding projects will be created if they don't already exist.
5
+ #
6
+ # You can, however, change the name of a project.
7
+ #
8
+ class Ketchup::Project
9
+ attr_accessor :name
10
+ attr_reader :shortcode_url, :created_at, :updated_at
11
+
12
+ # Set up a new Project object - although there's not much point calling this
13
+ # yourself, as it's only useful when populating with data from the server.
14
+ #
15
+ # @param [Ketchup::API] api An established API connection.
16
+ # @param [Hash] params The project details
17
+ #
18
+ def initialize(api, params = {})
19
+ @api = api
20
+
21
+ overwrite params
22
+ end
23
+
24
+ # Saves any changes to the project's name.
25
+ #
26
+ def save
27
+ overwrite @api.put("/projects/#{shortcode_url}.json", 'name' => name)
28
+ end
29
+
30
+ # Gets all the meetings tied to this particular project. This is not a special
31
+ # array, so it's best to create new meetings for a given project via the
32
+ # profile's meetings collection object instead.
33
+ #
34
+ # @return [Array] Meetings associated with this project.
35
+ #
36
+ def meetings
37
+ @meetings ||= @api.get("/projects/#{shortcode_url}/meetings.json").
38
+ collect { |hash|
39
+ Ketchup::Meeting.new @api, hash
40
+ }
41
+ end
42
+
43
+ private
44
+
45
+ def overwrite(attributes = {})
46
+ @shortcode_url = attributes['shortcode_url']
47
+ @created_at = attributes['created_at']
48
+ @updated_at = attributes['updated_at']
49
+ @name = attributes['name']
50
+ end
51
+ end
@@ -0,0 +1,120 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe Ketchup::API do
4
+ describe '#initialize' do
5
+ it "should return a new API object on success" do
6
+ FakeWeb.register_uri :get, /#{KetchupAPI}\/profile.json$/,
7
+ :body => '{"single_access_token":"foo"}'
8
+
9
+ Ketchup::API.new('user@domain.com', 'secret').should be_a(Ketchup::API)
10
+ end
11
+
12
+ it "should set the access token on success" do
13
+ stub_initial_api_request
14
+
15
+ Ketchup::API.new('user@domain.com', 'secret').
16
+ access_token.should == 'y3chHxh9Qk1Drkg1j0Bw'
17
+ end
18
+
19
+ it "should raise an exception on failure" do
20
+ FakeWeb.register_uri :get, /#{KetchupAPI}\/profile.json$/,
21
+ :body => 'Access Denied'
22
+
23
+ lambda {
24
+ Ketchup::API.new('user@domain.com', 'secret')
25
+ }.should raise_error(Ketchup::AccessDenied)
26
+ end
27
+ end
28
+
29
+ describe '#get' do
30
+ before :each do
31
+ stub_initial_api_request
32
+ @api = Ketchup::API.new('user@domain.com', 'secret')
33
+ end
34
+
35
+ it "should include the access token" do
36
+ Ketchup::API.should_receive(:get) do |path, options|
37
+ options[:query][:u].should == 'y3chHxh9Qk1Drkg1j0Bw'
38
+ end
39
+
40
+ @api.get '/'
41
+ end
42
+
43
+ it "should send through any provided options" do
44
+ Ketchup::API.should_receive(:get) do |path, options|
45
+ options[:query][:page].should == 4
46
+ end
47
+
48
+ @api.get '/', :page => 4
49
+ end
50
+ end
51
+
52
+ describe '#post' do
53
+ before :each do
54
+ stub_initial_api_request
55
+ @api = Ketchup::API.new('user@domain.com', 'secret')
56
+ end
57
+
58
+ it "should include the access token" do
59
+ Ketchup::API.should_receive(:post) do |path, options|
60
+ options[:body][:u].should == 'y3chHxh9Qk1Drkg1j0Bw'
61
+ end
62
+
63
+ @api.post '/'
64
+ end
65
+
66
+ it "should send through any provided options" do
67
+ Ketchup::API.should_receive(:post) do |path, options|
68
+ options[:body][:page].should == 4
69
+ end
70
+
71
+ @api.post '/', :page => 4
72
+ end
73
+ end
74
+
75
+ describe '#put' do
76
+ before :each do
77
+ stub_initial_api_request
78
+ @api = Ketchup::API.new('user@domain.com', 'secret')
79
+ end
80
+
81
+ it "should include the access token" do
82
+ Ketchup::API.should_receive(:put) do |path, options|
83
+ options[:body][:u].should == 'y3chHxh9Qk1Drkg1j0Bw'
84
+ end
85
+
86
+ @api.put '/'
87
+ end
88
+
89
+ it "should send through any provided options" do
90
+ Ketchup::API.should_receive(:put) do |path, options|
91
+ options[:body][:page].should == 4
92
+ end
93
+
94
+ @api.put '/', :page => 4
95
+ end
96
+ end
97
+
98
+ describe '#delete' do
99
+ before :each do
100
+ stub_initial_api_request
101
+ @api = Ketchup::API.new('user@domain.com', 'secret')
102
+ end
103
+
104
+ it "should include the access token" do
105
+ Ketchup::API.should_receive(:delete) do |path, options|
106
+ options[:body][:u].should == 'y3chHxh9Qk1Drkg1j0Bw'
107
+ end
108
+
109
+ @api.delete '/'
110
+ end
111
+
112
+ it "should send through any provided options" do
113
+ Ketchup::API.should_receive(:delete) do |path, options|
114
+ options[:body][:page].should == 4
115
+ end
116
+
117
+ @api.delete '/', :page => 4
118
+ end
119
+ end
120
+ end