ooor 2.0.4 → 2.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,18 +1,20 @@
1
1
  require 'active_support/core_ext/class/attribute'
2
2
  require 'active_support/core_ext/object/inclusion'
3
3
 
4
- # NOTE this is a scoped copy of ActiveRecord reflection.rb
5
- # the few necessary hacks are explicited with a FIXME or a NOTE
6
- # an addition Ooor specific reflection module completes this one explicitely
7
4
  module Ooor
8
5
  # = Active Record Reflection
6
+ # NOTE this is a shrinked copy of ActiveRecord reflection.rb
7
+ # the few necessary hacks are explicited with a FIXME or a NOTE
8
+ # an addition Ooor specific reflection module completes this one explicitely
9
9
  module Reflection # :nodoc:
10
10
  extend ActiveSupport::Concern
11
11
 
12
- included do
13
- class_attribute :reflections
14
- self.reflections = {}
15
- end
12
+ # NOTE we do the following differently in Ooor because we really don't want to share
13
+ # reflactions between the various sessions!!
14
+ # included do
15
+ # class_attribute :reflections
16
+ # self.reflections = {}
17
+ # end
16
18
 
17
19
  # Reflection enables to interrogate Active Record classes and objects
18
20
  # about their associations and aggregations. This information can,
@@ -6,11 +6,19 @@ module Ooor
6
6
  module ReflectionOoor # :nodoc:
7
7
  extend ActiveSupport::Concern
8
8
 
9
- def column_for_attribute(name)
10
- self.class.columns_hash[name.to_s]
11
- end
9
+ def column_for_attribute(name)
10
+ self.class.columns_hash[name.to_s]
11
+ end
12
12
 
13
13
  module ClassMethods
14
+ def reflections
15
+ @reflections ||= {}
16
+ end
17
+
18
+ def reflections=(reflections)
19
+ @reflections = reflections
20
+ end
21
+
14
22
  def columns_hash(view_fields=nil)
15
23
  if view_fields || !@t.columns_hash
16
24
  view_fields ||= {}
@@ -30,32 +38,23 @@ module Ooor
30
38
  def create_reflection(name)
31
39
  reload_fields_definition()
32
40
  options = {}
41
+ relation = all_fields[name]['relation']
42
+ options[:class_name] = relation
33
43
  if many2one_associations.keys.include?(name)
34
44
  macro = :belongs_to
35
- relation = many2one_associations[name]['relation'] #TODO prefix?
36
- const_get(relation)
37
- options[:class_name] = relation #TODO or pass it camelized already?
38
45
  elsif many2many_associations.keys.include?(name)
39
46
  macro = :has_and_belongs_to_many
40
47
  elsif one2many_associations.keys.include?(name)
41
48
  macro = :has_many
42
49
  end
43
50
  reflection = Reflection::AssociationReflection.new(macro, name, options, nil)#active_record) #TODO active_record?
44
- # case macro
45
- # when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
46
- # klass = options[:through] ? ThroughReflection : AssociationReflection
47
- # reflection = klass.new(macro, name, options, active_record)
48
- # when :composed_of
49
- # reflection = AggregateReflection.new(macro, name, options, active_record)
50
- # end
51
-
52
51
  self.reflections = self.reflections.merge(name => reflection)
53
52
  reflection
54
53
  end
55
54
 
56
55
  def reflect_on_association(association)
57
56
  reflections[association] ||= create_reflection(association.to_s).tap do |reflection|
58
- reflection.connection = connection
57
+ reflection.session = session
59
58
  end
60
59
  end
61
60
  end
@@ -69,7 +68,7 @@ module Ooor
69
68
  module Reflection # :nodoc:
70
69
 
71
70
  class MacroReflection
72
- attr_accessor :connection
71
+ attr_accessor :session
73
72
  end
74
73
 
75
74
  # Holds all the meta-data about an association as it was specified in the
@@ -89,7 +88,8 @@ module Ooor
89
88
  # instead. This allows plugins to hook into association object creation.
90
89
  def klass
91
90
  # @klass ||= active_record.send(:compute_type, class_name)
92
- @klass ||= connection.class_name_from_model_key(class_name).constantize
91
+ # @klass ||= session.class_name_from_model_key(class_name).constantize
92
+ @klass = session.const_get(class_name)
93
93
  end
94
94
 
95
95
  def initialize(macro, name, options, active_record)
@@ -4,7 +4,6 @@
4
4
  # Licensed under the MIT license, see MIT-LICENSE file
5
5
 
6
6
  #TODO chainability of where via scopes
7
- #TODO include relations for single read
8
7
 
9
8
  module Ooor
10
9
  # = Similar to Active Record Relation
@@ -41,7 +41,7 @@ module Ooor
41
41
  #actually finds many resources specified with scope = ids_array
42
42
  def find_single(scope, options)
43
43
  context = options[:context] || {}
44
- reload_fields_definition(false, context)
44
+ reload_fields_definition(false)
45
45
  fields = options[:fields] || options[:only] || fast_fields(options)
46
46
  fields += options[:include] if options[:include]
47
47
 
@@ -51,7 +51,7 @@ module Ooor
51
51
  is_collection, records = read_domain(context, fields, options)
52
52
  end
53
53
  active_resources = []
54
- records.each { |record| active_resources << new(record, [], context, true)}
54
+ records.each { |record| active_resources << new(record, [], true)}
55
55
  if is_collection
56
56
  active_resources
57
57
  else
@@ -68,12 +68,12 @@ module Ooor
68
68
  end
69
69
  scope.map! { |item| item_to_id(item, context) }.reject! {|item| !item}
70
70
  records = rpc_execute('read', scope, fields, context.dup)
71
- records.sort_by! {|r| scope.index(r["id"])} if @connection.config[:force_xml_rpc]
71
+ records.sort_by! {|r| scope.index(r["id"])} if @session.config[:force_xml_rpc]
72
72
  return is_collection, records
73
73
  end
74
74
 
75
75
  def read_domain(context, fields, options)
76
- if @connection.config[:force_xml_rpc]
76
+ if @session.config[:force_xml_rpc]
77
77
  domain = to_openerp_domain(options[:domain] || options[:conditions] || [])
78
78
  ids = rpc_execute('search', domain, options[:offset] || 0, options[:limit] || false, options[:order] || false, context.dup)
79
79
  records = rpc_execute('read', ids, fields, context.dup)
@@ -7,19 +7,19 @@ module Ooor
7
7
  module ClassMethods
8
8
  #Added methods to obtain report data for a model
9
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]
10
+ context = session.object.inject_session_context(:report, false, context)[0]
11
+ uid = @session.config[:user_id]
12
+ pass = @session.config[:password]
13
+ db = @session.config[:database]
14
14
  params = {model: openerp_model, id: ids[0], report_type: report_type}
15
- connection.report.report(db, uid, pass, report_name, ids, params, context)
15
+ session.report.report(db, uid, pass, report_name, ids, params, context)
16
16
  end
17
17
 
18
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)
19
+ uid = @session.config[:user_id]
20
+ pass = @session.config[:password]
21
+ db = @session.config[:database]
22
+ session.report.report_get(db, uid, pass, report_id)
23
23
  end
24
24
 
25
25
  def get_report_data(report_name, ids, report_type='pdf', context={})
@@ -66,7 +66,7 @@ module Ooor
66
66
 
67
67
  def create(password=@session.config[:db_password], db_name='ooor_test', demo=true, lang='en_US', user_password=@session.config[:password] || 'admin')
68
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)
69
+ process_id = @session.get_client(:xml, @session.base_url + "/db").call("create_database", password, db_name, demo, lang, user_password)
70
70
  sleep(2)
71
71
  while get_progress(password, process_id)[0] != 1
72
72
  @session.logger.info "..."
@@ -1,38 +1,60 @@
1
1
  require 'ooor/services'
2
+ require 'active_support/configurable'
3
+ require 'active_support/core_ext/hash/slice'
2
4
 
3
5
  module Ooor
4
- class Session < SimpleDelegator
6
+ class Session
7
+ include ActiveSupport::Configurable
5
8
  include Transport
6
9
 
7
- attr_accessor :web_session, :connection, :id
10
+ attr_accessor :web_session, :id, :models
8
11
 
9
12
  def common(); @common_service ||= CommonService.new(self); end
10
13
  def db(); @db_service ||= DbService.new(self); end
11
14
  def object(); @object_service ||= ObjectService.new(self); end
12
15
  def report(); @report_service ||= ReportService.new(self); end
13
16
 
14
- def initialize(connection, web_session, id)
15
- super(connection)
16
- @connection = connection
17
+ def initialize(config, web_session, id)
18
+ set_config(_config(config))
19
+ Object.const_set(config[:scope_prefix], Module.new) if config[:scope_prefix]
20
+ @models = {}
21
+ @local_context = {}
17
22
  @web_session = web_session || {}
18
23
  @id = id || web_session[:session_id]
19
24
  end
20
25
 
26
+ def set_config(configuration)
27
+ configuration.each do |k, v|
28
+ config.send "#{k}=", v
29
+ end
30
+ end
31
+
32
+ # a part of the config that will be mixed in the context of each session
33
+ def connection_session
34
+ HashWithIndifferentAccess.new(config[:connection_session] || {})
35
+ end
36
+
21
37
  def [](key)
22
- @session[key]
38
+ self[key]
23
39
  end
24
40
 
25
41
  def []=(key, value)
26
- @session[key] = value
42
+ self[key] = value
27
43
  end
28
44
 
29
45
  def global_login(options)
30
- config.merge!(options)
46
+ config.merge!(options.symbolize_keys)
31
47
  load_models(config[:models], options[:reload])
32
48
  end
33
49
 
50
+ def with_context(context)
51
+ @local_context = context
52
+ yield
53
+ @local_context = {}
54
+ end
55
+
34
56
  def session_context(context={})
35
- connection_session.merge(web_session.slice('lang', 'tz')).merge(context) # not just lang and tz?
57
+ connection_session.merge(web_session.slice('lang', 'tz')).merge(@local_context).merge(context) # not just lang and tz?
36
58
  end
37
59
 
38
60
  def const_get(model_key, lang=nil);
@@ -56,27 +78,40 @@ module Ooor
56
78
 
57
79
  def load_models(model_names=config[:models], reload=config[:reload])
58
80
  helper_paths.each do |dir|
59
- Dir[dir].each { |file| require file }
81
+ ::Dir[dir].each { |file| require file }
60
82
  end
61
- domain = model_names ? [['model', 'in', model_names]] : []
62
- search_domain = domain - [1]
63
- model_ids = object.object_service(:execute, "ir.model", :search, search_domain, 0, false, false, {}, false)
64
- models_records = object.object_service(:execute, "ir.model", :read, model_ids, ['model', 'name']) #TODO use search_read
65
- models_records.each do |opts|
66
- options = HashWithIndifferentAccess.new(opts.merge(scope_prefix: config[:scope_prefix], reload: reload, generate_constants: config[:generate_constants]))
83
+ search_domain = model_names ? [['model', 'in', model_names]] : []
84
+ models_records = read_model_data(search_domain)
85
+ models_records.reject {|opts| opts['model'] == '_unknown' }.each do |opts|
86
+ options = HashWithIndifferentAccess.new(opts.merge(scope_prefix: config[:scope_prefix],
87
+ reload: reload,
88
+ generate_constants: config[:generate_constants]))
67
89
  define_openerp_model(options)
68
90
  end
69
91
  end
70
92
 
93
+ def read_model_data(search_domain)
94
+ if config[:force_xml_rpc]
95
+ model_ids = object.object_service(:execute, "ir.model", :search, search_domain, 0, false, false, {}, false)
96
+ models_records = object.object_service(:execute, "ir.model", :read, model_ids, ['model', 'name'])
97
+ else
98
+ response = object.object_service(:search_read, "ir.model", 'search_read',
99
+ fields: ['model', 'name'],
100
+ offset: 0,
101
+ limit: false,
102
+ domain: search_domain,
103
+ sort: false,
104
+ context: {})
105
+ models_records = response["records"]
106
+ end
107
+ end
108
+
71
109
  def set_model_template!(klass, options)
72
110
  template = Ooor.model_registry.get_template(config, options[:model])
73
111
  if template
74
112
  klass.t = template
75
- klass.one2many_associations.keys.each do |meth|
76
- klass.define_nested_attributes_method(meth)
77
- end
78
113
  else
79
- template = Ooor::ModelTemplate.new
114
+ template = Ooor::ModelSchema.new
80
115
  template.openerp_model = options[:model]
81
116
  template.openerp_id = options[:id]
82
117
  template.description = options[:name]
@@ -100,7 +135,7 @@ module Ooor
100
135
  set_model_template!(klass, options)
101
136
  klass.name = model_class_name
102
137
  klass.scope_prefix = scope_prefix
103
- klass.connection = self
138
+ klass.session = self
104
139
  if options[:generate_constants] && (options[:reload] || !scope.const_defined?(model_class_name))
105
140
  scope.const_set(model_class_name, klass)
106
141
  end
@@ -112,9 +147,24 @@ module Ooor
112
147
  models[options[:model]]
113
148
  end
114
149
 
115
- def models; @models ||= {}; end
150
+ # def models; @models ||= {}; end
116
151
 
117
152
  def logger; Ooor.logger; end
118
153
 
154
+ def helper_paths
155
+ [File.dirname(__FILE__) + '/helpers/*', *config[:helper_paths]]
156
+ end
157
+
158
+ def class_name_from_model_key(model_key)
159
+ model_key.split('.').collect {|name_part| name_part.capitalize}.join
160
+ end
161
+
162
+ private
163
+
164
+ def _config(config)
165
+ c = config.is_a?(String) ? Ooor.load_config(config, env) : config #TODO env, see old Connection
166
+ HashWithIndifferentAccess.new(c)
167
+ end
168
+
119
169
  end
120
170
  end
@@ -1,49 +1,45 @@
1
1
  require 'active_support/core_ext/hash/indifferent_access'
2
2
  require 'ooor/session'
3
- require 'ooor/connection'
4
3
 
5
4
  module Ooor
6
5
  autoload :SecureRandom, 'securerandom'
6
+ # The SessionHandler allows to retrieve a session with its loaded proxies to OpenERP
7
7
  class SessionHandler
8
- def connection_spec(config)
9
- HashWithIndifferentAccess.new(config.slice(:url, :username, :password, :database, :scope_prefix, :helper_paths)) #TODO should really password be part of it?
8
+
9
+ def noweb_session_spec(config)
10
+ "#{config[:url]}-#{config[:database]}-#{config[:username]}"
10
11
  end
11
12
 
12
13
  def retrieve_session(config, id=nil, web_session={})
13
14
  id ||= SecureRandom.hex(16)
14
- if config[:reload] || !s = sessions[id]
15
- create_new_session(config, web_session, id)
15
+ if id == :noweb
16
+ spec = noweb_session_spec(config)
16
17
  else
17
- s.tap {|s| s.web_session.merge!(web_session)} #TODO merge config also?
18
+ spec = id
18
19
  end
19
- end
20
-
21
- def create_new_session(config, web_session, id=nil)
22
- c_spec = connection_spec(config)
23
- if connections[c_spec]
24
- Ooor::Session.new(connections[c_spec], web_session, id)
20
+ if config[:reload] || !s = sessions[spec]
21
+ config = Ooor.default_config.merge(config) if Ooor.default_config.is_a? Hash
22
+ Ooor::Session.new(config, web_session, id)
23
+ elsif noweb_session_spec(s.config) != noweb_session_spec(config)
24
+ config = Ooor.default_config.merge(config) if Ooor.default_config.is_a? Hash
25
+ Ooor::Session.new(config, web_session, id)
25
26
  else
26
- Ooor::Session.new(create_new_connection(config, c_spec), web_session, id).tap do |s|
27
- connections[c_spec] = s.connection
28
- end
27
+ s.tap {|s| s.web_session.merge!(web_session)} #TODO merge config also?
29
28
  end
30
29
  end
31
30
 
32
31
  def register_session(session)
33
32
  if session.config[:session_sharing]
34
33
  spec = session.web_session[:session_id]
34
+ elsif session.id != :noweb
35
+ spec = session.id
35
36
  else
36
- spec= session.id
37
+ spec = noweb_session_spec(session.config)
37
38
  end
38
39
  set_web_session(spec, session.web_session)
39
40
  sessions[spec] = session
40
41
  end
41
42
 
42
- def create_new_connection(config, spec)
43
- config = Ooor.default_config.merge(config) if Ooor.default_config.is_a? Hash
44
- Connection.new(config)
45
- end
46
-
47
43
  def reset!
48
44
  @sessions = {}
49
45
  @connections = {}
@@ -107,7 +107,9 @@ module Ooor
107
107
  true
108
108
  elsif type == 'boolean'&& value == 0 || value == "0"
109
109
  false
110
- elsif value == false and type != 'boolean'
110
+ elsif value == false && type != 'boolean'
111
+ nil
112
+ elsif (type == 'char' || type == 'text') && value == "" && @attributes[skey] == nil
111
113
  nil
112
114
  else
113
115
  value
@@ -120,13 +122,13 @@ module Ooor
120
122
  elsif value.is_a?(Array) && !self.class.many2one_associations.keys.index(skey)
121
123
  value.reject {|i| i == ''}.map {|i| i.is_a?(String) ? i.to_i : i}
122
124
  elsif value.is_a?(String)
123
- sanitize_associatio_as_string(skey, value)
125
+ sanitize_association_as_string(skey, value)
124
126
  else
125
127
  value
126
128
  end
127
129
  end
128
130
 
129
- def sanitize_associatio_as_string(skey, value)
131
+ def sanitize_association_as_string(skey, value)
130
132
  if self.class.polymorphic_m2o_associations.has_key?(skey)
131
133
  value
132
134
  elsif self.class.many2one_associations.has_key?(skey)
@@ -145,56 +147,51 @@ module Ooor
145
147
  end
146
148
 
147
149
  def to_openerp_hash
148
- attributes, associations = get_changed_values
149
- associations = cast_associations_to_openerp(associations)
150
- attributes.merge(associations)
150
+ attribute_keys, association_keys = get_changed_values
151
+ associations = {}
152
+ association_keys.each { |k| associations[k] = self.cast_association(k) }
153
+ @attributes.slice(*attribute_keys).merge(associations)
151
154
  end
152
155
 
153
156
  def get_changed_values
154
- attributes = {}
155
- associations = {}
156
-
157
- changed.each do |k|
158
- if self.class.associations_keys.index(k)
159
- associations[k] = @associations[k]#changes[k][1]
160
- elsif self.class.fields.has_key?(k)
161
- attributes[k]= @attributes[k]
162
- elsif !BLACKLIST.index(k)
163
- attributes[k] = changes[k][1]
164
- end
165
- end
166
- return attributes, associations
167
- end
168
-
169
- def cast_associations_to_openerp(associations=@associations)
170
- associations.each do |k, v|
171
- associations[k] = self.cast_association(k, v)
172
- end
157
+ attribute_keys = changed.select {|k| self.class.fields.has_key?(k)} - BLACKLIST
158
+ association_keys = changed.select {|k| self.class.associations_keys.index(k)}
159
+ return attribute_keys, association_keys
173
160
  end
174
161
 
175
162
  # talk OpenERP cryptic associations API
176
- def cast_association(k, v)
163
+ def cast_association(k)
177
164
  if self.class.one2many_associations[k]
178
- cast_o2m_assocation(v)
165
+ if @loaded_associations[k]
166
+ v = @loaded_associations[k].select {|i| i.changed?}
167
+ v = @associations[k] if v.empty?
168
+ else
169
+ v = @associations[k]
170
+ end
171
+ cast_o2m_association(v)
179
172
  elsif self.class.many2many_associations[k]
173
+ v = @associations[k]
180
174
  [[6, false, (v || []).map {|i| i.is_a?(Base) ? i.id : i}]]
181
175
  elsif self.class.many2one_associations[k]
176
+ v = @associations[k] # TODO support for nested
182
177
  cast_m2o_association(v)
183
178
  end
184
179
  end
185
180
 
186
- def cast_o2m_assocation(v)
187
- if v.is_a?(Hash)
188
- cast_o2m_nested_attributes(v)
189
- else
190
- v.collect do |value|
191
- if value.is_a?(Base)
181
+ def cast_o2m_association(v)
182
+ v.collect do |value|
183
+ if value.is_a?(Base)
184
+ if value.new_record?
192
185
  [0, 0, value.to_openerp_hash]
193
- elsif value.is_a?(Hash)
194
- [0, 0, value]
195
- else
196
- [1, value, {}]
186
+ elsif value.marked_for_destruction?
187
+ [2, value.id]
188
+ elsif value.id
189
+ [1, value.id , value.to_openerp_hash]
197
190
  end
191
+ elsif value.is_a?(Hash)
192
+ [0, 0, value]
193
+ else #use case?
194
+ [1, value, {}]
198
195
  end
199
196
  end
200
197
  end