ext_ooor 2.3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +354 -0
  4. data/Rakefile +5 -0
  5. data/bin/ooor +43 -0
  6. data/lib/ext_ooor.rb +5 -0
  7. data/lib/ext_ooor/version.rb +5 -0
  8. data/lib/generators/ooor/install_generator.rb +18 -0
  9. data/lib/generators/ooor/ooor.yml +49 -0
  10. data/lib/ooor.rb +230 -0
  11. data/lib/ooor/associations.rb +78 -0
  12. data/lib/ooor/autosave_association.rb +197 -0
  13. data/lib/ooor/base.rb +130 -0
  14. data/lib/ooor/base64.rb +20 -0
  15. data/lib/ooor/callbacks.rb +18 -0
  16. data/lib/ooor/errors.rb +120 -0
  17. data/lib/ooor/field_methods.rb +213 -0
  18. data/lib/ooor/helpers/core_helpers.rb +83 -0
  19. data/lib/ooor/locale.rb +11 -0
  20. data/lib/ooor/mini_active_resource.rb +86 -0
  21. data/lib/ooor/model_registry.rb +24 -0
  22. data/lib/ooor/model_schema.rb +25 -0
  23. data/lib/ooor/naming.rb +92 -0
  24. data/lib/ooor/nested_attributes.rb +57 -0
  25. data/lib/ooor/persistence.rb +353 -0
  26. data/lib/ooor/rack.rb +137 -0
  27. data/lib/ooor/railtie.rb +27 -0
  28. data/lib/ooor/reflection.rb +151 -0
  29. data/lib/ooor/reflection_ooor.rb +121 -0
  30. data/lib/ooor/relation.rb +204 -0
  31. data/lib/ooor/relation/finder_methods.rb +153 -0
  32. data/lib/ooor/report.rb +53 -0
  33. data/lib/ooor/serialization.rb +49 -0
  34. data/lib/ooor/services.rb +134 -0
  35. data/lib/ooor/session.rb +250 -0
  36. data/lib/ooor/session_handler.rb +66 -0
  37. data/lib/ooor/transport.rb +34 -0
  38. data/lib/ooor/transport/json_client.rb +65 -0
  39. data/lib/ooor/transport/xml_rpc_client.rb +15 -0
  40. data/lib/ooor/type_casting.rb +223 -0
  41. data/lib/ooor/version.rb +8 -0
  42. data/spec/cli_spec.rb +129 -0
  43. data/spec/helpers/test_helper.rb +11 -0
  44. data/spec/ooor_spec.rb +867 -0
  45. metadata +118 -0
@@ -0,0 +1,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