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
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
|