ludo-roart 0.1.11

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