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.
@@ -0,0 +1,78 @@
1
+ module Roart
2
+
3
+ module TicketPage
4
+
5
+ IntKeys = %w[id]
6
+
7
+ def to_hash
8
+ hash = HashWithIndifferentAccess.new
9
+ self.delete_if{|x| !x.include?(":")}
10
+ return false if self.size == 0
11
+ self.each do |ln|
12
+ ln = ln.split(":")
13
+ key = nil
14
+ if ln[0] && ln[0].match(/^CF-.+/)
15
+ key = ln.delete_at(0)
16
+ key = "cf_" + key[3..key.length].gsub(/ /, "_")
17
+ else
18
+ key = ln.delete_at(0).strip.underscore
19
+ end
20
+ value = ln.join(":").strip
21
+ hash[key] = value if key
22
+ end
23
+ hash["id"] = hash["id"].split("/").last.to_i
24
+ hash
25
+ end
26
+
27
+ def to_search_list_array
28
+ array = Array.new
29
+ self.each do |ticket|
30
+ ticket.extend(Roart::TicketPage)
31
+ ticket_hash = ticket.to_hash
32
+ array << ticket_hash if ticket_hash
33
+ end
34
+ array
35
+ end
36
+
37
+ def to_search_array
38
+ array = Array.new
39
+ self.delete_if{|x| !x.include?(":")}
40
+ raise TicketNotFoundError, "No tickets matching search criteria found." if self.size == 0
41
+ self.each do |ln|
42
+ hash = Hash.new
43
+ ln = ln.split(":")
44
+ id = ln.delete_at(0).strip.underscore
45
+ sub = ln.join(":").strip
46
+ hash[:id] = id.to_i
47
+ hash[:subject] = sub
48
+ hash[:full] = false
49
+ hash[:history] = false
50
+ array << hash
51
+ end
52
+ array
53
+ end
54
+
55
+ # TODO: Don't throw away attachments (/^ {13})
56
+ def to_history_hash
57
+ hash = HashWithIndifferentAccess.new
58
+ self.delete_if{|x| !x.include?(":") && !x.match(/^ {9}/) && !x.match(/^ {13}/)}
59
+ self.each do |ln|
60
+ if ln.match(/^ {9}/) && !ln.match(/^ {13}/)
61
+ hash[:content] << "\n" + ln.strip if hash[:content]
62
+ elsif ln.match(/^ {13}/)
63
+ hash[:attachments] << "\n" + ln.strip if hash[:attachments]
64
+ else
65
+ ln = ln.split(":")
66
+ unless ln.size == 1 || ln.first == 'Ticket' # we don't want to override the ticket method.
67
+ key = ln.delete_at(0).strip.underscore
68
+ value = ln.join(":").strip
69
+ hash[key] = IntKeys.include?(key) ? value.to_i : value
70
+ end
71
+ end
72
+ end
73
+ hash
74
+ end
75
+
76
+ end
77
+
78
+ end
@@ -0,0 +1,230 @@
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
+ obj.errors.clear
74
+ @validators.each{|validator| validator.call(obj)}
75
+ end
76
+
77
+ end
78
+
79
+ module ClassMethods
80
+
81
+ ALL_RANGE_OPTIONS = [ :is, :within, :in, :minimum, :min, :maximum, :max ].freeze
82
+ ALL_NUMERICALITY_CHECKS = { :greater_than => '>', :greater_than_or_equal_to => '>=',
83
+ :equal_to => '==', :less_than => '<', :less_than_or_equal_to => '<=',
84
+ :odd => 'odd?', :even => 'even?', :only_integer => 'is_a?' }.freeze
85
+
86
+ def validator
87
+ @validator ||= Validators.new
88
+ end
89
+
90
+ def validates_presence_of(*args)
91
+ args.each do |field|
92
+ validator_proc = lambda do |obj|
93
+ if obj.send(field.to_sym).nil? || obj.send(field.to_sym).blank?
94
+ obj.errors.add(field.to_sym, "Can't Be Blank")
95
+ end
96
+ end
97
+ self.validator.add(validator_proc)
98
+ end
99
+ end
100
+
101
+ def validates_format_of(*args)
102
+ options = args.last.is_a?(Hash) ? args.pop : {}
103
+ args.each do |field|
104
+ validator_proc = lambda do |obj|
105
+ unless obj.send(field.to_sym).match(options[:format])
106
+ obj.errors.add(field.to_sym, "Wrong Format")
107
+ end
108
+ end
109
+ self.validator.add(validator_proc)
110
+ end
111
+ end
112
+
113
+ def validates_length_of(*args)
114
+ options = args.last.is_a?(Hash) ? args.pop : {}
115
+ # Ensure that one and only one range option is specified.
116
+ range_options = ALL_RANGE_OPTIONS & options.keys
117
+ case range_options.size
118
+ when 0
119
+ raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.'
120
+ when 1
121
+ # Valid number of options; do nothing.
122
+ else
123
+ raise ArgumentError, 'Too many range options specified. Choose only one.'
124
+ end
125
+
126
+ option = range_options.first
127
+ option_value = options[range_options.first]
128
+ key = {:is => :wrong_length, :minimum => :too_short, :maximum => :too_long}[option]
129
+ custom_message = options[:message] || options[key]
130
+
131
+ args.each do |field|
132
+ case option
133
+ when :within, :in
134
+ raise ArgumentError, ":#{option} must be a Range" unless option_value.is_a?(Range)
135
+ validator_proc = lambda do |obj|
136
+ if obj.send(field.to_sym).length < option_value.begin
137
+ obj.errors.add(field.to_sym, "Must be more than #{option_value.begin} characters.")
138
+ end
139
+ if obj.send(field.to_sym).length > option_value.end
140
+ obj.errors.add(field.to_sym, "Must be less than #{option_value.end} characters")
141
+ end
142
+ end
143
+ self.validator.add(validator_proc)
144
+ when :min, :minium
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 more than #{option_value} characters.")
149
+ end
150
+ end
151
+ self.validator.add(validator_proc)
152
+ when :max, :maxium
153
+ raise ArgumentError, ":#{option} must be an Integer" unless option_value.is_a?(Integer)
154
+ validator_proc = lambda do |obj|
155
+ if obj.send(field.to_sym).length > option_value
156
+ obj.errors.add(field.to_sym, "Must be less than #{option_value} characters.")
157
+ end
158
+ end
159
+ self.validator.add(validator_proc)
160
+ when :is
161
+ raise ArgumentError, ":#{option} must be an Integer" unless option_value.is_a?(Integer)
162
+ validator_proc = lambda do |obj|
163
+ unless obj.send(field.to_sym).length == option_value
164
+ obj.errors.add(field.to_sym, "Must be #{option_value} characters.")
165
+ end
166
+ end
167
+ self.validator.add(validator_proc)
168
+ end
169
+ end
170
+ end
171
+
172
+ alias_method :validates_size_of, :validates_length_of
173
+
174
+ def validates_numericality_of(*args)
175
+ options = args.last.is_a?(Hash) ? args.pop : {}
176
+ numericality_options = ALL_NUMERICALITY_CHECKS.keys & options.keys
177
+ args.each do |field|
178
+ numericality_options.each do |option|
179
+ validator_proc = case option
180
+ when :only_integer
181
+ lambda do |obj|
182
+ unless obj.send(field.to_sym).send(ALL_NUMERICALITY_CHECKS[option], Integer )
183
+ obj.errors.add(field.to_sym, "Must be #{ALL_NUMERICALITY_CHECKS[option]}.")
184
+ end
185
+ end
186
+ when :even, :odd
187
+ lambda do |obj|
188
+ if obj.send(field.to_sym).send("is_a?".to_sym, Integer) == true
189
+ unless obj.send(field.to_sym).send(ALL_NUMERICALITY_CHECKS[option] )
190
+ obj.errors.add(field.to_sym, "Must be #{ALL_NUMERICALITY_CHECKS[option]}.")
191
+ end
192
+ else
193
+ obj.errors.add(field.to_sym, "Must be an #{option} Integer.")
194
+ end
195
+ end
196
+ else
197
+ raise ArgumentError, ":#{option} must be a number" unless options[option].is_a?(Numeric)
198
+ lambda do |obj|
199
+ unless obj.send(field.to_sym).send(ALL_NUMERICALITY_CHECKS[option], options[option] )
200
+ obj.errors.add(field.to_sym, "Must be #{ALL_NUMERICALITY_CHECKS[option]}.")
201
+ end
202
+ end
203
+ end
204
+ self.validator.add(validator_proc)
205
+ end
206
+ end
207
+ end
208
+
209
+ end
210
+
211
+ def validator
212
+ self.class.validator
213
+ end
214
+
215
+ def valid?
216
+ validator.validate self
217
+ self.errors.size == 0
218
+ end
219
+
220
+ def invalid?
221
+ !valid?
222
+ end
223
+
224
+ def errors
225
+ @errors ||= Errors.new(self)
226
+ end
227
+
228
+ end
229
+
230
+ end
data/roart.gemspec ADDED
@@ -0,0 +1,39 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{ludo-roart}
5
+ s.version = "0.1.11"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["PJ Davis"]
9
+ s.date = %q{2010-09-15}
10
+ s.description = %q{Interface for working with Request Tracker (RT) tickets inspired by ActiveRecord.}
11
+ s.email = %q{pj.davis@gmail.com}
12
+ s.extra_rdoc_files = ["History.txt", "README.rdoc", "spec/test_data/full_history.txt", "spec/test_data/search_ticket.txt", "spec/test_data/single_history.txt", "spec/test_data/ticket.txt"]
13
+ s.files = ["History.txt", "README.rdoc", "Rakefile", "lib/roart.rb", "lib/roart/callbacks.rb", "lib/roart/connection.rb", "lib/roart/connection_adapter.rb", "lib/roart/connection_adapters/mechanize_adapter.rb", "lib/roart/core/hash.rb", "lib/roart/core/content_formatter.rb", "lib/roart/errors.rb", "lib/roart/history.rb", "lib/roart/roart.rb", "lib/roart/ticket.rb", "lib/roart/ticket_page.rb", "lib/roart/validations.rb", "roart.gemspec", "spec/roart/callbacks_spec.rb", "spec/roart/connection_adapter_spec.rb", "spec/roart/connection_spec.rb", "spec/roart/core/hash_spec.rb", "spec/roart/history_spec.rb", "spec/roart/roart_spec.rb", "spec/roart/ticket_page_spec.rb", "spec/roart/ticket_spec.rb", "spec/roart/validation_spec.rb", "spec/roart_spec.rb", "spec/spec_helper.rb", "spec/test_data/full_history.txt", "spec/test_data/search_ticket.txt", "spec/test_data/single_history.txt", "spec/test_data/ticket.txt"]
14
+ s.homepage = %q{http://github.com/pjdavis/roart}
15
+ s.rdoc_options = ["--main", "README.rdoc"]
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = %q{roart}
18
+ s.rubygems_version = %q{1.3.7}
19
+ s.summary = %q{Interface for working with Request Tracker (RT) tickets inspired by ActiveRecord}
20
+
21
+ if s.respond_to? :specification_version then
22
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
23
+ s.specification_version = 3
24
+
25
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
26
+ s.add_runtime_dependency(%q<mechanize>, [">= 1.0.0"])
27
+ s.add_runtime_dependency(%q<activesupport>, [">= 2.0.0"])
28
+ s.add_development_dependency(%q<bones>, [">= 2.5.1"])
29
+ else
30
+ s.add_dependency(%q<mechanize>, [">= 1.0.0"])
31
+ s.add_dependency(%q<activesupport>, [">= 2.0.0"])
32
+ s.add_dependency(%q<bones>, [">= 2.5.1"])
33
+ end
34
+ else
35
+ s.add_dependency(%q<mechanize>, [">= 1.0.0"])
36
+ s.add_dependency(%q<activesupport>, [">= 2.0.0"])
37
+ s.add_dependency(%q<bones>, [">= 2.5.1"])
38
+ end
39
+ end
@@ -0,0 +1,49 @@
1
+ require File.join(File.dirname(__FILE__), %w[ .. spec_helper])
2
+
3
+ describe 'ticket callbacks' do
4
+
5
+ describe 'create callbacks' do
6
+
7
+ before do
8
+ post_data = @payload = {:queue => 'My Queue', :subject => 'A New Ticket'}
9
+ post_data.update(:id => 'ticket/new')
10
+ post_data = to_content_format(post_data)
11
+ mock_connection = mock('connection')
12
+ mock_connection.should_receive(:post).with('uri/REST/1.0/ticket/new', {:content => post_data}).and_return("RT/3.6.6 200 Ok\n\n# Ticket 267783 created.")
13
+ mock_connection.should_receive(:server).and_return('uri')
14
+ Roart::Ticket.should_receive(:connection).twice.and_return(mock_connection)
15
+ end
16
+
17
+ it 'should call before_create callback' do
18
+
19
+ ticket = Roart::Ticket.new(@payload)
20
+ ticket.should_receive(:before_create)
21
+ ticket.should_receive(:after_create)
22
+ ticket.save
23
+ end
24
+
25
+ end
26
+
27
+ describe 'update callbacks' do
28
+
29
+ before do
30
+ @post_data = @payload = {:subject => 'A New Ticket', :queue => 'My Queue'}.with_indifferent_access
31
+ @post_data[:subject] = 'An Old Ticket'
32
+ @post_data = to_content_format(@post_data)
33
+ @mock_connection = mock('connection')
34
+ @mock_connection.should_receive(:server).and_return('uri')
35
+ Roart::Ticket.should_receive(:connection).twice.and_return(@mock_connection)
36
+ end
37
+
38
+ it 'should call before_update callbacks' do
39
+ @mock_connection.should_receive(:post).with('uri/REST/1.0/ticket/1/edit', {:content => @post_data}).and_return("RT/3.6.6 200 Ok\n\n# Ticket 267783 updated.")
40
+ ticket = Roart::Ticket.send(:instantiate, @payload.update(:id => 1))
41
+ ticket.instance_variable_set("@saved", false)
42
+ ticket.should_receive(:before_update)
43
+ ticket.should_receive(:after_update)
44
+ ticket.save
45
+ end
46
+
47
+ end
48
+
49
+ 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::MechanizeAdapter.should_receive(:new).with(:adapter => 'mechanize').and_return(mock('mechanize'))
8
+ Roart::ConnectionAdapter.new(:adapter => 'mechanize')
9
+ end
10
+
11
+ end
@@ -0,0 +1,54 @@
1
+
2
+ require File.join(File.dirname(__FILE__), %w[ .. spec_helper])
3
+
4
+ describe "Connection" do
5
+
6
+ before do
7
+ @options = {:server => 'server', :user => 'user', :pass => 'pass', :adapter => 'whatev'}
8
+ end
9
+
10
+ it "should lazy load connection adapter" do
11
+ @options[:adapter] = 'mechanize'
12
+ Roart::ConnectionAdapter.should_not_receive(:new)
13
+ connection = Roart::Connection.new(@options)
14
+ end
15
+
16
+ it 'should raise an exception if it doesnt have all the options' do
17
+ lambda{Roart::Connection.new(:user => 'bad')}.should raise_error
18
+ end
19
+
20
+ it 'should give us the rest path' do
21
+ connection = Roart::Connection.new(@options)
22
+ connection.rest_path.should == 'server/REST/1.0/'
23
+ end
24
+
25
+ it 'should give us back the whole thing' do
26
+ @options[:adapter] = 'mechanize'
27
+ mock_mech = mock('mech')
28
+ Roart::ConnectionAdapters::MechanizeAdapter.should_receive(:new).with(@options).and_return(mock_mech)
29
+ mock_mech.should_receive(:login).with(@options)
30
+ mock_mech.should_receive(:get).with('uri').and_return('body')
31
+ connection = Roart::Connection.new(@options)
32
+ connection.get('uri').should == 'body'
33
+ end
34
+
35
+ describe 'get and post' do
36
+ before do
37
+ @agent = mock("agent").as_null_object
38
+ Roart::ConnectionAdapter.should_receive(:new).and_return(@agent)
39
+ end
40
+
41
+ it 'should respond to get' do
42
+ @agent.should_receive(:get).with('some_uri').and_return('body')
43
+ connection = Roart::Connection.new(@options)
44
+ connection.get("some_uri").should == 'body'
45
+ end
46
+
47
+ it 'should respond to post' do
48
+ @agent.should_receive(:post).with('some_uri', 'a payload').and_return('body')
49
+ connection = Roart::Connection.new(@options)
50
+ connection.post("some_uri", "a payload").should == 'body'
51
+ end
52
+ end
53
+
54
+ end