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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +354 -0
  4. data/Rakefile +5 -0
  5. data/bin/ooor +43 -0
  6. data/lib/ext_ooor.rb +5 -0
  7. data/lib/ext_ooor/version.rb +5 -0
  8. data/lib/generators/ooor/install_generator.rb +18 -0
  9. data/lib/generators/ooor/ooor.yml +49 -0
  10. data/lib/ooor.rb +230 -0
  11. data/lib/ooor/associations.rb +78 -0
  12. data/lib/ooor/autosave_association.rb +197 -0
  13. data/lib/ooor/base.rb +130 -0
  14. data/lib/ooor/base64.rb +20 -0
  15. data/lib/ooor/callbacks.rb +18 -0
  16. data/lib/ooor/errors.rb +120 -0
  17. data/lib/ooor/field_methods.rb +213 -0
  18. data/lib/ooor/helpers/core_helpers.rb +83 -0
  19. data/lib/ooor/locale.rb +11 -0
  20. data/lib/ooor/mini_active_resource.rb +86 -0
  21. data/lib/ooor/model_registry.rb +24 -0
  22. data/lib/ooor/model_schema.rb +25 -0
  23. data/lib/ooor/naming.rb +92 -0
  24. data/lib/ooor/nested_attributes.rb +57 -0
  25. data/lib/ooor/persistence.rb +353 -0
  26. data/lib/ooor/rack.rb +137 -0
  27. data/lib/ooor/railtie.rb +27 -0
  28. data/lib/ooor/reflection.rb +151 -0
  29. data/lib/ooor/reflection_ooor.rb +121 -0
  30. data/lib/ooor/relation.rb +204 -0
  31. data/lib/ooor/relation/finder_methods.rb +153 -0
  32. data/lib/ooor/report.rb +53 -0
  33. data/lib/ooor/serialization.rb +49 -0
  34. data/lib/ooor/services.rb +134 -0
  35. data/lib/ooor/session.rb +250 -0
  36. data/lib/ooor/session_handler.rb +66 -0
  37. data/lib/ooor/transport.rb +34 -0
  38. data/lib/ooor/transport/json_client.rb +65 -0
  39. data/lib/ooor/transport/xml_rpc_client.rb +15 -0
  40. data/lib/ooor/type_casting.rb +223 -0
  41. data/lib/ooor/version.rb +8 -0
  42. data/spec/cli_spec.rb +129 -0
  43. data/spec/helpers/test_helper.rb +11 -0
  44. data/spec/ooor_spec.rb +867 -0
  45. 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
@@ -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