roart 0.1.5.1 → 0.1.6

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.
@@ -1,3 +1,11 @@
1
+ ==0.1.6 / 2010-03-09
2
+ Updated the search feature to pull all the ticket information at the same time, instead of only loading the id and subject.
3
+ Fixed tests that were failing because of out-of-order strings.
4
+ Returned to a sane version numbering scheme, sorry about that.
5
+
6
+ ==0.1.5.2 / 2010-01-20
7
+ New ticket objects now come with a default attribute :text that specifies the initial log of the ticket.
8
+
1
9
  ==0.1.5.1 / 2010-01-20
2
10
  Fixed a bug that caused custom fields not to show up in the ticket attributes
3
11
 
@@ -2,7 +2,7 @@ require 'rubygems'
2
2
  module Roart
3
3
 
4
4
  # :stopdoc:
5
- VERSION = '0.1.5.1'
5
+ VERSION = '0.1.6'
6
6
  LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
7
7
  PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
8
8
  # :startdoc:
@@ -17,7 +17,7 @@ module Roart
17
17
  def initialize(conf)
18
18
  if conf.is_a?(String)
19
19
  raise "Loading Config File not yet implemented"
20
- elsif conf.is_a?(Hash)
20
+ elsif conf.class.name == Hash.name #TODO: Figure out why conf.is_a?(Hash) doesn't work
21
21
  @conf = conf
22
22
  end
23
23
  if Roart::check_keys(conf, Roart::Connections::RequiredConfig)
@@ -1,11 +1,11 @@
1
1
  class Array
2
-
2
+
3
3
  def to_hash
4
- h = Hash.new
4
+ h = HashWithIndifferentAccess.new
5
5
  self.each do |element|
6
- h.update(element.to_sym => nil)
6
+ h.update(element => nil)
7
7
  end
8
8
  h
9
9
  end
10
-
10
+
11
11
  end
@@ -1,15 +1,22 @@
1
+ require "#{File.dirname(__FILE__)}/hash/indifferent_access.rb"
1
2
  class Hash
2
3
  def to_content_format
3
- fields = self.map do |key,value|
4
+ fields = self.map do |key,value|
4
5
  unless value.nil?
5
6
  if key.to_s.match(/^cf_.+/)
6
- "CF-#{key.to_s[3..key.to_s.length].camelize.humanize}: #{value}"
7
+ "CF-#{key.to_s[3..key.to_s.length].gsub(/_/, " ").camelize.humanize}: #{value}"
7
8
  else
8
9
  "#{key.to_s.camelize}: #{value}"
9
10
  end
10
11
  end
11
12
  end
12
- content = fields.compact.join("\n")
13
+ content = fields.compact.sort.join("\n")
13
14
  end
14
-
15
+
16
+ def with_indifferent_access
17
+ hash = HashWithIndifferentAccess.new(self)
18
+ hash.default = self.default
19
+ hash
20
+ end
21
+
15
22
  end
@@ -0,0 +1,133 @@
1
+ # used from ActiveSupport
2
+ # Copyright (c) 2005-2009 David Heinemeier Hansson
3
+
4
+ # This class has dubious semantics and we only have it so that
5
+ # people can write params[:key] instead of params['key']
6
+ # and they get the same value for both keys.
7
+
8
+ class HashWithIndifferentAccess < Hash
9
+ def initialize(constructor = {})
10
+ if constructor.is_a?(Hash)
11
+ super()
12
+ update(constructor)
13
+ else
14
+ super(constructor)
15
+ end
16
+ end
17
+
18
+ def default(key = nil)
19
+ if key.is_a?(Symbol) && include?(key = key.to_s)
20
+ self[key]
21
+ else
22
+ super
23
+ end
24
+ end
25
+
26
+ alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
27
+ alias_method :regular_update, :update unless method_defined?(:regular_update)
28
+
29
+ # Assigns a new value to the hash:
30
+ #
31
+ # hash = HashWithIndifferentAccess.new
32
+ # hash[:key] = "value"
33
+ #
34
+ def []=(key, value)
35
+ regular_writer(convert_key(key), convert_value(value))
36
+ end
37
+
38
+ # Updates the instantized hash with values from the second:
39
+ #
40
+ # hash_1 = HashWithIndifferentAccess.new
41
+ # hash_1[:key] = "value"
42
+ #
43
+ # hash_2 = HashWithIndifferentAccess.new
44
+ # hash_2[:key] = "New Value!"
45
+ #
46
+ # hash_1.update(hash_2) # => {"key"=>"New Value!"}
47
+ #
48
+ def update(other_hash)
49
+ other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
50
+ self
51
+ end
52
+
53
+ alias_method :merge!, :update
54
+
55
+ # Checks the hash for a key matching the argument passed in:
56
+ #
57
+ # hash = HashWithIndifferentAccess.new
58
+ # hash["key"] = "value"
59
+ # hash.key? :key # => true
60
+ # hash.key? "key" # => true
61
+ #
62
+ def key?(key)
63
+ super(convert_key(key))
64
+ end
65
+
66
+ alias_method :include?, :key?
67
+ alias_method :has_key?, :key?
68
+ alias_method :member?, :key?
69
+
70
+ # Fetches the value for the specified key, same as doing hash[key]
71
+ def fetch(key, *extras)
72
+ super(convert_key(key), *extras)
73
+ end
74
+
75
+ # Returns an array of the values at the specified indices:
76
+ #
77
+ # hash = HashWithIndifferentAccess.new
78
+ # hash[:a] = "x"
79
+ # hash[:b] = "y"
80
+ # hash.values_at("a", "b") # => ["x", "y"]
81
+ #
82
+ def values_at(*indices)
83
+ indices.collect {|key| self[convert_key(key)]}
84
+ end
85
+
86
+ # Returns an exact copy of the hash.
87
+ def dup
88
+ HashWithIndifferentAccess.new(self)
89
+ end
90
+
91
+ # Merges the instantized and the specified hashes together, giving precedence to the values from the second hash
92
+ # Does not overwrite the existing hash.
93
+ def merge(hash)
94
+ self.dup.update(hash)
95
+ end
96
+
97
+ # Performs the opposite of merge, with the keys and values from the first hash taking precedence over the second.
98
+ # This overloaded definition prevents returning a regular hash, if reverse_merge is called on a HashWithDifferentAccess.
99
+ def reverse_merge(other_hash)
100
+ super other_hash.with_indifferent_access
101
+ end
102
+
103
+ # Removes a specified key from the hash.
104
+ def delete(key)
105
+ super(convert_key(key))
106
+ end
107
+
108
+ def stringify_keys!; self end
109
+ def symbolize_keys!; self end
110
+ def to_options!; self end
111
+
112
+ # Convert to a Hash with String keys.
113
+ def to_hash
114
+ Hash.new(default).merge(self)
115
+ end
116
+
117
+ protected
118
+ def convert_key(key)
119
+ key.kind_of?(Symbol) ? key.to_s : key
120
+ end
121
+
122
+ def convert_value(value)
123
+ case value
124
+ when Hash
125
+ value.with_indifferent_access
126
+ when Array
127
+ value.collect { |e| e.is_a?(Hash) ? e.with_indifferent_access : e }
128
+ else
129
+ value
130
+ end
131
+ end
132
+ end
133
+
@@ -1,25 +1,24 @@
1
1
  module Roart
2
-
2
+
3
3
  def self.check_keys!(hash, required)
4
- puts hash
5
- unless required.inject(true) do |inc, attr|
6
- inc ? hash.keys.include?(attr.to_sym) : nil
4
+ unless required.inject(true) do |inc, attr|
5
+ inc ? hash.keys.include?(attr) : nil
7
6
  end
8
- raise ArgumentError, "Not all required fields entered"
7
+ raise ArgumentError, "Not all required fields entered"
9
8
  end
10
9
  end
11
-
10
+
12
11
  def self.check_keys(hash, required)
13
- unless required.inject(true) do |inc, attr|
12
+ unless required.inject(true) do |inc, attr|
14
13
  inc ? hash.keys.include?(attr.to_sym) : nil
15
14
  end
16
15
  return false
17
16
  end
18
17
  return true
19
18
  end
20
-
19
+
21
20
  module MethodFunctions
22
-
21
+
23
22
  def add_methods!
24
23
  @attributes.each do |key, value|
25
24
  (class << self; self; end).send :define_method, key do
@@ -28,9 +27,9 @@ module Roart
28
27
  (class << self; self; end).send :define_method, "#{key}=" do |new_val|
29
28
  @attributes[key] = new_val
30
29
  end
31
- end
30
+ end
32
31
  end
33
-
32
+
34
33
  end
35
-
34
+
36
35
  end
@@ -2,7 +2,7 @@ module Roart
2
2
 
3
3
  module Tickets
4
4
 
5
- DefaultAttributes = %w(queue owner creator subject status priority initial_priority final_priority requestors cc admin_cc created starts started due resolved told last_updated time_estimated time_worked time_left)
5
+ DefaultAttributes = %w(queue owner creator subject status priority initial_priority final_priority requestors cc admin_cc created starts started due resolved told last_updated time_estimated time_worked time_left text)
6
6
  RequiredAttributes = %w(queue subject)
7
7
 
8
8
  end
@@ -62,17 +62,25 @@ module Roart
62
62
  self.before_update
63
63
  uri = "#{self.class.connection.server}/REST/1.0/ticket/#{self.id}/edit"
64
64
  payload = @attributes.clone
65
- payload.delete(:id)
65
+ payload.delete("text")
66
+ payload.delete("id") # Can't have text in an update, only create, use comment for updateing
66
67
  payload = payload.to_content_format
68
+ puts payload
67
69
  resp = self.class.connection.post(uri, :content => payload)
70
+ puts resp
68
71
  resp = resp.split("\n")
69
72
  raise "Ticket Update Failed" unless resp.first.include?("200")
70
- if resp[2].match(/^# Ticket (\d+) updated./)
71
- self.after_update
72
- true
73
- else
74
- false
73
+ resp.each do |line|
74
+ puts "line"
75
+ if line.match(/^# Ticket (\d+) updated./)
76
+ self.after_update
77
+ puts "FOUND"
78
+ return true
79
+ else
80
+ #TODO: Add warnign to ticket
81
+ end
75
82
  end
83
+ return false
76
84
  end
77
85
  end
78
86
 
@@ -111,14 +119,17 @@ module Roart
111
119
  resp = self.class.connection.post(uri, :content => payload)
112
120
  resp = resp.split("\n")
113
121
  raise "Ticket Create Failed" unless resp.first.include?("200")
114
- if tid = resp[2].match(/^# Ticket (\d+) created./)
115
- @attributes[:id] = tid[1].to_i
116
- self.after_create
117
- @new_record = false
118
- true
119
- else
120
- false
122
+ resp.each do |line|
123
+ if tid = line.match(/^# Ticket (\d+) created./)
124
+ @attributes[:id] = tid[1].to_i
125
+ self.after_create
126
+ @new_record = false
127
+ return true
128
+ else
129
+ #TODO: Add Warnings To Ticket
130
+ end
121
131
  end
132
+ return false
122
133
  end
123
134
 
124
135
  def create! #:nodoc:
@@ -241,7 +252,7 @@ module Roart
241
252
  array << object
242
253
  end
243
254
  return array
244
- elsif attrs.is_a?(Hash)
255
+ elsif attrs.is_a?(Hash) || attrs.is_a?(HashWithIndifferentAccess)
245
256
  object = self.allocate
246
257
  object.instance_variable_set("@attributes", attrs)
247
258
  object.send("add_methods!")
@@ -279,11 +290,37 @@ module Roart
279
290
  end
280
291
  end
281
292
 
293
+ def page_list_array(uri) #:nodoc:
294
+ page = self.connection.get(uri)
295
+ raise TicketSystemError, "Can't get ticket." unless page
296
+ page = page.split("\n")
297
+ status = page.delete_at(0)
298
+ if status.include?("200")
299
+ page = page.join("\n")
300
+ chunks = page.split(/^--$/)
301
+ page = []
302
+ for chunk in chunks
303
+ chunk = chunk.split("\n")
304
+ chunk.delete_if{|x| !x.include?(":")}
305
+ page << chunk
306
+ end
307
+ page
308
+ else
309
+ raise TicketSystemInterfaceError, "Error Getting Ticket: #{status}"
310
+ end
311
+ end
312
+
282
313
  def get_tickets_from_search_uri(uri) #:nodoc:
283
- page = page_array(uri)
314
+ page = page_list_array(uri + "&format=l")
284
315
  page.extend(Roart::TicketPage)
285
- page = page.to_search_array
286
- self.instantiate(page)
316
+ page = page.to_search_list_array
317
+ array = Array.new
318
+ for ticket in page
319
+ ticket = self.instantiate(ticket)
320
+ ticket.instance_variable_set("@full", true)
321
+ array << ticket
322
+ end
323
+ array
287
324
  end
288
325
 
289
326
  def get_ticket_from_uri(uri) #:nodoc:
@@ -5,7 +5,7 @@ module Roart
5
5
  IntKeys = %w[id]
6
6
 
7
7
  def to_hash
8
- hash = Hash.new
8
+ hash = HashWithIndifferentAccess.new
9
9
  self.delete_if{|x| !x.include?(":")}
10
10
  self.each do |ln|
11
11
  ln = ln.split(":")
@@ -17,12 +17,21 @@ module Roart
17
17
  key = ln.delete_at(0).strip.underscore
18
18
  end
19
19
  value = ln.join(":").strip
20
- hash[key.to_sym] = value if key
20
+ hash[key] = value if key
21
21
  end
22
- hash[:id] = hash[:id].split("/").last.to_i
22
+ hash["id"] = hash["id"].split("/").last.to_i
23
23
  hash
24
24
  end
25
25
 
26
+ def to_search_list_array
27
+ array = Array.new
28
+ self.each do |ticket|
29
+ ticket.extend(Roart::TicketPage)
30
+ array << ticket.to_hash
31
+ end
32
+ array
33
+ end
34
+
26
35
  def to_search_array
27
36
  array = Array.new
28
37
  self.delete_if{|x| !x.include?(":")}
@@ -42,7 +51,7 @@ module Roart
42
51
 
43
52
  # TODO: Don't throw away attachments (/^ {13})
44
53
  def to_history_hash
45
- hash = Hash.new
54
+ hash = HashWithIndifferentAccess.new
46
55
  self.delete_if{|x| !x.include?(":") && !x.match(/^ {9}/) && !x.match(/^ {13}/)}
47
56
  self.each do |ln|
48
57
  if ln.match(/^ {9}/) && !ln.match(/^ {13}/)
@@ -54,7 +63,7 @@ module Roart
54
63
  unless ln.size == 1 || ln.first == 'Ticket' # we don't want to override the ticket method.
55
64
  key = ln.delete_at(0).strip.underscore
56
65
  value = ln.join(":").strip
57
- hash[key.to_sym] = IntKeys.include?(key) ? value.to_i : value
66
+ hash[key] = IntKeys.include?(key) ? value.to_i : value
58
67
  end
59
68
  end
60
69
  end
@@ -1,37 +1,37 @@
1
1
  module Roart
2
-
2
+
3
3
  class Errors
4
4
  include Enumerable
5
-
5
+
6
6
  def initialize(obj)
7
7
  @base, @errors = obj, {}
8
8
  end
9
-
9
+
10
10
  def add_to_base(msg)
11
11
  add(:base, msg)
12
12
  end
13
-
13
+
14
14
  def add(field, message)
15
15
  @errors[field.to_sym] ||= []
16
16
  @errors[field.to_sym] << message
17
17
  end
18
-
18
+
19
19
  def on_base
20
20
  on(:base)
21
21
  end
22
-
22
+
23
23
  def on(field)
24
24
  errors = @errors[field.to_sym]
25
25
  return nil if errors.nil?
26
26
  errors
27
27
  end
28
-
28
+
29
29
  alias :[] :on
30
-
30
+
31
31
  def each
32
32
  @errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } }
33
33
  end
34
-
34
+
35
35
  # Returns true if no errors have been added.
36
36
  def empty?
37
37
  @errors.empty?
@@ -41,42 +41,42 @@ module Roart
41
41
  def clear
42
42
  @errors = {}
43
43
  end
44
-
45
- # Returns the total number of errors added. Two errors added to the same attribute will be counted as such.
44
+
45
+ # Returns the total number of errors added. Two errors added to the same attribute will be counted as such.
46
46
  def size
47
47
  @errors.values.inject(0) { |error_count, attribute| error_count + attribute.size }
48
48
  end
49
-
49
+
50
50
  alias_method :count, :size
51
51
  alias_method :length, :size
52
-
52
+
53
53
  end
54
-
54
+
55
55
  module Validations
56
-
56
+
57
57
  def self.included(model)
58
58
  model.extend ClassMethods
59
59
 
60
60
  end
61
-
61
+
62
62
  class Validators
63
-
63
+
64
64
  def initialize
65
65
  @validators = []
66
66
  end
67
-
67
+
68
68
  def add(validator)
69
69
  @validators << validator
70
70
  end
71
-
71
+
72
72
  def validate(obj)
73
73
  @validators.each{|validator| validator.call(obj)}
74
74
  end
75
-
75
+
76
76
  end
77
-
77
+
78
78
  module ClassMethods
79
-
79
+
80
80
  ALL_RANGE_OPTIONS = [ :is, :within, :in, :minimum, :min, :maximum, :max ].freeze
81
81
  ALL_NUMERICALITY_CHECKS = { :greater_than => '>', :greater_than_or_equal_to => '>=',
82
82
  :equal_to => '==', :less_than => '<', :less_than_or_equal_to => '<=',
@@ -85,23 +85,23 @@ module Roart
85
85
  def validator
86
86
  @validator ||= Validators.new
87
87
  end
88
-
88
+
89
89
  def validates_presence_of(*args)
90
-
90
+
91
91
  end
92
-
92
+
93
93
  def validates_format_of(*args)
94
94
  options = args.last.is_a?(Hash) ? args.pop : {}
95
95
  args.each do |field|
96
96
  validator_proc = lambda do |obj|
97
97
  unless obj.send(field.to_sym).match(options[:format])
98
- obj.errors.add(field.to_sym, "Wrong Format")
98
+ obj.errors.add(field.to_sym, "Wrong Format")
99
99
  end
100
100
  end
101
101
  self.validator.add(validator_proc)
102
102
  end
103
103
  end
104
-
104
+
105
105
  def validates_length_of(*args)
106
106
  options = args.last.is_a?(Hash) ? args.pop : {}
107
107
  # Ensure that one and only one range option is specified.
@@ -114,19 +114,19 @@ module Roart
114
114
  else
115
115
  raise ArgumentError, 'Too many range options specified. Choose only one.'
116
116
  end
117
-
117
+
118
118
  option = range_options.first
119
119
  option_value = options[range_options.first]
120
120
  key = {:is => :wrong_length, :minimum => :too_short, :maximum => :too_long}[option]
121
121
  custom_message = options[:message] || options[key]
122
-
122
+
123
123
  args.each do |field|
124
124
  case option
125
125
  when :within, :in
126
- raise ArgumentError, ":#{option} must be a Range" unless option_value.is_a?(Range)
126
+ raise ArgumentError, ":#{option} must be a Range" unless option_value.is_a?(Range)
127
127
  validator_proc = lambda do |obj|
128
128
  if obj.send(field.to_sym).length < option_value.begin
129
- obj.errors.add(field.to_sym, "Must be more than #{option_value.begin} characters.")
129
+ obj.errors.add(field.to_sym, "Must be more than #{option_value.begin} characters.")
130
130
  end
131
131
  if obj.send(field.to_sym).length > option_value.end
132
132
  obj.errors.add(field.to_sym, "Must be less than #{option_value.end} characters")
@@ -137,7 +137,7 @@ module Roart
137
137
  raise ArgumentError, ":#{option} must be an Integer" unless option_value.is_a?(Integer)
138
138
  validator_proc = lambda do |obj|
139
139
  if obj.send(field.to_sym).length < option_value
140
- obj.errors.add(field.to_sym, "Must be more than #{option_value} characters.")
140
+ obj.errors.add(field.to_sym, "Must be more than #{option_value} characters.")
141
141
  end
142
142
  end
143
143
  self.validator.add(validator_proc)
@@ -145,29 +145,28 @@ module Roart
145
145
  raise ArgumentError, ":#{option} must be an Integer" unless option_value.is_a?(Integer)
146
146
  validator_proc = lambda do |obj|
147
147
  if obj.send(field.to_sym).length > option_value
148
- obj.errors.add(field.to_sym, "Must be less than #{option_value} characters.")
148
+ obj.errors.add(field.to_sym, "Must be less than #{option_value} characters.")
149
149
  end
150
150
  end
151
151
  self.validator.add(validator_proc)
152
152
  when :is
153
153
  raise ArgumentError, ":#{option} must be an Integer" unless option_value.is_a?(Integer)
154
154
  validator_proc = lambda do |obj|
155
- puts "#{field} is #{option_value}"
156
155
  unless obj.send(field.to_sym).length == option_value
157
- obj.errors.add(field.to_sym, "Must be #{option_value} characters.")
156
+ obj.errors.add(field.to_sym, "Must be #{option_value} characters.")
158
157
  end
159
158
  end
160
159
  self.validator.add(validator_proc)
161
160
  end
162
161
  end
163
162
  end
164
-
163
+
165
164
  alias_method :validates_size_of, :validates_length_of
166
-
165
+
167
166
  def validates_numericality_of(*args)
168
167
  options = args.last.is_a?(Hash) ? args.pop : {}
169
168
  numericality_options = ALL_NUMERICALITY_CHECKS & options.keys
170
-
169
+
171
170
  case numericality_options
172
171
  when 0
173
172
  raise ArgumentError, "Options Unspecified. Specify an option to use."
@@ -176,46 +175,46 @@ module Roart
176
175
  else
177
176
  raise ArgumentError, "Too many options specified"
178
177
  end
179
-
178
+
180
179
  option = numericality_options.first
181
180
  option_value = options[numericality_options.first]
182
181
  key = {:is => :wrong_length, :minimum => :too_short, :maximum => :too_long}[option]
183
182
  custom_message = options[:message] || options[key]
184
-
183
+
185
184
  args.each do |field|
186
185
  numericality_options.each do |option|
187
186
  case option
188
187
  when :odd, :even
189
188
  unless raw_value.to_i.method(ALL_NUMERICALITY_CHECKS[option])[]
190
- record.errors.add(attr_name, option, :value => raw_value, :default => configuration[:message])
189
+ record.errors.add(attr_name, option, :value => raw_value, :default => configuration[:message])
191
190
  end
192
191
  else
193
192
  record.errors.add(attr_name, option, :default => configuration[:message], :value => raw_value, :count => configuration[option]) unless raw_value.method(ALL_NUMERICALITY_CHECKS[option])[configuration[option]]
194
193
  end
195
194
  end
196
195
  end
197
-
196
+
198
197
  end
199
-
198
+
200
199
  end
201
-
200
+
202
201
  def validator
203
202
  self.class.validator
204
203
  end
205
-
204
+
206
205
  def valid?
207
206
  validator.validate self
208
207
  self.errors.size == 0
209
208
  end
210
-
209
+
211
210
  def invalid?
212
211
  !valid?
213
212
  end
214
-
213
+
215
214
  def errors
216
215
  @errors ||= Errors.new(self)
217
216
  end
218
-
217
+
219
218
  end
220
-
219
+
221
220
  end