roart 0.1.4

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/History.txt ADDED
@@ -0,0 +1,17 @@
1
+ ==0.1.4 / 2009-08-19
2
+ Implemented callbacks, you can now hook into the ticket life-cycle with before and after create and update.
3
+ Saving works like ActiveRecord now, with save returning true if successful and false otherwise, and save! raising an error if unsuccessful.
4
+ Creating tickets is more ActiveRecord like now, with new not saving to the ticketing system automatically, you must call save or save!. There is a create function to do this now. Although this does break backward compatability, I'm not going to bump the minor because a) nobody has forked this yet, and b) i skipped 0.
5
+ You can now specify a default queue when you subclass Roart::Ticket. just add a default_queue 'queue name' to your class and that queue will automatically be searched unless you specify a queue.
6
+
7
+ ==0.1.3 / 2009-08-18
8
+ Can now update tickets with #save. Returns true if saved, raises an error if not.
9
+
10
+ ==0.1.2 / 2009-08-18
11
+ Added Create ticket functionality. Ticket.new will create a ticket in the RT system and return that ticket.
12
+
13
+ == 0.1.1 / 2009-08-17
14
+ Added History functionality. #histories now loads an array of history objects into memory and you can access them like you would a regular array. A few connivence methods (last, count) have also been added.
15
+
16
+ == 0.1.0 / 2009-08-13
17
+ Initial Commit. Base functionality added. Can search for tickets, and see all info on tickets. Ticket History not yet implemented.
data/README.rdoc ADDED
@@ -0,0 +1,85 @@
1
+ == Roart
2
+
3
+ \___\__o/
4
+ / \
5
+
6
+
7
+ by PJ Davis
8
+ http://github.com/pjdavis/roart
9
+
10
+ == DESCRIPTION:
11
+ If you are using Best Practical's Request Tracker (RT) and you need to interact with tickets from other applications, Roart provides an interface that is slightly reminiscent of ActiveRecord.
12
+
13
+
14
+ == FEATURES/PROBLEMS:
15
+
16
+ * Access RT Tickets through an ActiveRecord like API
17
+
18
+ * This has only been tested against RT 3.6. Changes to the REST interface in later versions of RT may break stuff.
19
+
20
+ == SYNOPSIS:
21
+
22
+ * Create a class to interact with your ticket system
23
+
24
+ require 'rubygems'
25
+ require 'roart'
26
+
27
+ class Ticket < Roart::Ticket
28
+ connection :server => 'http://my.rt.server.com', :user => 'myuser', :pass => 'mypass'
29
+
30
+ end
31
+
32
+ * Search for tickets
33
+
34
+ my_tickets = Ticket.find(:all, :queue => 'Scutters', :status => [:new, :open])
35
+ my_tickets.each do |ticket|
36
+ puts ticket.subject
37
+ end
38
+
39
+ #-> New John Wayne packages
40
+ #-> Medi-lab training
41
+
42
+ * See all info for a ticket
43
+
44
+ my_ticket = Ticket.find(:first, :queue => 'Issues', :status => :new)
45
+ ticket.creator #-> rimmer@reddwarf.com
46
+ ticket.subject #-> Where is the Bomb?
47
+
48
+ * Get history for a ticket
49
+
50
+ my_ticket.histories #-> Returns an array of history objects
51
+
52
+ * Create a new ticket
53
+
54
+ issue = Ticket.new(:queue => 'some_queue', :subject => 'This is not working for me')
55
+ issue.id #-> 'ticket/new'
56
+ issue.save
57
+ issue.id #-> 23423
58
+
59
+ * Update a ticket
60
+
61
+ ticket = Ticket.find(23452)
62
+ ticket.subject #-> "Some Subject for a ticket."
63
+ ticket.subject #-> "Smoke me a kipper, I'll be back for breakfast."
64
+ ticket.save
65
+ ticket.subject #->"Smoke me a kipper, I'll be back for breakfast."
66
+
67
+
68
+ == REQUIREMENTS:
69
+
70
+ * mechanize
71
+ * A working RT3 install.
72
+
73
+ == INSTALL:
74
+
75
+ $ gem sources -a http://gems.github.com
76
+ $ sudo gem install pjdavis-roart
77
+
78
+ == LICENSE:
79
+
80
+ (C) PJ Davis <pj.davis@gmail.com>
81
+
82
+ This program is free software; you can redistribute it and/or modify it under
83
+ the terms of the WTFPL, Version 2, as
84
+ published by Sam Hocevar. See http://sam.zoy.org/wtfpl/ for more details.
85
+
data/Rakefile ADDED
@@ -0,0 +1,35 @@
1
+ # Look in the tasks/setup.rb file for the various options that can be
2
+ # configured in this Rakefile. The .rake files in the tasks directory
3
+ # are where the options are used.
4
+
5
+ begin
6
+ require 'bones'
7
+ Bones.setup
8
+ rescue LoadError
9
+ begin
10
+ load 'tasks/setup.rb'
11
+ rescue LoadError
12
+ raise RuntimeError, '### please install the "bones" gem ###'
13
+ end
14
+ end
15
+
16
+ ensure_in_path 'lib'
17
+ require 'roart'
18
+
19
+ task :default => 'spec:run'
20
+
21
+ PROJ.name = 'roart'
22
+ PROJ.ignore_file = '.gitignore'
23
+ PROJ.authors = 'PJ Davis'
24
+ PROJ.email = 'pj.davis@gmail.com'
25
+ PROJ.url = 'http://github.com/pjdavis/roart'
26
+ PROJ.version = Roart::VERSION
27
+ PROJ.rubyforge.name = 'roart'
28
+ PROJ.exclude = %w(.git pkg coverage)
29
+ PROJ.description = "Interface for working with Request Tracker (RT) tickets inspired by ActiveRecord."
30
+ PROJ.rdoc.main = 'README.rdoc'
31
+ depend_on 'mechanize'
32
+
33
+ PROJ.spec.opts << '--color'
34
+
35
+ # EOF
data/lib/roart.rb ADDED
@@ -0,0 +1,49 @@
1
+ require 'rubygems'
2
+ module Roart
3
+
4
+ # :stopdoc:
5
+ VERSION = '0.1.4'
6
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
7
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
8
+ # :startdoc:
9
+
10
+ # Returns the version string for the library.
11
+ #
12
+ def self.version
13
+ VERSION
14
+ end
15
+
16
+ # Returns the library path for the module. If any arguments are given,
17
+ # they will be joined to the end of the libray path using
18
+ # <tt>File.join</tt>.
19
+ #
20
+ def self.libpath( *args )
21
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
22
+ end
23
+
24
+ # Returns the lpath for the module. If any arguments are given,
25
+ # they will be joined to the end of the path using
26
+ # <tt>File.join</tt>.
27
+ #
28
+ def self.path( *args )
29
+ args.empty? ? PATH : ::File.join(PATH, args.flatten)
30
+ end
31
+
32
+ # Utility method used to require all files ending in .rb that lie in the
33
+ # directory below this file that has the same name as the filename passed
34
+ # in. Optionally, a specific _directory_ name can be passed in such that
35
+ # the _filename_ does not have to be equivalent to the directory.
36
+ #
37
+ def self.require_all_libs_relative_to( fname, dir = nil )
38
+ dir ||= ::File.basename(fname, '.*')
39
+ search_me = ::File.expand_path(
40
+ ::File.join(::File.dirname(fname), dir, '**', '*.rb'))
41
+
42
+ Dir.glob(search_me).sort.each {|rb| require rb}
43
+ end
44
+
45
+ end # module Roart
46
+
47
+ Roart.require_all_libs_relative_to(__FILE__)
48
+
49
+ # EOF
@@ -0,0 +1,25 @@
1
+ module Roart
2
+
3
+ # Callbacks are implemented to do a bit of logic either before or after a part of the object life cycle. These can be overridden in your Ticket class and will be called at the approprate times.
4
+ #
5
+ module Callbacks
6
+
7
+ # called just before a ticket that has not been saved to the ticketing system is saved.
8
+ #
9
+ def before_create; end
10
+
11
+ # Called immediately a ticket that has not been saved is saved.
12
+ #
13
+ def before_update; end
14
+
15
+ # called just before a ticket that has been updated is saved to the ticketing system
16
+ #
17
+ def after_create; end
18
+
19
+ # called just after a ticket that has been updated is saved to the ticketing system
20
+ #
21
+ def after_update; end
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,62 @@
1
+ require 'yaml'
2
+ require 'mechanize'
3
+
4
+ module Roart
5
+
6
+ module Connections
7
+ RequiredConfig = %w(server user pass)
8
+
9
+ end
10
+
11
+ class Connection
12
+
13
+ attr_reader :agent
14
+
15
+ def initialize(conf)
16
+
17
+ if conf.is_a?(String)
18
+ raise "Loading Config File not yet implemented"
19
+ elsif conf.is_a?(Hash)
20
+ Roart::check_keys!(conf, Roart::Connections::RequiredConfig)
21
+ @conf = conf
22
+ end
23
+
24
+ @agent = login
25
+ add_methods!
26
+ end
27
+
28
+ def rest_path
29
+ self.server + '/REST/1.0/'
30
+ end
31
+
32
+ def get(uri)
33
+ @agent.get(uri).body
34
+ end
35
+
36
+ def post(uri, payload)
37
+ @agent.post(uri, payload).body
38
+ end
39
+
40
+ protected
41
+
42
+ def login
43
+ agent = WWW::Mechanize.new
44
+ page = agent.get(@conf[:server])
45
+ form = page.form('login')
46
+ form.user = @conf[:user]
47
+ form.pass = @conf[:pass]
48
+ page = agent.submit form
49
+ agent
50
+ end
51
+
52
+ def add_methods!
53
+ @conf.each do |key, value|
54
+ (class << self; self; end).send :define_method, key do
55
+ return value
56
+ end
57
+ end
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -0,0 +1,11 @@
1
+ class Array
2
+
3
+ def to_hash
4
+ h = Hash.new
5
+ self.each do |element|
6
+ h.update(element.to_sym => nil)
7
+ end
8
+ h
9
+ end
10
+
11
+ end
@@ -0,0 +1,15 @@
1
+ class Hash
2
+ def to_content_format
3
+ fields = self.map do |key,value|
4
+ unless value.nil?
5
+ if key.to_s.match(/^cf_.+/)
6
+ "CF-#{key.to_s[3..key.to_s.length].camelize.humanize}: #{value}"
7
+ else
8
+ "#{key.to_s.camelize}: #{value}"
9
+ end
10
+ end
11
+ end
12
+ content = fields.compact.join("\n")
13
+ end
14
+
15
+ end
@@ -0,0 +1,21 @@
1
+ # used from ActiveSupport
2
+ # Copyright (c) 2005-2009 David Heinemeier Hansson
3
+
4
+ class String
5
+
6
+ def underscore
7
+ self.gsub(/::/, '/').
8
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
9
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
10
+ tr("-", "_").
11
+ downcase
12
+ end
13
+
14
+ def camelize
15
+ self.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
16
+ end
17
+
18
+ def humanize
19
+ self.gsub(/_id$/, "").gsub(/_/, " ").capitalize
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ module Roart
2
+
3
+ class RoartError < StandardError; end
4
+
5
+ class ArgumentError < RoartError; end
6
+
7
+ class TicketSystemError < RoartError; end
8
+
9
+ class TicketSystemInterfaceError < RoartError; end
10
+
11
+ end
@@ -0,0 +1,115 @@
1
+ module Roart
2
+
3
+ module Histories
4
+
5
+ DefaultAttributes = %w(creator type description content created)
6
+ RequiredAttributes = %w(creator type)
7
+
8
+ end
9
+
10
+ class HistoryArray < Array
11
+
12
+ def ticket
13
+ @default_options[:ticket]
14
+ end
15
+
16
+ def all
17
+ self
18
+ end
19
+
20
+ def count
21
+ self.size
22
+ end
23
+
24
+ def last
25
+ self[self.size - 1]
26
+ end
27
+
28
+ end
29
+
30
+ class History
31
+
32
+ #TODO Figure out why i can't include Roart::MethodFunctions
33
+ def add_methods!
34
+ @attributes.each do |key, value|
35
+ (class << self; self; end).send :define_method, key do
36
+ return value
37
+ end
38
+ end
39
+ end
40
+
41
+ class << self
42
+
43
+ def default(options)
44
+ history = self.dup
45
+ history.instance_variable_set("@default_options", options)
46
+ history.all
47
+ end
48
+
49
+ def ticket
50
+ @default_options[:ticket]
51
+ end
52
+
53
+ def all
54
+ @histories ||= get_all
55
+ end
56
+
57
+ def default_options
58
+ @default_options
59
+ end
60
+
61
+ protected
62
+
63
+ def instantiate(attrs)
64
+ object = nil
65
+ if attrs.is_a?(Array)
66
+ array = Array.new
67
+ attrs.each do |attr|
68
+ object = self.allocate
69
+ object.instance_variable_set("@attributes", attr.merge(self.default_options))
70
+ object.send("add_methods!")
71
+ array << object
72
+ end
73
+ return array
74
+ elsif attrs.is_a?(Hash)
75
+ object = self.allocate
76
+ object.instance_variable_set("@attributes", attrs.merge(self.default_options))
77
+ object.send("add_methods!")
78
+ end
79
+ object
80
+ end
81
+
82
+ def get_all
83
+ page = get_page
84
+ raise TicketSystemError, "Can't get history." unless page
85
+ raise TicketSystemInterfaceError, "Error getting history for Ticket: #{ticket.id}." unless page.split("\n")[0].include?("200")
86
+ history_array = get_histories_from_page(page)
87
+ history_array
88
+ end
89
+
90
+ def get_histories_from_page(page)
91
+ full_history = HistoryArray.new
92
+ for history in page.split(/^--$/)
93
+ history = history.split("\n")
94
+ history.extend(Roart::TicketPage)
95
+ full_history << self.instantiate(history.to_history_hash)
96
+ end
97
+ full_history.instance_variable_set("@default_options", @default_options)
98
+ full_history
99
+ end
100
+
101
+ def get_page
102
+ @default_options[:ticket].class.connection.get(uri_for(@default_options[:ticket]))
103
+ end
104
+
105
+ def uri_for(ticket)
106
+ uri = self.default_options[:ticket].class.connection.rest_path + "ticket/#{ticket.id}/history?format=l"
107
+ end
108
+
109
+ end
110
+
111
+ protected
112
+
113
+ end
114
+
115
+ end