adaptation 0.1.10 → 1.0.1

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.
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