ooor 2.0.4 → 2.0.5

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