ext_ooor 2.3.0.1

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