acts_as_sdata 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
File without changes
@@ -0,0 +1,136 @@
1
+
2
+
3
+ module SData
4
+ module PayloadMap
5
+ def define_payload_map(map)
6
+ include InstanceMethods
7
+ cattr_accessor :payload_map
8
+ self.payload_map = PayloadMapHash.new(map)
9
+ self.payload_map.each do |name, opts|
10
+ has_sdata_attr(name, opts)
11
+ end
12
+ end
13
+
14
+ def has_sdata_attr(*args)
15
+ options = args.last.is_a?(Hash) ? args.pop : {:static_value => nil, :precedence => 50}
16
+ args.each do | name |
17
+ options[:method_name] ||= name
18
+ options[:method_name_with_deleted] = options[:method_name]
19
+
20
+ method_name = options[:method_name]
21
+ payload_map[name] ||= options
22
+
23
+ case field_type(options)
24
+ when :static_value
25
+ define_static_value_reader method_name, options[:static_value]
26
+
27
+ when :baze_field
28
+ define_baze_field_reference method_name, options[:baze_field]
29
+
30
+ when :method
31
+
32
+ when :proc
33
+ define_proc_caller method_name, options[:proc]
34
+
35
+ if options.has_key?(:proc_with_deleted)
36
+ method_name_with_deleted = options[:method_name_with_deleted] = "#{method_name.to_s}_with_deleted"
37
+ define_proc_with_deleted_caller method_name_with_deleted, options[:proc_with_deleted]
38
+ end
39
+
40
+ else
41
+ raise SData::Exceptions::VirtualBase::InvalidSDataAttribute.new(
42
+ "#{args.join(", ")}: must supply a static_value, baze_field, method or proc")
43
+ end
44
+ end
45
+ end
46
+
47
+ def has_sdata_association(*args)
48
+ options = args.last.is_a?(Hash) ? args.pop : {:precedence => 50, :type => :association}
49
+ options[:type] ||= :association
50
+ raise SData::Exceptions::VirtualBase::InvalidSDataAssociation.new(
51
+ "#{args.join(", ")}: must supply a proc or method") unless [:proc, :method].any?{|k|options.has_key?(k)}
52
+ raise SData::Exceptions::VirtualBase::InvalidSDataAssociation.new(
53
+ "#{args.join(", ")}: invalid association type '#{options[:type]}") unless [:association, :child].include?(options[:type])
54
+ args.push options
55
+ has_sdata_attr(*args)
56
+ end
57
+
58
+ module InstanceMethods
59
+ def payload
60
+ self
61
+ end
62
+
63
+
64
+ # Walks the payload, loading each association, descending into children, and yielding the tuple
65
+ # [payload_map_definition, node_object(s)] for each node. Can use without a block
66
+ # to fire all faults -- sync uses this when caching changed objects.
67
+ def associations_with_deleted(expand=:all_children)
68
+ return if expand == :none
69
+ payload_map.each_pair do |child_node_name, child_node_data|
70
+ if child_node_data[:type] == :association
71
+ expand = :none
72
+ elsif child_node_data[:type] == :child
73
+ expand = :all_children
74
+ else
75
+ next
76
+ end
77
+ child = __send__(child_node_data[:method_name_with_deleted])
78
+ yield child_node_data.merge(:name => child_node_name), child if block_given?
79
+ case child
80
+ when Array
81
+ child.each{ |grandchild| grandchild.associations_with_deleted(expand) if child.is_a?(SData::VirtualBase)}
82
+ when SData::VirtualBase
83
+ associations_with_deleted(expand)
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ protected
90
+
91
+ def field_type(options)
92
+ [:static_value, :baze_field, :method, :proc].each do |type|
93
+ if options.has_key?(type)
94
+ return type
95
+ end
96
+ end
97
+ nil
98
+ end
99
+
100
+ def define_static_value_reader(method_name, value)
101
+ class_eval do
102
+ define_method method_name do
103
+ value
104
+ end
105
+ end
106
+ end
107
+
108
+ def define_baze_field_reference(method_name, baze_field)
109
+ class_eval do
110
+ define_method method_name do
111
+ self.baze.__send__ baze_field
112
+ end
113
+ end
114
+ end
115
+
116
+ def define_proc_caller(method_name, block)
117
+ class_eval do
118
+ define_method method_name, block
119
+ end
120
+ end
121
+
122
+ def define_proc_with_deleted_caller(method_name, block)
123
+ cache_var = "@#{method_name.to_s}_cached"
124
+ class_eval do
125
+ define_method(method_name) do
126
+ unless instance_variable_get(cache_var)
127
+ instance_variable_set(cache_var, instance_eval(&block))
128
+ end
129
+ instance_variable_get(cache_var)
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
135
+
136
+ SData::VirtualBase.extend SData::PayloadMap
@@ -0,0 +1,39 @@
1
+ module SData
2
+ module PayloadMap
3
+ class PayloadMapHash < Hash
4
+ def initialize(hash)
5
+ merge!(hash)
6
+ end
7
+
8
+ def attrs
9
+ reject{|k,v| ! [:static_value, :baze_field].any?{|type| v.has_key?(type)} }
10
+ end
11
+
12
+ def static_values
13
+ subset :static_value
14
+ end
15
+
16
+ def baze_fields
17
+ subset :baze_field
18
+ end
19
+
20
+ def procs
21
+ subset :proc
22
+ end
23
+
24
+ def procs_with_deleted
25
+ subset :proc_with_deleted, :proc
26
+ end
27
+
28
+ protected
29
+
30
+ def subset(*mapping_type)
31
+ mapping_type = [mapping_type].flatten
32
+ raw_subset = select { |key, value| mapping_type.any?{|type| value.has_key?(type) }}
33
+ hash = Hash[raw_subset]
34
+ hash.merge(hash){ |key, value| value[mapping_type.detect{|type| value.include?(type)}] }
35
+ end
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,31 @@
1
+ module SData
2
+ class Predicate < Struct.new(:field, :relation, :value)
3
+ def self.parse(fields_map, predicate_string)
4
+ # Gotcha on Rightscale that escaped + before it hits controller, but doesn't escape other special chars!
5
+ escaped_predicate_string = CGI::unescape(predicate_string.gsub('+', '%2B'))
6
+ match_data = escaped_predicate_string.match(/(\w+)\s(gt|lt|eq|ne|lteq|gteq)\s('?.+'?|'')/) || []
7
+
8
+ canonical_field_name = match_data[1].underscore.to_sym unless match_data[1].nil?
9
+ self.new fields_map[canonical_field_name], match_data[2], strip_quotes(match_data[3])
10
+ end
11
+
12
+ def self.strip_quotes(value)
13
+ return value unless value.is_a?(String)
14
+ value = value.gsub("%27", "'")
15
+ return value unless value =~ /'.*?'/
16
+ return value[1,value.length-2]
17
+ end
18
+
19
+ def to_conditions
20
+ if valid?
21
+ ConditionsBuilder.build_conditions field, relation.to_sym, value
22
+ else
23
+ raise Sage::BusinessLogic::Exception::IncompatibleDataException, "Invalid predicate string"
24
+ end
25
+ end
26
+
27
+ def valid?
28
+ field && relation && value
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,143 @@
1
+ module SData
2
+ class RouteMapper < Struct.new(:router, :resource_name, :options)
3
+ def map_sdata_routes!
4
+ #RADAR: the order of the below statements makes a difference
5
+
6
+ map_sdata_show_instance
7
+ map_sdata_show_instance_with_condition_and_predicate
8
+ map_sdata_show_instance_with_condition
9
+ map_sdata_show_instance_with_predicate
10
+
11
+ map_sdata_create_link
12
+ map_sdata_create_instance
13
+ map_sdata_update_instance
14
+
15
+ map_sdata_sync_source
16
+ map_sdata_sync_source_status
17
+ map_sdata_sync_source_delete
18
+ map_sdata_receive_sync_results
19
+
20
+ map_sdata_collection_with_condition
21
+ map_sdata_collection
22
+ end
23
+
24
+ def self.urlize(string)
25
+ string.gsub("'", "(%27|')").gsub("\s", "(%20|\s)")
26
+ end
27
+
28
+ protected
29
+
30
+ CONDITION = urlize("{:condition,([$](linked))}")
31
+ PREDICATE = urlize("{:predicate,[A-z]+\s[A-z]+\s'?[^']*'?}")
32
+
33
+ TRACKING_ID = urlize("{:trackingID,'[^']+'}")
34
+
35
+ # http://localhost:3000/sdata/billingboss/crmErp/-/tradingAccounts
36
+ def map_sdata_collection
37
+ map_route "#{name_in_path}", 'sdata_collection', :get
38
+ end
39
+
40
+ # http://localhost:3000/sdata/billingboss/crmErp/-/tradingAccounts/$syncSource('someid')
41
+ def map_sdata_sync_source_status
42
+ map_route "#{name_in_path}\/$syncSource\\(:trackingID\\)", 'sdata_collection_sync_feed_status', :get
43
+ end
44
+
45
+ # http://localhost:3000/sdata/billingboss/crmErp/-/tradingAccounts/$syncSource
46
+ def map_sdata_sync_source_delete
47
+ map_route "#{name_in_path}\/$syncSource\\(:trackingID\\)", 'sdata_collection_sync_feed_delete', :delete
48
+ end
49
+
50
+ # http://localhost:3000/sdata/billingboss/crmErp/-/tradingAccounts/$linked
51
+ def map_sdata_collection_with_condition
52
+ map_route "#{name_in_path}\/#{CONDITION}", 'sdata_collection', :get
53
+ end
54
+
55
+ # http://localhost:3000/sdata/billingboss/crmErp/-/tradingAccounts('1')
56
+ def map_sdata_show_instance
57
+ map_route "#{name_in_path}\\(:instance_id\\)", 'sdata_show_instance', :get
58
+ end
59
+
60
+ # http://localhost:3000/sdata/billingboss/crmErp/-/tradingAccounts/$linked('1')
61
+ def map_sdata_show_instance_with_condition
62
+ map_route "#{name_in_path}/#{CONDITION}\\(:instance_id\\)", 'sdata_show_instance', :get
63
+ end
64
+
65
+ # http://localhost:3000/sdata/billingboss/crmErp/-/tradingAccounts(name eq 'asdf')
66
+ # http://localhost:3000/sdata/billingboss/crmErp/-/tradingAccounts(name eq asdf)
67
+ # http://localhost:3000/sdata/billingboss/crmErp/-/tradingAccounts(name eq '')
68
+ def map_sdata_show_instance_with_predicate
69
+ map_route "#{name_in_path}\\(#{PREDICATE}\\)", 'sdata_show_instance', :get
70
+ end
71
+
72
+ # /$linked(name eq 'Second')
73
+ def map_sdata_show_instance_with_condition_and_predicate
74
+ map_route "#{name_in_path}/#{CONDITION}\\(#{PREDICATE}\\)", 'sdata_show_instance', :get
75
+ end
76
+
77
+ # http://localhost:3000/sdata/billingboss/crmErp/-/tradingAccounts
78
+ def map_sdata_create_instance
79
+ map_route "#{name_in_path}", 'sdata_create_instance', :post
80
+ end
81
+
82
+ # http://localhost:3000/sdata/billingboss/crmErp/-/tradingAccounts/$linked
83
+ def map_sdata_create_link
84
+ map_route "#{name_in_path}/#{CONDITION}", 'sdata_create_link', :post
85
+ end
86
+
87
+ # http://localhost:3000/sdata/billingboss/crmErp/-/tradingAccounts('1')
88
+ def map_sdata_update_instance
89
+ map_route "#{name_in_path}\\(:instance_id\\)", 'sdata_update_instance', :put
90
+ end
91
+
92
+ # http://localhost:3000/sdata/billingboss/crmErp/-/tradingAccounts/$syncSource
93
+ def map_sdata_sync_source
94
+ map_route "#{name_in_path}/$syncSource", 'sdata_collection_sync_feed', :post
95
+ end
96
+
97
+ # http://localhost:3000/sdata/billingboss/crmErp/-/tradingAccounts/$syncResults
98
+ def map_sdata_receive_sync_results
99
+ map_route "#{name_in_path}/$syncResults\\(:trackingID\\)", 'sdata_collection_sync_results', :post
100
+ end
101
+
102
+
103
+ def map_route(path, action, method)
104
+ path = prefix.chomp('/') + '/' + path if prefix?
105
+ path = path + ".sdata" if formatted_paths?
106
+
107
+ router.connect path, :controller => controller_with_namespace, :action => action, :conditions => { :method => method }, :priority => 99
108
+ end
109
+
110
+ def controller_with_namespace
111
+ @controller_with_namespace ||= "#{namespace}#{controller}"
112
+ end
113
+
114
+ def namespace
115
+ @namespace ||= (options[:namespace] ? "#{options[:namespace]}/" : "")
116
+ end
117
+
118
+ def controller
119
+ @controller ||= resource_name.to_s.pluralize
120
+ end
121
+
122
+ def name_in_path
123
+ @name_in_path ||= resource_name.to_s.camelize(:lower).pluralize
124
+ end
125
+
126
+ def formatted_paths?
127
+ @formatted_paths ||= options[:formatted_paths]
128
+ end
129
+
130
+ def prefix
131
+ if options[:prefix]
132
+ @prefix = options[:prefix] + '/{:dataset,([^\/]+)}'
133
+ else
134
+ @prefix = '/'
135
+ end
136
+ end
137
+
138
+ def prefix?
139
+ !! prefix
140
+ end
141
+
142
+ end
143
+ end
@@ -0,0 +1,10 @@
1
+ module SData
2
+ module RouterMixin
3
+ def sdata_resource(name, options={})
4
+ route_creator = SData::RouteMapper.new(self, name, options)
5
+ route_creator.map_sdata_routes!
6
+ end
7
+ end
8
+ end
9
+
10
+ ActionController::Routing::RouteSet::Mapper.__send__ :include, SData::RouterMixin
@@ -0,0 +1,122 @@
1
+ module SData
2
+ module Sync
3
+ module ControllerMixin
4
+ def syncs_sdata(options={})
5
+ raise "cannot sync sdata unless controller first acts_as_sdata" if self.sdata_options.nil?
6
+ cattr_accessor :sdata_sync_options
7
+ self.sdata_sync_options = options
8
+
9
+ include InstanceMethods
10
+ end
11
+
12
+ module InstanceMethods
13
+ module Actions
14
+ # TODO: error handling
15
+ def sdata_collection_sync_feed
16
+ # get the target digest
17
+ payload = params[:entry].sdata_payload
18
+
19
+ raise "no payload!" if payload.blank?
20
+
21
+ sd_sync_run = SData::SdSyncRun.find_by_tracking_id(tracking_id)
22
+ raise Sage::BusinessLogic::Exception::AccessDeniedException, "Access denied" unless sd_sync_run.nil?
23
+
24
+ # prepare a sync run to hold our state
25
+ sd_sync_run = SData::SdSyncRun.new( :tracking_id => tracking_id,
26
+ :run_name => params[:runName],
27
+ # :created_at => params[:runStamp],
28
+ :sd_class => model_class.sdata_name,
29
+ :created_by => target_user)
30
+ sd_sync_run.target_digest = payload.sync_digest
31
+
32
+ sd_sync_run.start!
33
+ sd_sync_run.process!
34
+ xml = sd_sync_run.to_xml.to_s
35
+ render :xml => xml, :content_type => "application/xml", :location => sync_source_url(sd_sync_run.tracking_id), :status => 202
36
+ end
37
+
38
+ def sdata_collection_sync_feed_status
39
+ sd_sync_run = SData::SdSyncRun.find_by_tracking_id(tracking_id)
40
+ raise Sage::BusinessLogic::Exception::AccessDeniedException, "Access denied" if sd_sync_run.nil?
41
+ assert_access_to sd_sync_run
42
+ if sd_sync_run.finished?
43
+ @total_results = sd_sync_run.total_results
44
+ feed = build_sdata_feed(:feed => {:title => "#{model_class.sdata_name} synchronization feed #{params[:trackingID]}"})
45
+ feed[SData.config[:schemas]['sync'], 'syncMode'] << "catchUp"
46
+
47
+ feed.sync_digest = sd_sync_run.source_digest
48
+
49
+ atom_entries = []
50
+ errors = []
51
+ sd_sync_run.objects[zero_based_start_index,records_to_return].each do |obj|
52
+ begin
53
+ # RADAR: this (the (params) part) will allow user to insert a different dataset (and possibly
54
+ # other data) than that which was synchronized during the run. If this is somehow a security
55
+ # problem, need to freeze that data during synctime. an be also solved by freezing username
56
+ # in feed and matching dataset against it at accestime.
57
+ # RADAR: children (e.g. line items) will be embedded in parent (e.g. invoice) during this
58
+ # request, but from live data rather than syncronized one, is this a potential problem?
59
+ feed.entries << obj.to_atom(params.merge(:sync => true)){|entry| entry.sync_syncState = obj.sd_sync_state }
60
+ rescue Exception => e
61
+ errors << ApplicationDiagnosis.new(:exception => e).to_xml(:feed)
62
+ end
63
+ end
64
+ #TODO: syntactic sugar if possible (such as diagnosing_errors(&block) which does the dirty work)
65
+ errors.each do |error|
66
+ feed[SData.config[:schemas]['sdata'], 'diagnosis'] << error
67
+ end
68
+ populate_open_search_for(feed)
69
+ build_feed_links_for(feed)
70
+ render :xml => feed, :content_type => "application/atom+xml; type=feed"
71
+ else
72
+ render :xml => sd_sync_run.to_xml.to_s,
73
+ :content_type => "application/atom+xml; type=feed",
74
+ :location => sync_source_url(sd_sync_run.tracking_id),
75
+ :status => 202
76
+ end
77
+ end
78
+
79
+ def sdata_collection_sync_feed_delete
80
+ sd_sync_run = SData::SdSyncRun.find_by_tracking_id(tracking_id)
81
+ sd_sync_run.destroy
82
+ render :text => "OK"
83
+ end
84
+
85
+ def sdata_collection_sync_results
86
+ render :text => "Not implemented!"
87
+ end
88
+ end
89
+ module AuxiliaryMethods
90
+ protected
91
+
92
+ # TODO: sort out profusion of urls endpoints and config classes!
93
+ def resource_url
94
+ case self.action_name
95
+ when 'sdata_collection_sync_feed_status',
96
+ 'sdata_collection_sync_feed_status',
97
+ 'sdata_collection_sync_feed_delete'
98
+ super + "/$syncSource('#{tracking_id}')"
99
+ else
100
+ super
101
+ end
102
+ end
103
+
104
+ def sync_source_url(track)
105
+ endpoint = target_user.endpoint
106
+ "#{endpoint}/#{SData.sdata_collection_url_component(model_class)}/$syncSource('#{track}')"
107
+ end
108
+
109
+ def tracking_id
110
+ CGI::unescape(params[:trackingID]).gsub(/^'/,'').gsub(/'$/,'')
111
+ end
112
+
113
+
114
+ end
115
+ include Actions
116
+ include AuxiliaryMethods
117
+ end
118
+ end
119
+ end
120
+ end
121
+ ActionController::Base.extend SData::Sync::ControllerMixin
122
+ # Atom::Entry.elements "sync:digest", :class => SData::SdDigest::AtomWrapper