acts_as_sdata 1.0.0

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.
Files changed (79) hide show
  1. data/.gitignore +2 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.textile +200 -0
  4. data/Rakefile +20 -0
  5. data/VERSION +1 -0
  6. data/config/sdata.yml +13 -0
  7. data/config/sdata.yml.example +20 -0
  8. data/config/sdata.yml.tmpl.staging +14 -0
  9. data/generators/acts_as_sdata/acts_as_sdata_generator.rb +9 -0
  10. data/generators/acts_as_sdata/templates/migration.rb +69 -0
  11. data/init.rb +36 -0
  12. data/lib/s_data/active_record_extensions/base.rb +7 -0
  13. data/lib/s_data/active_record_extensions/mixin.rb +157 -0
  14. data/lib/s_data/active_record_extensions/sdata_uuid_mixin.rb +133 -0
  15. data/lib/s_data/atom_extensions/content_mixin.rb +14 -0
  16. data/lib/s_data/atom_extensions/entry_mixin.rb +41 -0
  17. data/lib/s_data/atom_extensions/nodes/digest.rb +48 -0
  18. data/lib/s_data/atom_extensions/nodes/payload.rb +34 -0
  19. data/lib/s_data/atom_extensions/nodes/sync_state.rb +14 -0
  20. data/lib/s_data/conditions_builder.rb +59 -0
  21. data/lib/s_data/controller_mixin.rb +11 -0
  22. data/lib/s_data/controller_mixin/actions.rb +87 -0
  23. data/lib/s_data/controller_mixin/collection_scope.rb +57 -0
  24. data/lib/s_data/controller_mixin/s_data_feed.rb +87 -0
  25. data/lib/s_data/controller_mixin/s_data_instance.rb +35 -0
  26. data/lib/s_data/diagnosis/application_controller_mixin.rb +16 -0
  27. data/lib/s_data/diagnosis/diagnosis.rb +130 -0
  28. data/lib/s_data/diagnosis/diagnosis_mapper.rb +39 -0
  29. data/lib/s_data/exceptions.rb +10 -0
  30. data/lib/s_data/formatting.rb +13 -0
  31. data/lib/s_data/namespace_definitions.rb +19 -0
  32. data/lib/s_data/payload.rb +158 -0
  33. data/lib/s_data/payload_map.rb +0 -0
  34. data/lib/s_data/payload_map/payload_map.rb +136 -0
  35. data/lib/s_data/payload_map/payload_map_hash.rb +39 -0
  36. data/lib/s_data/predicate.rb +31 -0
  37. data/lib/s_data/route_mapper.rb +143 -0
  38. data/lib/s_data/router_mixin.rb +10 -0
  39. data/lib/s_data/sync/controller_mixin.rb +122 -0
  40. data/lib/s_data/sync/sdata_syncing_mixin.rb +17 -0
  41. data/lib/s_data/virtual_base.rb +114 -0
  42. data/test/functional/Rakefile +0 -0
  43. data/test/unit/active_record_mixin/active_record_mixin_spec.rb +20 -0
  44. data/test/unit/active_record_mixin/acts_as_sdata_spec.rb +41 -0
  45. data/test/unit/active_record_mixin/find_by_sdata_instance_id_spec.rb +34 -0
  46. data/test/unit/active_record_mixin/payload_spec.rb +622 -0
  47. data/test/unit/active_record_mixin/to_atom_spec.rb +85 -0
  48. data/test/unit/atom_entry_mixin/atom_entry_mixin_spec.rb +11 -0
  49. data/test/unit/atom_entry_mixin/to_attributes_spec.rb +30 -0
  50. data/test/unit/class_stubs/address.rb +19 -0
  51. data/test/unit/class_stubs/contact.rb +25 -0
  52. data/test/unit/class_stubs/customer.rb +70 -0
  53. data/test/unit/class_stubs/model_base.rb +17 -0
  54. data/test/unit/class_stubs/payload.rb +15 -0
  55. data/test/unit/class_stubs/sd_uuid.rb +28 -0
  56. data/test/unit/class_stubs/user.rb +40 -0
  57. data/test/unit/conditions_builder_spec.rb +54 -0
  58. data/test/unit/controller_mixin/acts_as_sdata_spec.rb +29 -0
  59. data/test/unit/controller_mixin/build_sdata_feed_spec.rb +50 -0
  60. data/test/unit/controller_mixin/controller_mixin_spec.rb +22 -0
  61. data/test/unit/controller_mixin/diagnosis_spec.rb +232 -0
  62. data/test/unit/controller_mixin/sdata_collection_spec.rb +78 -0
  63. data/test/unit/controller_mixin/sdata_create_instance_spec.rb +173 -0
  64. data/test/unit/controller_mixin/sdata_opensearch_and_links_spec.rb +382 -0
  65. data/test/unit/controller_mixin/sdata_scope/linked_model_spec.rb +58 -0
  66. data/test/unit/controller_mixin/sdata_scope/non_linked_model_spec.rb +66 -0
  67. data/test/unit/controller_mixin/sdata_scope/scoping_in_config_spec.rb +64 -0
  68. data/test/unit/controller_mixin/sdata_show_instance_spec.rb +98 -0
  69. data/test/unit/controller_mixin/sdata_update_instance_spec.rb +65 -0
  70. data/test/unit/payload_map/payload_map_hash_spec.rb +84 -0
  71. data/test/unit/payload_map/payload_map_spec.rb +144 -0
  72. data/test/unit/predicate_spec.rb +59 -0
  73. data/test/unit/router_mixin/routes_spec.rb +138 -0
  74. data/test/unit/spec.opts +4 -0
  75. data/test/unit/spec_helper.rb +47 -0
  76. data/test/unit/spec_helpers/nokogiri_extensions.rb +16 -0
  77. data/test/unit/sync_controller_mixin/controller_mixin_spec.rb +22 -0
  78. data/test/unit/sync_controller_mixin/sdata_collection_sync_feed_spec.rb +69 -0
  79. metadata +175 -0
@@ -0,0 +1,17 @@
1
+ module SData
2
+ module Sync
3
+ module SdataSyncingMixin
4
+ def sd_digest
5
+ SData::SdDigest.find_or_create(self.owner, self.sdata_name)
6
+ end
7
+
8
+ def sd_sync_state
9
+ @sd_sync_state ||= SdSyncState.find(:first, :conditions => {:sd_uuid_id => self.sd_uuid, :sd_digest_id => self.sd_digest})
10
+ end
11
+
12
+ def sd_sync_state=(s)
13
+ @sd_sync_state = s
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,114 @@
1
+ module SData
2
+ class VirtualBase
3
+ extend Forwardable
4
+ include SData::Sync::SdataSyncingMixin
5
+
6
+ class << self
7
+ attr_accessor :baze_class
8
+ end
9
+
10
+ class_inheritable_accessor :owner_key
11
+ self.owner_key = 'created_by_id'
12
+
13
+ attr_accessor :baze
14
+ def_delegators :baze, :id, :created_at, :updated_at, :save, :save!, :update_attributes, :update_attributes!, :created_by, :reload
15
+
16
+ def initialize(the_baze, the_type=nil)
17
+ @uuid_class = SData::SdUuid
18
+ self.baze = the_baze
19
+ if self.respond_to?('sdata_type') && the_type
20
+ self.sdata_type = the_type
21
+ end
22
+ super()
23
+ end
24
+
25
+ # temporary
26
+ def method_missing(meth, *args, &block)
27
+ if @payload
28
+ @payload.send(meth, *args, &block)
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ def baze_class_name
35
+ self.class.baze_class_name
36
+ end
37
+
38
+ def reference
39
+ # GCRM requires reference to be present and unique to each GCRM model. Decided to use baze id, but
40
+ # alone it is insufficient because some GCRM models (e.g. Address) can have the same baze id for different
41
+ # bazes (e.g. customer vs user). So using the form below.
42
+ # Adding GCRM model name is fine but unneeded. GCRM name _only_, without BB model name, is insufficient.
43
+ "#{self.baze.class.name.demodulize}_#{self.baze.id}"
44
+ end
45
+
46
+ def sd_class
47
+ self.class.name
48
+ end
49
+
50
+ def self.build_for(data, the_type=nil)
51
+ if data.is_a? Array
52
+ data.map {|item| virtual_base_for_object(item, the_type) }.compact
53
+ else
54
+ virtual_base_for_object(data, the_type)
55
+ end
56
+ end
57
+
58
+ def self.virtual_base_for_object(obj, the_type=nil)
59
+ if obj
60
+ vb = self.new(obj, the_type)
61
+ vb = DeletedObjectProxy.from_virtual_base(vb) if obj.class.paranoid? && obj.deleted?
62
+ vb
63
+ else
64
+ nil
65
+ end
66
+ end
67
+
68
+ def owner
69
+ raise "Security problem: owner not defined in subclass!"
70
+ end
71
+
72
+ # TODO -- should return all bb models that are used to composite the virtual model (or at least the subset whose modifications must be tracked)
73
+
74
+ def self.baze_classes
75
+ [self.baze_class]
76
+ end
77
+
78
+ def self.baze_class_name
79
+ baze_class.nil? ? nil : baze_class.name.demodulize
80
+ end
81
+
82
+ def self.sdata_name
83
+ name.demodulize
84
+ end
85
+
86
+ def self.find(*params)
87
+ self.new(self.baze_class.find(*params))
88
+ end
89
+
90
+ def self.first(*params)
91
+ self.new(self.baze_class.first(*params))
92
+ end
93
+
94
+ def self.all(*params)
95
+ self.collection(self.baze_class.all(*params))
96
+ end
97
+
98
+ def self.collection(arr)
99
+ arr.map do |obj|
100
+ case obj
101
+ when VirtualBase, DeletedObjectProxy
102
+ obj
103
+ when ActiveRecord::Base
104
+ self.new(obj)
105
+ else
106
+ obj
107
+ end
108
+ end
109
+ end
110
+
111
+ end
112
+ VirtualBase.extend SData::ActiveRecordExtensions::Mixin
113
+ VirtualBase.extend SData::ActiveRecordExtensions::SdataUuidableMixin
114
+ end
File without changes
@@ -0,0 +1,20 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ describe SData::ActiveRecordExtensions::Mixin do
4
+ describe "given a class which behaves like ActiveRecord::Base" do
5
+ before :all do
6
+ Base = Class.new
7
+ end
8
+
9
+ describe "when ActiveRecordExtensions::Mixin is included" do
10
+ before :each do
11
+ Base.extend SData::ActiveRecordExtensions::Mixin
12
+ end
13
+
14
+ it "should define .acts_as_data class method" do
15
+ Base.should respond_to(:acts_as_sdata)
16
+ Base.new.should_not respond_to(:acts_as_sdata)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,41 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+
4
+ describe SData::ActiveRecordExtensions::Mixin do
5
+ describe "given a class extended by ActiveRecordExtensions" do
6
+ before :all do
7
+ Base = Class.new
8
+ Base.extend SData::ActiveRecordExtensions::Mixin
9
+ end
10
+
11
+ describe ".acts_as_sdata" do
12
+ before :each do
13
+ @options = { :title => lambda { "#{id}: #{name}" },
14
+ :summary => lambda { "#{name}" } }
15
+
16
+ options = @options
17
+ Base.class_eval do
18
+ acts_as_sdata options
19
+ end
20
+ end
21
+
22
+ it "should save passed options to variable accessible by class" do
23
+ Base.sdata_options.should == @options
24
+ end
25
+
26
+ it "should save passed options to variable accessible by instances" do
27
+ Base.new.sdata_options.should == @options
28
+ end
29
+
30
+ it "should include instance methods" do
31
+ Base.new.should respond_to(:to_atom)
32
+ end
33
+
34
+ describe "when :instance_id is passed" do
35
+ it "should check if passed attribute is unique" do
36
+ pending "Not implemented yet"
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,34 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ describe SData::ActiveRecordExtensions::Mixin, "#find_by_sdata_instance_id" do
4
+ describe "given a class extended by ActiveRecordExtensions" do
5
+ before :all do
6
+ Model = Class.new(ActiveRecord::Base)
7
+ Model.extend SData::ActiveRecordExtensions::Mixin
8
+ end
9
+
10
+ describe "when @@sdata_options contain :instance_id" do
11
+ before :each do
12
+ Model.class_eval { acts_as_sdata :instance_id => :email }
13
+ end
14
+
15
+ it "should find by a field assigned to :instance_id option value" do
16
+ email = "e@ma.il"
17
+ Model.should_receive(:find).with(:first, :conditions => { :email => email }).once
18
+ Model.find_by_sdata_instance_id(email)
19
+ end
20
+ end
21
+
22
+ describe "when @@sdata_options does not contain :instance_id" do
23
+ before :each do
24
+ Model.class_eval { acts_as_sdata }
25
+ end
26
+
27
+ it "should consider :id as SData instance ID" do
28
+ id = 1
29
+ Model.should_receive(:find).with(id).once
30
+ Model.find_by_sdata_instance_id(id)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,622 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ describe SData::ActiveRecordExtensions::Mixin, "#to_atom" do
4
+ describe "given a class extended by ActiveRecordExtensions" do
5
+ before :all do
6
+ SData::SdUuid.extend SData::ActiveRecordExtensions::SdataUuidMixin
7
+ SData::SdUuid.class_eval { acts_as_sdata_uuid }
8
+ [User, Customer, Contact, Address].each do |model|
9
+ model.extend SData::ActiveRecordExtensions::Mixin
10
+ model.extend SData::ActiveRecordExtensions::SdataUuidableMixin
11
+ model.class_eval { acts_as_sdata }
12
+ end
13
+ Customer.class_eval { has_sdata_uuid }
14
+ Contact.class_eval { has_sdata_uuid }
15
+ end
16
+
17
+ def customer_attributes
18
+ ["crmErp:address", "crmErp:associatedContacts", "crmErp:createdAt", "crmErp:hashValue", "crmErp:myContacts", "crmErp:myDefaultContact", "crmErp:name", "crmErp:number", "crmErp:simpleElements", "crmErp:updatedAt"].to_set
19
+ end
20
+
21
+ def contact_attributes
22
+ ["crmErp:createdAt", "crmErp:customerId", "crmErp:name", "crmErp:updatedAt"].to_set
23
+ end
24
+
25
+ def payload_header_assertions(payload)
26
+ payload.children.size.should == 1
27
+ customer = payload.xpath('crmErp:customer').first
28
+ customer.attributes_with_ns.keys.to_set.should == ["sdata:key", "sdata:url", "sdata:uuid"].to_set
29
+ customer.attributes_with_ns["sdata:key"].should == "12345"
30
+ customer.attributes_with_ns["sdata:url"].should == "http://www.example.com/sdata/example/myContract/myDataSet/customers('12345')"
31
+ customer.attributes_with_ns["sdata:uuid"].should == 'CUST-10000'
32
+ end
33
+
34
+ describe "given the payload generating conditions" do
35
+ before :each do
36
+ Customer.extend SData::PayloadMap
37
+ @customer = Customer.new.populate_defaults
38
+ @customer.id = 12345
39
+ @customer.contacts[0].id = 123
40
+ @customer.contacts[1].id = 456
41
+ @customer.contacts[1].name = "Second Contact Name"
42
+ @customer.contacts.each do |contact|
43
+ contact.populate_defaults
44
+ end
45
+ @customer.address.populate_defaults
46
+ end
47
+
48
+ def payload(options)
49
+ xml = Nokogiri::XML(@customer.to_atom(options).to_xml) { |config| config.noblanks }
50
+ xml.xpath('xmlns:entry/sdata:payload').first
51
+ end
52
+
53
+ it "describes elements with recursively included children without including association data" do
54
+ payload = payload(:dataset => 'myDataSet', :include => '$children')
55
+
56
+ payload_header_assertions(payload)
57
+
58
+ customer = payload.xpath('crmErp:customer')
59
+ customer.children.map(&:name_with_ns).to_set.should == customer_attributes
60
+ customer.children.each do |element|
61
+ case element.name_with_ns
62
+
63
+ when 'crmErp:associatedContacts'
64
+ element.attributes_with_ns.keys.should == ['sdata:url']
65
+ element.attributes_with_ns['sdata:url'].should == "http://www.example.com/sdata/example/myContract/myDataSet/customer('12345')/associatedContacts"
66
+ element.children.map(&:name_with_ns).to_set.should == ["crmErp:contact", "crmErp:contact"].to_set
67
+ element.children.each do |child_element|
68
+ case child_element.attributes_with_ns['sdata:key']
69
+ when '123'
70
+ child_element.attributes_with_ns.keys.to_set.should == ['sdata:key', 'sdata:url', 'sdata:uuid'].to_set
71
+ child_element.attributes_with_ns['sdata:key'].should == "123"
72
+ child_element.attributes_with_ns['sdata:url'].should == "http://www.example.com/sdata/example/myContract/myDataSet/contacts('123')"
73
+ child_element.attributes_with_ns['sdata:uuid'].should == "C-123-456"
74
+ child_element.children.should be_blank
75
+ when '456'
76
+ child_element.attributes_with_ns.keys.to_set.should == ['sdata:key', 'sdata:url'].to_set
77
+ child_element.attributes_with_ns['sdata:key'].should == "456"
78
+ child_element.attributes_with_ns['sdata:url'].should == "http://www.example.com/sdata/example/myContract/myDataSet/contacts('456')"
79
+ child_element.children.should be_blank
80
+ else
81
+ raise "Unknown contact attribute: #{child_element.attributes_with_ns['sdata:key']}"
82
+ end
83
+ end
84
+ when 'crmErp:simpleElements'
85
+ element.children.map(&:name_with_ns).to_set.should == ["crmErp:simpleElement", "crmErp:simpleElement"].to_set
86
+ element.xpath('crmErp:simpleElement/text()').map(&:to_s).should == ["element 1", "element 2"]
87
+ when 'crmErp:hashValue'
88
+ element.children.size.should == 1
89
+ element.children[0].name_with_ns.should == "crmErp:simpleObjectKey"
90
+ element.children[0].text.should == "simple_object_value"
91
+ when 'crmErp:myContacts'
92
+
93
+ element.attributes_with_ns.keys.should == ['sdata:url']
94
+ element.attributes_with_ns['sdata:url'].should == "http://www.example.com/sdata/example/myContract/myDataSet/customer('12345')/myContacts"
95
+ element.children.map(&:name_with_ns).to_set.should == ["crmErp:contact", "crmErp:contact"].to_set
96
+ element.children.each do |child_element|
97
+ case child_element.attributes_with_ns['sdata:key']
98
+ when '123'
99
+ child_element.attributes_with_ns.keys.to_set.should == ['sdata:key', 'sdata:url', 'sdata:uuid'].to_set
100
+ child_element.attributes_with_ns['sdata:key'].should == "123"
101
+ child_element.attributes_with_ns['sdata:url'].should == "http://www.example.com/sdata/example/myContract/myDataSet/contacts('123')"
102
+ child_element.attributes_with_ns['sdata:uuid'].should == "C-123-456"
103
+ child_element.children.map(&:name_with_ns).to_set.should == contact_attributes
104
+ child_element.children.each do |grandchild_element|
105
+ case grandchild_element.name_with_ns
106
+ when "crmErp:createdAt"
107
+ Time.parse(grandchild_element.text).should < Time.now-2.days
108
+ when "crmErp:name"
109
+ grandchild_element.attributes_with_ns["xsi:nil"].should == nil
110
+ grandchild_element.text.should == "Contact Name"
111
+ when "crmErp:customerId"
112
+ grandchild_element.attributes_with_ns["xsi:nil"].should == 'true'
113
+ when "crmErp:updatedAt"
114
+ Time.parse(grandchild_element.text).should < Time.now-1.day
115
+ else
116
+ raise "Unknown contact element: #{grandchild_element.name_with_ns}"
117
+ end
118
+ end
119
+ when '456'
120
+ child_element.attributes_with_ns.keys.to_set.should == ['sdata:key', 'sdata:url'].to_set
121
+ child_element.attributes_with_ns['sdata:key'].should == "456"
122
+ child_element.attributes_with_ns['sdata:url'].should == "http://www.example.com/sdata/example/myContract/myDataSet/contacts('456')"
123
+ child_element.children.map(&:name_with_ns).to_set.should == contact_attributes
124
+ child_element.children.each do |grandchild_element|
125
+ case grandchild_element.name_with_ns
126
+ when "crmErp:createdAt"
127
+ Time.parse(grandchild_element.text).should < Time.now-2.days
128
+ when "crmErp:name"
129
+ grandchild_element.attributes_with_ns["xsi:nil"].should == nil
130
+ grandchild_element.text.should == "Second Contact Name"
131
+ when "crmErp:customerId"
132
+ grandchild_element.attributes_with_ns["xsi:nil"].should == 'true'
133
+ when "crmErp:updatedAt"
134
+ Time.parse(child_element.text).should < Time.now-1.day
135
+ else
136
+ raise "Unknown contact element: #{grandchild_element.name_with_ns}"
137
+ end
138
+ end
139
+ else
140
+ raise "Unknown contact attribute: #{child_element.attributes_with_ns['sdata:key']}"
141
+ end
142
+ end
143
+ when 'crmErp:myDefaultContact'
144
+ element.attributes_with_ns["sdata:key"].should == "123"
145
+ element.attributes_with_ns["sdata:url"].should == "http://www.example.com/sdata/example/myContract/myDataSet/contacts('123')"
146
+ element.children.map(&:name_with_ns).to_set.should == contact_attributes
147
+ element.children.each do |child_element|
148
+ case child_element.name_with_ns
149
+ when "crmErp:createdAt"
150
+ Time.parse(child_element.text).should < Time.now-2.days
151
+ when "crmErp:name"
152
+ child_element.text.should == "Contact Name"
153
+ when "crmErp:customerId"
154
+ child_element.attributes_with_ns["xsi:nil"].should == 'true'
155
+ when "crmErp:updatedAt"
156
+ Time.parse(child_element.text).should < Time.now-1.day
157
+ when "crmErp:id"
158
+ element.text.should_not be_nil
159
+ else
160
+ raise "Unknown contact element: #{child_element.name_with_ns}"
161
+ end
162
+ end
163
+ when 'crmErp:address'
164
+ element.attributes_with_ns["sdata:key"].should == "12345"
165
+ element.attributes_with_ns["sdata:url"].should == "http://www.example.com/sdata/example/myContract/myDataSet/addresses('12345')"
166
+ element.children.each do |child_element|
167
+ case child_element.name_with_ns
168
+ when "crmErp:createdAt"
169
+ Time.parse(child_element.text).should < Time.now-2.days
170
+ when "crmErp:customerId"
171
+ child_element.attributes_with_ns["xsi:nil"].should == 'true'
172
+ when "crmErp:updatedAt"
173
+ Time.parse(child_element.text).should < Time.now-1.day
174
+ when "crmErp:city"
175
+ child_element.text.should == 'Vancouver'
176
+ else
177
+ raise "Unknown address element: #{child_element.name_with_ns}"
178
+ end
179
+ end
180
+ when 'crmErp:name'
181
+ element.text.should == "Customer Name"
182
+ when 'crmErp:number'
183
+ element.text.should == "12345"
184
+ when "crmErp:createdAt"
185
+ Time.parse(element.text).should < Time.now-2.days
186
+ when "crmErp:updatedAt"
187
+ Time.parse(element.text).should < Time.now-1.days
188
+ else
189
+ raise "Unexpected customer element: #{element.name_with_ns}"
190
+ end
191
+ end
192
+ end
193
+
194
+ it "describes elements with recursively included children without including association data when in sync mode" do
195
+ payload = payload(:dataset => 'myDataSet', :sync => 'true')
196
+
197
+ payload_header_assertions(payload)
198
+
199
+ customer = payload.xpath('crmErp:customer')
200
+ customer.children.map(&:name_with_ns).to_set.should == customer_attributes
201
+ customer.children.each do |element|
202
+ case element.name_with_ns
203
+
204
+ when 'crmErp:associatedContacts'
205
+ element.attributes_with_ns.keys.should == ['sdata:url']
206
+ element.attributes_with_ns['sdata:url'].should == "http://www.example.com/sdata/example/myContract/myDataSet/customer('12345')/associatedContacts"
207
+ element.children.map(&:name_with_ns).to_set.should == ["crmErp:contact", "crmErp:contact"].to_set
208
+ element.children.each do |child_element|
209
+ case child_element.attributes_with_ns['sdata:key']
210
+ when '123'
211
+ child_element.attributes_with_ns.keys.to_set.should == ['sdata:key', 'sdata:url', 'sdata:uuid'].to_set
212
+ child_element.attributes_with_ns['sdata:key'].should == "123"
213
+ child_element.attributes_with_ns['sdata:url'].should == "http://www.example.com/sdata/example/myContract/myDataSet/contacts('123')"
214
+ child_element.attributes_with_ns['sdata:uuid'].should == "C-123-456"
215
+ child_element.children.should be_blank
216
+ when '456'
217
+ child_element.attributes_with_ns.keys.to_set.should == ['sdata:key', 'sdata:url'].to_set
218
+ child_element.attributes_with_ns['sdata:key'].should == "456"
219
+ child_element.attributes_with_ns['sdata:url'].should == "http://www.example.com/sdata/example/myContract/myDataSet/contacts('456')"
220
+ child_element.children.should be_blank
221
+ else
222
+ raise "Unknown contact attribute: #{child_element.attributes_with_ns['sdata:key']}"
223
+ end
224
+ end
225
+ when 'crmErp:simpleElements'
226
+ element.children.map(&:name_with_ns).to_set.should == ["crmErp:simpleElement", "crmErp:simpleElement"].to_set
227
+ element.xpath('crmErp:simpleElement/text()').map(&:to_s).should == ["element 1", "element 2"]
228
+ when 'crmErp:hashValue'
229
+ element.children.size.should == 1
230
+ element.children[0].name_with_ns.should == "crmErp:simpleObjectKey"
231
+ element.children[0].text.should == "simple_object_value"
232
+ when 'crmErp:myContacts'
233
+ element.attributes_with_ns.keys.should == ['sdata:url']
234
+ element.attributes_with_ns['sdata:url'].should == "http://www.example.com/sdata/example/myContract/myDataSet/customer('12345')/myContacts"
235
+ element.children.map(&:name_with_ns).to_set.should == ["crmErp:contact", "crmErp:contact"].to_set
236
+ element.children.each do |child_element|
237
+ case child_element.attributes_with_ns['sdata:key']
238
+ when '123'
239
+ child_element.attributes_with_ns.keys.to_set.should == ['sdata:key', 'sdata:url', 'sdata:uuid'].to_set
240
+ child_element.attributes_with_ns['sdata:key'].should == "123"
241
+ child_element.attributes_with_ns['sdata:url'].should == "http://www.example.com/sdata/example/myContract/myDataSet/contacts('123')"
242
+ child_element.attributes_with_ns['sdata:uuid'].should == "C-123-456"
243
+ child_element.children.map(&:name_with_ns).to_set.should == contact_attributes
244
+ child_element.children.each do |grandchild_element|
245
+ case grandchild_element.name_with_ns
246
+ when "crmErp:createdAt"
247
+ Time.parse(grandchild_element.text).should < Time.now-2.days
248
+ when "crmErp:name"
249
+ grandchild_element.attributes_with_ns["xsi:nil"].should == nil
250
+ grandchild_element.text.should == "Contact Name"
251
+ when "crmErp:customerId"
252
+ grandchild_element.attributes_with_ns["xsi:nil"].should == 'true'
253
+ when "crmErp:updatedAt"
254
+ Time.parse(grandchild_element.text).should < Time.now-1.day
255
+ else
256
+ raise "Unknown contact element: #{grandchild_element.name_with_ns}"
257
+ end
258
+ end
259
+ when '456'
260
+ child_element.attributes_with_ns.keys.to_set.should == ['sdata:key', 'sdata:url'].to_set
261
+ child_element.attributes_with_ns['sdata:key'].should == "456"
262
+ child_element.attributes_with_ns['sdata:url'].should == "http://www.example.com/sdata/example/myContract/myDataSet/contacts('456')"
263
+ child_element.children.map(&:name_with_ns).to_set.should == contact_attributes
264
+ child_element.children.each do |grandchild_element|
265
+ case grandchild_element.name_with_ns
266
+ when "crmErp:createdAt"
267
+ Time.parse(grandchild_element.text).should < Time.now-2.days
268
+ when "crmErp:name"
269
+ grandchild_element.attributes_with_ns["xsi:nil"].should == nil
270
+ grandchild_element.text.should == "Second Contact Name"
271
+ when "crmErp:customerId"
272
+ grandchild_element.attributes_with_ns["xsi:nil"].should == 'true'
273
+ when "crmErp:updatedAt"
274
+ Time.parse(child_element.text).should < Time.now-1.day
275
+ else
276
+ raise "Unknown contact element: #{grandchild_element.name_with_ns}"
277
+ end
278
+ end
279
+ else
280
+ raise "Unknown contact attribute: #{child_element.attributes_with_ns['sdata:key']}"
281
+ end
282
+ end
283
+ when 'crmErp:myDefaultContact'
284
+ element.attributes_with_ns["sdata:key"].should == "123"
285
+ element.attributes_with_ns["sdata:url"].should == "http://www.example.com/sdata/example/myContract/myDataSet/contacts('123')"
286
+ element.children.map(&:name_with_ns).to_set.should == contact_attributes
287
+ element.children.each do |child_element|
288
+ case child_element.name_with_ns
289
+ when "crmErp:createdAt"
290
+ Time.parse(child_element.text).should < Time.now-2.days
291
+ when "crmErp:name"
292
+ child_element.text.should == "Contact Name"
293
+ when "crmErp:customerId"
294
+ child_element.attributes_with_ns["xsi:nil"].should == 'true'
295
+ when "crmErp:updatedAt"
296
+ Time.parse(child_element.text).should < Time.now-1.day
297
+ when "crmErp:id"
298
+ element.text.should_not be_nil
299
+ else
300
+ raise "Unknown contact element: #{child_element.name_with_ns}"
301
+ end
302
+ end
303
+ when 'crmErp:address'
304
+ element.attributes_with_ns["sdata:key"].should == "12345"
305
+ element.attributes_with_ns["sdata:url"].should == "http://www.example.com/sdata/example/myContract/myDataSet/addresses('12345')"
306
+ element.children.each do |child_element|
307
+ case child_element.name_with_ns
308
+ when "crmErp:createdAt"
309
+ Time.parse(child_element.text).should < Time.now-2.days
310
+ when "crmErp:customerId"
311
+ child_element.attributes_with_ns["xsi:nil"].should == 'true'
312
+ when "crmErp:updatedAt"
313
+ Time.parse(child_element.text).should < Time.now-1.day
314
+ when "crmErp:city"
315
+ child_element.text.should == 'Vancouver'
316
+ else
317
+ raise "Unknown address element: #{child_element.name_with_ns}"
318
+ end
319
+ end
320
+ when 'crmErp:name'
321
+ element.text.should == "Customer Name"
322
+ when 'crmErp:number'
323
+ element.text.should == "12345"
324
+ when "crmErp:createdAt"
325
+ Time.parse(element.text).should < Time.now-2.days
326
+ when "crmErp:updatedAt"
327
+ Time.parse(element.text).should < Time.now-1.days
328
+ else
329
+ raise "Unexpected customer element: #{element.name_with_ns}"
330
+ end
331
+ end
332
+ end
333
+
334
+ it "describes elements with immediate children only" do
335
+ payload = payload(:dataset => 'myDataSet')
336
+
337
+ payload_header_assertions(payload)
338
+ customer = payload.xpath('crmErp:customer')
339
+ customer.children.map(&:name_with_ns).to_set.should == customer_attributes
340
+ customer.children.each do |element|
341
+ case element.name_with_ns
342
+ when 'crmErp:associatedContacts'
343
+ element.attributes_with_ns.keys.should == ['sdata:url']
344
+ element.attributes_with_ns['sdata:url'].should == "http://www.example.com/sdata/example/myContract/myDataSet/customer('12345')/associatedContacts"
345
+ element.children.map(&:name_with_ns).to_set.should == ["crmErp:contact", "crmErp:contact"].to_set
346
+ element.children.each do |child_element|
347
+ case child_element.attributes_with_ns['sdata:key']
348
+ when '123'
349
+ child_element.attributes_with_ns.keys.to_set.should == ['sdata:key', 'sdata:url', 'sdata:uuid'].to_set
350
+ child_element.attributes_with_ns['sdata:key'].should == "123"
351
+ child_element.attributes_with_ns['sdata:url'].should == "http://www.example.com/sdata/example/myContract/myDataSet/contacts('123')"
352
+ child_element.attributes_with_ns['sdata:uuid'].should == "C-123-456"
353
+ child_element.children.should be_blank
354
+ when '456'
355
+ child_element.attributes_with_ns.keys.to_set.should == ['sdata:key', 'sdata:url'].to_set
356
+ child_element.attributes_with_ns['sdata:key'].should == "456"
357
+ child_element.attributes_with_ns['sdata:url'].should == "http://www.example.com/sdata/example/myContract/myDataSet/contacts('456')"
358
+ child_element.children.should be_blank
359
+ else
360
+ raise "Unknown contact attribute: #{child_element.attributes_with_ns['sdata:key']}"
361
+ end
362
+ end
363
+ when 'crmErp:simpleElements'
364
+ element.children.map(&:name_with_ns).to_set.should == ["crmErp:simpleElement", "crmErp:simpleElement"].to_set
365
+ element.xpath('crmErp:simpleElement/text()').map(&:to_s).should == ["element 1", "element 2"]
366
+ when 'crmErp:hashValue'
367
+ element.children.size.should == 1
368
+ element.children[0].name_with_ns.should == "crmErp:simpleObjectKey"
369
+ element.children[0].text.should == "simple_object_value"
370
+ when 'crmErp:myContacts'
371
+ element.attributes_with_ns.keys.should == ['sdata:url']
372
+ element.attributes_with_ns['sdata:url'].should == "http://www.example.com/sdata/example/myContract/myDataSet/customer('12345')/myContacts"
373
+ element.attributes_with_ns.keys.should == ['sdata:url']
374
+ element.attributes_with_ns['sdata:url'].should == "http://www.example.com/sdata/example/myContract/myDataSet/customer('12345')/myContacts"
375
+ element.children.map(&:name_with_ns).to_set.should == ["crmErp:contact", "crmErp:contact"].to_set
376
+ element.children.each do |child_element|
377
+ case child_element.attributes_with_ns['sdata:key']
378
+ when '123'
379
+ child_element.attributes_with_ns.keys.to_set.should == ['sdata:key', 'sdata:url', 'sdata:uuid'].to_set
380
+ child_element.attributes_with_ns['sdata:key'].should == "123"
381
+ child_element.attributes_with_ns['sdata:url'].should == "http://www.example.com/sdata/example/myContract/myDataSet/contacts('123')"
382
+ child_element.attributes_with_ns['sdata:uuid'].should == "C-123-456"
383
+ child_element.children.should be_blank
384
+ when '456'
385
+ child_element.attributes_with_ns.keys.to_set.should == ['sdata:key', 'sdata:url'].to_set
386
+ child_element.attributes_with_ns['sdata:key'].should == "456"
387
+ child_element.attributes_with_ns['sdata:url'].should == "http://www.example.com/sdata/example/myContract/myDataSet/contacts('456')"
388
+ child_element.children.should be_blank
389
+ else
390
+ raise "Unknown contact attribute: #{child_element.attributes_with_ns['sdata:key']}"
391
+ end
392
+ end
393
+ when 'crmErp:myDefaultContact'
394
+ element.attributes_with_ns["sdata:key"].should == "123"
395
+ element.attributes_with_ns["sdata:url"].should == "http://www.example.com/sdata/example/myContract/myDataSet/contacts('123')"
396
+ element.children.size.should == 0
397
+ when 'crmErp:name'
398
+ element.text.should == "Customer Name"
399
+ when 'crmErp:number'
400
+ element.text.should == "12345"
401
+ when "crmErp:createdAt"
402
+ Time.parse(element.text).should < Time.now-2.days
403
+ when "crmErp:updatedAt"
404
+ Time.parse(element.text).should < Time.now-1.days
405
+ when 'crmErp:address'
406
+ element.attributes_with_ns["sdata:key"].should == "12345"
407
+ element.attributes_with_ns["sdata:url"].should == "http://www.example.com/sdata/example/myContract/myDataSet/addresses('12345')"
408
+
409
+ element.children.each do |child_element|
410
+ case child_element.name_with_ns
411
+ when "crmErp:createdAt"
412
+ Time.parse(child_element.text).should < Time.now-2.days
413
+ when "crmErp:customerId"
414
+ child_element.attributes_with_ns["xsi:nil"].should == 'true'
415
+ when "crmErp:updatedAt"
416
+ Time.parse(child_element.text).should < Time.now-1.day
417
+ when "crmErp:city"
418
+ child_element.text.should == 'Vancouver'
419
+ else
420
+ raise "Unknown address element: #{child_element.name_with_ns}"
421
+ end
422
+ end
423
+ else
424
+ raise "Unexpected customer element: #{element.name_with_ns}"
425
+ end
426
+ end
427
+ end
428
+
429
+ it "shows no payload at all with precedence 0" do
430
+ payload = payload(:dataset => 'myDataSet', :precedence => 0)
431
+
432
+ payload.should be_nil
433
+ end
434
+
435
+ it "shows header info only with precedence 1" do
436
+ payload = payload(:dataset => 'myDataSet', :precedence => 1)
437
+
438
+ payload_header_assertions(payload)
439
+ customer = payload.xpath('crmErp:customer').first
440
+ customer.children.size.should == 0
441
+ end
442
+
443
+ it "shows no uuid if match is not found" do
444
+ @customer.id = 4321
445
+ payload = payload(:dataset => 'myDataSet', :precedence => 1)
446
+
447
+ customer = payload.xpath('crmErp:customer').first
448
+ customer.attributes_with_ns.keys.to_set.should == ["sdata:key", "sdata:url"].to_set
449
+ customer.children.size.should == 0
450
+ end
451
+
452
+ it "shows only some attributes with precedence 2" do
453
+ payload = payload(:dataset => 'myDataSet', :precedence => 2)
454
+
455
+ payload_header_assertions(payload)
456
+
457
+ customer = payload.xpath('crmErp:customer').first
458
+ customer.children.map(&:name_with_ns).to_set.should == ["crmErp:name"].to_set
459
+ customer.children.each do |element|
460
+ case element.name_with_ns
461
+ when 'crmErp:name'
462
+ element.text.should == "Customer Name"
463
+ when "crmErp:id"
464
+ element.text.should_not be_nil
465
+ else
466
+ raise "Unexpected customer element: #{element.name_with_ns}"
467
+ end
468
+ end
469
+ end
470
+
471
+ it "applies precendence filter to child attributes as well" do
472
+ payload = payload(:dataset => 'myDataSet', :include => "$children", :precedence => 3)
473
+
474
+ payload_header_assertions(payload)
475
+ customer = payload.xpath('crmErp:customer').first
476
+ customer.children.map(&:name_with_ns).to_set.should == ["crmErp:associatedContacts", "crmErp:createdAt", "crmErp:myDefaultContact", "crmErp:name", "crmErp:updatedAt"].to_set
477
+ customer.children.each do |element|
478
+ case element.name_with_ns
479
+ when 'crmErp:associatedContacts'
480
+ element.attributes_with_ns.keys.should == ['sdata:url']
481
+ element.attributes_with_ns['sdata:url'].should == "http://www.example.com/sdata/example/myContract/myDataSet/customer('12345')/associatedContacts"
482
+ element.children.map(&:name_with_ns).to_set.should == ["crmErp:contact", "crmErp:contact"].to_set
483
+ element.children.each do |child_element|
484
+ case child_element.attributes_with_ns['sdata:key']
485
+ when '123'
486
+ child_element.attributes_with_ns.keys.to_set.should == ['sdata:key', 'sdata:url', 'sdata:uuid'].to_set
487
+ child_element.attributes_with_ns['sdata:key'].should == "123"
488
+ child_element.attributes_with_ns['sdata:url'].should == "http://www.example.com/sdata/example/myContract/myDataSet/contacts('123')"
489
+ child_element.attributes_with_ns['sdata:uuid'].should == "C-123-456"
490
+ child_element.children.should be_blank
491
+ when '456'
492
+ child_element.attributes_with_ns.keys.to_set.should == ['sdata:key', 'sdata:url'].to_set
493
+ child_element.attributes_with_ns['sdata:key'].should == "456"
494
+ child_element.attributes_with_ns['sdata:url'].should == "http://www.example.com/sdata/example/myContract/myDataSet/contacts('456')"
495
+ child_element.children.should be_blank
496
+ else
497
+ raise "Unknown contact attribute: #{child_element.attributes_with_ns['sdata:key']}"
498
+ end
499
+ end
500
+ when 'crmErp:name'
501
+ element.text.should == "Customer Name"
502
+ when "crmErp:createdAt"
503
+ Time.parse(element.text).should < Time.now-2.days
504
+ when "crmErp:updatedAt"
505
+ Time.parse(element.text).should < Time.now-1.days
506
+ when 'crmErp:myDefaultContact'
507
+ element.attributes_with_ns["sdata:key"].should == "123"
508
+ element.attributes_with_ns["sdata:url"].should == "http://www.example.com/sdata/example/myContract/myDataSet/contacts('123')"
509
+ element.children.map(&:name_with_ns).to_set.should == ["crmErp:customerId", "crmErp:name"].to_set
510
+ element.children.each do |child_element|
511
+ case child_element.name_with_ns
512
+ when "crmErp:name"
513
+ child_element.text.should == "Contact Name"
514
+ when "crmErp:customerId"
515
+ child_element.attributes_with_ns["xsi:nil"].should == 'true'
516
+ when "crmErp:id"
517
+ element.text.should_not be_nil
518
+ else
519
+ raise "Unknown contact element: #{child_element.name_with_ns}"
520
+ end
521
+ end
522
+ when "crmErp:id"
523
+ element.text.should_not be_nil
524
+ else
525
+ raise "Unexpected customer element: #{element.name_with_ns}"
526
+ end
527
+ end
528
+ end
529
+
530
+ it "shows custom content and descriptor fields when requested" do
531
+ Customer.class_eval { acts_as_sdata(:content => :sdata_content) }
532
+ Contact.class_eval { acts_as_sdata(:content => :sdata_content) }
533
+
534
+ payload = payload(:dataset => 'myDataSet', :include => "$descriptor,$children")
535
+
536
+ payload.children.size.should == 1
537
+ customer = payload.xpath('crmErp:customer').first
538
+ customer.name_with_ns.should == 'crmErp:customer'
539
+ customer.attributes_with_ns.keys.to_set.should == ["sdata:descriptor", "sdata:key", "sdata:url", "sdata:uuid"].to_set
540
+ customer.attributes_with_ns["sdata:key"].should == "12345"
541
+ customer.attributes_with_ns["sdata:url"].should == "http://www.example.com/sdata/example/myContract/myDataSet/customers('12345')"
542
+ customer.attributes_with_ns["sdata:descriptor"].should == "Customer #12345: Customer Name"
543
+ customer.children.each do |element|
544
+ case element.name_with_ns
545
+ when 'crmErp:myDefaultContact'
546
+ element.attributes_with_ns.keys.to_set.should == ["sdata:descriptor", "sdata:key", "sdata:url", "sdata:uuid"].to_set
547
+ element.attributes_with_ns['sdata:descriptor'].should == "Contact #123: Contact Name"
548
+ when 'crmErp:myContacts'
549
+ found_with_uuid, found_without_uuid = false
550
+ element.children.size.should == 2
551
+ element.children.each do |child_element|
552
+ if child_element.attributes_with_ns['sdata:uuid']
553
+ found_with_uuid = true
554
+ child_element.attributes_with_ns.keys.to_set.should == ["sdata:descriptor", "sdata:key", "sdata:url", "sdata:uuid"].to_set
555
+ child_element.attributes_with_ns['sdata:uuid'].should == "C-123-456"
556
+ else
557
+ found_without_uuid = true
558
+ child_element.attributes_with_ns.keys.to_set.should == ["sdata:descriptor", "sdata:key", "sdata:url"].to_set
559
+ end
560
+ child_element.attributes_with_ns['sdata:descriptor'].should =~ /Contact ##{child_element.attributes_with_ns['sdata:key']}.*/
561
+ end
562
+ found_with_uuid.should == true
563
+ found_without_uuid.should == true
564
+ end
565
+ end
566
+ end
567
+
568
+ it "makes include=something_other_than_$children_or_$descriptor do absolutely nothing until we implement the algorithm properly" do
569
+ [true, false].each do |bool|
570
+ SData::Payload.stub :is_sync? => bool
571
+ payload_1 = payload(:dataset => 'myDataSet')
572
+ payload_2 = payload(:dataset => 'myDataSet', :include => 'associatedContacts')
573
+ payload_3 = payload(:dataset => 'myDataSet', :include => 'myContacts')
574
+ payload_1.to_xml.should == payload_2.to_xml
575
+ payload_1.to_xml.should == payload_3.to_xml
576
+ end
577
+ end
578
+
579
+ it "includes associations on nested include request for that association and properly parses nesting levels" do
580
+ # ?include='child/grandchild' should find and include the child, then traverse thru child attributes
581
+ # and find the grandchild, to include it too.
582
+ # But it should not search for or include 'grandchild' association (or any other association except
583
+ # 'child') from outside of the child's tree, nor should it search for or include 'child' association
584
+ # (or any other association) from inside the child's tree.
585
+ pending # Not due June 1
586
+ end
587
+
588
+ it "only shows requested attributes when a simple select parameter is given" do
589
+ payload = payload(:dataset => 'myDataSet', :select => 'name,number')
590
+
591
+ payload_header_assertions(payload)
592
+ customer = payload.xpath('crmErp:customer').first
593
+ customer.children.map(&:name_with_ns).to_set.should == ['crmErp:name', 'crmErp:number'].to_set
594
+ customer.children.each do |element|
595
+ case element.name_with_ns
596
+ when 'crmErp:name'
597
+ element.text.should == "Customer Name"
598
+ when 'crmErp:number'
599
+ element.text.should == "12345"
600
+ else
601
+ raise "Unexpected customer element: #{element.name_with_ns}"
602
+ end
603
+ end
604
+ end
605
+
606
+ it "only shows requested attributes when a nested select parameter is given and properly parses nesting level" do
607
+ pending # Not due June 1
608
+ end
609
+
610
+ it "properly escapes xml content in user data" do
611
+ Customer.class_eval { acts_as_sdata(:content => :sdata_content) }
612
+ Contact.class_eval { acts_as_sdata(:content => :sdata_content) }
613
+ @customer.name = "</crmErp:name><div>asdf</div>"
614
+ @customer.number = "<div>123456</div>"
615
+ @customer.to_atom(:dataset => 'myDataSet').sdata_payload.to_xml.to_s.include?("</crmErp:name><div>asdf</div>").should == false
616
+ @customer.to_atom(:dataset => 'myDataSet').sdata_payload.to_xml.to_s.include?("<div>123456</div>").should == false
617
+ @customer.to_atom(:dataset => 'myDataSet').sdata_payload.to_xml.to_s.include?("&lt;/crmErp:name&gt;&lt;div&gt;asdf&lt;/div&gt;").should == true
618
+ @customer.to_atom(:dataset => 'myDataSet').sdata_payload.to_xml.to_s.include?("&lt;div&gt;123456&lt;/div&gt;").should == true
619
+ end
620
+ end
621
+ end
622
+ end