roart 0.1.5.1 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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