ludo-roart 0.1.11

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,45 @@
1
+ ==0.1.9 / 2010-05-23
2
+ Removed the String/Hash extensions copied from ActiveSupport and just added a dependency on ActiveSupport. Removed to_hash added to Array, as it conflicts with ActiveSupport's deep_merge. (via hennk)
3
+ ==0.1.8 / 2010-03-16
4
+ Added validate presence of.
5
+ Updated specs to test validations.
6
+ No Longer depend on mechanize version 0.9.3 and get rid of those pesky deprecation warnings.
7
+ The connection adapter classes are now suffixed with "Adapter", so Mechanize is now MechanizeAdapter. You still specify it with :adapter => 'mechanize', however.
8
+
9
+ ==0.1.7 / 2010-03-11
10
+ Added some new error classes, add removed any extraneous raise 'text' (via jrbingham)
11
+ Removed debugging puts from the ticket file.
12
+
13
+ ==0.1.6 / 2010-03-09
14
+ Updated the search feature to pull all the ticket information at the same time, instead of only loading the id and subject.
15
+ Fixed tests that were failing because of out-of-order strings.
16
+ Returned to a sane version numbering scheme, sorry about that.
17
+
18
+ ==0.1.5.2 / 2010-01-20
19
+ New ticket objects now come with a default attribute :text that specifies the initial log of the ticket.
20
+
21
+ ==0.1.5.1 / 2010-01-20
22
+ Fixed a bug that caused custom fields not to show up in the ticket attributes
23
+
24
+ ==0.1.5 / 2010-01-19
25
+ Can add transactions ticket using #comment method. (via btucker)
26
+ Can authenticate again after the Ticket class is declared. (via newellista)
27
+ More search fields are now avaliable. (via threetee)
28
+
29
+ ==0.1.4 / 2009-08-19
30
+ Implemented callbacks, you can now hook into the ticket life-cycle with before and after create and update.
31
+ Saving works like ActiveRecord now, with save returning true if successful and false otherwise, and save! raising an error if unsuccessful.
32
+ 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.
33
+ 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.
34
+
35
+ ==0.1.3 / 2009-08-18
36
+ Can now update tickets with #save. Returns true if saved, raises an error if not.
37
+
38
+ ==0.1.2 / 2009-08-18
39
+ Added Create ticket functionality. Ticket.new will create a ticket in the RT system and return that ticket.
40
+
41
+ == 0.1.1 / 2009-08-17
42
+ 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.
43
+
44
+ == 0.1.0 / 2009-08-13
45
+ 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,89 @@
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
+ * Comment on a Ticket
68
+ ticket = Ticket.find(23452)
69
+ ticket.comment("This is a lovely Ticket", :time_worked => 45, :cc => 'someone@example.com'))
70
+
71
+
72
+ == REQUIREMENTS:
73
+
74
+ * mechanize
75
+ * A working RT3 install.
76
+
77
+ == INSTALL:
78
+
79
+ $ gem sources -a http://gems.github.com
80
+ $ sudo gem install pjdavis-roart
81
+
82
+ == LICENSE:
83
+
84
+ (C) PJ Davis <pj.davis@gmail.com>
85
+
86
+ This program is free software; you can redistribute it and/or modify it under
87
+ the terms of the WTFPL, Version 2, as
88
+ published by Sam Hocevar. See http://sam.zoy.org/wtfpl/ for more details.
89
+
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' #this will go away when a stdlib adapter gets written
32
+
33
+ PROJ.spec.opts << '--color'
34
+
35
+ # EOF
data/lib/roart.rb ADDED
@@ -0,0 +1,52 @@
1
+ require 'rubygems'
2
+ require 'active_support'
3
+ require 'active_support/core_ext'
4
+
5
+ module Roart
6
+
7
+ # :stopdoc:
8
+ VERSION = '0.1.9'
9
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
10
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
11
+ # :startdoc:
12
+
13
+ # Returns the version string for the library.
14
+ #
15
+ def self.version
16
+ VERSION
17
+ end
18
+
19
+ # Returns the library path for the module. If any arguments are given,
20
+ # they will be joined to the end of the libray path using
21
+ # <tt>File.join</tt>.
22
+ #
23
+ def self.libpath( *args )
24
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
25
+ end
26
+
27
+ # Returns the lpath for the module. If any arguments are given,
28
+ # they will be joined to the end of the path using
29
+ # <tt>File.join</tt>.
30
+ #
31
+ def self.path( *args )
32
+ args.empty? ? PATH : ::File.join(PATH, args.flatten)
33
+ end
34
+
35
+ # Utility method used to require all files ending in .rb that lie in the
36
+ # directory below this file that has the same name as the filename passed
37
+ # in. Optionally, a specific _directory_ name can be passed in such that
38
+ # the _filename_ does not have to be equivalent to the directory.
39
+ #
40
+ def self.require_all_libs_relative_to( fname, dir = nil )
41
+ dir ||= ::File.basename(fname, '.*')
42
+ search_me = ::File.expand_path(
43
+ ::File.join(::File.dirname(fname), dir, '**', '*.rb'))
44
+
45
+ Dir.glob(search_me).sort.each {|rb| require rb}
46
+ end
47
+
48
+ end # module Roart
49
+
50
+ Roart.require_all_libs_relative_to(__FILE__)
51
+
52
+ # 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,65 @@
1
+ require 'yaml'
2
+
3
+ module Roart
4
+
5
+ module Connections
6
+ RequiredConfig = %w(server adapter)
7
+ RequiredToLogin = %w( user pass )
8
+
9
+ end
10
+
11
+ class Connection
12
+
13
+ attr_reader :agent
14
+ attr_reader :conf
15
+
16
+ def initialize(conf)
17
+ if conf.is_a?(String)
18
+ raise RoartError, "Loading Config File not yet implemented"
19
+ elsif conf.class.name == Hash.name #TODO: Figure out why conf.is_a?(Hash) doesn't work
20
+ @conf = conf
21
+ end
22
+ if Roart::check_keys(conf, Roart::Connections::RequiredConfig)
23
+ @agent = @conf[:login]
24
+ add_methods!
25
+ else
26
+ raise RoartError, "Configuration Error"
27
+ end
28
+ end
29
+
30
+ def authenticate(conf)
31
+ if Roart::check_keys(conf, Roart::Connections::RequiredToLogin)
32
+ connection.authenticate(conf)
33
+ self
34
+ end
35
+ end
36
+
37
+ def rest_path
38
+ self.server + '/REST/1.0/'
39
+ end
40
+
41
+ def get(uri)
42
+ connection.get(uri)
43
+ end
44
+
45
+ def post(uri, payload)
46
+ connection.post(uri, payload)
47
+ end
48
+
49
+ protected
50
+
51
+ def connection
52
+ @connection ||= ConnectionAdapter.new(conf)
53
+ end
54
+
55
+ def add_methods!
56
+ @conf.each do |key, value|
57
+ (class << self; self; end).send :define_method, key do
58
+ return value
59
+ end
60
+ end
61
+ end
62
+
63
+ end
64
+
65
+ end
@@ -0,0 +1,21 @@
1
+ require 'forwardable'
2
+
3
+ module Roart
4
+
5
+ class ConnectionAdapter
6
+ extend Forwardable
7
+
8
+ def initialize(config)
9
+ @adapter = Roart::ConnectionAdapters.const_get(config[:adapter].capitalize + "Adapter").new(config)
10
+ @adapter.login(config) if config[:user] && config[:pass]
11
+ end
12
+
13
+ def authenticate(config)
14
+ @adapter.login(config)
15
+ end
16
+
17
+ def_delegators :@adapter, :get, :post
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,32 @@
1
+ require 'mechanize'
2
+
3
+ module Roart
4
+ module ConnectionAdapters
5
+ class MechanizeAdapter
6
+
7
+ def initialize(config)
8
+ @conf = config
9
+ end
10
+
11
+ def login(config)
12
+ @conf.merge!(config)
13
+ agent = Mechanize.new
14
+ page = agent.get(@conf[:server])
15
+ form = page.form('login')
16
+ form.user = @conf[:user]
17
+ form.pass = @conf[:pass]
18
+ page = agent.submit form
19
+ @agent = agent
20
+ end
21
+
22
+ def get(uri)
23
+ @agent.get(uri).body
24
+ end
25
+
26
+ def post(uri, payload)
27
+ @agent.post(uri, payload).body
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,12 @@
1
+ module Roart
2
+ class ContentFormatter
3
+ # The following is only based on quick trial&error on my part. The RT Api (at least in 3.6) does not have good documentation.
4
+ # Strings in a RT request:
5
+ # - are not allowed to contain '\r'
6
+ # - must use equally padded new-lines to distinguish them from the beginning of a new field (we use 2-space padding)
7
+ def self.format_string(string)
8
+ string.gsub("\r", '').gsub("\n", "\n ")
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,22 @@
1
+ class Hash
2
+ def to_content_format
3
+ fields = self.map do |key,value|
4
+ unless value.nil?
5
+ value = Roart::ContentFormatter.format_string(value.to_s)
6
+ if key.to_s.match(/^cf_.+/)
7
+ "CF-#{key.to_s[3..key.to_s.length].gsub(/_/, " ").camelize.humanize}: #{value}"
8
+ else
9
+ "#{key.to_s.camelize}: #{value}"
10
+ end
11
+ end
12
+ end
13
+ content = fields.compact.sort.join("\n")
14
+ end
15
+
16
+ def with_indifferent_access
17
+ hash = HashWithIndifferentAccess.new(self)
18
+ hash.default = self.default
19
+ hash
20
+ end
21
+
22
+ end
@@ -0,0 +1,13 @@
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
+ class TicketNotFoundError < RoartError; end
12
+
13
+ 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