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