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
data/lib/ooor/rack.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Ooor
|
4
|
+
|
5
|
+
DEFAULT_OOOR_SESSION_CONFIG_MAPPER = Proc.new do |env|
|
6
|
+
Ooor.logger.debug "\n\nWARNING: using DEFAULT_OOOR_SESSION_CONFIG_MAPPER, you should probably define your own instead!
|
7
|
+
You can define an Ooor::Rack.ooor_session_config_mapper block that will be evaled
|
8
|
+
in the context of the rack middleware call after user is authenticated using Warden.
|
9
|
+
Use it to map a Warden authentication to the OpenERP authentication you want.\n"""
|
10
|
+
{}
|
11
|
+
end
|
12
|
+
|
13
|
+
DEFAULT_OOOR_PUBLIC_SESSION_CONFIG_MAPPER = DEFAULT_OOOR_SESSION_CONFIG_MAPPER
|
14
|
+
|
15
|
+
DEFAULT_OOOR_ENV_DECORATOR = Proc.new do |env|
|
16
|
+
end
|
17
|
+
|
18
|
+
module RackBehaviour
|
19
|
+
extend ActiveSupport::Concern
|
20
|
+
module ClassMethods
|
21
|
+
def ooor_session_config_mapper(&block)
|
22
|
+
@ooor_session_config_mapper = block if block
|
23
|
+
@ooor_session_config_mapper || DEFAULT_OOOR_SESSION_CONFIG_MAPPER
|
24
|
+
end
|
25
|
+
|
26
|
+
def ooor_public_session_config_mapper(&block)
|
27
|
+
@ooor_public_session_config_mapper = block if block
|
28
|
+
@ooor_public_session_config_mapper || DEFAULT_OOOR_PUBLIC_SESSION_CONFIG_MAPPER
|
29
|
+
end
|
30
|
+
|
31
|
+
def decorate_env(&block)
|
32
|
+
@ooor_env_decorator = block if block
|
33
|
+
@ooor_env_decorator || DEFAULT_OOOR_ENV_DECORATOR
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def set_ooor!(env)
|
38
|
+
ooor_session = self.get_ooor_session(env)
|
39
|
+
ooor_public_session = self.get_ooor_public_session(env)
|
40
|
+
if defined?(I18n) && I18n.locale
|
41
|
+
lang = Ooor::Locale.to_erp_locale(I18n.locale)
|
42
|
+
elsif http_lang = env["HTTP_ACCEPT_LANGUAGE"]
|
43
|
+
lang = http_lang.split(',')[0].gsub('-', '_')
|
44
|
+
else
|
45
|
+
lang = ooor_session.config['lang'] || 'en_US'
|
46
|
+
end
|
47
|
+
context = {'lang' => lang} #TODO also deal with timezone
|
48
|
+
env['ooor'] = {'context' => context, 'ooor_session'=> ooor_session, 'ooor_public_session' => ooor_public_session}
|
49
|
+
Ooor::Rack.decorate_env.call(env)
|
50
|
+
end
|
51
|
+
|
52
|
+
def get_ooor_public_session(env)
|
53
|
+
config = Ooor.default_config.merge(Ooor::Rack.ooor_public_session_config_mapper.call(env))
|
54
|
+
Ooor.session_handler.retrieve_session(config, :noweb)
|
55
|
+
end
|
56
|
+
|
57
|
+
def get_ooor_session(env)
|
58
|
+
ruby_session_id = ::Rack::Request.new(env).session.id
|
59
|
+
session = Ooor.session_handler.sessions[ruby_session_id]
|
60
|
+
unless session # session could have been used by an other worker, try getting it
|
61
|
+
config = Ooor.default_config.merge(Ooor::Rack.ooor_session_config_mapper.call(env))
|
62
|
+
if config[:session_sharing] # same session_id as Odoo mode
|
63
|
+
cookies_hash = env['rack.request.cookie_hash'] || ::Rack::Request.new(env).cookies
|
64
|
+
spec = cookies_hash['session_id']
|
65
|
+
else
|
66
|
+
spec = ruby_session_id
|
67
|
+
end
|
68
|
+
web_session = Ooor.session_handler.get_web_session(spec) if spec # created by some other worker?
|
69
|
+
web_session ||= {session_id: cookies_hash['session_id']} if config[:session_sharing]
|
70
|
+
session = Ooor.session_handler.retrieve_session(config, spec, web_session)
|
71
|
+
session.config[:params] = {email: env['warden'].try(:user).try(:email)}
|
72
|
+
end
|
73
|
+
session
|
74
|
+
end
|
75
|
+
|
76
|
+
def set_ooor_session!(env, status, headers, body)
|
77
|
+
case headers["Set-Cookie"]
|
78
|
+
when nil, ''
|
79
|
+
headers["Set-Cookie"] = ""
|
80
|
+
when Array
|
81
|
+
headers["Set-Cookie"] = headers["Set-Cookie"].join("\n")
|
82
|
+
end
|
83
|
+
|
84
|
+
ooor_session = env['ooor']['ooor_session']
|
85
|
+
if ooor_session.config[:session_sharing]
|
86
|
+
share_openerp_session!(headers, ooor_session)
|
87
|
+
end
|
88
|
+
response = ::Rack::Response.new body, status, headers
|
89
|
+
response.finish
|
90
|
+
end
|
91
|
+
|
92
|
+
def share_openerp_session!(headers, ooor_session)
|
93
|
+
if ooor_session.config[:username] == 'admin'
|
94
|
+
if ooor_session.config[:force_session_sharing]
|
95
|
+
Ooor.logger.debug "Warning! force_session_sharing mode with admin user, this may be a serious security breach! Are you really in development mode?"
|
96
|
+
else
|
97
|
+
raise "Sharing OpenERP session for admin user is suicidal (use force_session_sharing in dev mode and be paranoiac about it)"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
cookie = ooor_session.web_session[:cookie]
|
101
|
+
headers["Set-Cookie"] = [headers["Set-Cookie"], cookie].join("\n")
|
102
|
+
|
103
|
+
if ooor_session.web_session[:sid] #v7
|
104
|
+
session_id = ooor_session.web_session[:session_id]
|
105
|
+
headers["Set-Cookie"] = [headers["Set-Cookie"],
|
106
|
+
"instance0|session_id=%22#{session_id}%22; Path=/",
|
107
|
+
"last_used_database=#{ooor_session.config[:database]}; Path=/",
|
108
|
+
"session_id=#{session_id}; Path=/",
|
109
|
+
].join("\n")
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
class Rack
|
116
|
+
include RackBehaviour
|
117
|
+
|
118
|
+
attr_accessor :app, :env
|
119
|
+
|
120
|
+
def initialize(app=nil)
|
121
|
+
@app=app
|
122
|
+
end
|
123
|
+
|
124
|
+
def call(env)
|
125
|
+
threadsafed = dup
|
126
|
+
threadsafed.env = env
|
127
|
+
threadsafed._call
|
128
|
+
end
|
129
|
+
|
130
|
+
def _call
|
131
|
+
set_ooor!(env)
|
132
|
+
status, headers, body = @app.call(env)
|
133
|
+
set_ooor_session!(env, status, headers, body)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
data/lib/ooor/railtie.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require "rails/railtie"
|
2
|
+
require "ooor/rack"
|
3
|
+
require "yaml"
|
4
|
+
|
5
|
+
module Ooor
|
6
|
+
class Railtie < Rails::Railtie
|
7
|
+
initializer "ooor.middleware" do |app|
|
8
|
+
Ooor.logger = Rails.logger unless $0 != 'irb'
|
9
|
+
Ooor.cache_store = Rails.cache
|
10
|
+
Ooor.new("#{Rails.root}/config/ooor.yml")
|
11
|
+
Ooor.logger.level = Ooor.default_config[:log_level] if Ooor.default_config[:log_level]
|
12
|
+
|
13
|
+
unless Ooor.default_config[:disable_locale_switcher]
|
14
|
+
if defined?(Rack::I18nLocaleSwitcher)
|
15
|
+
app.middleware.use '::Rack::I18nLocaleSwitcher'
|
16
|
+
else
|
17
|
+
puts "Could not load Rack::I18nLocaleSwitcher, if your application is internationalized, make sure to include rack-i18n_locale_switcher in your Gemfile"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
if defined?(::Warden::Manager)
|
21
|
+
app.middleware.insert_after ::Warden::Manager, ::Ooor::Rack
|
22
|
+
else
|
23
|
+
app.middleware.insert_after ::Rack::Head, ::Ooor::Rack
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'active_support/core_ext/class/attribute'
|
2
|
+
require 'active_support/core_ext/object/inclusion'
|
3
|
+
|
4
|
+
module Ooor
|
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
|
+
module Reflection # :nodoc:
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
|
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
|
18
|
+
|
19
|
+
# Reflection enables to interrogate Active Record classes and objects
|
20
|
+
# about their associations and aggregations. This information can,
|
21
|
+
# for example, be used in a form builder that takes an Active Record object
|
22
|
+
# and creates input fields for all of the attributes depending on their type
|
23
|
+
# and displays the associations to other objects.
|
24
|
+
#
|
25
|
+
# MacroReflection class has info for AggregateReflection and AssociationReflection
|
26
|
+
# classes.
|
27
|
+
module ClassMethods
|
28
|
+
#def create_reflection(macro, name, options, active_record) #NOTE overriden in Ooor
|
29
|
+
|
30
|
+
# Returns an array of AggregateReflection objects for all the aggregations in the class.
|
31
|
+
def reflect_on_all_aggregations
|
32
|
+
reflections.values.grep(AggregateReflection)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
|
36
|
+
#
|
37
|
+
# Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
|
38
|
+
#
|
39
|
+
def reflect_on_aggregation(aggregation)
|
40
|
+
reflections[aggregation].is_a?(AggregateReflection) ? reflections[aggregation] : nil
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns an array of AssociationReflection objects for all the
|
44
|
+
# associations in the class. If you only want to reflect on a certain
|
45
|
+
# association type, pass in the symbol (<tt>:has_many</tt>, <tt>:has_one</tt>,
|
46
|
+
# <tt>:belongs_to</tt>) as the first parameter.
|
47
|
+
#
|
48
|
+
# Example:
|
49
|
+
#
|
50
|
+
# Account.reflect_on_all_associations # returns an array of all associations
|
51
|
+
# Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
|
52
|
+
#
|
53
|
+
def reflect_on_all_associations(macro = nil)
|
54
|
+
association_reflections = reflections.values.grep(AssociationReflection)
|
55
|
+
macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
|
56
|
+
end
|
57
|
+
|
58
|
+
# def reflect_on_association(association) # NOTE overriden in Ooor
|
59
|
+
|
60
|
+
# Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
|
61
|
+
def reflect_on_all_autosave_associations
|
62
|
+
reflections.values.select { |reflection| reflection.options[:autosave] }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
# Abstract base class for AggregateReflection and AssociationReflection. Objects of
|
68
|
+
# AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
|
69
|
+
class MacroReflection
|
70
|
+
# Returns the name of the macro.
|
71
|
+
#
|
72
|
+
# <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:balance</tt>
|
73
|
+
# <tt>has_many :clients</tt> returns <tt>:clients</tt>
|
74
|
+
attr_reader :name
|
75
|
+
|
76
|
+
# Returns the macro type.
|
77
|
+
#
|
78
|
+
# <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:composed_of</tt>
|
79
|
+
# <tt>has_many :clients</tt> returns <tt>:has_many</tt>
|
80
|
+
attr_reader :macro
|
81
|
+
|
82
|
+
# Returns the hash of options used for the macro.
|
83
|
+
#
|
84
|
+
# <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>{ :class_name => "Money" }</tt>
|
85
|
+
# <tt>has_many :clients</tt> returns +{}+
|
86
|
+
attr_reader :options
|
87
|
+
|
88
|
+
attr_reader :active_record
|
89
|
+
|
90
|
+
attr_reader :plural_name # :nodoc:
|
91
|
+
|
92
|
+
def initialize(macro, name, options, active_record)
|
93
|
+
@macro = macro
|
94
|
+
@name = name
|
95
|
+
@options = options
|
96
|
+
@active_record = active_record
|
97
|
+
# @plural_name = active_record.pluralize_table_names ? #FIXME hacked for OOOR
|
98
|
+
# name.to_s.pluralize : name.to_s
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns the class for the macro.
|
102
|
+
#
|
103
|
+
# <tt>composed_of :balance, :class_name => 'Money'</tt> returns the Money class
|
104
|
+
# <tt>has_many :clients</tt> returns the Client class
|
105
|
+
# def klass #NOTE overriden in Ooor
|
106
|
+
# @klass ||= class_name.constantize
|
107
|
+
# end
|
108
|
+
|
109
|
+
# Returns the class name for the macro.
|
110
|
+
#
|
111
|
+
# <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>'Money'</tt>
|
112
|
+
# <tt>has_many :clients</tt> returns <tt>'Client'</tt>
|
113
|
+
def class_name
|
114
|
+
@class_name ||= (options[:class_name] || derive_class_name).to_s
|
115
|
+
end
|
116
|
+
|
117
|
+
# Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
|
118
|
+
# and +other_aggregation+ has an options hash assigned to it.
|
119
|
+
def ==(other_aggregation)
|
120
|
+
super ||
|
121
|
+
other_aggregation.kind_of?(self.class) &&
|
122
|
+
name == other_aggregation.name &&
|
123
|
+
other_aggregation.options &&
|
124
|
+
active_record == other_aggregation.active_record
|
125
|
+
end
|
126
|
+
|
127
|
+
# def sanitized_conditions #:nodoc: #NOTE not applicable in Ooor
|
128
|
+
# @sanitized_conditions ||= klass.send(:sanitize_sql, options[:conditions]) if options[:conditions]
|
129
|
+
# end
|
130
|
+
|
131
|
+
private
|
132
|
+
def derive_class_name
|
133
|
+
name.to_s.camelize
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
# Holds all the meta-data about an aggregation as it was specified in the
|
139
|
+
# Active Record class.
|
140
|
+
class AggregateReflection < MacroReflection #:nodoc:
|
141
|
+
end
|
142
|
+
|
143
|
+
# Holds all the meta-data about an association as it was specified in the
|
144
|
+
# Active Record class.
|
145
|
+
#class AssociationReflection < MacroReflection #:nodoc: #NOTE totally overriden in Ooor
|
146
|
+
|
147
|
+
# Holds all the meta-data about a :through association as it was specified
|
148
|
+
# in the Active Record class.
|
149
|
+
#class ThroughReflection < AssociationReflection #:nodoc: #NOTE commented out because not used in Ooor
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'active_support/core_ext/class/attribute'
|
2
|
+
require 'active_support/core_ext/object/inclusion'
|
3
|
+
|
4
|
+
module Ooor
|
5
|
+
# = Ooor Reflection
|
6
|
+
module ReflectionOoor # :nodoc:
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
def column_for_attribute(name)
|
10
|
+
self.class.columns_hash[name.to_s]
|
11
|
+
end
|
12
|
+
|
13
|
+
def has_attribute?(attr_name)
|
14
|
+
self.class.columns_hash.key?(attr_name.to_s)
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
def reflections
|
19
|
+
@reflections ||= {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def reflections=(reflections)
|
23
|
+
@reflections = reflections
|
24
|
+
end
|
25
|
+
|
26
|
+
def columns_hash(view_fields=nil)
|
27
|
+
if view_fields || !@t.columns_hash
|
28
|
+
view_fields ||= {}
|
29
|
+
reload_fields_definition()
|
30
|
+
@t.columns_hash ||= {}
|
31
|
+
@t.fields.each do |k, field|
|
32
|
+
unless @t.associations_keys.index(k)
|
33
|
+
@t.columns_hash[k] = field.merge({type: to_rails_type(view_fields[k] && view_fields[k]['type'] || field['type'])})
|
34
|
+
end
|
35
|
+
end
|
36
|
+
@t.columns_hash
|
37
|
+
else
|
38
|
+
@t.columns_hash
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def create_reflection(name)
|
43
|
+
reload_fields_definition()
|
44
|
+
options = {}
|
45
|
+
relation = all_fields[name]['relation']
|
46
|
+
options[:class_name] = relation
|
47
|
+
if many2one_associations.keys.include?(name)
|
48
|
+
macro = :belongs_to
|
49
|
+
elsif many2many_associations.keys.include?(name)
|
50
|
+
macro = :has_and_belongs_to_many
|
51
|
+
elsif one2many_associations.keys.include?(name)
|
52
|
+
macro = :has_many
|
53
|
+
end
|
54
|
+
reflection = Reflection::AssociationReflection.new(macro, name, options, nil)#active_record) #TODO active_record?
|
55
|
+
self.reflections = self.reflections.merge(name => reflection)
|
56
|
+
reflection
|
57
|
+
end
|
58
|
+
|
59
|
+
def reflect_on_association(association)
|
60
|
+
reflections[association] ||= create_reflection(association.to_s).tap do |reflection|
|
61
|
+
reflection.session = session
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
module Ooor
|
71
|
+
# = Active Record Reflection
|
72
|
+
module Reflection # :nodoc:
|
73
|
+
|
74
|
+
class MacroReflection
|
75
|
+
attr_accessor :session
|
76
|
+
end
|
77
|
+
|
78
|
+
# Holds all the meta-data about an association as it was specified in the
|
79
|
+
# Active Record class.
|
80
|
+
class AssociationReflection < MacroReflection #:nodoc:
|
81
|
+
# Returns the target association's class.
|
82
|
+
#
|
83
|
+
# class Author < ActiveRecord::Base
|
84
|
+
# has_many :books
|
85
|
+
# end
|
86
|
+
#
|
87
|
+
# Author.reflect_on_association(:books).klass
|
88
|
+
# # => Book
|
89
|
+
#
|
90
|
+
# <b>Note:</b> Do not call +klass.new+ or +klass.create+ to instantiate
|
91
|
+
# a new association object. Use +build_association+ or +create_association+
|
92
|
+
# instead. This allows plugins to hook into association object creation.
|
93
|
+
def klass
|
94
|
+
# @klass ||= active_record.send(:compute_type, class_name)
|
95
|
+
# @klass ||= session.class_name_from_model_key(class_name).constantize
|
96
|
+
@klass = session.const_get(class_name)
|
97
|
+
end
|
98
|
+
|
99
|
+
def initialize(macro, name, options, active_record)
|
100
|
+
super
|
101
|
+
@collection = macro.in?([:has_many, :has_and_belongs_to_many])
|
102
|
+
end
|
103
|
+
|
104
|
+
# Returns a new, unsaved instance of the associated class. +options+ will
|
105
|
+
# be passed to the class's constructor.
|
106
|
+
def build_association(*options, &block)
|
107
|
+
klass.new(*options, &block)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Returns whether or not this association reflection is for a collection
|
111
|
+
# association. Returns +true+ if the +macro+ is either +has_many+ or
|
112
|
+
# +has_and_belongs_to_many+, +false+ otherwise.
|
113
|
+
def collection?
|
114
|
+
@collection
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
# OOOR: OpenObject On Ruby
|
2
|
+
# Copyright (C) 2009-TODAY Akretion LTDA (<http://www.akretion.com>).
|
3
|
+
# Author: Raphaël Valyi
|
4
|
+
# Licensed under the MIT license, see MIT-LICENSE file
|
5
|
+
|
6
|
+
#TODO chainability of where via scopes
|
7
|
+
|
8
|
+
module Ooor
|
9
|
+
# = Similar to Active Record Relation
|
10
|
+
# subset of https://github.com/rails/rails/blob/master/activerecord/lib/active_record/relation/query_methods.rb
|
11
|
+
class Relation
|
12
|
+
attr_reader :klass, :loaded
|
13
|
+
attr_accessor :options, :count_field, :includes_values, :eager_load_values, :preload_values,
|
14
|
+
:select_values, :group_values, :order_values, :reorder_flag, :joins_values, :where_values, :having_values,
|
15
|
+
:limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value, :from_value, :page_value, :per_value
|
16
|
+
alias :loaded? :loaded
|
17
|
+
alias :model :klass
|
18
|
+
|
19
|
+
def build_where(opts, other = [])#TODO OpenERP domain is more than just the intersection of restrictions
|
20
|
+
case opts
|
21
|
+
when Array || '|' || '&'
|
22
|
+
[opts]
|
23
|
+
when Hash
|
24
|
+
opts.keys.map {|key|["#{key}", "=", opts[key]]}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def where(opts, *rest)
|
29
|
+
relation = clone
|
30
|
+
if opts.is_a?(Array) && opts.any? {|e| e.is_a? Array}
|
31
|
+
relation.where_values = opts
|
32
|
+
else
|
33
|
+
relation.where_values += build_where(opts, rest) unless opts.blank?
|
34
|
+
end
|
35
|
+
relation
|
36
|
+
end
|
37
|
+
|
38
|
+
# def having(*args)
|
39
|
+
# relation = clone
|
40
|
+
# relation.having_values += build_where(*args) unless args.blank?
|
41
|
+
# relation
|
42
|
+
# end
|
43
|
+
|
44
|
+
def limit(value)
|
45
|
+
relation = clone
|
46
|
+
relation.limit_value = value
|
47
|
+
relation
|
48
|
+
end
|
49
|
+
|
50
|
+
def offset(value)
|
51
|
+
relation = clone
|
52
|
+
relation.offset_value = value
|
53
|
+
relation
|
54
|
+
end
|
55
|
+
|
56
|
+
def order(*args)
|
57
|
+
relation = clone
|
58
|
+
relation.order_values += args.flatten unless args.blank? || args[0] == false
|
59
|
+
relation
|
60
|
+
end
|
61
|
+
|
62
|
+
def includes(*values)
|
63
|
+
relation = clone
|
64
|
+
relation.includes_values = values
|
65
|
+
relation
|
66
|
+
end
|
67
|
+
|
68
|
+
# def count(column_name = nil, options = {}) #TODO possible to implement?
|
69
|
+
# column_name, options = nil, column_name if column_name.is_a?(Hash)
|
70
|
+
# calculate(:count, column_name, options)
|
71
|
+
# end
|
72
|
+
|
73
|
+
def initialize(klass, options={})
|
74
|
+
@klass = klass
|
75
|
+
@where_values = []
|
76
|
+
@loaded = false
|
77
|
+
@options = options
|
78
|
+
@count_field = false
|
79
|
+
@offset_value = 0
|
80
|
+
@order_values = []
|
81
|
+
end
|
82
|
+
|
83
|
+
def new(*args, &block)
|
84
|
+
#TODO inject current domain in *args
|
85
|
+
@klass.new(*args, &block)
|
86
|
+
end
|
87
|
+
|
88
|
+
alias build new
|
89
|
+
|
90
|
+
def reload
|
91
|
+
reset
|
92
|
+
to_a # force reload
|
93
|
+
self
|
94
|
+
end
|
95
|
+
|
96
|
+
def initialize_copy(other)
|
97
|
+
reset
|
98
|
+
end
|
99
|
+
|
100
|
+
def reset
|
101
|
+
@first = @last = @to_sql = @order_clause = @scope_for_create = @arel = @loaded = nil
|
102
|
+
@should_eager_load = @join_dependency = nil
|
103
|
+
@records = []
|
104
|
+
self
|
105
|
+
end
|
106
|
+
|
107
|
+
def apply_finder_options(options)
|
108
|
+
relation = clone
|
109
|
+
relation.options = options #TODO this may be too simplified for chainability, merge smartly instead?
|
110
|
+
relation
|
111
|
+
end
|
112
|
+
|
113
|
+
def where_values
|
114
|
+
if @options && @options[:domain]
|
115
|
+
@options[:domain]
|
116
|
+
else
|
117
|
+
@where_values
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# A convenience wrapper for <tt>find(:all, *args)</tt>. You can pass in all the
|
122
|
+
# same arguments to this method as you can to <tt>find(:all)</tt>.
|
123
|
+
def all(*args)
|
124
|
+
args.any? ? apply_finder_options(args.first).to_a : to_a
|
125
|
+
end
|
126
|
+
|
127
|
+
def first(*args)
|
128
|
+
limit(1).order('id').all(*args).first
|
129
|
+
end
|
130
|
+
|
131
|
+
def last(*args)
|
132
|
+
limit(1).order('id DESC').all(*args).first
|
133
|
+
end
|
134
|
+
|
135
|
+
def to_a
|
136
|
+
if loaded?
|
137
|
+
@records
|
138
|
+
else
|
139
|
+
if @order_values.empty?
|
140
|
+
search_order = false
|
141
|
+
else
|
142
|
+
search_order = @order_values.join(", ")
|
143
|
+
end
|
144
|
+
|
145
|
+
if @options && @options[:name_search]
|
146
|
+
name_search = @klass.name_search(@options[:name_search], where_values, 'ilike', @options[:context], @limit_value)
|
147
|
+
@records = name_search.map do |tuple|
|
148
|
+
@klass.new({name: tuple[1]}, []).tap { |r| r.id = tuple[0] } #TODO load the fields optionally
|
149
|
+
end
|
150
|
+
else
|
151
|
+
load_records_page(search_order)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def eager_loading?
|
157
|
+
false
|
158
|
+
end
|
159
|
+
|
160
|
+
def inspect
|
161
|
+
entries = to_a.take([limit_value, 11].compact.min).map!(&:inspect)
|
162
|
+
entries[10] = '...' if entries.size == 11
|
163
|
+
|
164
|
+
"#<#{self.class.name} [#{entries.join(', ')}]>"
|
165
|
+
end
|
166
|
+
|
167
|
+
protected
|
168
|
+
|
169
|
+
def load_records_page(search_order)
|
170
|
+
if @per_value && @page_value
|
171
|
+
offset = @per_value * @page_value
|
172
|
+
limit = @per_value
|
173
|
+
else
|
174
|
+
offset = @offset_value
|
175
|
+
limit = @limit_value || false
|
176
|
+
end
|
177
|
+
@loaded = true
|
178
|
+
opts = @options.merge({
|
179
|
+
domain: where_values,
|
180
|
+
offset: offset,
|
181
|
+
limit: limit,
|
182
|
+
order: search_order,
|
183
|
+
includes: includes_values,
|
184
|
+
})
|
185
|
+
scope = @options.delete(:ids) || :all
|
186
|
+
if scope == []
|
187
|
+
@records = []
|
188
|
+
else
|
189
|
+
@records = @klass.find(scope, opts)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def method_missing(method, *args, &block)
|
194
|
+
if Array.method_defined?(method)
|
195
|
+
to_a.send(method, *args, &block)
|
196
|
+
elsif @klass.respond_to?(method)
|
197
|
+
@klass.send(method, *args, &block)
|
198
|
+
else
|
199
|
+
@klass.rpc_execute(method.to_s, to_a.map {|record| record.id}, *args)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|
204
|
+
end
|