ooor 1.9.2 → 2.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 (45) hide show
  1. data/README.md +23 -71
  2. data/Rakefile +5 -0
  3. data/bin/ooor +1 -0
  4. data/lib/ooor.rb +87 -129
  5. data/lib/ooor/associations.rb +64 -0
  6. data/lib/ooor/base.rb +218 -0
  7. data/lib/{app/models → ooor}/base64.rb +0 -0
  8. data/lib/ooor/connection.rb +37 -0
  9. data/lib/ooor/errors.rb +120 -0
  10. data/lib/ooor/field_methods.rb +153 -0
  11. data/lib/{app → ooor}/helpers/core_helpers.rb +2 -2
  12. data/lib/ooor/locale.rb +13 -0
  13. data/lib/ooor/mini_active_resource.rb +94 -0
  14. data/lib/ooor/model_registry.rb +19 -0
  15. data/lib/ooor/naming.rb +73 -0
  16. data/lib/ooor/rack.rb +114 -0
  17. data/lib/ooor/railtie.rb +41 -0
  18. data/lib/ooor/reflection.rb +537 -0
  19. data/lib/ooor/reflection_ooor.rb +92 -0
  20. data/lib/{app/models → ooor}/relation.rb +61 -22
  21. data/lib/ooor/relation/finder_methods.rb +113 -0
  22. data/lib/ooor/report.rb +53 -0
  23. data/lib/{app/models → ooor}/serialization.rb +18 -6
  24. data/lib/ooor/services.rb +133 -0
  25. data/lib/ooor/session.rb +120 -0
  26. data/lib/ooor/session_handler.rb +63 -0
  27. data/lib/ooor/transport.rb +34 -0
  28. data/lib/ooor/transport/json_client.rb +53 -0
  29. data/lib/ooor/transport/xml_rpc_client.rb +15 -0
  30. data/lib/ooor/type_casting.rb +193 -0
  31. data/lib/ooor/version.rb +8 -0
  32. data/spec/helpers/test_helper.rb +11 -0
  33. data/spec/install_nightly.sh +17 -0
  34. data/spec/ooor_spec.rb +197 -79
  35. data/spec/requirements.txt +19 -0
  36. metadata +58 -20
  37. data/lib/app/models/client_xmlrpc.rb +0 -34
  38. data/lib/app/models/open_object_resource.rb +0 -486
  39. data/lib/app/models/services.rb +0 -47
  40. data/lib/app/models/type_casting.rb +0 -134
  41. data/lib/app/models/uml.rb +0 -210
  42. data/lib/app/ui/action_window.rb +0 -96
  43. data/lib/app/ui/client_base.rb +0 -36
  44. data/lib/app/ui/form_model.rb +0 -88
  45. data/ooor.yml +0 -27
@@ -0,0 +1,92 @@
1
+ require 'active_support/core_ext/class/attribute'
2
+ require 'active_support/core_ext/object/inclusion'
3
+
4
+ module Ooor
5
+ # = Ooor Reflection
6
+ module ReflectionOoor # :nodoc:
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+ def set_columns_hash(view_fields={})
11
+ reload_fields_definition()
12
+ @t.columns_hash ||= {}
13
+ @t.fields.each do |k, field|
14
+ unless @t.associations_keys.index(k)
15
+ @t.columns_hash[k] = field.merge({type: to_rails_type(view_fields[k] && view_fields[k]['type'] || field['type'])})
16
+ end
17
+ end
18
+ @t.columns_hash
19
+ end
20
+
21
+ def column_for_attribute(name)
22
+ columns_hash[name.to_s]
23
+ end
24
+
25
+ def create_reflection(name)
26
+ options = {}
27
+ if many2one_associations.keys.include?(name)
28
+ macro = :belongs_to
29
+ relation = many2one_associations[name]['relation'] #TODO prefix?
30
+ const_get(relation)
31
+ options[:class_name] = relation #TODO or pass it camelized already?
32
+ elsif many2many_associations.keys.include?(name)
33
+ macro = :has_and_belongs_to_many
34
+ elsif one2many_associations.keys.include?(name)
35
+ macro = :has_many
36
+ end
37
+ reflection = Reflection::AssociationReflection.new(macro, name, options, nil)#active_record) #TODO active_record?
38
+ # case macro
39
+ # when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
40
+ # klass = options[:through] ? ThroughReflection : AssociationReflection
41
+ # reflection = klass.new(macro, name, options, active_record)
42
+ # when :composed_of
43
+ # reflection = AggregateReflection.new(macro, name, options, active_record)
44
+ # end
45
+
46
+ self.reflections = self.reflections.merge(name => reflection)
47
+ reflection
48
+ end
49
+
50
+ def reflect_on_association(association)
51
+ reflections[association] ||= create_reflection(association.to_s).tap do |reflection|
52
+ reflection.connection = connection
53
+ end
54
+ end
55
+ end
56
+
57
+ end
58
+ end
59
+
60
+
61
+ module Ooor
62
+ # = Active Record Reflection
63
+ module Reflection # :nodoc:
64
+
65
+ class MacroReflection
66
+ attr_accessor :connection
67
+ end
68
+
69
+ # Holds all the meta-data about an association as it was specified in the
70
+ # Active Record class.
71
+ class AssociationReflection < MacroReflection #:nodoc:
72
+ # Returns the target association's class.
73
+ #
74
+ # class Author < ActiveRecord::Base
75
+ # has_many :books
76
+ # end
77
+ #
78
+ # Author.reflect_on_association(:books).klass
79
+ # # => Book
80
+ #
81
+ # <b>Note:</b> Do not call +klass.new+ or +klass.create+ to instantiate
82
+ # a new association object. Use +build_association+ or +create_association+
83
+ # instead. This allows plugins to hook into association object creation.
84
+ def klass
85
+ # @klass ||= active_record.send(:compute_type, class_name)
86
+ @klass ||= connection.class_name_from_model_key(class_name).constantize
87
+ end
88
+
89
+ end
90
+
91
+ end
92
+ end
@@ -1,5 +1,5 @@
1
1
  # OOOR: OpenObject On Ruby
2
- # Copyright (C) 2009-2012 Akretion LTDA (<http://www.akretion.com>).
2
+ # Copyright (C) 2009-TODAY Akretion LTDA (<http://www.akretion.com>).
3
3
  # Author: Raphaël Valyi
4
4
  # Licensed under the MIT license, see MIT-LICENSE file
5
5
 
@@ -9,16 +9,15 @@
9
9
  module Ooor
10
10
  # = Similar to Active Record Relation
11
11
  class Relation
12
-
13
12
  attr_reader :klass, :loaded
14
- attr_accessor :context, :count_field, :includes_values, :eager_load_values, :preload_values,
13
+ attr_accessor :options, :count_field, :includes_values, :eager_load_values, :preload_values,
15
14
  :select_values, :group_values, :order_values, :reorder_flag, :joins_values, :where_values, :having_values,
16
- :limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value, :from_value
15
+ :limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value, :from_value, :page_value, :per_value
17
16
  alias :loaded? :loaded
18
-
19
- def build_where(opts, other = [])
17
+
18
+ def build_where(opts, other = [])#TODO OpenERP domain is more than just the intersection of restrictions
20
19
  case opts
21
- when Array
20
+ when Array || '|' || '&'
22
21
  [opts]
23
22
  when Hash
24
23
  opts.keys.map {|key|["#{key}", "=", opts[key]]}
@@ -27,7 +26,11 @@ module Ooor
27
26
 
28
27
  def where(opts, *rest)
29
28
  relation = clone
30
- relation.where_values += build_where(opts, rest) unless opts.blank?
29
+ if opts.is_a?(Array) && opts.any? {|e| e.is_a? Array}
30
+ relation.where_values = opts
31
+ else
32
+ relation.where_values += build_where(opts, rest) unless opts.blank?
33
+ end
31
34
  relation
32
35
  end
33
36
 
@@ -60,14 +63,13 @@ module Ooor
60
63
  calculate(:count, column_name, options)
61
64
  end
62
65
 
63
- def initialize(klass)
66
+ def initialize(klass, options={})
64
67
  @klass = klass
65
68
  @where_values = []
66
69
  @loaded = false
67
- @context = {}
70
+ @options = options
68
71
  @count_field = false
69
- @limit_value = false
70
- @offset_value = false
72
+ @offset_value = 0
71
73
  @order_values = []
72
74
  end
73
75
 
@@ -93,24 +95,61 @@ module Ooor
93
95
  self
94
96
  end
95
97
 
98
+ def apply_finder_options(options)
99
+ relation = clone
100
+ relation.options = options #TODO this may be too simplified for chainability, merge smartly instead?
101
+ relation
102
+ end
103
+
104
+ def where_values
105
+ if @options && @options[:domain]
106
+ @options[:domain]
107
+ else
108
+ @where_values
109
+ end
110
+ end
111
+
96
112
  # A convenience wrapper for <tt>find(:all, *args)</tt>. You can pass in all the
97
113
  # same arguments to this method as you can to <tt>find(:all)</tt>.
98
114
  def all(*args)
99
- #args.any? ? apply_finder_options(args.first).to_a : to_a TODO
100
- to_a
115
+ args.any? ? apply_finder_options(args.first).to_a : to_a
101
116
  end
102
117
 
103
118
  def to_a
104
- return @records if loaded?
105
- if @order_values.empty?
106
- search_order = false
119
+ if loaded?
120
+ @records
107
121
  else
108
- search_order = @order_values.join(", ")
122
+ if @order_values.empty?
123
+ search_order = false
124
+ else
125
+ search_order = @order_values.join(", ")
126
+ end
127
+
128
+ if @options && @options[:name_search]
129
+ name_search = @klass.name_search(@options[:name_search], where_values, 'ilike', @options[:context], @limit_value)
130
+ @records = name_search.map do |tuple|
131
+ r = @klass.new({name: tuple[1]}, [])
132
+ r.id = tuple[0]
133
+ r #TODO load the fields optionally
134
+ end
135
+ else
136
+ if @per_value && @page_value
137
+ offset = @per_value * @page_value
138
+ limit = @per_value
139
+ else
140
+ offset = @offset_value
141
+ limit = @limit_value || false
142
+ end
143
+ @loaded = true
144
+ opts = @options.merge({
145
+ domain: where_values,
146
+ offset: offset,
147
+ limit: limit,
148
+ order: search_order,
149
+ })
150
+ @records = @klass.find(:all, opts)
151
+ end
109
152
  end
110
- ids = @klass.rpc_execute('search', @where_values, @offset_value, @limit_value, search_order, @context, @count_field)
111
- @records = @klass.find(ids)
112
- @loaded = true
113
- @records
114
153
  end
115
154
 
116
155
  def eager_loading?
@@ -0,0 +1,113 @@
1
+ require 'active_support/concern'
2
+
3
+ module Ooor
4
+ module FinderMethods
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ def find(*arguments)
9
+ if arguments.size == 1 &&
10
+ arguments[0].is_a?(Hash) ||
11
+ (arguments[0].is_a?(Array) && !([arguments[0][1]] & Ooor::TypeCasting::OPERATORS).empty?)
12
+ find_single(nil, {domain: arguments[0]})
13
+ else
14
+ find_dispatch(*arguments)
15
+ end
16
+ end
17
+
18
+ private
19
+ def find_dispatch(*arguments)
20
+ scope = arguments.slice!(0)
21
+ options = arguments.slice!(0) || {}
22
+ if (!scope.is_a?(Array) && !options.is_a?(Hash))
23
+ scope = [scope] + [options] + arguments
24
+ options = {}
25
+ end
26
+ case scope
27
+ when :all then find_single(nil, options)
28
+ when :first then find_first_or_last(options)
29
+ when :last then find_first_or_last(options, "DESC")
30
+ when :one then find_one(options)
31
+ else find_single(scope, options)
32
+ end
33
+ end
34
+
35
+ def find_first_or_last(options, ordering = "ASC")
36
+ options[:order] ||= "id #{ordering}"
37
+ options[:limit] = 1
38
+ find_single(nil, options)[0]
39
+ end
40
+
41
+ #actually finds many resources specified with scope = ids_array
42
+ def find_single(scope, options)
43
+ context = options[:context] || {}
44
+ reload_fields_definition(false, context)
45
+ fields = options[:fields] || options[:only] || fast_fields(options)
46
+ fields += options[:include] if options[:include]
47
+
48
+ if scope
49
+ is_collection, records = read_scope(context, fields, scope)
50
+ else
51
+ is_collection, records = read_domain(context, fields, options)
52
+ end
53
+ active_resources = []
54
+ records.each { |record| active_resources << new(record, [], context, true)}
55
+ if is_collection
56
+ active_resources
57
+ else
58
+ active_resources[0]
59
+ end
60
+ end
61
+
62
+ def read_scope(context, fields, scope)
63
+ if scope.is_a? Array
64
+ is_collection = true
65
+ else
66
+ scope = [scope]
67
+ is_collection = false
68
+ end
69
+ scope.map! { |item| item_to_id(item, context) }.reject! {|item| !item}
70
+ records = rpc_execute('read', scope, fields, context.dup)
71
+ records.sort_by! {|r| scope.index(r["id"])} if @connection.config[:force_xml_rpc]
72
+ return is_collection, records
73
+ end
74
+
75
+ def read_domain(context, fields, options)
76
+ if @connection.config[:force_xml_rpc]
77
+ domain = to_openerp_domain(options[:domain] || options[:conditions] || [])
78
+ ids = rpc_execute('search', domain, options[:offset] || 0, options[:limit] || false, options[:order] || false, context.dup)
79
+ records = rpc_execute('read', ids, fields, context.dup)
80
+ else
81
+ domain = to_openerp_domain(options[:domain] || options[:conditions] || [])
82
+ response = object_service(:search_read, openerp_model, 'search_read', {
83
+ fields: fields,
84
+ offset: options[:offset] || 0,
85
+ limit: options[:limit] || false,
86
+ domain: domain,
87
+ sort: options[:order] || false,
88
+ context: context
89
+ })
90
+ records = response["records"]
91
+ end
92
+ return true, records
93
+ end
94
+
95
+ def item_to_id(item, context)
96
+ if item.is_a?(String)
97
+ if item.to_i == 0#triggers ir_model_data absolute reference lookup
98
+ tab = item.split(".")
99
+ domain = [['name', '=', tab[-1]]]
100
+ domain << ['module', '=', tab[-2]] if tab[-2]
101
+ ir_model_data = const_get('ir.model.data').find(:first, domain: domain, context: context)
102
+ ir_model_data && ir_model_data.res_id && search([['id', '=', ir_model_data.res_id]], 0, false, false, context)[0]
103
+ else
104
+ item.to_i
105
+ end
106
+ else
107
+ item
108
+ end
109
+ end
110
+
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,53 @@
1
+ require 'active_support/concern'
2
+
3
+ module Ooor
4
+ module Report
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ #Added methods to obtain report data for a model
9
+ def report(report_name, ids, report_type='pdf', context={}) #TODO move to ReportService
10
+ context = connection.object.inject_session_context(:report, false, context)[0]
11
+ uid = @connection.config[:user_id]
12
+ pass = @connection.config[:password]
13
+ db = @connection.config[:database]
14
+ params = {model: openerp_model, id: ids[0], report_type: report_type}
15
+ connection.report.report(db, uid, pass, report_name, ids, params, context)
16
+ end
17
+
18
+ def report_get(report_id)
19
+ uid = @connection.config[:user_id]
20
+ pass = @connection.config[:password]
21
+ db = @connection.config[:database]
22
+ connection.report.report_get(db, uid, pass, report_id)
23
+ end
24
+
25
+ def get_report_data(report_name, ids, report_type='pdf', context={})
26
+ report_id = report(report_name, ids, report_type, context)
27
+ if report_id
28
+ state = false
29
+ attempt = 0
30
+ while not state
31
+ report = self.report_get(report_id)
32
+ state = report["state"]
33
+ attempt = 1
34
+ if not state
35
+ sleep(0.1)
36
+ attempt += 1
37
+ else
38
+ return [report["result"],report["format"]]
39
+ end
40
+ if attempt > 100
41
+ logger.debug "OOOR RPC: 'Printing Aborted!'"
42
+ break
43
+ end
44
+ end
45
+ else
46
+ logger.debug "OOOR RPC: 'report not found'"
47
+ end
48
+ return nil
49
+ end
50
+
51
+ end
52
+ end
53
+ end
@@ -1,25 +1,41 @@
1
1
  # OOOR: OpenObject On Ruby
2
- # Copyright (C) 2009-2012 Akretion LTDA (<http://www.akretion.com>).
2
+ # Copyright (C) 2009-TODAY Akretion LTDA (<http://www.akretion.com>).
3
3
  # Author: Raphaël Valyi
4
4
  # Licensed under the MIT license, see MIT-LICENSE file
5
5
 
6
6
  module Ooor
7
7
  module Serialization
8
8
 
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ self.include_root_in_json = false
13
+ end
14
+
9
15
  def serializable_hash(options = nil)
10
16
  options ||= {}
11
17
  hash = super(options)
12
18
 
13
19
  attribute_names = attributes.keys.sort
14
20
  included_associations = {}
21
+ serialize_many2one(included_associations)
22
+ serialize_x_to_many(included_associations)
23
+
24
+ method_names = Array.wrap(options[:methods]).map { |n| n if respond_to?(n.to_s) }.compact
25
+ Hash[(attribute_names + method_names).map { |n| [n, send(n)] }].merge(included_associations)
26
+ end
27
+
28
+ def serialize_many2one(included_associations)
15
29
  self.class.many2one_associations.keys.each do |k|
16
- if loaded_associations[k].is_a? OpenObjectResource
30
+ if loaded_associations[k].is_a? Base
17
31
  included_associations[k] = loaded_associations[k].as_json[loaded_associations[k].class.openerp_model.gsub('.', '_')]
18
32
  elsif associations[k].is_a? Array
19
33
  included_associations[k] = {"id" => associations[k][0], "name" => associations[k][1]}
20
34
  end
21
35
  end
36
+ end
22
37
 
38
+ def serialize_x_to_many(included_associations)
23
39
  (self.class.one2many_associations.keys + self.class.many2many_associations.keys).each do |k|
24
40
  if loaded_associations[k].is_a? Array
25
41
  included_associations[k] = loaded_associations[k].map {|item| item.as_json[item.class.openerp_model.gsub('.', '_')]}
@@ -27,11 +43,7 @@ module Ooor
27
43
  included_associations[k] = associations[k].map {|id| {"id" => id}} if associations[k]
28
44
  end
29
45
  end
30
-
31
- method_names = Array.wrap(options[:methods]).map { |n| n if respond_to?(n.to_s) }.compact
32
- Hash[(attribute_names + method_names).map { |n| [n, send(n)] }].merge(included_associations)
33
46
  end
34
47
 
35
48
  end
36
49
  end
37
-
@@ -0,0 +1,133 @@
1
+ # OOOR: OpenObject On Ruby
2
+ # Copyright (C) 2009-2013 Akretion LTDA (<http://www.akretion.com>).
3
+ # Author: Raphaël Valyi
4
+ # Licensed under the MIT license, see MIT-LICENSE file
5
+
6
+ require 'json'
7
+
8
+ module Ooor
9
+ autoload :InvalidSessionError, 'ooor/errors'
10
+
11
+ class Service
12
+ def initialize(session)
13
+ @session = session
14
+ end
15
+
16
+ def self.define_service(service, methods)
17
+ methods.each do |meth|
18
+ self.instance_eval do
19
+ define_method meth do |*args|
20
+ endpoint = @session.get_client(:xml, "#{@session.base_url}/#{service.to_s.gsub('ooor_alias_', '')}") #TODO make that transport agnostic
21
+ endpoint.call(meth.gsub('ooor_alias_', ''), *args)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+
29
+ class CommonService < Service
30
+ define_service(:common, %w[ir_get ir_set ir_del about ooor_alias_login logout timezone_get get_available_updates get_migration_scripts get_server_environment login_message check_connectivity about get_stats list_http_services version authenticate get_available_updates set_loglevel get_os_time get_sqlcount])
31
+
32
+ def login(db, username, password)
33
+ @session.logger.debug "OOOR login - db: #{db}, username: #{username}"
34
+
35
+ if @session.config[:force_xml_rpc]
36
+ send("ooor_alias_login", db, username, password)
37
+ else
38
+ conn = @session.get_client(:json, "#{@session.base_jsonrpc2_url}")
39
+ response = conn.post do |req|
40
+ req.url '/web/session/authenticate'
41
+ req.headers['Content-Type'] = 'application/json'
42
+ req.body = {method: 'call', params: { db: db, login: username, password: password}}.to_json
43
+ end
44
+ @session.web_session[:cookie] = response.headers["set-cookie"]
45
+ if response.status == 200
46
+ sid_part1 = @session.web_session[:cookie].split("sid=")[1]
47
+ if sid_part1
48
+ @session.web_session[:sid] = @session.web_session[:cookie].split("sid=")[1].split(";")[0] # NOTE side is required on v7 but not on v8, this enables to sniff if we are on v7
49
+ end
50
+ json_response = JSON.parse(response.body)
51
+ @session.web_session[:session_id] = json_response['result']['session_id']
52
+ user_id = json_response['result']['uid']
53
+ @session.config[:user_id] = user_id
54
+ Ooor.session_handler.register_session(@session)
55
+ user_id
56
+ else
57
+ raise Faraday::Error::ClientError.new(response.status, response)
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+
64
+ class DbService < Service
65
+ define_service(:db, %w[get_progress drop dump restore rename db_exist list change_admin_password list_lang server_version migrate_databases create_database duplicate_database])
66
+
67
+ def create(password=@session.config[:db_password], db_name='ooor_test', demo=true, lang='en_US', user_password=@session.config[:password] || 'admin')
68
+ @session.logger.info "creating database #{db_name} this may take a while..."
69
+ process_id = @session.get_client(:xml, @session.base_url + "/db").call("create", password, db_name, demo, lang, user_password)
70
+ sleep(2)
71
+ while get_progress(password, process_id)[0] != 1
72
+ @session.logger.info "..."
73
+ sleep(0.5)
74
+ end
75
+ @session.global_login(username: 'admin', password: user_password, database: db_name)
76
+ end
77
+ end
78
+
79
+
80
+ class ObjectService < Service
81
+ define_service(:object, %w[execute exec_workflow])
82
+
83
+ def object_service(service, obj, method, *args)
84
+ unless @session.config[:user_id]
85
+ @session.common.login(@session.config[:database], @session.config[:username], @session.config[:password])
86
+ end
87
+ args = inject_session_context(service, method, *args)
88
+ uid = @session.config[:user_id]
89
+ db = @session.config[:database]
90
+ @session.logger.debug "OOOR object service: rpc_method: #{service}, db: #{db}, uid: #{uid}, pass: #, obj: #{obj}, method: #{method}, *args: #{args.inspect}"
91
+ if @session.config[:force_xml_rpc]
92
+ pass = @session.config[:password]
93
+ send(service, db, uid, pass, obj, method, *args)
94
+ else
95
+ unless @session.web_session[:session_id]
96
+ @session.common.login(@session.config[:database], @session.config[:username], @session.config[:password])
97
+ end
98
+ json_conn = @session.get_client(:json, "#{@session.base_jsonrpc2_url}")
99
+ json_conn.oe_service(@session.web_session, service, obj, method, *args)
100
+ end
101
+ rescue InvalidSessionError
102
+ @session.config[:force_xml_rpc] = true #TODO set v6 version too
103
+ retry
104
+ rescue SessionExpiredError
105
+ @session.logger.debug "session for uid: #{uid} has expired, trying to login again"
106
+ @session.common.login(@session.config[:database], @session.config[:username], @session.config[:password])
107
+ retry
108
+ end
109
+
110
+ def inject_session_context(service, method, *args)
111
+ if service == :object && (i = Ooor.irregular_context_position(method)) && args.size >= i
112
+ c = HashWithIndifferentAccess.new(args[i])
113
+ args[i] = @session.session_context(c)
114
+ elsif args[-1].is_a? Hash #context
115
+ if args[-1][:context]
116
+ c = HashWithIndifferentAccess.new(args[-1][:context])
117
+ args[-1][:context] = @session.session_context(c)
118
+ else
119
+ c = HashWithIndifferentAccess.new(args[-1])
120
+ args[-1] = @session.session_context(c)
121
+ end
122
+ end
123
+ args
124
+ end
125
+
126
+ end
127
+
128
+
129
+ class ReportService < Service
130
+ define_service(:report, %w[report report_get render_report]) #TODO make use json rpc transport too
131
+ end
132
+
133
+ end