axtro-roart 0.1.8

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.
@@ -0,0 +1,43 @@
1
+ ==0.1.8 / 2010-03-16
2
+ Added validate presence of.
3
+ Updated specs to test validations.
4
+ No Longer depend on mechanize version 0.9.3 and get rid of those pesky deprecation warnings.
5
+ The connection adapter classes are now suffixed with "Adapter", so Mechanize is now MechanizeAdapter. You still specify it with :adapter => 'mechanize', however.
6
+
7
+ ==0.1.7 / 2010-03-11
8
+ Added some new error classes, add removed any extraneous raise 'text' (via jrbingham)
9
+ Removed debugging puts from the ticket file.
10
+
11
+ ==0.1.6 / 2010-03-09
12
+ Updated the search feature to pull all the ticket information at the same time, instead of only loading the id and subject.
13
+ Fixed tests that were failing because of out-of-order strings.
14
+ Returned to a sane version numbering scheme, sorry about that.
15
+
16
+ ==0.1.5.2 / 2010-01-20
17
+ New ticket objects now come with a default attribute :text that specifies the initial log of the ticket.
18
+
19
+ ==0.1.5.1 / 2010-01-20
20
+ Fixed a bug that caused custom fields not to show up in the ticket attributes
21
+
22
+ ==0.1.5 / 2010-01-19
23
+ Can add transactions ticket using #comment method. (via btucker)
24
+ Can authenticate again after the Ticket class is declared. (via newellista)
25
+ More search fields are now avaliable. (via threetee)
26
+
27
+ ==0.1.4 / 2009-08-19
28
+ Implemented callbacks, you can now hook into the ticket life-cycle with before and after create and update.
29
+ Saving works like ActiveRecord now, with save returning true if successful and false otherwise, and save! raising an error if unsuccessful.
30
+ 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.
31
+ 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.
32
+
33
+ ==0.1.3 / 2009-08-18
34
+ Can now update tickets with #save. Returns true if saved, raises an error if not.
35
+
36
+ ==0.1.2 / 2009-08-18
37
+ Added Create ticket functionality. Ticket.new will create a ticket in the RT system and return that ticket.
38
+
39
+ == 0.1.1 / 2009-08-17
40
+ 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.
41
+
42
+ == 0.1.0 / 2009-08-13
43
+ Initial Commit. Base functionality added. Can search for tickets, and see all info on tickets. Ticket History not yet implemented.
@@ -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
+
@@ -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
@@ -0,0 +1,49 @@
1
+ require 'rubygems'
2
+ module Roart
3
+
4
+ # :stopdoc:
5
+ VERSION = '0.1.8'
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,63 @@
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
+ @connection = ConnectionAdapter.new(@conf)
26
+ else
27
+ raise RoartError, "Configuration Error"
28
+ end
29
+ end
30
+
31
+ def authenticate(conf)
32
+ if Roart::check_keys(conf, Roart::Connections::RequiredToLogin)
33
+ @connection.authenticate(conf)
34
+ self
35
+ end
36
+ end
37
+
38
+ def rest_path
39
+ self.server + '/REST/1.0/'
40
+ end
41
+
42
+ def get(uri)
43
+ @connection.get(uri)
44
+ end
45
+
46
+ def post(uri, payload)
47
+ @connection.post(uri, payload)
48
+ end
49
+
50
+ protected
51
+
52
+
53
+ def add_methods!
54
+ @conf.each do |key, value|
55
+ (class << self; self; end).send :define_method, key do
56
+ return value
57
+ end
58
+ end
59
+ end
60
+
61
+ end
62
+
63
+ 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,11 @@
1
+ class Array
2
+
3
+ def to_hash
4
+ h = HashWithIndifferentAccess.new
5
+ self.each do |element|
6
+ h.update(element => nil)
7
+ end
8
+ h
9
+ end
10
+
11
+ end
@@ -0,0 +1,22 @@
1
+ require "#{File.dirname(__FILE__)}/hash/indifferent_access.rb"
2
+ class Hash
3
+ def to_content_format
4
+ fields = self.map do |key,value|
5
+ unless value.nil?
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,135 @@
1
+ unless defined?(HashWithIndifferentAccess)
2
+ # used from ActiveSupport
3
+ # Copyright (c) 2005-2009 David Heinemeier Hansson
4
+
5
+ # This class has dubious semantics and we only have it so that
6
+ # people can write params[:key] instead of params['key']
7
+ # and they get the same value for both keys.
8
+
9
+ class HashWithIndifferentAccess < Hash
10
+ def initialize(constructor = {})
11
+ if constructor.is_a?(Hash)
12
+ super()
13
+ update(constructor)
14
+ else
15
+ super(constructor)
16
+ end
17
+ end
18
+
19
+ def default(key = nil)
20
+ if key.is_a?(Symbol) && include?(key = key.to_s)
21
+ self[key]
22
+ else
23
+ super
24
+ end
25
+ end
26
+
27
+ alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
28
+ alias_method :regular_update, :update unless method_defined?(:regular_update)
29
+
30
+ # Assigns a new value to the hash:
31
+ #
32
+ # hash = HashWithIndifferentAccess.new
33
+ # hash[:key] = "value"
34
+ #
35
+ def []=(key, value)
36
+ regular_writer(convert_key(key), convert_value(value))
37
+ end
38
+
39
+ # Updates the instantized hash with values from the second:
40
+ #
41
+ # hash_1 = HashWithIndifferentAccess.new
42
+ # hash_1[:key] = "value"
43
+ #
44
+ # hash_2 = HashWithIndifferentAccess.new
45
+ # hash_2[:key] = "New Value!"
46
+ #
47
+ # hash_1.update(hash_2) # => {"key"=>"New Value!"}
48
+ #
49
+ def update(other_hash)
50
+ other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
51
+ self
52
+ end
53
+
54
+ alias_method :merge!, :update
55
+
56
+ # Checks the hash for a key matching the argument passed in:
57
+ #
58
+ # hash = HashWithIndifferentAccess.new
59
+ # hash["key"] = "value"
60
+ # hash.key? :key # => true
61
+ # hash.key? "key" # => true
62
+ #
63
+ def key?(key)
64
+ super(convert_key(key))
65
+ end
66
+
67
+ alias_method :include?, :key?
68
+ alias_method :has_key?, :key?
69
+ alias_method :member?, :key?
70
+
71
+ # Fetches the value for the specified key, same as doing hash[key]
72
+ def fetch(key, *extras)
73
+ super(convert_key(key), *extras)
74
+ end
75
+
76
+ # Returns an array of the values at the specified indices:
77
+ #
78
+ # hash = HashWithIndifferentAccess.new
79
+ # hash[:a] = "x"
80
+ # hash[:b] = "y"
81
+ # hash.values_at("a", "b") # => ["x", "y"]
82
+ #
83
+ def values_at(*indices)
84
+ indices.collect {|key| self[convert_key(key)]}
85
+ end
86
+
87
+ # Returns an exact copy of the hash.
88
+ def dup
89
+ HashWithIndifferentAccess.new(self)
90
+ end
91
+
92
+ # Merges the instantized and the specified hashes together, giving precedence to the values from the second hash
93
+ # Does not overwrite the existing hash.
94
+ def merge(hash)
95
+ self.dup.update(hash)
96
+ end
97
+
98
+ # Performs the opposite of merge, with the keys and values from the first hash taking precedence over the second.
99
+ # This overloaded definition prevents returning a regular hash, if reverse_merge is called on a HashWithDifferentAccess.
100
+ def reverse_merge(other_hash)
101
+ super other_hash.with_indifferent_access
102
+ end
103
+
104
+ # Removes a specified key from the hash.
105
+ def delete(key)
106
+ super(convert_key(key))
107
+ end
108
+
109
+ def stringify_keys!; self end
110
+ def symbolize_keys!; self end
111
+ def to_options!; self end
112
+
113
+ # Convert to a Hash with String keys.
114
+ def to_hash
115
+ Hash.new(default).merge(self)
116
+ end
117
+
118
+ protected
119
+ def convert_key(key)
120
+ key.kind_of?(Symbol) ? key.to_s : key
121
+ end
122
+
123
+ def convert_value(value)
124
+ case value
125
+ when Hash
126
+ value.with_indifferent_access
127
+ when Array
128
+ value.collect { |e| e.is_a?(Hash) ? e.with_indifferent_access : e }
129
+ else
130
+ value
131
+ end
132
+ end
133
+ end
134
+
135
+ end