roart 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,8 @@
1
+ ==0.1.5 / 2010-01-19
2
+ Can add transactions ticket using #comment method. (via btucker)
3
+ Can authenticate again after the Ticket class is declared. (via newellista)
4
+ More search fields are now avaliable. (via threetee)
5
+
1
6
  ==0.1.4 / 2009-08-19
2
7
  Implemented callbacks, you can now hook into the ticket life-cycle with before and after create and update.
3
8
  Saving works like ActiveRecord now, with save returning true if successful and false otherwise, and save! raising an error if unsuccessful.
data/README.rdoc CHANGED
@@ -1,61 +1,61 @@
1
1
  == Roart
2
-
2
+
3
3
  \___\__o/
4
4
  / \
5
5
 
6
-
6
+
7
7
  by PJ Davis
8
8
  http://github.com/pjdavis/roart
9
9
 
10
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.
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
12
 
13
13
 
14
14
  == FEATURES/PROBLEMS:
15
15
 
16
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.
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
19
 
20
20
  == SYNOPSIS:
21
21
 
22
22
  * Create a class to interact with your ticket system
23
-
23
+
24
24
  require 'rubygems'
25
25
  require 'roart'
26
-
26
+
27
27
  class Ticket < Roart::Ticket
28
28
  connection :server => 'http://my.rt.server.com', :user => 'myuser', :pass => 'mypass'
29
-
29
+
30
30
  end
31
-
31
+
32
32
  * Search for tickets
33
33
 
34
34
  my_tickets = Ticket.find(:all, :queue => 'Scutters', :status => [:new, :open])
35
35
  my_tickets.each do |ticket|
36
36
  puts ticket.subject
37
37
  end
38
-
38
+
39
39
  #-> New John Wayne packages
40
40
  #-> Medi-lab training
41
-
41
+
42
42
  * See all info for a ticket
43
-
43
+
44
44
  my_ticket = Ticket.find(:first, :queue => 'Issues', :status => :new)
45
45
  ticket.creator #-> rimmer@reddwarf.com
46
46
  ticket.subject #-> Where is the Bomb?
47
-
47
+
48
48
  * Get history for a ticket
49
-
49
+
50
50
  my_ticket.histories #-> Returns an array of history objects
51
-
51
+
52
52
  * Create a new ticket
53
53
 
54
54
  issue = Ticket.new(:queue => 'some_queue', :subject => 'This is not working for me')
55
55
  issue.id #-> 'ticket/new'
56
56
  issue.save
57
57
  issue.id #-> 23423
58
-
58
+
59
59
  * Update a ticket
60
60
 
61
61
  ticket = Ticket.find(23452)
@@ -63,20 +63,24 @@ If you are using Best Practical's Request Tracker (RT) and you need to interact
63
63
  ticket.subject #-> "Smoke me a kipper, I'll be back for breakfast."
64
64
  ticket.save
65
65
  ticket.subject #->"Smoke me a kipper, I'll be back for breakfast."
66
-
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
+
67
71
 
68
72
  == REQUIREMENTS:
69
73
 
70
74
  * mechanize
71
75
  * A working RT3 install.
72
-
76
+
73
77
  == INSTALL:
74
78
 
75
79
  $ gem sources -a http://gems.github.com
76
80
  $ sudo gem install pjdavis-roart
77
81
 
78
82
  == LICENSE:
79
-
83
+
80
84
  (C) PJ Davis <pj.davis@gmail.com>
81
85
 
82
86
  This program is free software; you can redistribute it and/or modify it under
@@ -2,53 +2,55 @@ require 'yaml'
2
2
  require 'mechanize'
3
3
 
4
4
  module Roart
5
-
5
+
6
6
  module Connections
7
- RequiredConfig = %w(server user pass)
8
-
7
+ RequiredConfig = %w(server adapter)
8
+ RequiredToLogin = %w( user pass )
9
+
9
10
  end
10
-
11
+
11
12
  class Connection
12
-
13
+
13
14
  attr_reader :agent
14
-
15
+ attr_reader :conf
16
+
15
17
  def initialize(conf)
16
-
17
18
  if conf.is_a?(String)
18
19
  raise "Loading Config File not yet implemented"
19
20
  elsif conf.is_a?(Hash)
20
- Roart::check_keys!(conf, Roart::Connections::RequiredConfig)
21
21
  @conf = conf
22
22
  end
23
-
24
- @agent = login
25
- add_methods!
23
+ if Roart::check_keys(conf, Roart::Connections::RequiredConfig)
24
+ @agent = @conf[:login]
25
+ add_methods!
26
+ @connection = ConnectionAdapter.new(@conf)
27
+ else
28
+ raise "Configuration Error"
29
+ end
30
+ end
31
+
32
+ def authenticate(conf)
33
+ if Roart::check_keys(conf, Roart::Connections::RequiredToLogin)
34
+ @connection.authenticate(conf)
35
+ self
36
+ end
26
37
  end
27
-
38
+
28
39
  def rest_path
29
40
  self.server + '/REST/1.0/'
30
41
  end
31
-
42
+
32
43
  def get(uri)
33
- @agent.get(uri).body
44
+ @connection.get(uri)
34
45
  end
35
-
46
+
36
47
  def post(uri, payload)
37
- @agent.post(uri, payload).body
48
+ @connection.post(uri, payload)
38
49
  end
39
-
50
+
40
51
  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
+
53
+
52
54
  def add_methods!
53
55
  @conf.each do |key, value|
54
56
  (class << self; self; end).send :define_method, key do
@@ -56,7 +58,7 @@ module Roart
56
58
  end
57
59
  end
58
60
  end
59
-
61
+
60
62
  end
61
-
63
+
62
64
  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).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,30 @@
1
+ module Roart
2
+ module ConnectionAdapters
3
+ class Mechanize
4
+
5
+ def initialize(config)
6
+ @conf = config
7
+ end
8
+
9
+ def login(config)
10
+ @conf.merge!(config)
11
+ agent = WWW::Mechanize.new
12
+ page = agent.get(@conf[:server])
13
+ form = page.form('login')
14
+ form.user = @conf[:user]
15
+ form.pass = @conf[:pass]
16
+ page = agent.submit form
17
+ @agent = agent
18
+ end
19
+
20
+ def get(uri)
21
+ @agent.get(uri).body
22
+ end
23
+
24
+ def post(uri, payload)
25
+ @agent.post(uri, payload).body
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -18,4 +18,9 @@ class String
18
18
  def humanize
19
19
  self.gsub(/_id$/, "").gsub(/_/, " ").capitalize
20
20
  end
21
+
22
+ def blank?
23
+ self == ""
24
+ end
25
+
21
26
  end
data/lib/roart/roart.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  module Roart
2
2
 
3
3
  def self.check_keys!(hash, required)
4
+ puts hash
4
5
  unless required.inject(true) do |inc, attr|
5
6
  inc ? hash.keys.include?(attr.to_sym) : nil
6
7
  end
@@ -8,6 +9,14 @@ module Roart
8
9
  end
9
10
  end
10
11
 
12
+ def self.check_keys(hash, required)
13
+ unless required.inject(true) do |inc, attr|
14
+ inc ? hash.keys.include?(attr.to_sym) : nil
15
+ end
16
+ return false
17
+ end
18
+ return true
19
+ end
11
20
 
12
21
  module MethodFunctions
13
22
 
data/lib/roart/ticket.rb CHANGED
@@ -1,38 +1,40 @@
1
1
  module Roart
2
-
2
+
3
3
  module Tickets
4
-
4
+
5
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)
6
6
  RequiredAttributes = %w(queue subject)
7
-
7
+
8
8
  end
9
-
9
+
10
10
  class Ticket
11
-
11
+
12
12
  include Roart::MethodFunctions
13
13
  include Roart::Callbacks
14
-
14
+ require File.join(File.dirname(__FILE__), %w[ validations.rb ])
15
+ include Roart::Validations
16
+
15
17
  attr_reader :full, :history, :saved
16
-
18
+
17
19
  # Creates a new ticket. Attributes queue and subject are required. Expects a hash with the attributes of the ticket.
18
20
  #
19
21
  # ticket = MyTicket.new(:queue => "Some Queue", :subject => "The System is Down.")
20
22
  # ticket.id #-> This will be the ID of the ticket in the RT System.
21
23
  #
22
- def initialize(attributes)
23
- Roart::check_keys!(attributes, Roart::Tickets::RequiredAttributes)
24
- if attributes.is_a?(Hash)
24
+ def initialize(attributes=nil)
25
+ if attributes
25
26
  @attributes = Roart::Tickets::DefaultAttributes.to_hash.merge(attributes)
26
- @attributes.update(:id => 'ticket/new')
27
- @saved = false
28
27
  else
29
- raise ArgumentError, "Expects a hash."
28
+ @attributes = Roart::Tickets::DefaultAttributes.to_hash
30
29
  end
30
+ @attributes.update(:id => 'ticket/new')
31
+ @saved = false
31
32
  @history = false
33
+ @new_record = true
32
34
  add_methods!
33
35
  end
34
-
35
- # Loads all information for a ticket from RT and lets full to true.
36
+
37
+ # Loads all information for a ticket from RT and lets full to true.
36
38
  # This changes the ticket object and adds methods for all the fields on the ticket.
37
39
  # Custom fields will be prefixed with 'cf' so a custom field of 'phone'
38
40
  # would be cf_phone. custom fields hold their case from how they are defined in RT, so a custom field of PhoneNumber would be cf_PhoneNumber and a custom field of phone_number would be cf_phone_number
@@ -44,13 +46,13 @@ module Roart
44
46
  add_methods!
45
47
  end
46
48
  end
47
-
49
+
48
50
  #loads the ticket history from rt
49
51
  #
50
52
  def histories
51
53
  @histories ||= Roart::History.default(:ticket => self)
52
54
  end
53
-
55
+
54
56
  # if a ticket is new, calling save will create it in the ticketing system and assign the id that it gets to the id attribute. It returns true if the save was successful, and false if something went wrong
55
57
  #
56
58
  def save
@@ -88,16 +90,20 @@ module Roart
88
90
  raise "Ticket Comment Failed" unless resp.first.include?("200")
89
91
  !!resp[2].match(/^# Message recorded/)
90
92
  end
91
-
93
+
92
94
  # works just like save, but if the save fails, it raises an exception instead of silently returning false
93
95
  #
94
96
  def save!
95
97
  raise "Ticket Create Failed" unless self.save
96
98
  true
97
99
  end
98
-
100
+
101
+ def new_record?
102
+ return @new_record
103
+ end
104
+
99
105
  protected
100
-
106
+
101
107
  def create #:nodoc:
102
108
  self.before_create
103
109
  uri = "#{self.class.connection.server}/REST/1.0/ticket/new"
@@ -108,19 +114,20 @@ module Roart
108
114
  if tid = resp[2].match(/^# Ticket (\d+) created./)
109
115
  @attributes[:id] = tid[1].to_i
110
116
  self.after_create
117
+ @new_record = false
111
118
  true
112
119
  else
113
120
  false
114
- end
121
+ end
115
122
  end
116
-
123
+
117
124
  def create! #:nodoc:
118
125
  raise "Ticket Create Failed" unless self.create
119
126
  true
120
127
  end
121
-
128
+
122
129
  class << self #class methods
123
-
130
+
124
131
  # Searches for a ticket or group of tickets with an active record like interface.
125
132
  #
126
133
  # Find has 3 different ways to search for tickets
@@ -133,15 +140,15 @@ module Roart
133
140
  #
134
141
  # ====Parameters
135
142
  # * <tt>:queue</tt> or <tt>:queues</tt> - the name of a queue in the ticket system. This can be specified as a string, a symbol or an array of strings or symbols. The array will search for tickets included in either queue.
136
- # * <tt>:status</tt> - the status of the tickets to search for. This can be specified as a string, a symbol or an array of strings or symbols.
143
+ # * <tt>:status</tt> - the status of the tickets to search for. This can be specified as a string, a symbol or an array of strings or symbols.
137
144
  # * <tt>:subject</tt>, <tt>:content</tt>, <tt>content_type</tt>, <tt>file_name</tt> - takes a string and searches for that string in the respective field.
138
145
  # * <tt>:created</tt>, <tt>:started</tt>, <tt>:resolved</tt>, <tt>:told</tt>, <tt>:last_updated</tt>, <tt>:starts</tt>, <tt>:due</tt>, <tt>:updated</tt> - looks for dates for the respective fields. Can take a Range, Array, String, Time. Range will find all tickets between the two dates (after the first, before the last). Array works the same way, using #first and #last on the array. The elements should be either db-time formatted strings or Time objects. Time will be formatted as a db string. String will be passed straight to the search.
139
- # * <tt>:custom_fields</tt> - takes a hash of custom fields to search for. the key should be the name of the field exactly how it is in RT and the value will be what to search for.
146
+ # * <tt>:custom_fields</tt> - takes a hash of custom fields to search for. the key should be the name of the field exactly how it is in RT and the value will be what to search for.
140
147
  #
141
148
  # ==== Examples
142
149
  #
143
150
  # # find first
144
- # MyTicket.find(:first)
151
+ # MyTicket.find(:first)
145
152
  # MyTicket.find(:first, :queue => 'My Queue')
146
153
  # MyTicket.find(:first, :status => [:new, :open])
147
154
  # MyTicket.find(:first, :queue => 'My Queue', :status => :resolved)
@@ -163,24 +170,46 @@ module Roart
163
170
  else find_by_ids(args, options)
164
171
  end
165
172
  end
166
-
167
173
 
168
- # Gives or Sets the connection object for the RT Server.
169
- # Accepts 3 parameters :server, :user, and :pass. Call this
170
- # at the top of your subclass to create the connection,
174
+
175
+ # Accepts parameters for connecting to an RT server.
176
+ # Required:
177
+ # :server sets the URL for the rt server, :ie http://rt.server.com/
178
+ # Optional:
179
+ # :user sets the username to connect to RT
180
+ # :pass sets the password for the user to connect with
181
+ # :adapter is the connection adapter to connect with. Defaults to Mechanize
182
+ #
171
183
  # class Ticket < Roart::Ticket
172
184
  # connection :server => 'server', :user => 'user', :pass => 'pass'
173
185
  # end
174
186
  #
175
187
  def connection(options=nil)
176
188
  if options
177
- @connection = Roart::Connection.new(options)
189
+ @connection = Roart::Connection.new({:adapter => "mechanize"}.merge(options))
178
190
  else
179
191
  defined?(@connection) ? @connection : nil
180
192
  end
181
193
  end
182
-
183
- # Adds a default queue to search each time. This is overridden by
194
+
195
+ # Sets the username and password used to connect to the RT server
196
+ # Required:
197
+ # :user sets the username to connect to RT
198
+ # :pass sets the password for the user to connect with
199
+ # This can be used to change a connection once the Ticket class has
200
+ # been initialized. Not required if you sepecify :user and :pass in
201
+ # the connection method
202
+ #
203
+ # class Ticket < Roart::Ticket
204
+ # connection :server => 'server'
205
+ # authenticate :user => 'user', :pass => 'pass'
206
+ # end
207
+ #
208
+ def authenticate(options)
209
+ @connection.authenticate(options)
210
+ end
211
+
212
+ # Adds a default queue to search each time. This is overridden by
184
213
  # specifically including a :queue option in your find method. This can
185
214
  # be an array of queue names or a string with a single queue name.
186
215
  #
@@ -191,16 +220,16 @@ module Roart
191
220
  defined?(@default_queue) ? @default_queue : nil
192
221
  end
193
222
  end
194
-
223
+
195
224
  # creates a new ticket object and immediately saves it to the database.
196
225
  def create(options)
197
226
  ticket = self.new(options)
198
227
  ticket.save
199
228
  ticket
200
229
  end
201
-
230
+
202
231
  protected
203
-
232
+
204
233
  def instantiate(attrs) #:nodoc:
205
234
  object = nil
206
235
  if attrs.is_a?(Array)
@@ -218,43 +247,45 @@ module Roart
218
247
  object.send("add_methods!")
219
248
  end
220
249
  object.instance_variable_set("@history", false)
250
+ object.instance_variable_set("@new_record", false)
221
251
  object
222
252
  end
223
-
253
+
224
254
  def find_initial(options={}) #:nodoc:
225
255
  options.update(:limit => 1)
226
256
  find_all(options).first
227
257
  end
228
-
258
+
229
259
  def find_all(options) #:nodoc:
230
260
  uri = construct_search_uri(options)
231
261
  tickets = get_tickets_from_search_uri(uri)
232
262
  end
233
-
263
+
234
264
  def find_by_ids(args, options) #:nodoc:
265
+ raise "First argument must be :all or :first, or an ID with no hash options" unless args.first.is_a?(Fixnum) || args.first.is_a?(String)
235
266
  get_ticket_by_id(args.first)
236
267
  end
237
-
268
+
238
269
  def page_array(uri) #:nodoc:
239
270
  page = self.connection.get(uri)
240
271
  raise TicketSystemError, "Can't get ticket." unless page
241
- page = page.split("\n")
272
+ page = page.split("\n")
242
273
  status = page.delete_at(0)
243
- if status.include?("200")
244
- page.delete_if{|x| !x.include?(":")}
274
+ if status.include?("200")
275
+ page.delete_if{|x| !x.include?(":")}
245
276
  page
246
277
  else
247
278
  raise TicketSystemInterfaceError, "Error Getting Ticket: #{status}"
248
279
  end
249
280
  end
250
-
281
+
251
282
  def get_tickets_from_search_uri(uri) #:nodoc:
252
283
  page = page_array(uri)
253
284
  page.extend(Roart::TicketPage)
254
285
  page = page.to_search_array
255
286
  self.instantiate(page)
256
287
  end
257
-
288
+
258
289
  def get_ticket_from_uri(uri) #:nodoc:
259
290
  page = page_array(uri)
260
291
  page.extend(Roart::TicketPage)
@@ -263,20 +294,20 @@ module Roart
263
294
  ticket.instance_variable_set("@full", true)
264
295
  ticket
265
296
  end
266
-
297
+
267
298
  def get_ticket_by_id(id) #:nodoc:
268
299
  uri = "#{self.connection.server}/REST/1.0/ticket/"
269
300
  uri << id.to_s
270
301
  get_ticket_from_uri(uri)
271
302
  end
272
-
303
+
273
304
  def construct_search_uri(options={}) #:nodoc:
274
305
  uri = "#{self.connection.server}/REST/1.0/search/ticket?"
275
306
  uri << 'orderby=-Created&' if options.delete(:order)
276
307
  unless options.empty? && default_queue.nil?
277
308
  uri << 'query= '
278
309
  query = Array.new
279
-
310
+
280
311
  if options[:queues] || options[:queue]
281
312
  add_queue!(query, options[:queues] || options[:queue])
282
313
  else
@@ -286,14 +317,14 @@ module Roart
286
317
  add_searches!(query, options)
287
318
  add_status!(query, options[:status])
288
319
  add_custom_fields!(query, options[:custom_fields])
289
-
320
+
290
321
  query << options[:conditions].to_s.chomp if options[:conditions]
291
-
322
+
292
323
  uri << query.join(" AND ")
293
324
  end
294
325
  uri
295
326
  end
296
-
327
+
297
328
  def add_queue!(uri, queue) #:nodoc:
298
329
  return false unless queue
299
330
  if queue.is_a?(Array)
@@ -306,7 +337,7 @@ module Roart
306
337
  uri << "Queue = '#{queue.to_s}'"
307
338
  end
308
339
  end
309
-
340
+
310
341
  def add_custom_fields!(uri, options) #:nodoc:
311
342
  return false unless options
312
343
  options.each do |field, value|
@@ -321,7 +352,7 @@ module Roart
321
352
  end
322
353
  end
323
354
  end
324
-
355
+
325
356
  def add_status!(uri, options) #:nodoc:
326
357
  return false unless options
327
358
  parts = Array.new
@@ -336,9 +367,9 @@ module Roart
336
367
  end
337
368
  uri << parts
338
369
  end
339
-
370
+
340
371
  def add_searches!(uri, options) #:nodoc:
341
- search_fields = %w( subject content content_type file_name)
372
+ search_fields = %w( subject content content_type file_name owner requestors cc admin_cc)
342
373
  options.each do |key, value|
343
374
  if search_fields.include?(key.to_s)
344
375
  key = key.to_s.camelize
@@ -354,7 +385,7 @@ module Roart
354
385
  end
355
386
  end
356
387
  end
357
-
388
+
358
389
  def add_dates!(uri, options) #:nodoc:
359
390
  date_field = %w( created started resolved told last_updated starts due updated )
360
391
  options.each do |key, value|
@@ -373,9 +404,9 @@ module Roart
373
404
  end
374
405
  end
375
406
  end
376
-
407
+
377
408
  end
378
-
409
+
379
410
  end
380
-
411
+
381
412
  end
@@ -0,0 +1,221 @@
1
+ module Roart
2
+
3
+ class Errors
4
+ include Enumerable
5
+
6
+ def initialize(obj)
7
+ @base, @errors = obj, {}
8
+ end
9
+
10
+ def add_to_base(msg)
11
+ add(:base, msg)
12
+ end
13
+
14
+ def add(field, message)
15
+ @errors[field.to_sym] ||= []
16
+ @errors[field.to_sym] << message
17
+ end
18
+
19
+ def on_base
20
+ on(:base)
21
+ end
22
+
23
+ def on(field)
24
+ errors = @errors[field.to_sym]
25
+ return nil if errors.nil?
26
+ errors
27
+ end
28
+
29
+ alias :[] :on
30
+
31
+ def each
32
+ @errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } }
33
+ end
34
+
35
+ # Returns true if no errors have been added.
36
+ def empty?
37
+ @errors.empty?
38
+ end
39
+
40
+ # Removes all errors that have been added.
41
+ def clear
42
+ @errors = {}
43
+ end
44
+
45
+ # Returns the total number of errors added. Two errors added to the same attribute will be counted as such.
46
+ def size
47
+ @errors.values.inject(0) { |error_count, attribute| error_count + attribute.size }
48
+ end
49
+
50
+ alias_method :count, :size
51
+ alias_method :length, :size
52
+
53
+ end
54
+
55
+ module Validations
56
+
57
+ def self.included(model)
58
+ model.extend ClassMethods
59
+
60
+ end
61
+
62
+ class Validators
63
+
64
+ def initialize
65
+ @validators = []
66
+ end
67
+
68
+ def add(validator)
69
+ @validators << validator
70
+ end
71
+
72
+ def validate(obj)
73
+ @validators.each{|validator| validator.call(obj)}
74
+ end
75
+
76
+ end
77
+
78
+ module ClassMethods
79
+
80
+ ALL_RANGE_OPTIONS = [ :is, :within, :in, :minimum, :min, :maximum, :max ].freeze
81
+ ALL_NUMERICALITY_CHECKS = { :greater_than => '>', :greater_than_or_equal_to => '>=',
82
+ :equal_to => '==', :less_than => '<', :less_than_or_equal_to => '<=',
83
+ :odd => 'odd?', :even => 'even?' }.freeze
84
+
85
+ def validator
86
+ @validator ||= Validators.new
87
+ end
88
+
89
+ def validates_presence_of(*args)
90
+
91
+ end
92
+
93
+ def validates_format_of(*args)
94
+ options = args.last.is_a?(Hash) ? args.pop : {}
95
+ args.each do |field|
96
+ validator_proc = lambda do |obj|
97
+ unless obj.send(field.to_sym).match(options[:format])
98
+ obj.errors.add(field.to_sym, "Wrong Format")
99
+ end
100
+ end
101
+ self.validator.add(validator_proc)
102
+ end
103
+ end
104
+
105
+ def validates_length_of(*args)
106
+ options = args.last.is_a?(Hash) ? args.pop : {}
107
+ # Ensure that one and only one range option is specified.
108
+ range_options = ALL_RANGE_OPTIONS & options.keys
109
+ case range_options.size
110
+ when 0
111
+ raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.'
112
+ when 1
113
+ # Valid number of options; do nothing.
114
+ else
115
+ raise ArgumentError, 'Too many range options specified. Choose only one.'
116
+ end
117
+
118
+ option = range_options.first
119
+ option_value = options[range_options.first]
120
+ key = {:is => :wrong_length, :minimum => :too_short, :maximum => :too_long}[option]
121
+ custom_message = options[:message] || options[key]
122
+
123
+ args.each do |field|
124
+ case option
125
+ when :within, :in
126
+ raise ArgumentError, ":#{option} must be a Range" unless option_value.is_a?(Range)
127
+ validator_proc = lambda do |obj|
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.")
130
+ end
131
+ if obj.send(field.to_sym).length > option_value.end
132
+ obj.errors.add(field.to_sym, "Must be less than #{option_value.end} characters")
133
+ end
134
+ end
135
+ self.validator.add(validator_proc)
136
+ when :min, :minium
137
+ raise ArgumentError, ":#{option} must be an Integer" unless option_value.is_a?(Integer)
138
+ validator_proc = lambda do |obj|
139
+ if obj.send(field.to_sym).length < option_value
140
+ obj.errors.add(field.to_sym, "Must be more than #{option_value} characters.")
141
+ end
142
+ end
143
+ self.validator.add(validator_proc)
144
+ when :max, :maxium
145
+ raise ArgumentError, ":#{option} must be an Integer" unless option_value.is_a?(Integer)
146
+ validator_proc = lambda do |obj|
147
+ if obj.send(field.to_sym).length > option_value
148
+ obj.errors.add(field.to_sym, "Must be less than #{option_value} characters.")
149
+ end
150
+ end
151
+ self.validator.add(validator_proc)
152
+ when :is
153
+ raise ArgumentError, ":#{option} must be an Integer" unless option_value.is_a?(Integer)
154
+ validator_proc = lambda do |obj|
155
+ puts "#{field} is #{option_value}"
156
+ unless obj.send(field.to_sym).length == option_value
157
+ obj.errors.add(field.to_sym, "Must be #{option_value} characters.")
158
+ end
159
+ end
160
+ self.validator.add(validator_proc)
161
+ end
162
+ end
163
+ end
164
+
165
+ alias_method :validates_size_of, :validates_length_of
166
+
167
+ def validates_numericality_of(*args)
168
+ options = args.last.is_a?(Hash) ? args.pop : {}
169
+ numericality_options = ALL_NUMERICALITY_CHECKS & options.keys
170
+
171
+ case numericality_options
172
+ when 0
173
+ raise ArgumentError, "Options Unspecified. Specify an option to use."
174
+ when 1
175
+ #continue
176
+ else
177
+ raise ArgumentError, "Too many options specified"
178
+ end
179
+
180
+ option = numericality_options.first
181
+ option_value = options[numericality_options.first]
182
+ key = {:is => :wrong_length, :minimum => :too_short, :maximum => :too_long}[option]
183
+ custom_message = options[:message] || options[key]
184
+
185
+ args.each do |field|
186
+ numericality_options.each do |option|
187
+ case option
188
+ when :odd, :even
189
+ unless raw_value.to_i.method(ALL_NUMERICALITY_CHECKS[option])[]
190
+ record.errors.add(attr_name, option, :value => raw_value, :default => configuration[:message])
191
+ end
192
+ else
193
+ 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
+ end
195
+ end
196
+ end
197
+
198
+ end
199
+
200
+ end
201
+
202
+ def validator
203
+ self.class.validator
204
+ end
205
+
206
+ def valid?
207
+ validator.validate self
208
+ self.errors.size == 0
209
+ end
210
+
211
+ def invalid?
212
+ !valid?
213
+ end
214
+
215
+ def errors
216
+ @errors ||= Errors.new(self)
217
+ end
218
+
219
+ end
220
+
221
+ end
data/lib/roart.rb CHANGED
@@ -2,7 +2,7 @@ require 'rubygems'
2
2
  module Roart
3
3
 
4
4
  # :stopdoc:
5
- VERSION = '0.1.4'
5
+ VERSION = '0.1.5'
6
6
  LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
7
7
  PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
8
8
  # :startdoc:
data/roart.gemspec CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{roart}
5
- s.version = "0.1.4"
5
+ s.version = "0.1.5"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["PJ Davis"]
9
- s.date = %q{2009-08-19}
9
+ s.date = %q{2010-01-12}
10
10
  s.description = %q{Interface for working with Request Tracker (RT) tickets inspired by ActiveRecord.}
11
11
  s.email = %q{pj.davis@gmail.com}
12
12
  s.extra_rdoc_files = ["History.txt", "README.rdoc", "spec/test_data/full_history.txt", "spec/test_data/single_history.txt"]
@@ -34,4 +34,4 @@ Gem::Specification.new do |s|
34
34
  s.add_dependency(%q<mechanize>, [">= 0.9.0"])
35
35
  s.add_dependency(%q<bones>, [">= 2.5.1"])
36
36
  end
37
- end
37
+ end
@@ -0,0 +1,11 @@
1
+
2
+ require File.join(File.dirname(__FILE__), %w[ .. spec_helper])
3
+
4
+ describe "ConnectionAdapter" do
5
+
6
+ it 'should give us back a connection' do
7
+ Roart::ConnectionAdapters::Mechanize.should_receive(:new).with(:adapter => 'mechanize').and_return(mock('mechanize'))
8
+ Roart::ConnectionAdapter.new(:adapter => 'mechanize')
9
+ end
10
+
11
+ end
@@ -2,6 +2,49 @@
2
2
  require File.join(File.dirname(__FILE__), %w[ .. spec_helper])
3
3
 
4
4
  describe "Connection" do
5
+
6
+ describe 'get and post' do
7
+
8
+ before do
9
+ @agent = mock("agent", :null_object => true)
10
+ @options = {:server => 'server', :user => 'user', :pass => 'pass', :adapter => 'whatev'}
11
+ Roart::ConnectionAdapter.should_receive(:new).and_return(@agent)
12
+ end
13
+
14
+ it 'should respond to get' do
15
+ @agent.should_receive(:get).with('some_uri').and_return('body')
16
+ connection = Roart::Connection.new(@options)
17
+ connection.get("some_uri").should == 'body'
18
+ end
19
+
20
+ it 'should respond to post' do
21
+ @agent.should_receive(:post).with('some_uri', 'a payload').and_return('body')
22
+ connection = Roart::Connection.new(@options)
23
+ connection.post("some_uri", "a payload").should == 'body'
24
+ end
25
+
26
+ end
27
+
28
+ it 'should raise an exception if it doesnt have all the options' do
29
+ lambda{Roart::Connection.new(:user => 'bad')}.should raise_error
30
+ end
31
+
32
+ it 'should give us the rest path' do
33
+ @agent = mock("agent", :null_object => true)
34
+ @options = {:server => 'server', :user => 'user', :pass => 'pass', :adapter => 'whatev'}
35
+ Roart::ConnectionAdapter.should_receive(:new).and_return(@agent)
36
+ connection = Roart::Connection.new(@options)
37
+ connection.rest_path.should == 'server/REST/1.0/'
38
+ end
5
39
 
40
+ it 'should give us back the whole thing' do
41
+ mock_mech = mock('mech')
42
+ @options = {:server => 'server', :user => 'user', :pass => 'pass', :adapter => 'mechanize'}
43
+ Roart::ConnectionAdapters::Mechanize.should_receive(:new).with(@options).and_return(mock_mech)
44
+ mock_mech.should_receive(:login).with(@options)
45
+ mock_mech.should_receive(:get).with('uri').and_return('body')
46
+ connection = Roart::Connection.new(@options)
47
+ connection.get('uri').should == 'body'
48
+ end
6
49
 
7
50
  end
@@ -3,8 +3,8 @@ require File.join(File.dirname(__FILE__), %w[ .. spec_helper])
3
3
  describe "Ticket" do
4
4
 
5
5
  it "should have a connection" do
6
- Roart::Connection.should_receive(:new).with('some options').and_return(true)
7
- Roart::Ticket.connection('some options').should == true
6
+ Roart::Connection.should_receive(:new).with(:some => 'some options', :adapter => "www").and_return(true)
7
+ Roart::Ticket.connection(:some => 'some options', :adapter => "www").should == true
8
8
  end
9
9
 
10
10
  it "should find the first ticket" do
@@ -341,7 +341,9 @@ describe "Ticket" do
341
341
 
342
342
  it 'should be able to save a new ticket' do
343
343
  ticket = Roart::Ticket.new(@payload)
344
+ ticket.new_record?.should be_true
344
345
  ticket.save
346
+ ticket.new_record?.should be_false
345
347
  end
346
348
 
347
349
  it 'should be able to create a ticket' do
@@ -360,8 +362,9 @@ describe "Ticket" do
360
362
  describe 'updating tickets' do
361
363
 
362
364
  before do
363
- @post_data = @payload = {:subject => 'A New Ticket', :queue => 'My Queue'}
364
- @post_data[:subject] = 'An Old Ticket'
365
+ @payload = {:queue => 'My Queue', :subject => 'An Old Ticket'}
366
+ @post_data = {:queue => 'My Queue', :subject => 'An Old Ticket'}
367
+ @payload[:subject] = 'A New Ticket'
365
368
  @post_data = to_content_format(@post_data)
366
369
  @mock_connection = mock('connection')
367
370
  @mock_connection.should_receive(:server).and_return('uri')
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roart
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - PJ Davis
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-10-12 00:00:00 -04:00
12
+ date: 2010-01-19 00:00:00 -06:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -50,6 +50,8 @@ files:
50
50
  - lib/roart.rb
51
51
  - lib/roart/callbacks.rb
52
52
  - lib/roart/connection.rb
53
+ - lib/roart/connection_adapter.rb
54
+ - lib/roart/connection_adapters/mechanize_adapter.rb
53
55
  - lib/roart/core/array.rb
54
56
  - lib/roart/core/hash.rb
55
57
  - lib/roart/core/string.rb
@@ -58,8 +60,10 @@ files:
58
60
  - lib/roart/roart.rb
59
61
  - lib/roart/ticket.rb
60
62
  - lib/roart/ticket_page.rb
63
+ - lib/roart/validations.rb
61
64
  - roart.gemspec
62
65
  - spec/roart/callbacks_spec.rb
66
+ - spec/roart/connection_adapter_spec.rb
63
67
  - spec/roart/connection_spec.rb
64
68
  - spec/roart/core/array_spec.rb
65
69
  - spec/roart/core/hash_spec.rb