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