adaptation 0.1.10 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -65,3 +65,10 @@
65
65
  - bug: test environment not set properly since 0.1.7
66
66
  * 0.1.10
67
67
  - generated mock publish.rb bug corrected
68
+ -------------------
69
+ * 1.0.0
70
+ - Migrated from ROXML to XmlSimple. Messages don't need to be defined any more, so
71
+ has_many, has_one... and partials are deprecated.
72
+ * 1.0.1
73
+ - Fixed problems with activerecord > 2.2.2
74
+ - Requires activerecord >= 2.3.3
data/README CHANGED
@@ -40,8 +40,7 @@ Adaptation is available as a ruby gem, so the easiest way should be:
40
40
 
41
41
  This will generate a an adaptor file tree under folder _myadaptor_.
42
42
 
43
- 2. If no message oriented middleware has been already set, change directory into _myadaptor_
44
- and start the <b>mom</b>:
43
+ 2. If no message oriented middleware (mom) has been already set, you can start one typing:
45
44
 
46
45
  > mom
47
46
 
@@ -50,14 +49,14 @@ Adaptation is available as a ruby gem, so the easiest way should be:
50
49
  3. Subscribe your adaptor to the <b>mom</b>, so it will be executed when a message is received on
51
50
  a topic your adaptor is interested in:
52
51
 
53
- > script/subscribe
52
+ > ruby script/subscribe
54
53
 
55
54
  By default this will try to subscribe to a *mom* listening on <em>localhost:8080</em>, using port
56
55
  <em>8081</em> to subscribe (subscribing means starting a new server that listens for
57
56
  message publication notifications). These values can be changed editing <em>config/mom.yml</em>. In <em>mom.yml</em>
58
57
  you can also specify wich topics your adaptor is interested in.
59
58
 
60
- 4. Right now you should have a <b>mom</b>_ listening for messages on <em>localhost:8080</em>, and an _adaptor_
59
+ 4. Right now you should have a <b>mom</b> listening for messages on <em>localhost:8080</em>, and an _adaptor_
61
60
  subscribed to that *mom* and listening on <em>localhost:8081</em>, and interested in all topics available.
62
61
 
63
62
  This environment can be tested by executing the following from _myadaptor_ folder:
@@ -68,8 +67,8 @@ Adaptation is available as a ruby gem, so the easiest way should be:
68
67
  This message should be displayed in the subscriber terminal when delivered by the *mom*.
69
68
  Nothing would be executed, because a _Helloworld_ message[link:../rdoc/classes/Adaptation/Message.html] to
70
69
  map this xml message and a _HelloworldAdaptor_[link:../rdoc/classes/Adaptation/Adaptor.html] to process it don't
71
- exist yet. Since these classes aren't implemented, Adaptation will pass the message as a xml _String_ to the
72
- default _ApplicationAdaptor_ adaptor, but its _process_ method is still empty, so nothing will happen.
70
+ exist yet. Since these classes aren't implemented, Adaptation will pass the message as an Adaptation::Message object
71
+ to the default _ApplicationAdaptor_ adaptor, but its _process_ method is still empty, so nothing will happen.
73
72
 
74
73
  To see something happening the _process_ method in the default _ApplicationAdaptor_ could be implemented,
75
74
  editing file <em>myadaptor/app/adaptors/application.rb</em>:
@@ -109,8 +108,7 @@ Adaptation is available as a ruby gem, so the easiest way should be:
109
108
  create app/adaptors/helloworld_adaptor.rb
110
109
  create test/functional/helloworld_adaptor_test.rb
111
110
 
112
- and to edit <em>app/adaptors/helloworld_adaptor</em> to make something happen
113
- when a message is received:
111
+ and to edit <em>app/adaptors/helloworld_adaptor</em> to make something happen when a message is received:
114
112
 
115
113
  class HelloworldAdaptor < ApplicationAdaptor
116
114
 
@@ -120,8 +118,8 @@ Adaptation is available as a ruby gem, so the easiest way should be:
120
118
 
121
119
  end
122
120
 
123
- We can notice that _helloworld_ variable is not a _String_ now, because Adaptation
124
- has been able to map it to a Adaptation::Message object, and that the _HelloworldAdaptor_
121
+ We can notice that _helloworld_ variable is an instance of _Helloworld_ class (because Adaptation
122
+ has been able to map it to a subclass of Adaptation::Message object), and that the _HelloworldAdaptor_
125
123
  inherits from _ApplicationAdaptor_, so functionality repeated in different _Adaptors_[link:../rdoc/classes/Adaptation/Adaptor.html]
126
124
  can be placed in _ApplicationAdaptor_.
127
125
 
@@ -186,7 +184,87 @@ test
186
184
 
187
185
 
188
186
  == Debugging
189
- <em>TODO: ruby script/console + ruby-debug</em>
187
+
188
+ Adaptation includes a console from wich we can access Adaptation::Message and ActiveRecord classes defined in
189
+ the <em>adaptor</em> we are working on.
190
+
191
+ To open the console type:
192
+
193
+ > ruby script/console [environment]
194
+
195
+ From the command prompt that should appear, we can instantiate ActiveRecord objects defined in app/models and
196
+ Adaptation::Message objects defined in app/messages.
197
+ With the previous example, where an Adaptation::Message subclass called Helloworld was defined in app/messages,
198
+ we could do:
199
+
200
+ > ruby script/console test
201
+ $ >> Loading test environment (Adaptation X.X.X)
202
+ $ hw = HelloWorld.new("<helloworld>hello</helloworld>")
203
+ $ hw.content
204
+ $ >> "hello"
205
+ $ hw.to_xml
206
+ $ >> "<helloworld>hello</helloworld>"
207
+
208
+ We could edit app/messages/hellworld.rb to add a validation:
209
+
210
+ class Helloworld < Adaptation::Message
211
+ validates_presence_of :atr
212
+ end
213
+
214
+ and check if it works with the console:
215
+
216
+ > ruby script/console
217
+ $ >> Loading test environment (Adaptation X.X.X)
218
+ $ hw = HelloWorld.new("<helloworld>hello</helloworld>")
219
+ $ hw.valid?
220
+ $ >> false
221
+ $ hw = HelloWorld.new("<helloworld atr="something">hello</helloworld>")
222
+ $ hw.valid?
223
+ $ >> true
224
+
225
+ If we define ActiveRecord::Base subclasses in app/models we can do the same mapping
226
+ database rows instead of xml messages, if config/database.yml is properly configured
227
+ to access the database.
228
+ If we had a <em>users</em> table in our database, we could generate a model to access
229
+ it with ActiveRecord:
230
+
231
+ > ruby script/generate model user
232
+ exists app/models/
233
+ exists test/fixtures/
234
+ create app/models/user.rb
235
+
236
+ and use it from the console:
237
+
238
+ > ruby script/console
239
+ $ >> Loading test environment (Adaptation X.X.X)
240
+ $ u = User.find(:first)
241
+
190
242
 
191
243
  == Testing
192
- <em>TODO</em>
244
+
245
+ As it can be seen when generating adaptors and messages, adaptation automatically generates empty functional
246
+ tests for adaptors and empty unit tests for messages.
247
+
248
+ Tests can be run typing:
249
+
250
+ > ruby test.rb
251
+
252
+ Tests can use fixtures stored in <em>test/fixtures</em> folder. There are two kind of fixtures:
253
+
254
+ - <b>database fixtures</b>: the same database fixtures a {rails}[http://www.rubyonrails.org] application uses to fill its tables in tests. These fixtures are usually stored in <em>*.yml</em> files, and are used to reset database contents before each test, so it is important to configure the test database properly in config/database.yml. More about ActiveRecord fixtures {here}[http://api.rubyonrails.org/classes/Fixtures.html].
255
+
256
+ - <b>xml fixtures</b>: these fixtures are plain xml strored in <em>*.xml</em>, and are used to build Adaptation::Message objects and probably process them with our adaptor in the tests. These fixtures can also be created dynamically with Erb templetes. This is an example of a xml fixture built with Erb:
257
+
258
+ <20_people>
259
+ <% 20.times do |c| %>
260
+ <person num="<%= c %>"/>
261
+ <% end %>
262
+ </20_people>
263
+
264
+ For the _HellworldAdaptor_ in our example, we should find a functional test in <em>test/functional/helloworld_adaptor_test.rb</em>, and for the _Helloworld_ message a unit test in <em>test/unit/helloworld_test.rb</em>.
265
+
266
+ Tests usually perfrom {assertions}[link:../rdoc/classes/ActiveSupport/TestCase.html]. There are a couple of methods that may be useful when testing _adaptors_, {*get_message_form_fixture*}[link:../rdoc/classes/ActiveSupport/TestCase.html#M000044] and {*message*}[link:../rdoc/classes/ActiveSupport/TestCase.html#M000043]:
267
+
268
+ - *get_message_from_fixture*: Returns an Adaptation::Message object from a fixture, without processing it (or an instance of the corresponding subclass, if it's defined).
269
+
270
+ - *message*: Builds a message from a xml fixture file and processes it the same way messages from the mom are processed by Adaptation, but using the test environment. Messages published with {publish}[link:../rdoc/classes/Adaptation/Adaptor.html#M000170] will be published to a mocked MOM (and can be checked with _assert_message_published_).
@@ -9,7 +9,7 @@ class Adaptation::Adaptor
9
9
  xml_message = options.first
10
10
  message_type = xml_message[1..(xml_message.index(/(>| )/) - 1)]
11
11
  message_class = Adaptation::Message.get_class_object(message_type.capitalize)
12
- message_object = message_class.to_object(xml_message)
12
+ message_object = message_class.new(xml_message)
13
13
  end
14
14
 
15
15
  mom = File.new(ADAPTOR_ROOT + '/test/mocks/test/mom.txt', "a")
@@ -6,3 +6,7 @@ $:.unshift("#{ADAPTOR_ROOT}/app/adaptors")
6
6
 
7
7
  require 'rubygems'
8
8
  require "adaptation/test/test_help"
9
+
10
+ class ActiveSupport::TestCase
11
+ fixtures :all
12
+ end
@@ -1,6 +1,7 @@
1
- require 'roxml'
1
+ require 'xmlsimple'
2
2
  require 'yaml'
3
3
  require 'active_record'
4
+ require 'active_record/base'
4
5
  require 'adaptation/validateable'
5
6
  require 'adaptation/message'
6
7
  require 'adaptation/adaptor'
@@ -10,22 +10,39 @@ module Adaptation
10
10
  #
11
11
  class Adaptor
12
12
 
13
- def process message
13
+ def process message #:nodoc:
14
14
  end
15
15
 
16
+ # Returns the logger to output results in the current environment log file.
17
+ # Example:
18
+ # > logger.info "this is going to log/development.log"
16
19
  def logger
17
20
  Adaptation::Base.logger
18
21
  end
19
-
20
- def publish *options
21
- message_object = nil
22
- if options.first.is_a?(Message)
23
- message_object = options.first
22
+
23
+ # Publishes a message to the MOM. The message can be an instance of Adaptation::Message or a String.
24
+ #
25
+ # When executed in test environment messages are not published to the MOM. They are written to a mocked MOM
26
+ # and their publication can be asserted in tests with assert_message_published[link:/classes/ActiveSupport/TestCase.html#M000038].
27
+ #
28
+ # By default it uses <b>script/publish</b> ito publish. This can be overwritten specifying the
29
+ # <b>oappublish</b> instruction in configuration file <b>config/settings.yml</b>.
30
+ #
31
+ # By default it publishes in topic <b>ADAPTATION</b>. This can be overwritten specifying the <b>application</b> setting
32
+ # in <b>config/settings.yml</b> file.
33
+ #
34
+ # Example settings file:
35
+ # oappublish: /bin/echo
36
+ # application: MY_TOPIC
37
+ def publish *options
38
+ message_object = nil
39
+ if options.first.is_a?(Message)
40
+ message_object = options.first
24
41
  elsif options.first.is_a?(String)
25
42
  xml_message = options.first
26
43
  message_type = xml_message[1..(xml_message.index(/(>| )/) - 1)]
27
44
  message_class = Adaptation::Message.get_class_object(message_type.capitalize)
28
- message_object = message_class.to_object(xml_message)
45
+ message_object = message_class.new(xml_message)
29
46
  end
30
47
 
31
48
  xml = message_object.to_xml.to_s.gsub("'", "\"")
@@ -37,7 +54,7 @@ module Adaptation
37
54
 
38
55
  end
39
56
 
40
- def self.get_class_object(adaptor_class) # nodoc
57
+ def self.get_class_object(adaptor_class) #:nodoc:
41
58
  Object.const_get(adaptor_class) rescue nil
42
59
  end
43
60
 
@@ -61,19 +61,14 @@ module Adaptation
61
61
  adaptor = message = nil
62
62
 
63
63
  message_class = Adaptation::Message.get_class_object(message_type.capitalize)
64
- message = message_class.nil? ? xml_message : message_class.to_object(xml_message)
65
- # TODO: the xml is returned as a String if a class to map it is not found;
66
- # in future versions Adaptation may build a valid Adaptation::Message even
67
- # without implementation for this type of message
64
+ message = message_class.nil? ? Adaptation::Message.new(xml_message) : message_class.new(xml_message)
68
65
 
69
66
  adaptor_class = Adaptation::Adaptor.get_class_object("#{message_type.capitalize}Adaptor")
70
67
  adaptor = adaptor_class.nil? ? ApplicationAdaptor.new : adaptor_class.new rescue Adaptation::Adaptor.new
71
68
 
72
- unless message.is_a?(String) # TODO: remove when feature explained in line 67 implemented
73
- unless message.valid?
74
- @@logger.info "WARNING:Message doesn't validate!"
75
- return
76
- end
69
+ unless message.valid?
70
+ @@logger.info "WARNING:Message doesn't validate!"
71
+ return
77
72
  end
78
73
 
79
74
  adaptor.process message
@@ -2,102 +2,44 @@ module Adaptation
2
2
 
3
3
  #= Adaptation::Message -- XML to classes mapping.
4
4
  #
5
- #Adaptation::Message connects ruby objects and xml data to create a model where
6
- #logic and data are presented in one wrapping. It's is inspired on activerecord, and pretends
7
- #to solve xml data management problems in a similar way activerecord handles database data
8
- #management.
5
+ #Adaptation::Message maps xml data into a ruby object. It also provides validators
6
+ #(inspired by ActiveRecord[http://api.rubyonrails.org/classes/ActiveRecord/Validations.html] ORM)
7
+ #to check xml format.
9
8
  #
10
- #Adaptation::Message classes describe xml structures, allowing separation of common structures as
11
- #standalone classes. It offers a set of macros to specify wich structures are part of a bigger one and
12
- #to allow validation of the different parts.
9
+ #Examples:
13
10
  #
14
- #A summary of the major features and their usage:
11
+ # contact = Adaptation::Message.new('<contact><name kind="surname">Name</name></contact>')
15
12
  #
16
- #* Automated mapping between classes and xml attributes and elements.
13
+ # contact.name.content # -> "Name"
14
+ # contact.names.first.content # -> "Name"
15
+ # contact.names.length # -> 1
16
+ # contact.name.kind # -> "surname"
17
17
  #
18
- # class Contact < Adaptation::Message
19
- # has_one :text, :name
20
- # end
18
+ #Let's add some validations:
21
19
  #
22
- # ...is automatically mapped to a xml structure with root element named "contact", such as:
20
+ # class Contact < Adaptation::Message
21
+ # validates_presence_of :name
22
+ # end
23
23
  #
24
- # <contact>
25
- # <name>Name</name>
26
- # </contact>
24
+ # contact = Contact.new('<contact><name kind="surname">Name</name></contact>')
25
+ # contact.valid? # -> true
27
26
  #
28
- # ...which gives Contact#name
27
+ # contact = Contact.new('<contact><phone>555</phone></contact>')
28
+ # contact.valid? # -> false
29
29
  #
30
30
  #
31
- #* Associations between objects controlled by simple meta-programming macros.
31
+ # class SeriousContact < Adaptation::Message
32
+ # maps_xml :contact # tell Adaptation that xml data like <contact>...</contact> is mapped by this class
33
+ # validates_value_of :kind, "surname", :in => :names
34
+ # end
32
35
  #
33
- # class Agenda < Adaptation::Message
34
- # has_one :attribute, :type
35
- # has_one :text, :owner
36
- # has_many :contacts
37
- # end
36
+ # contact = SeriousContact.new('<contact><name kind="surname">Name</name></contact>')
37
+ # contact.valid? # -> true
38
38
  #
39
- # class Contact < Adaptation::Message
40
- # has_one :text, :name
41
- # end
42
- #
43
- # Agenda class would be mapping the following xml structure:
44
- #
45
- # <agenda type="...">
46
- # <owner>...</owner>
47
- # <contact>...</contact>
48
- # <contact>...</contact>
49
- # ...
50
- # </agenda>
51
- #
52
- # while the _Contact_ class would map:
53
- #
54
- # <contact>
55
- # <name>...</name>
56
- # </contact>
57
- #
58
- # The Contact class is a partial, a structure included in a bigger structure, so its
59
- # file name starts with an underscore: _contact.rb
60
- #
61
- # We could have wrote Agenda like this, to change the contacts container name:
62
- #
63
- # class Agenda < Adaptation::Message
64
- # has_one :attribute, :type
65
- # has_one :text, :owner
66
- # has_many :contacts, :in => :contact_list
67
- # end
68
- #
69
- # and Contact like:
70
- #
71
- # class Contact < Adaptation::Message
72
- # has_one :text, :name
73
- # end
74
- #
75
- # Then the mapping in Agenda would be:
76
- #
77
- # <agenda type="...">
78
- # <owner>...</owner>
79
- # <contact_list>
80
- # <contact>...</contact>
81
- # </contact_list>
82
- # </agenda>
83
- #
84
- #
85
- #* Validation rules.
86
- #
87
- # Adaptation::Message uses {ActiveRecord::Validations}[http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html].
88
- # This means that {ActiveRecord validations}[http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html]
89
- # can be used in an Adaptation::Message object and that custom validations can be easily implemented.
39
+ # contact = SeriousContact.new('<contact><name kind="alias">Alias</name></contact>')
40
+ # contact.valid? # -> false
90
41
  #
91
- # Adaptation::Message also defines some {custom validation methods}[link:../rdoc/classes/ActiveRecord/Validations/ClassMethods.html].
92
- #
93
- # Validations usage example:
94
- #
95
- # class Agenda < Adaptation::Message
96
- # has_one :attribute, :type
97
- # has_one :text, :owner
98
- # validates_presence_of :owner, :type
99
- # validates_value_of :type, "indexed"
100
- # end
42
+ #More on validations here[link:../rdoc/classes/ActiveRecord/Validations/ClassMethods.html].
101
43
  #
102
44
  #
103
45
  class Message
@@ -109,167 +51,86 @@ module Adaptation
109
51
  cattr_reader :objects
110
52
 
111
53
  include Validateable
112
- include ROXML
113
54
 
55
+ # Constructor. Transforms xml passsed as a <em>String</em> to an object wich methods map the input xml elements and attributes.
56
+ def initialize xml_string
57
+ @hash_with_root = XmlSimple.xml_in("<adaptation_wrapper>" + xml_string + "</adaptation_wrapper>", 'ForceArray' => false, 'AttrPrefix' => true)
58
+
59
+ first_value = @hash_with_root.values.first
60
+ hash = first_value.is_a?(String) ? {"content" => first_value} : first_value
61
+ array = hash.is_a?(Array) ? hash : [hash]
114
62
 
115
- # Maps an xml attribute or element.
116
- #
117
- # Example 1: Mapping an attribute:
118
- # <xml attr="something"/>
119
- #
120
- # class Xml < Adaptation::Message
121
- # has_one :attribute, :attr
122
- # end
123
- #
124
- # Example 2: Mapping an xml element that contains text.
125
- # <xml>
126
- # <element>something</element>
127
- # </xml>
128
- #
129
- # class Xml < Adaptation::Message
130
- # has_one :text, :element
131
- # end
132
- #
133
- # Example 3: Mapping an xml element that contains xml data.
134
- # <xml>
135
- # <element>
136
- # <subelement>something</subelement>
137
- # </element>
138
- # </xml>
139
- #
140
- # class Xml < Adaptation::Message
141
- # has_one :object, :element
142
- # end
143
- #
144
- # The element xml structure would be described in a separate partial file <em>_element.rb</em>:
145
- # class Element < Adaptation::Message
146
- # has_one :text, :subelement
147
- # end
148
- #
149
- def self.has_one *symbols
150
- xml_tag = symbols[0]
151
- case xml_tag
152
- when :object
153
- @objects = [] if @objects.nil?
154
- unless @objects.include?(symbols[1])
155
- @objects << symbols[1]
156
-
157
- klass = get_class_object(symbols[1])
158
- if klass.nil?
159
- require "#{ADAPTOR_ROOT}/app/messages/_#{symbols[1].to_s}.rb"
160
- klass = get_class_object(symbols[1])
63
+ array.each do |h|
64
+ if end_of_tree?(h)
65
+ h.each_pair do |k, v|
66
+ if !v.is_a?(Array)
67
+ is_attribute = k.include?("@") ? true : false
68
+ var = k.gsub("@","")
69
+ self.class_eval "attr_accessor :#{var}"
70
+ eval "@#{var} = v"
71
+ var2 = pluralize(var)
72
+ if !is_attribute and var != var2
73
+ self.class_eval "attr_accessor :#{var2}"
74
+ eval "@#{var2} = []; @#{var2} << '#{var}'"
75
+ end
76
+ else
77
+ var = pluralize(k.gsub("@",""))
78
+ self.class_eval "attr_accessor :#{var}"
79
+ eval "@#{var} = []"
80
+ v.each do |val|
81
+ if is_attribute?(val)
82
+ xml_substring = XmlSimple.xml_out(val, 'NoIndent' => true, 'RootName' => k, 'AttrPrefix' => true)
83
+ eval "@#{var} << Adaptation::Message.new('#{xml_substring}')"
84
+ else
85
+ eval "@#{var} << '#{val}'"
86
+ end
87
+ end
161
88
  end
162
-
163
- xml_object symbols[1], klass
164
- validates_associated symbols[1]
165
89
  end
166
- when :attribute
167
- @attributes = [] if @attributes.nil?
168
- unless @attributes.include?(symbols[1])
169
- @attributes << symbols[1]
170
- s = ""
171
- @attributes.each do |a|
172
- s << a.to_s
90
+ else
91
+ h.each_pair do |k,v|
92
+ if k[0..0] == "@"
93
+ var = k.gsub("@","")
94
+ self.class_eval "attr_accessor :#{var}"
95
+ eval "@#{var} = '#{v}'"
96
+ else
97
+ self.class_eval "attr_accessor :#{k}"
98
+ xml_substring = ""
99
+ if !v.is_a?(Array)
100
+ xml_substring = XmlSimple.xml_out(v, 'NoIndent' => true, 'RootName' => k, 'AttrPrefix' => true)
101
+ eval "@#{k} = Adaptation::Message.new('#{xml_substring}')"
102
+ k2 = pluralize(k)
103
+ if k != k2
104
+ self.class_eval "attr_accessor :#{k2}"
105
+ eval "@#{k2} = []; @#{k2} << @#{k}"
106
+ end
107
+ else
108
+ k2 = pluralize(k)
109
+ self.class_eval "attr_accessor :#{k2}"
110
+ eval "@#{k2} = [];"
111
+ v.each do |val|
112
+ xml_substring = XmlSimple.xml_out(val, 'NoIndent' => true, 'RootName' => k, 'AttrPrefix' => true)
113
+ eval "@#{k} = Adaptation::Message.new('#{xml_substring}')"
114
+ eval "@#{k2} << @#{k}"
115
+ end
116
+ end
173
117
  end
174
- xml_attribute symbols[1]
175
- end
176
- when :text
177
- @texts = [] if @texts.nil?
178
- unless @texts.include?(symbols[1])
179
- @texts << symbols[1]
180
- xml_text symbols[1]
181
118
  end
119
+ end
182
120
  end
121
+
183
122
  end
184
123
 
185
- # Maps an xml element text.
186
- #
187
- # If an element contains plain text data and has not been declared in its parent element with
188
- # <em>has_one :text</em>, it must declare itself with <em>has_text</em> to be able to contain text.
189
- #
190
- # Example:
191
- #
192
- # <xml>
193
- # <element attr="something">something more</element>
194
- # </xml>
195
- #
196
- # class Xml < Adaptation::Message
197
- # has_one :object, :element
198
- # end
199
- #
200
- # In this case element cannot be declared just like <em>has_one :text</em>, because it also has
201
- # an attribute. It must be declared like <em>has_one :object</em>. Then the element declares itself
202
- # in a file called <em>_element.rb</em> like this:
203
- #
204
- # class Element < Adaptation::Message
205
- # has_one :attribute, :attr
206
- # has_text
207
- # end
208
- #
209
- def self.has_text
210
- if @has_text.nil?
211
- @has_text = true
212
- xml_text :text, nil, ROXML::TEXT_CONTENT
213
- end
124
+ def self.has_one *symbols #:nodoc:
125
+ logger.info "has_one is deprecated and not necessary"
126
+ end
127
+
128
+ def self.has_text #:nodoc:
129
+ logger.info "has_text is deprecated and not necessary"
214
130
  end
215
131
 
216
- # Maps many xml subelements.
217
- #
218
- # Example 1:
219
- #
220
- # <xml>
221
- # <subelement>...</subelement>
222
- # <subelement>...</subelement>
223
- # <subelement>...</subelement>
224
- # </xml>
225
- #
226
- # class Xml < Adaptation::Message
227
- # has_many :subelements
228
- # end
229
- #
230
- # Example 2:
231
- #
232
- # <xml>
233
- # <subelement_list>
234
- # <subelement>...</subelement>
235
- # <subelement>...</subelement>
236
- # <subelement>...</subelement>
237
- # </subelement_list>
238
- # </xml>
239
- #
240
- # class Xml < Adaptation::Message
241
- # has_many :subelements, :in => :subelement_list
242
- # end
243
- #
244
- def self.has_many *options
245
- configuration = {}
246
- configuration.update(options.pop) if options.last.is_a?(Hash)
247
-
248
- class_with_container = nil
249
- if configuration[:in]
250
- class_with_container = options[0].to_s.capitalize[0..-2] + ":#{configuration[:in].to_s}"
251
- else
252
- class_with_container = options[0].to_s.capitalize[0..-2] + ":#{options[0].to_s}"
253
- end
254
- unless @@classes_with_brothers.include?(class_with_container)
255
- @@classes_with_brothers << class_with_container
256
- end
257
-
258
- @has_many = [] if @has_many.nil?
259
- unless @has_many.include?(options[0])
260
- @has_many << options[0]
261
-
262
- load "#{ADAPTOR_ROOT}/app/messages/_#{options[0].to_s[0..-2]}.rb"
263
- klass = get_class_object(options[0].to_s.capitalize[0..-2])
264
-
265
- if configuration[:in]
266
- xml_object configuration[:in], klass, ROXML::TAG_ARRAY, configuration[:in].to_s
267
- validates_associated configuration[:in]
268
- else
269
- xml_object options[0], klass, ROXML::TAG_ARRAY #, options[0].to_s
270
- validates_associated options[0]
271
- end
272
- end
132
+ def self.has_many *options #:nodoc:
133
+ logger.info "has_many is deprecated and not necessary"
273
134
  end
274
135
 
275
136
  # Defines the xml element that this class is mapping. This is useful to avoid class
@@ -302,7 +163,6 @@ module Adaptation
302
163
  #
303
164
  def self.maps_xml element
304
165
  @mapped_xml = element
305
- xml_name element.to_s
306
166
  end
307
167
 
308
168
  # Returns the xml element this class is mapping
@@ -321,21 +181,57 @@ module Adaptation
321
181
  klass
322
182
  end
323
183
 
324
- # This is the constructor. Instead of <em>Adaptation::Message#new</em>, an <em>Adaptation::Message</em>
325
- # instance is created like this:
326
- #
327
- # m = Adaptation::Message.to_object("<xml>valid xml</xml>")
328
- #
329
- def self.to_object xml_message
330
- parse xml_message
184
+ # <em>Deprecated</em>, use <em>new</em> instead.
185
+ def self.to_object xml_message #:nodoc:
186
+ logger.info "to_object is deprecated, use new instead"
187
+ self.new(xml_message)
331
188
  end
332
189
 
333
- # Alias for {Adaptation::Message#valid?}[http://api.rubyonrails.org/classes/ActiveRecord/Validations.html#M002099]
334
190
  # <em>Deprecated</em>, use <em>valid?</em> instead.
335
- def check
191
+ def check #:nodoc:
192
+ logger.info "check is deprecated, use valid? instead"
336
193
  valid?
337
194
  end
338
195
 
196
+ def self.logger#:nodoc:#
197
+ Adaptation::Base.logger rescue Logger.new(STDOUT)
198
+ end
199
+
200
+ def to_xml
201
+ xml_out(@hash_with_root).gsub("\"","'").gsub(/(<|<\/)content(>| *\/>)/,"")
202
+ end
203
+
204
+ def to_hash
205
+ @hash_with_root
206
+ end
207
+
208
+ private
209
+
210
+ def end_of_tree?(v) #:nodoc:
211
+ return true if v.has_key? "content"
212
+ return true if v.values.length == 1 and v.values.first.is_a?(Array) and v.values.first.reject{|val| val.is_a?(String) or is_attribute?(val)}.length == 0
213
+ false
214
+ end
215
+
216
+ def xml_in(xml_string)
217
+ XmlSimple.xml_in(xml_string, 'ForceArray' => false, 'AttrPrefix' => true, 'KeepRoot' => true)
218
+ end
219
+
220
+ def xml_out(xml_hash)
221
+ XmlSimple.xml_out(xml_hash, 'NoIndent' => true, 'RootName' => k, 'AttrPrefix' => true)
222
+ end
223
+
224
+ # TODO: improve this
225
+ def pluralize(v) #:nodoc:
226
+ v[(v.length - 1)..v.length] == "s" ? v : v + "s"
227
+ end
228
+
229
+ def is_attribute?(h) #:nodoc:
230
+ return false unless h.is_a?(Hash)
231
+ return true if h.length == 1 and h.values.first.is_a?(String)
232
+ false
233
+ end
234
+
339
235
  end
340
236
 
341
237
  end
@@ -5,58 +5,37 @@ require 'adaptation'
5
5
 
6
6
  Adaptation::Initializer.run
7
7
 
8
- if File.exists?("config/database.yml")
9
- begin
10
- require 'active_record/fixtures'
11
- rescue Exception => e
12
- puts "Problem with database fixtures: #{e}"
13
- end
14
- Test::Unit::TestCase.fixture_path = 'test/fixtures'
8
+ begin
9
+ require 'active_record/test_case'
10
+ require 'active_record/fixtures'
11
+ rescue Exception => e
12
+ puts "Problem with database fixtures: #{e}"
13
+ raise e
15
14
  end
16
15
 
17
16
  require 'erb'
18
17
 
19
- class Test::Unit::TestCase
18
+ class ActiveSupport::TestCase
19
+
20
+ include ActiveRecord::TestFixtures
21
+ self.fixture_path = "#{ADAPTOR_ROOT}/test/fixtures/"
22
+ self.use_instantiated_fixtures = false
23
+ self.use_transactional_fixtures = false
20
24
 
21
25
  # Asserts that a message[link:/classes/Adaptation/Message.html] in a xml
22
26
  # fixture file is converted into an Adaptation::Message that if serialized
23
27
  # again to xml is equivalent to the xml data in the initial fixture file.
24
- # An Adaptation::Message object cretaed from a xml fixture, will only
25
- # have the xml tags specified in its class definition (using has_one,
26
- # has_many, has_text...) so this assertion can be useful to check that
27
- # the class is defined correctly.
28
28
  def assert_parsed message_symbol
29
29
  data, message_object = load_message_fixture message_symbol
30
-
31
- parsed_data = REXML::Document.new data
30
+
31
+ #parsed_data = Adaptation::Message.new(data)
32
32
  error = build_message error,
33
33
  "? not parsed ok:\n initial: ?\n parsed: ?",
34
34
  message_symbol.to_s,
35
- parsed_data.to_s,
36
- message_object.to_xml.to_s
37
- assert_block error do
38
- compare_xml_elements parsed_data.root, message_object.to_xml
39
- end
40
- end
41
-
42
- # Asserts that a message[link:/classes/Adaptation/Message.html] in a xml
43
- # fixture file is converted into an Adaptation::Message that if serialized
44
- # again to xml is not equivalent to the xml data in the initial fixture file.
45
- # An Adaptation::Message object cretaed from a xml fixture, will only
46
- # have the xml tags specified in its class definition (using has_one,
47
- # has_many, has_text...) so this assertion can be useful to check that
48
- # undesired xml tags are filtered.
49
- def assert_not_parsed message_symbol
50
- data, message_object = load_message_fixture message_symbol
51
-
52
- parsed_data = REXML::Document.new data
53
- error = build_message error,
54
- "? shouldn't be parsed ok:\n data: ?\n real: ?",
55
- message_symbol.to_s,
56
- parsed_data.to_s,
57
- message_object.to_xml.to_s
35
+ data,
36
+ message_object.to_xml
58
37
  assert_block error do
59
- !compare_xml_elements parsed_data.root, message_object.to_xml
38
+ compare_xml_elements data, message_object.to_xml
60
39
  end
61
40
  end
62
41
 
@@ -100,7 +79,7 @@ class Test::Unit::TestCase
100
79
  # build Message object with xml_data
101
80
  message_type = xml_message[1..(xml_message.index(/(>| )/) - 1)]
102
81
  message_class = Adaptation::Message.get_class_object(message_type.capitalize)
103
- message_object = message_class.to_object(xml_message)
82
+ message_object = message_class.nil? ? Adaptation::Message.new(xml_message) : message_class.new(xml_message)
104
83
  end
105
84
 
106
85
  # check for all messages "published" in the mom (that's file /tmp/mom.txt),
@@ -108,10 +87,9 @@ class Test::Unit::TestCase
108
87
  message_found = false
109
88
  expected = published = ""
110
89
  File.open(ADAPTOR_ROOT + '/test/mocks/test/mom.txt', 'r').each{ |line|
111
- mom_message = REXML::Document.new line
112
90
  published = line.chop
113
91
  expected = message_object.to_xml.to_s
114
- if compare_xml_elements mom_message.root, message_object.to_xml
92
+ if compare_xml_elements published, message_object.to_xml
115
93
  message_found = true
116
94
  break
117
95
  end
@@ -219,9 +197,10 @@ class Test::Unit::TestCase
219
197
  end
220
198
 
221
199
  # Builds a message[link:/classes/Adaptation/Message.html] from a xml fixture
222
- # file and processes it the same way mesages from the mom are processed by
223
- # adaptation, but using a test environment. Published messages will be
224
- # published to a mock MOM.
200
+ # file and processes it the same way messages from the mom are processed by
201
+ # adaptation, but using a test environment. Messages published with
202
+ # {publish}[link:/classes/Adaptation/Adaptor.html#M000170] will be published
203
+ # to a mocked MOM (and can be checked with _assert_message_published_)
225
204
  def message message_symbol
226
205
  # build a message object from fixture
227
206
  message_xml, message_object = load_message_fixture message_symbol
@@ -241,8 +220,8 @@ class Test::Unit::TestCase
241
220
 
242
221
  end
243
222
 
244
- # Retuns a message[link:/classes/Adaptation/Message.html] object
245
- # from a fixture, without processing it
223
+ # Returns an Adaptation::Message object from a fixture, without processing it
224
+ # (or an instance of the corresponding subclass, if it's defined).
246
225
  def get_message_from_fixture message_symbol
247
226
  load_message_fixture(message_symbol)[1]
248
227
  end
@@ -264,11 +243,13 @@ class Test::Unit::TestCase
264
243
  data = get_message_fixture(fixture_symbol.to_s)
265
244
  class_name = data[1..(data.index(/(>| )/) - 1)].capitalize
266
245
  message_class = Adaptation::Message.get_class_object(class_name)
267
- message_object = message_class.nil? ? data : message_class.to_object(data)
246
+ message_object = message_class.nil? ? Adaptation::Message.new(data) : message_class.new(data)
268
247
  [data, message_object]
269
248
  end
270
249
 
271
250
  def compare_xml_elements element1, element2 #:nodoc:
251
+ element1 = REXML::Document.new(element1) if element1.is_a?(String)
252
+ element2 = REXML::Document.new(element2) if element2.is_a?(String)
272
253
  if element1.has_attributes?
273
254
  if !element2.has_attributes?
274
255
  return false
@@ -21,7 +21,7 @@ module Validateable
21
21
  module ClassMethods
22
22
  # Define class methods here.
23
23
 
24
- def self_and_descendents_from_active_record#nodoc:
24
+ def self_and_descendents_from_active_record # :nodoc:
25
25
  klass = self
26
26
  classes = [klass]
27
27
  while klass != klass.base_class
@@ -45,10 +45,97 @@ module Validateable
45
45
 
46
46
  end
47
47
 
48
+ #= Adaptation::Message Validations
49
+ # {ActiveRecord validations}[http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html] should work
50
+ # the same way, except those that have been overwritten here.
51
+ #
48
52
  module ActiveRecord
49
53
  module Validations
50
54
  module ClassMethods
51
55
 
56
+ # Validates whether the specified xml attribute/element is present (not nil or blank).
57
+ #
58
+ # Example 1:
59
+ #
60
+ # class Leftwing < Adaptation::Message
61
+ # validates_presence_of :side
62
+ # end
63
+ #
64
+ # lw = Leftwing.new("<leftwing side=\"left\"/>")
65
+ # lw.valid? # -> true
66
+ # lw = Leftwing.new("<leftwing noside=\"left\"/>")
67
+ # lw.valid? # -> false
68
+ #
69
+ # Example 2:
70
+ #
71
+ # class Bird < Adaptation::Message
72
+ # validates_presence_of :side, :in => "birds.wings"
73
+ # end
74
+ #
75
+ # b = Bired.new('<birds><bird><wings><wing side="left"/><wing side="right"/></wings></bird></birds>')
76
+ # b.valid? # -> true
77
+ # b = Bired.new('<birds><bird><wings><wing side="left"/><wing noside="right"/></wings></bird></birds>')
78
+ # b.valid? # -> false
79
+ #
80
+ def validates_presence_of(*attr_names)
81
+ configuration = {
82
+ :message => 'cannot be blank'
83
+ }
84
+ configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
85
+
86
+ if configuration[:in].nil?
87
+ validates_each(attr_names, configuration) do |record, attr_name, value|
88
+ string = record.send(attr_names[0].to_sym)
89
+ unless string.nil?
90
+ string = string.content if string.is_a?(Adaptation::Message)
91
+ end
92
+ if string.blank?
93
+ record.errors.add(attr_name, configuration[:message])
94
+ end
95
+ end
96
+ else
97
+ validates_each(attr_names, configuration) do |record, attr_name, value|
98
+ missing = false
99
+ configuration_in = configuration[:in].to_s
100
+ elements = configuration_in.to_s.split('.')
101
+ subelement = record
102
+ while elements.length > 1
103
+ subelement = record.send(elements[0].to_sym)
104
+ elements.slice!(0)
105
+ end
106
+
107
+ if !subelement.is_a?(Array)
108
+ subelement.send(elements[0].to_sym).each do |s|
109
+ string = s.send(attr_names[0].to_sym)
110
+ unless string.nil?
111
+ string = string.content unless string.is_a?(String)
112
+ end
113
+ if string.blank?
114
+ missing = true
115
+ break
116
+ end
117
+ end
118
+ else
119
+ subelement.each do |sub|
120
+ sub.send(elements[0].to_sym).each do |s|
121
+ string = s.send(attr_names[0].to_sym)
122
+ unless string.nil?
123
+ string = string.content unless string.is_a?(String)
124
+ end
125
+ if string.blank?
126
+ missing = true
127
+ break
128
+ end
129
+ end
130
+ break if missing
131
+ end
132
+ end
133
+
134
+ record.errors.add(attr_name, configuration[:message]) if missing
135
+ end
136
+ end
137
+ end
138
+
52
139
  # Validates whether the value of the specified xml attribute/element is the expected one.
53
140
  #
54
141
  # Example 1:
@@ -56,8 +143,6 @@ module ActiveRecord
56
143
  # <leftwing side="left"/>
57
144
  #
58
145
  # class Leftwing < Adaptation::Message
59
- # has_one :attribute, :side
60
- #
61
146
  # validates_value_of :side, "left"
62
147
  # end
63
148
  #
@@ -65,8 +150,6 @@ module ActiveRecord
65
150
  # <bird><wings><wing side="left"/><wing side="right"/></wings></bird>
66
151
  #
67
152
  # class Bird < Adaptation::Message
68
- # has_many :wings
69
- #
70
153
  # validates_value_of :side, "left", :in => :wings
71
154
  # end
72
155
  #
@@ -78,15 +161,30 @@ module ActiveRecord
78
161
 
79
162
  if configuration[:in].nil?
80
163
  validates_each(attr_names, configuration) do |record, attr_name, value|
81
- if (attr_names[1].to_s != record.send(attr_names[0].to_sym))
164
+ string = record.send(attr_names[0].to_sym)
165
+ unless string.nil?
166
+ string = string.content unless string.is_a?(String)
167
+ end
168
+ if (attr_names[1].to_s != string)
82
169
  record.errors.add(attr_name, configuration[:message])
83
170
  end
84
171
  end
85
172
  else
86
173
  validates_each(attr_names, configuration) do |record, attr_name, value|
87
174
  found = false
88
- record.send(configuration[:in]).each do |s|
89
- if (attr_names[1].to_s == s.send(attr_names[0].to_sym))
175
+ configuration_in = configuration[:in].to_s
176
+ elements = configuration_in.to_s.split('.')
177
+ subelement = record
178
+ while elements.length > 1
179
+ subelement = record.send(elements[0].to_sym)
180
+ elements.slice!(0)
181
+ end
182
+ subelement.send(elements[0].to_sym).each do |s|
183
+ string = s.send(attr_names[0].to_sym)
184
+ unless string.nil?
185
+ string = string.content unless string.is_a?(String)
186
+ end
187
+ if (attr_names[1].to_s == string)
90
188
  found = true
91
189
  break
92
190
  end
@@ -99,13 +197,15 @@ module ActiveRecord
99
197
  # Validates whether the value of the specified xml attribute/element has a valid email format.
100
198
  #
101
199
  # Example:
102
- # <contact email="mail@xample.com">...</contact>
103
200
  #
104
201
  # class Contact < Adaptation::Message
105
- # has_one :attribute, :email
106
- #
107
202
  # validates_as_email :email
108
- # end
203
+ # end
204
+ #
205
+ # c = Contact.new('<contact email="mail@xample.com">...</contact>')
206
+ # c.valid? # -> true
207
+ # c.email = "nomail"
208
+ # c.valid? # -> false
109
209
  #
110
210
  def validates_as_email(*attr_names)
111
211
  configuration = {
@@ -1,8 +1,8 @@
1
1
  module Adaptation
2
2
  module VERSION #:nodoc:
3
- MAJOR = 0
4
- MINOR = 1
5
- TINY = 10
3
+ MAJOR = 1
4
+ MINOR = 0
5
+ TINY = 1
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -1,14 +1,13 @@
1
1
  require File.dirname(__FILE__) + '<%= '/..' * class_nesting_depth %>/../test_helper'
2
2
 
3
- class <%= class_name %>AdaptorTest < Test::Unit::TestCase
4
-
5
- #fixtures :your_table_name_here
3
+ class <%= class_name %>AdaptorTest < ActiveSupport::TestCase
6
4
 
7
5
  def setup
8
6
  end
9
7
 
10
8
  # Replace this with your real tests.
11
- def test_truth
9
+ test "should be true" do
12
10
  assert true
13
11
  end
12
+
14
13
  end
@@ -1,24 +1,16 @@
1
1
  require File.dirname(__FILE__) + '<%= '/..' * class_nesting_depth %>/../test_helper'
2
2
 
3
- class <%= class_name %>Test < Test::Unit::TestCase
3
+ class <%= class_name %>Test < ActiveSupport::TestCase
4
4
 
5
5
  # Replace this with your real tests.
6
- def test_truth
6
+ test "should be true" do
7
7
  assert true
8
8
  end
9
9
 
10
- # assert the message is defined correctly comparing the initial xml
11
- # from the fixture with the xml obtained from the Adaptation::Message
12
- # object created with that fixture
13
- #def test_not_parsed
14
- # assert_parsed :<%= file_name %>
15
- #end
16
-
17
10
  # assert message passes our validations
18
11
  #def test_validates
19
12
  # assert_validates :<%= file_name %>
20
13
  # assert_not_validates :<%= file_name %>_incorrect
21
14
  #end
22
15
 
23
-
24
16
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: adaptation
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.10
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Xavi Vila Morell
@@ -9,18 +9,18 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-05-18 00:00:00 +02:00
12
+ date: 2009-07-24 00:00:00 +02:00
13
13
  default_executable: adaptation
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
- name: roxml
16
+ name: xml-simple
17
17
  type: :runtime
18
18
  version_requirement:
19
19
  version_requirements: !ruby/object:Gem::Requirement
20
20
  requirements:
21
- - - "="
21
+ - - ">="
22
22
  - !ruby/object:Gem::Version
23
- version: "1.2"
23
+ version: 1.0.12
24
24
  version:
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: activerecord
@@ -28,9 +28,9 @@ dependencies:
28
28
  version_requirement:
29
29
  version_requirements: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "="
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 2.2.2
33
+ version: 2.3.3
34
34
  version:
35
35
  description: Adaptation is a framework for building "adaptors" for web-applications, so they can interact with other web-applications publishing messages and being subscribed to a message queue.
36
36
  email: xavi@oaproject.net