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