ext_ooor 2.3.0.1
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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +354 -0
- data/Rakefile +5 -0
- data/bin/ooor +43 -0
- data/lib/ext_ooor.rb +5 -0
- data/lib/ext_ooor/version.rb +5 -0
- data/lib/generators/ooor/install_generator.rb +18 -0
- data/lib/generators/ooor/ooor.yml +49 -0
- data/lib/ooor.rb +230 -0
- data/lib/ooor/associations.rb +78 -0
- data/lib/ooor/autosave_association.rb +197 -0
- data/lib/ooor/base.rb +130 -0
- data/lib/ooor/base64.rb +20 -0
- data/lib/ooor/callbacks.rb +18 -0
- data/lib/ooor/errors.rb +120 -0
- data/lib/ooor/field_methods.rb +213 -0
- data/lib/ooor/helpers/core_helpers.rb +83 -0
- data/lib/ooor/locale.rb +11 -0
- data/lib/ooor/mini_active_resource.rb +86 -0
- data/lib/ooor/model_registry.rb +24 -0
- data/lib/ooor/model_schema.rb +25 -0
- data/lib/ooor/naming.rb +92 -0
- data/lib/ooor/nested_attributes.rb +57 -0
- data/lib/ooor/persistence.rb +353 -0
- data/lib/ooor/rack.rb +137 -0
- data/lib/ooor/railtie.rb +27 -0
- data/lib/ooor/reflection.rb +151 -0
- data/lib/ooor/reflection_ooor.rb +121 -0
- data/lib/ooor/relation.rb +204 -0
- data/lib/ooor/relation/finder_methods.rb +153 -0
- data/lib/ooor/report.rb +53 -0
- data/lib/ooor/serialization.rb +49 -0
- data/lib/ooor/services.rb +134 -0
- data/lib/ooor/session.rb +250 -0
- data/lib/ooor/session_handler.rb +66 -0
- data/lib/ooor/transport.rb +34 -0
- data/lib/ooor/transport/json_client.rb +65 -0
- data/lib/ooor/transport/xml_rpc_client.rb +15 -0
- data/lib/ooor/type_casting.rb +223 -0
- data/lib/ooor/version.rb +8 -0
- data/spec/cli_spec.rb +129 -0
- data/spec/helpers/test_helper.rb +11 -0
- data/spec/ooor_spec.rb +867 -0
- metadata +118 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
# Odoo. Versions 7 and up are supported.
|
2
|
+
#
|
3
|
+
default: &default
|
4
|
+
global_context:
|
5
|
+
'lang': fr_FR
|
6
|
+
bootstrap: true
|
7
|
+
|
8
|
+
|
9
|
+
development:
|
10
|
+
<<: *default
|
11
|
+
database: db
|
12
|
+
username: admin
|
13
|
+
password: admin
|
14
|
+
url: http://localhost:8069
|
15
|
+
|
16
|
+
|
17
|
+
# database to use when you run tests with "rake".
|
18
|
+
test:
|
19
|
+
<<: *default
|
20
|
+
username: admin
|
21
|
+
password: admin
|
22
|
+
url: http://localhost:8069
|
23
|
+
|
24
|
+
|
25
|
+
# As with config/secrets.yml, you never want to store sensitive information,
|
26
|
+
# like your Odoo portal password, in your source code. If your source code is
|
27
|
+
# ever seen by anyone, they now have access to your ERP with the portal user.
|
28
|
+
#
|
29
|
+
# Instead, provide the password as a unix environment variable when you boot
|
30
|
+
# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
|
31
|
+
# for a full rundown on how to provide these environment variables in a
|
32
|
+
# production deployment.
|
33
|
+
#
|
34
|
+
# On Heroku and other platform providers, you may have a full connection URL
|
35
|
+
# available as an environment variable. For example:
|
36
|
+
#
|
37
|
+
# OOOR_URL="ooor://myuser:mypass@localhost:port/somedatabase"
|
38
|
+
#
|
39
|
+
# You can use this database configuration with:
|
40
|
+
#
|
41
|
+
# production:
|
42
|
+
# url: <%%= ENV['OOOR_URL'] %>
|
43
|
+
#
|
44
|
+
production:
|
45
|
+
<<: *default
|
46
|
+
database: prod_db
|
47
|
+
username: portal_user
|
48
|
+
password: <%%= ENV['OOOR_PASSWORD'] %>
|
49
|
+
url: https://prod_host
|
data/lib/ooor.rb
ADDED
@@ -0,0 +1,230 @@
|
|
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 'active_support/dependencies/autoload'
|
7
|
+
require 'active_support/concern'
|
8
|
+
require 'active_support/cache'
|
9
|
+
require 'logger'
|
10
|
+
|
11
|
+
|
12
|
+
module Ooor
|
13
|
+
extend ActiveSupport::Autoload
|
14
|
+
autoload :Base
|
15
|
+
autoload :ModelSchema
|
16
|
+
autoload :Persistence
|
17
|
+
autoload :AutosaveAssociation
|
18
|
+
autoload :NestedAttributes
|
19
|
+
autoload :Callbacks
|
20
|
+
autoload :Cache, 'active_support/cache'
|
21
|
+
autoload :Serialization
|
22
|
+
autoload :Relation
|
23
|
+
autoload :TypeCasting
|
24
|
+
autoload :Naming
|
25
|
+
autoload :Associations
|
26
|
+
autoload :FieldMethods
|
27
|
+
autoload :Report
|
28
|
+
autoload :Locale
|
29
|
+
autoload :Transport
|
30
|
+
autoload :Block
|
31
|
+
autoload :MiniActiveResource
|
32
|
+
autoload :SessionHandler
|
33
|
+
autoload :ModelRegistry
|
34
|
+
autoload :UnknownAttributeOrAssociationError, 'ooor/errors'
|
35
|
+
autoload :OpenERPServerError, 'ooor/errors'
|
36
|
+
autoload :HashWithIndifferentAccess, 'active_support/core_ext/hash/indifferent_access'
|
37
|
+
|
38
|
+
autoload_under 'relation' do
|
39
|
+
autoload :FinderMethods
|
40
|
+
end
|
41
|
+
|
42
|
+
module OoorBehavior
|
43
|
+
extend ActiveSupport::Concern
|
44
|
+
module ClassMethods
|
45
|
+
|
46
|
+
attr_accessor :default_config, :default_session, :cache_store
|
47
|
+
|
48
|
+
IRREGULAR_CONTEXT_POSITIONS = {
|
49
|
+
import_data: 5,
|
50
|
+
fields_view_get: 2,
|
51
|
+
search: 4,
|
52
|
+
name_search: 3,
|
53
|
+
read_group: 5,
|
54
|
+
fields_get: 1,
|
55
|
+
read: 2,
|
56
|
+
perm_read: 1,
|
57
|
+
check_recursion: 1
|
58
|
+
}
|
59
|
+
|
60
|
+
def new(config={})
|
61
|
+
defaults = HashWithIndifferentAccess.new({generate_constants: true})
|
62
|
+
formated_config = format_config(config)
|
63
|
+
self.default_config = defaults.merge(formated_config)
|
64
|
+
session = session_handler.retrieve_session(default_config, :noweb)
|
65
|
+
if default_config[:database] && default_config[:password] && default_config[:bootstrap] != false
|
66
|
+
session.global_login()
|
67
|
+
end
|
68
|
+
Ooor.default_session = session
|
69
|
+
end
|
70
|
+
|
71
|
+
def cache(store=nil)
|
72
|
+
@cache_store ||= ActiveSupport::Cache.lookup_store(store)
|
73
|
+
end
|
74
|
+
|
75
|
+
def xtend(model_name, &block)
|
76
|
+
@extensions ||= {}
|
77
|
+
@extensions[model_name] ||= []
|
78
|
+
@extensions[model_name] << block
|
79
|
+
@extensions
|
80
|
+
end
|
81
|
+
|
82
|
+
def extensions
|
83
|
+
@extensions ||= {}
|
84
|
+
end
|
85
|
+
|
86
|
+
def session_handler() @session_handler ||= SessionHandler.new; end
|
87
|
+
def model_registry() @model_registry ||= ModelRegistry.new; end
|
88
|
+
|
89
|
+
def logger
|
90
|
+
@logger ||= Logger.new($stdout)
|
91
|
+
end
|
92
|
+
|
93
|
+
def logger=(logger)
|
94
|
+
@logger = logger
|
95
|
+
end
|
96
|
+
|
97
|
+
def irregular_context_position(method)
|
98
|
+
IRREGULAR_CONTEXT_POSITIONS.merge(default_config[:irregular_context_positions] || {})[method.to_sym]
|
99
|
+
end
|
100
|
+
|
101
|
+
# gives a hash config from a connection string or a yaml file, injects default values
|
102
|
+
def format_config(config)
|
103
|
+
if config.is_a?(String) && config.end_with?('.yml')
|
104
|
+
env = defined?(Rails.env) ? Rails.env : nil
|
105
|
+
config = load_config_file(config, env)
|
106
|
+
end
|
107
|
+
if config.is_a?(String)
|
108
|
+
cs = config
|
109
|
+
config = HashWithIndifferentAccess.new()
|
110
|
+
elsif config[:ooor_url]
|
111
|
+
cs = config[:ooor_url]
|
112
|
+
elsif ENV['OOOR_URL']
|
113
|
+
cs = ENV['OOOR_URL'].dup()
|
114
|
+
end
|
115
|
+
config.merge!(parse_connection_string(cs)) if cs
|
116
|
+
defaults = HashWithIndifferentAccess.new({
|
117
|
+
url: 'http://localhost:8069',
|
118
|
+
username: 'admin'
|
119
|
+
})
|
120
|
+
defaults[:password] = ENV['OOOR_PASSWORD'] if ENV['OOOR_PASSWORD']
|
121
|
+
defaults[:username] = ENV['OOOR_USERNAME'] if ENV['OOOR_USERNAME']
|
122
|
+
defaults[:database] = ENV['OOOR_DATABASE'] if ENV['OOOR_DATABASE']
|
123
|
+
defaults.merge(config)
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def load_config_file(config_file=nil, env=nil)
|
130
|
+
config_file ||= defined?(Rails.root) && "#{Rails.root}/config/ooor.yml" || 'ooor.yml'
|
131
|
+
config_parsed = ::YAML.load(ERB.new(File.new(config_file).read).result)
|
132
|
+
HashWithIndifferentAccess.new(config_parsed)[env || 'development']
|
133
|
+
rescue SystemCallError
|
134
|
+
Ooor.logger.error """failed to load OOOR yaml configuration file.
|
135
|
+
make sure your app has a #{config_file} file correctly set up
|
136
|
+
if not, just copy/paste the default ooor.yml file from the OOOR Gem
|
137
|
+
to #{Rails.root}/config/ooor.yml and customize it properly\n\n"""
|
138
|
+
{}
|
139
|
+
end
|
140
|
+
|
141
|
+
def parse_connection_string(cs)
|
142
|
+
if cs.start_with?('ooor://') && ! cs.index('@')
|
143
|
+
cs.sub!(/^ooor:\/\//, '@')
|
144
|
+
end
|
145
|
+
|
146
|
+
cs.sub!(/^http:\/\//, '')
|
147
|
+
cs.sub!(/^ooor:/, '')
|
148
|
+
cs.sub!(/^ooor:/, '')
|
149
|
+
cs.sub!('//', '')
|
150
|
+
if cs.index('ssl=true')
|
151
|
+
ssl = true
|
152
|
+
cs.gsub!('?ssl=true', '').gsub!('ssl=true', '')
|
153
|
+
end
|
154
|
+
if cs.index(' -s')
|
155
|
+
ssl = true
|
156
|
+
cs.gsub!(' -s', '')
|
157
|
+
end
|
158
|
+
|
159
|
+
if cs.index('@')
|
160
|
+
parts = cs.split('@')
|
161
|
+
right = parts[1]
|
162
|
+
left = parts[0]
|
163
|
+
if right.index('/')
|
164
|
+
parts = right.split('/')
|
165
|
+
database = parts[1]
|
166
|
+
host, port = parse_host_port(parts[0])
|
167
|
+
else
|
168
|
+
host, port = parse_host_port(right)
|
169
|
+
end
|
170
|
+
|
171
|
+
if left.index(':')
|
172
|
+
user_pwd = left.split(':')
|
173
|
+
username = user_pwd[0]
|
174
|
+
password = user_pwd[1]
|
175
|
+
else
|
176
|
+
if left.index('.') && !database
|
177
|
+
username = left.split('.')[0]
|
178
|
+
database = left.split('.')[1]
|
179
|
+
else
|
180
|
+
username = left
|
181
|
+
end
|
182
|
+
end
|
183
|
+
else
|
184
|
+
host, port = parse_host_port(cs)
|
185
|
+
end
|
186
|
+
|
187
|
+
host ||= 'localhost'
|
188
|
+
port ||= 8069
|
189
|
+
ssl = true if port == 443
|
190
|
+
username = 'admin' if username.blank?
|
191
|
+
{
|
192
|
+
url: "#{ssl ? 'https' : 'http'}://#{host}:#{port}",
|
193
|
+
username: username,
|
194
|
+
database: database,
|
195
|
+
password: password,
|
196
|
+
}.select { |_, value| !value.nil? } # .compact() on Rails > 4
|
197
|
+
end
|
198
|
+
|
199
|
+
def parse_host_port(host_port)
|
200
|
+
if host_port.index(':')
|
201
|
+
host_port = host_port.split(':')
|
202
|
+
host = host_port[0]
|
203
|
+
port = host_port[1].to_i
|
204
|
+
else
|
205
|
+
host = host_port
|
206
|
+
port = 80
|
207
|
+
end
|
208
|
+
return host, port
|
209
|
+
end
|
210
|
+
|
211
|
+
end
|
212
|
+
|
213
|
+
def with_ooor_session(config={}, id=:noweb)
|
214
|
+
session = Ooor.session_handler.retrieve_session(config, id)
|
215
|
+
yield session
|
216
|
+
end
|
217
|
+
|
218
|
+
def with_ooor_default_session(config={})
|
219
|
+
if config
|
220
|
+
Ooor.new(config)
|
221
|
+
else
|
222
|
+
Ooor.default_session
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
include OoorBehavior
|
228
|
+
end
|
229
|
+
|
230
|
+
require 'ooor/railtie' if defined?(Rails)
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Ooor
|
2
|
+
module Associations
|
3
|
+
|
4
|
+
# similar to ActiveRecord CollectionProxy but without lazy loading work yet
|
5
|
+
class CollectionProxy < Relation
|
6
|
+
|
7
|
+
def to_ary
|
8
|
+
to_a.dup
|
9
|
+
end
|
10
|
+
# alias_method :to_a, :to_ary
|
11
|
+
|
12
|
+
def class
|
13
|
+
Array
|
14
|
+
end
|
15
|
+
|
16
|
+
def is_a?(*args)
|
17
|
+
@records.is_a?(*args)
|
18
|
+
end
|
19
|
+
|
20
|
+
def kind_of?(*args)
|
21
|
+
@records.kind_of?(*args)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
# fakes associations like much like ActiveRecord according to the cached OpenERP data model
|
28
|
+
def relationnal_result(method_name, *arguments)
|
29
|
+
self.class.reload_fields_definition(false)
|
30
|
+
if self.class.many2one_associations.has_key?(method_name)
|
31
|
+
load_m2o_association(method_name, *arguments)
|
32
|
+
elsif self.class.polymorphic_m2o_associations.has_key?(method_name)# && @associations[method_name]
|
33
|
+
load_polymorphic_m2o_association(method_name, *arguments)
|
34
|
+
# values = @associations[method_name].split(',')
|
35
|
+
# self.class.const_get(values[0]).find(values[1], arguments.extract_options!)
|
36
|
+
else # o2m or m2m
|
37
|
+
load_x2m_association(method_name, *arguments)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def load_polymorphic_m2o_association(method_name, *arguments)
|
44
|
+
if @associations[method_name]
|
45
|
+
values = @associations[method_name].split(',')
|
46
|
+
self.class.const_get(values[0]).find(values[1], arguments.extract_options!)
|
47
|
+
else
|
48
|
+
false
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def load_m2o_association(method_name, *arguments)
|
53
|
+
if !@associations[method_name]
|
54
|
+
nil
|
55
|
+
else
|
56
|
+
if @associations[method_name].is_a?(Integer)
|
57
|
+
id = @associations[method_name]
|
58
|
+
display_name = nil
|
59
|
+
else
|
60
|
+
id = @associations[method_name][0]
|
61
|
+
display_name = @associations[method_name][1]
|
62
|
+
end
|
63
|
+
rel = self.class.many2one_associations[method_name]['relation']
|
64
|
+
self.class.const_get(rel).new({id: id, _display_name: display_name}, [], true, false, true)
|
65
|
+
# self.class.const_get(rel).find(id, arguments.extract_options!)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def load_x2m_association(method_name, *arguments)
|
70
|
+
model_key = self.class.all_fields[method_name]['relation']
|
71
|
+
ids = @associations[method_name] || []
|
72
|
+
options = arguments.extract_options!
|
73
|
+
related_class = self.class.const_get(model_key)
|
74
|
+
CollectionProxy.new(related_class, {}).apply_finder_options(options.merge(ids: ids))
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'active_support/concern'
|
3
|
+
|
4
|
+
module Ooor
|
5
|
+
# = Ooor Autosave Association, adapted from ActiveRecord 4.1
|
6
|
+
#
|
7
|
+
# +AutosaveAssociation+ is a module that takes care of automatically saving
|
8
|
+
# associated records when their parent is saved. In addition to saving, it
|
9
|
+
# also destroys any associated records that were marked for destruction.
|
10
|
+
# (See +mark_for_destruction+ and <tt>marked_for_destruction?</tt>).
|
11
|
+
#
|
12
|
+
# Saving of the parent, its associations, and the destruction of marked
|
13
|
+
# associations, all happen inside a transaction. This should never leave the
|
14
|
+
# database in an inconsistent state.
|
15
|
+
#
|
16
|
+
# If validations for any of the associations fail, their error messages will
|
17
|
+
# be applied to the parent (TODO)
|
18
|
+
module AutosaveAssociation
|
19
|
+
extend ActiveSupport::Concern
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
private
|
23
|
+
|
24
|
+
# same as ActiveRecord
|
25
|
+
def define_non_cyclic_method(name, &block)
|
26
|
+
define_method(name) do |*args|
|
27
|
+
result = true; @_already_called ||= {}
|
28
|
+
# Loop prevention for validation of associations
|
29
|
+
unless @_already_called[name]
|
30
|
+
begin
|
31
|
+
@_already_called[name]=true
|
32
|
+
result = instance_eval(&block)
|
33
|
+
ensure
|
34
|
+
@_already_called[name]=false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
result
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Adds validation and save callbacks for the association as specified by
|
43
|
+
# the +reflection+.
|
44
|
+
#
|
45
|
+
# For performance reasons, we don't check whether to validate at runtime.
|
46
|
+
# However the validation and callback methods are lazy and those methods
|
47
|
+
# get created when they are invoked for the very first time. However,
|
48
|
+
# this can change, for instance, when using nested attributes, which is
|
49
|
+
# called _after_ the association has been defined. Since we don't want
|
50
|
+
# the callbacks to get defined multiple times, there are guards that
|
51
|
+
# check if the save or validation methods have already been defined
|
52
|
+
# before actually defining them.
|
53
|
+
def add_autosave_association_callbacks(reflection) # TODO add support for m2o
|
54
|
+
save_method = :"autosave_associated_records_for_#{reflection.name}"
|
55
|
+
validation_method = :"validate_associated_records_for_#{reflection.name}"
|
56
|
+
collection = true #reflection.collection?
|
57
|
+
unless method_defined?(save_method)
|
58
|
+
if collection
|
59
|
+
before_save :before_save_collection_association
|
60
|
+
define_non_cyclic_method(save_method) { save_collection_association(reflection) }
|
61
|
+
before_save save_method
|
62
|
+
# NOTE Ooor is different from ActiveRecord here: we run the nested callbacks before saving
|
63
|
+
# the whole hash of values including the nested records
|
64
|
+
# Doesn't use after_save as that would save associations added in after_create/after_update twice
|
65
|
+
# after_create save_method
|
66
|
+
# after_update save_method
|
67
|
+
else
|
68
|
+
raise raise ArgumentError, "Not implemented in Ooor; seems OpenERP won't support such nested attribute in the same transaction anyhow"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
if reflection.validate? && !method_defined?(validation_method)
|
73
|
+
method = (collection ? :validate_collection_association : :validate_single_association)
|
74
|
+
define_non_cyclic_method(validation_method) { send(method, reflection) }
|
75
|
+
validate validation_method
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Reloads the attributes of the object as usual and clears <tt>marked_for_destruction</tt> flag.
|
81
|
+
def reload(options = nil)
|
82
|
+
@marked_for_destruction = false
|
83
|
+
@destroyed_by_association = nil
|
84
|
+
super
|
85
|
+
end
|
86
|
+
|
87
|
+
# Marks this record to be destroyed as part of the parents save transaction.
|
88
|
+
# This does _not_ actually destroy the record instantly, rather child record will be destroyed
|
89
|
+
# when <tt>parent.save</tt> is called.
|
90
|
+
#
|
91
|
+
# Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
|
92
|
+
def mark_for_destruction
|
93
|
+
@marked_for_destruction = true
|
94
|
+
end
|
95
|
+
|
96
|
+
# Returns whether or not this record will be destroyed as part of the parents save transaction.
|
97
|
+
#
|
98
|
+
# Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
|
99
|
+
def marked_for_destruction?
|
100
|
+
@marked_for_destruction
|
101
|
+
end
|
102
|
+
|
103
|
+
# Records the association that is being destroyed and destroying this
|
104
|
+
# record in the process.
|
105
|
+
def destroyed_by_association=(reflection)
|
106
|
+
@destroyed_by_association = reflection
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns the association for the parent being destroyed.
|
110
|
+
#
|
111
|
+
# Used to avoid updating the counter cache unnecessarily.
|
112
|
+
def destroyed_by_association
|
113
|
+
@destroyed_by_association
|
114
|
+
end
|
115
|
+
|
116
|
+
# Returns whether or not this record has been changed in any way (including whether
|
117
|
+
# any of its nested autosave associations are likewise changed)
|
118
|
+
def changed_for_autosave?
|
119
|
+
new_record? || changed? || marked_for_destruction? # TODO || nested_records_changed_for_autosave?
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
# Returns the record for an association collection that should be validated
|
125
|
+
# or saved. If +autosave+ is +false+ only new records will be returned,
|
126
|
+
# unless the parent is/was a new record itself.
|
127
|
+
def associated_records_to_validate_or_save(association, new_record, autosave)
|
128
|
+
if new_record
|
129
|
+
association && association.target
|
130
|
+
elsif autosave
|
131
|
+
association.target.find_all { |record| record.changed_for_autosave? }
|
132
|
+
else
|
133
|
+
association.target.find_all { |record| record.new_record? }
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# go through nested autosave associations that are loaded in memory (without loading
|
138
|
+
# any new ones), and return true if is changed for autosave
|
139
|
+
# def nested_records_changed_for_autosave?
|
140
|
+
# self.class.reflect_on_all_autosave_associations.any? do |reflection|
|
141
|
+
# association = association_instance_get(reflection.name)
|
142
|
+
# association && Array.wrap(association.target).any? { |a| a.changed_for_autosave? }
|
143
|
+
# end
|
144
|
+
# end
|
145
|
+
|
146
|
+
# Is used as a before_save callback to check while saving a collection
|
147
|
+
# association whether or not the parent was a new record before saving.
|
148
|
+
def before_save_collection_association
|
149
|
+
@new_record_before_save = new_record?
|
150
|
+
true
|
151
|
+
end
|
152
|
+
|
153
|
+
# Saves any new associated records, or all loaded autosave associations if
|
154
|
+
# <tt>:autosave</tt> is enabled on the association.
|
155
|
+
#
|
156
|
+
# In addition, it destroys all children that were marked for destruction
|
157
|
+
# with mark_for_destruction.
|
158
|
+
#
|
159
|
+
# This all happens inside a transaction, _if_ the Transactions module is included into
|
160
|
+
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
|
161
|
+
def save_collection_association(reflection)
|
162
|
+
# if association = association_instance_get(reflection.name)
|
163
|
+
if target = @loaded_associations[reflection.name] #TODO use a real Association wrapper
|
164
|
+
association = OpenStruct.new(target: target)
|
165
|
+
autosave = reflection.options[:autosave]
|
166
|
+
|
167
|
+
if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
|
168
|
+
# NOTE saving the object with its nested associations will properly destroy records in OpenERP
|
169
|
+
# no need to do it now like in ActiveRecord
|
170
|
+
records.each do |record|
|
171
|
+
next if record.destroyed?
|
172
|
+
|
173
|
+
saved = true
|
174
|
+
|
175
|
+
if autosave != false && (@new_record_before_save || record.new_record?)
|
176
|
+
if autosave
|
177
|
+
# saved = association.insert_record(record, false)
|
178
|
+
record.run_callbacks(:save) { false }
|
179
|
+
record.run_callbacks(:create) { false }
|
180
|
+
# else
|
181
|
+
# association.insert_record(record) unless reflection.nested?
|
182
|
+
end
|
183
|
+
elsif autosave
|
184
|
+
record.run_callbacks(:save) {false}
|
185
|
+
record.run_callbacks(:update) {false}
|
186
|
+
# saved = record.save(:validate => false)
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
end
|
191
|
+
# reconstruct the scope now that we know the owner's id
|
192
|
+
# association.reset_scope if association.respond_to?(:reset_scope)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
197
|
+
end
|