ooor 1.9.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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