ooor 2.0.3 → 2.0.4
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/README.md +1 -0
- data/bin/ooor +1 -0
- data/lib/ooor.rb +2 -0
- data/lib/ooor/associations.rb +30 -40
- data/lib/ooor/base.rb +22 -113
- data/lib/ooor/callbacks.rb +18 -0
- data/lib/ooor/field_methods.rb +133 -85
- data/lib/ooor/helpers/core_helpers.rb +9 -63
- data/lib/ooor/mini_active_resource.rb +0 -18
- data/lib/ooor/naming.rb +2 -2
- data/lib/ooor/persistence.rb +140 -0
- data/lib/ooor/railtie.rb +2 -1
- data/lib/ooor/reflection.rb +11 -399
- data/lib/ooor/reflection_ooor.rb +36 -11
- data/lib/ooor/relation.rb +48 -23
- data/lib/ooor/type_casting.rb +108 -75
- data/lib/ooor/version.rb +1 -1
- data/spec/ooor_spec.rb +133 -27
- metadata +35 -40
@@ -19,9 +19,9 @@ Ooor.xtend('ir.module.module') do
|
|
19
19
|
dependency_modules = []
|
20
20
|
modules.select { |m| m.dependencies_id }.each do |mod|
|
21
21
|
mod.dependencies_id.each do |dep|
|
22
|
-
dep_module =
|
23
|
-
|
24
|
-
|
22
|
+
dep_module = self.find(:first,
|
23
|
+
:domain => [['name', '=', dep.name]],
|
24
|
+
:fields => ['id', 'state', 'dependencies_id'])
|
25
25
|
if dep_module.nil?
|
26
26
|
raise RuntimeError, "#{dep.name} not found"
|
27
27
|
end
|
@@ -32,66 +32,13 @@ Ooor.xtend('ir.module.module') do
|
|
32
32
|
dependency_modules.uniq { |m| m.id }
|
33
33
|
end
|
34
34
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
# - modules : A [] of valid IrModuleModule instance
|
40
|
-
# Return
|
41
|
-
# - True
|
42
|
-
# Usage Example:
|
43
|
-
# res = IrModuleModule.install_modules(@openerp, modules)
|
44
|
-
def self.install_modules(modules, dependencies=false)
|
45
|
-
res = true
|
46
|
-
if dependencies
|
47
|
-
dependency_modules = get_dependencies(modules)
|
48
|
-
modules.concat(dependency_modules) if dependency_modules
|
49
|
-
end
|
50
|
-
modules_toinstall_ids = []
|
51
|
-
modules_toupgrade_ids = []
|
52
|
-
# If not installed, do it. Otherwise update it
|
53
|
-
modules.each do |m|
|
54
|
-
if m.state == 'uninstalled'
|
55
|
-
m.state = 'to install'
|
56
|
-
m.save
|
57
|
-
modules_toinstall_ids << m.id
|
58
|
-
elsif m.state == 'installed'
|
59
|
-
m.state = 'to upgrade'
|
60
|
-
m.save
|
61
|
-
modules_toupgrade_ids << m.id
|
62
|
-
elsif m.state == 'to install'
|
63
|
-
modules_toinstall_ids << m.id
|
64
|
-
elsif m.state == 'to upgrade'
|
65
|
-
modules_toupgrade_ids << m.id
|
66
|
-
end
|
67
|
-
end
|
68
|
-
#First installed required modules, then upgrade the others
|
69
|
-
upgrade = BaseModuleUpgrade.create()
|
70
|
-
upgrade.upgrade_module()
|
71
|
-
# IrModuleModule.button_install(modules_toinstall_ids)
|
72
|
-
# IrModuleModule.button_upgrade(modules_toupgrade_ids)
|
73
|
-
|
74
|
-
if res
|
75
|
-
return true
|
76
|
-
else
|
77
|
-
raise "!!! --- HELPER ERROR : install_modules was unable to install needed modules.."
|
78
|
-
end
|
79
|
-
openerp.load_models() # reload in order to have model Classes for modules installed
|
80
|
-
end
|
81
|
-
|
82
|
-
def print_uml
|
83
|
-
l = IrModelData.find(:all, :domain => {:model=>"ir.model", :module=>name})
|
84
|
-
model_names = []
|
85
|
-
l.each {|i| model_names << i.name.gsub('_', '.').gsub(/^model.report/, '').gsub(/^model./, '')}
|
86
|
-
classes = []
|
87
|
-
model_names.each {|i| begin classes << Object.const_get(IrModel.class_name_from_model_key i); rescue; end}
|
88
|
-
classes.reject! {|m| m.openerp_model.index("report")} #NOTE we would need a more robust test
|
89
|
-
begin
|
90
|
-
classes.reject! {|m| IrModel.read(m.openerp_id, ['osv_memory'])['osv_memory']}
|
91
|
-
rescue
|
35
|
+
def self.install_modules(modules)
|
36
|
+
modules = modules.map { |name| self.find(:first, domain: {name: name})}
|
37
|
+
modules.each do |mod|
|
38
|
+
mod.button_install unless mod.state == "installed"
|
92
39
|
end
|
93
|
-
|
94
|
-
|
40
|
+
wizard = BaseModuleUpgrade.create
|
41
|
+
wizard.upgrade_module
|
95
42
|
end
|
96
43
|
|
97
44
|
def print_dependency_graph
|
@@ -122,7 +69,6 @@ Ooor.xtend('ir.module.module') do
|
|
122
69
|
system("tred < #{self.name}-pre.dot > #{self.name}.dot")
|
123
70
|
cmd_line2 = "dot -Tcmapx -o#{self.name}.map -Tpng -o#{self.name}.png #{self.name}.dot"
|
124
71
|
system(cmd_line2)
|
125
|
-
|
126
72
|
end
|
127
73
|
|
128
74
|
end
|
@@ -68,24 +68,6 @@ module Ooor
|
|
68
68
|
self.class.__send__(:split_options, options)
|
69
69
|
end
|
70
70
|
|
71
|
-
def method_missing(method_symbol, *arguments) #:nodoc:
|
72
|
-
method_name = method_symbol.to_s
|
73
|
-
|
74
|
-
if method_name =~ /(=|\?)$/
|
75
|
-
case $1
|
76
|
-
when "="
|
77
|
-
attributes[$`] = arguments.first
|
78
|
-
when "?"
|
79
|
-
attributes[$`]
|
80
|
-
end
|
81
|
-
else
|
82
|
-
return attributes[method_name] if attributes.include?(method_name)
|
83
|
-
# not set right now but we know about it
|
84
|
-
return nil if known_attributes.include?(method_name)
|
85
|
-
super
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
71
|
include ActiveModel::Conversion
|
90
72
|
include ActiveModel::Serializers::JSON
|
91
73
|
include ActiveModel::Serializers::Xml
|
data/lib/ooor/naming.rb
CHANGED
@@ -10,7 +10,7 @@ module Ooor
|
|
10
10
|
namespace = self.parents.detect do |n|
|
11
11
|
n.respond_to?(:use_relative_model_naming?) && n.use_relative_model_naming?
|
12
12
|
end
|
13
|
-
ActiveModel::Name.new(self, namespace, self.description).tap do |r|
|
13
|
+
ActiveModel::Name.new(self, namespace, self.description || self.openerp_model).tap do |r|
|
14
14
|
def r.param_key
|
15
15
|
@klass.openerp_model.gsub('.', '_')
|
16
16
|
end
|
@@ -52,7 +52,7 @@ module Ooor
|
|
52
52
|
def alias(context={})
|
53
53
|
# NOTE in v8, see if we can use ModelConvert here https://github.com/akretion/openerp-addons/blob/trunk-website-al/website/models/ir_http.py#L126
|
54
54
|
if connection.config[:aliases]
|
55
|
-
lang = context['lang'] || connection.config[
|
55
|
+
lang = context['lang'] || connection.config['lang'] || 'en_US'
|
56
56
|
if alias_data = connection.config[:aliases][lang]
|
57
57
|
alias_data.select{|key, value| value == openerp_model }.keys[0] || openerp_model
|
58
58
|
else
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# OOOR: OpenObject On Ruby
|
2
|
+
# Copyright (C) 2009-2014 Akretion LTDA (<http://www.akretion.com>).
|
3
|
+
# Author: Raphaël Valyi
|
4
|
+
# Licensed under the MIT license, see MIT-LICENSE file
|
5
|
+
|
6
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
7
|
+
require 'active_model/attribute_methods'
|
8
|
+
require 'active_model/dirty'
|
9
|
+
require 'ooor/errors'
|
10
|
+
|
11
|
+
module Ooor
|
12
|
+
|
13
|
+
# the base class for proxies to OpenERP objects
|
14
|
+
module Persistence
|
15
|
+
|
16
|
+
def load(attributes, remove_root=false, persisted=false)#an attribute might actually be a association too, will be determined here
|
17
|
+
self.class.reload_fields_definition(false, object_session)
|
18
|
+
raise ArgumentError, "expected an attributes Hash, got #{attributes.inspect}" unless attributes.is_a?(Hash)
|
19
|
+
@prefix_options, attributes = split_options(attributes)
|
20
|
+
@associations ||= {}
|
21
|
+
@attributes ||= {}
|
22
|
+
@loaded_associations = {}
|
23
|
+
attributes.each do |key, value|
|
24
|
+
self.send "#{key}=".to_sym, value if self.respond_to?("#{key}=".to_sym)
|
25
|
+
end
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
#takes care of reading OpenERP default field values.
|
30
|
+
def initialize(attributes = {}, default_get_list=false, context={}, persisted=false)
|
31
|
+
@attributes = {}
|
32
|
+
@prefix_options = {}
|
33
|
+
@ir_model_data_id = attributes.delete(:ir_model_data_id)
|
34
|
+
@object_session = {}
|
35
|
+
@object_session = HashWithIndifferentAccess.new(context)
|
36
|
+
@persisted = persisted
|
37
|
+
self.class.reload_fields_definition(false, @object_session)
|
38
|
+
if default_get_list == []
|
39
|
+
load(attributes)
|
40
|
+
else
|
41
|
+
load_with_defaults(attributes, default_get_list)
|
42
|
+
end.tap do
|
43
|
+
if id
|
44
|
+
@previously_changed = ActiveSupport::HashWithIndifferentAccess.new # see ActiveModel::Dirty reset_changes
|
45
|
+
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Saves (+create+) or \updates (+write+) a resource. Delegates to +create+ if the object is \new,
|
51
|
+
# +update+ if it exists.
|
52
|
+
def save(context={}, reload=true)
|
53
|
+
create_or_update(context, reload)
|
54
|
+
end
|
55
|
+
|
56
|
+
def create_or_update(context={}, reload=true)
|
57
|
+
run_callbacks :save do
|
58
|
+
new? ? create_record(context, reload) : update_record(context, reload)
|
59
|
+
end
|
60
|
+
rescue ValidationError => e
|
61
|
+
e.extract_validation_error!(errors)
|
62
|
+
return false
|
63
|
+
end
|
64
|
+
|
65
|
+
# Create (i.e., \save to OpenERP service) the \new resource.
|
66
|
+
def create(context={}, reload=true)
|
67
|
+
create_or_update(context, reload)
|
68
|
+
end
|
69
|
+
|
70
|
+
def create_record(context={}, reload=true)
|
71
|
+
run_callbacks :create do
|
72
|
+
self.id = rpc_execute('create', to_openerp_hash, context)
|
73
|
+
if @ir_model_data_id
|
74
|
+
IrModelData.create(model: self.class.openerp_model,
|
75
|
+
'module' => @ir_model_data_id[0],
|
76
|
+
'name' => @ir_model_data_id[1],
|
77
|
+
'res_id' => self.id)
|
78
|
+
end
|
79
|
+
@persisted = true
|
80
|
+
reload_fields(context) if reload
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def update_attributes(attributes, context={}, reload=true)
|
85
|
+
load(attributes, false) && save(context, reload)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Update the resource on the remote service.
|
89
|
+
def update(context={}, reload=true, keys=nil)
|
90
|
+
create_or_update(context, reload, keys)
|
91
|
+
end
|
92
|
+
|
93
|
+
def update_record(context={}, reload=true)
|
94
|
+
run_callbacks :update do
|
95
|
+
rpc_execute('write', [self.id], to_openerp_hash, context)
|
96
|
+
reload_fields(context) if reload
|
97
|
+
@persisted = true
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
#Deletes the record in OpenERP and freezes this instance to reflect that no changes should be made (since they can’t be persisted).
|
102
|
+
def destroy(context={})
|
103
|
+
run_callbacks :destroy do
|
104
|
+
rpc_execute('unlink', [self.id], context)
|
105
|
+
@destroyed = true
|
106
|
+
freeze
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
#OpenERP copy method, load persisted copied Object
|
111
|
+
def copy(defaults={}, context={})
|
112
|
+
self.class.find(rpc_execute('copy', self.id, defaults, context), context: context)
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def load_with_defaults(attributes, default_get_list)
|
118
|
+
defaults = rpc_execute("default_get", default_get_list || self.class.fields.keys + self.class.associations_keys, object_session.dup)
|
119
|
+
attributes = HashWithIndifferentAccess.new(defaults.merge(attributes.reject {|k, v| v.blank? }))
|
120
|
+
load(attributes)
|
121
|
+
end
|
122
|
+
|
123
|
+
def load_on_change_result(result, field_name, field_value)
|
124
|
+
if result["warning"]
|
125
|
+
self.class.logger.info result["warning"]["title"]
|
126
|
+
self.class.logger.info result["warning"]["message"]
|
127
|
+
end
|
128
|
+
attrs = @attributes.merge(field_name => field_value)
|
129
|
+
attrs.merge!(result["value"])
|
130
|
+
load(attrs)
|
131
|
+
end
|
132
|
+
|
133
|
+
def reload_fields(context)
|
134
|
+
record = self.class.find(self.id, context: context)
|
135
|
+
load(record.attributes.merge(record.associations))
|
136
|
+
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new # see ActiveModel::Dirty
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|
data/lib/ooor/railtie.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "rails/railtie"
|
2
2
|
require "ooor/rack"
|
3
|
+
require "yaml"
|
3
4
|
|
4
5
|
module Ooor
|
5
6
|
class Railtie < Rails::Railtie
|
@@ -29,7 +30,7 @@ module Ooor
|
|
29
30
|
|
30
31
|
def load_config(config_file=nil, env=nil)
|
31
32
|
config_file ||= defined?(Rails.root) && "#{Rails.root}/config/ooor.yml" || 'ooor.yml'
|
32
|
-
@config = HashWithIndifferentAccess.new(YAML.load_file(config_file)[env || 'development'])
|
33
|
+
@config = HashWithIndifferentAccess.new(::YAML.load_file(config_file)[env || 'development'])
|
33
34
|
rescue SystemCallError
|
34
35
|
Ooor.logger.error """failed to load OOOR yaml configuration file.
|
35
36
|
make sure your app has a #{config_file} file correctly set up
|
data/lib/ooor/reflection.rb
CHANGED
@@ -2,7 +2,7 @@ require 'active_support/core_ext/class/attribute'
|
|
2
2
|
require 'active_support/core_ext/object/inclusion'
|
3
3
|
|
4
4
|
# NOTE this is a scoped copy of ActiveRecord reflection.rb
|
5
|
-
# the few necessary hacks are explicited with a FIXME
|
5
|
+
# the few necessary hacks are explicited with a FIXME or a NOTE
|
6
6
|
# an addition Ooor specific reflection module completes this one explicitely
|
7
7
|
module Ooor
|
8
8
|
# = Active Record Reflection
|
@@ -23,18 +23,7 @@ module Ooor
|
|
23
23
|
# MacroReflection class has info for AggregateReflection and AssociationReflection
|
24
24
|
# classes.
|
25
25
|
module ClassMethods
|
26
|
-
def create_reflection(macro, name, options, active_record)
|
27
|
-
case macro
|
28
|
-
when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
|
29
|
-
klass = options[:through] ? ThroughReflection : AssociationReflection
|
30
|
-
reflection = klass.new(macro, name, options, active_record)
|
31
|
-
when :composed_of
|
32
|
-
reflection = AggregateReflection.new(macro, name, options, active_record)
|
33
|
-
end
|
34
|
-
|
35
|
-
self.reflections = self.reflections.merge(name => reflection)
|
36
|
-
reflection
|
37
|
-
end
|
26
|
+
#def create_reflection(macro, name, options, active_record) #NOTE overriden in Ooor
|
38
27
|
|
39
28
|
# Returns an array of AggregateReflection objects for all the aggregations in the class.
|
40
29
|
def reflect_on_all_aggregations
|
@@ -64,14 +53,7 @@ module Ooor
|
|
64
53
|
macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
|
65
54
|
end
|
66
55
|
|
67
|
-
#
|
68
|
-
#
|
69
|
-
# Account.reflect_on_association(:owner) # returns the owner AssociationReflection
|
70
|
-
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
|
71
|
-
#
|
72
|
-
def reflect_on_association(association)
|
73
|
-
reflections[association].is_a?(AssociationReflection) ? reflections[association] : nil
|
74
|
-
end
|
56
|
+
# def reflect_on_association(association) # NOTE overriden in Ooor
|
75
57
|
|
76
58
|
# Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
|
77
59
|
def reflect_on_all_autosave_associations
|
@@ -118,9 +100,9 @@ module Ooor
|
|
118
100
|
#
|
119
101
|
# <tt>composed_of :balance, :class_name => 'Money'</tt> returns the Money class
|
120
102
|
# <tt>has_many :clients</tt> returns the Client class
|
121
|
-
def klass
|
122
|
-
@klass ||= class_name.constantize
|
123
|
-
end
|
103
|
+
# def klass #NOTE overriden in Ooor
|
104
|
+
# @klass ||= class_name.constantize
|
105
|
+
# end
|
124
106
|
|
125
107
|
# Returns the class name for the macro.
|
126
108
|
#
|
@@ -140,9 +122,9 @@ module Ooor
|
|
140
122
|
active_record == other_aggregation.active_record
|
141
123
|
end
|
142
124
|
|
143
|
-
def sanitized_conditions #:nodoc:
|
144
|
-
@sanitized_conditions ||= klass.send(:sanitize_sql, options[:conditions]) if options[:conditions]
|
145
|
-
end
|
125
|
+
# def sanitized_conditions #:nodoc: #NOTE not applicable in Ooor
|
126
|
+
# @sanitized_conditions ||= klass.send(:sanitize_sql, options[:conditions]) if options[:conditions]
|
127
|
+
# end
|
146
128
|
|
147
129
|
private
|
148
130
|
def derive_class_name
|
@@ -158,380 +140,10 @@ module Ooor
|
|
158
140
|
|
159
141
|
# Holds all the meta-data about an association as it was specified in the
|
160
142
|
# Active Record class.
|
161
|
-
class AssociationReflection < MacroReflection #:nodoc:
|
162
|
-
# Returns the target association's class.
|
163
|
-
#
|
164
|
-
# class Author < ActiveRecord::Base
|
165
|
-
# has_many :books
|
166
|
-
# end
|
167
|
-
#
|
168
|
-
# Author.reflect_on_association(:books).klass
|
169
|
-
# # => Book
|
170
|
-
#
|
171
|
-
# <b>Note:</b> Do not call +klass.new+ or +klass.create+ to instantiate
|
172
|
-
# a new association object. Use +build_association+ or +create_association+
|
173
|
-
# instead. This allows plugins to hook into association object creation.
|
174
|
-
def klass
|
175
|
-
@klass ||= active_record.send(:compute_type, class_name)
|
176
|
-
end
|
177
|
-
|
178
|
-
def initialize(macro, name, options, active_record)
|
179
|
-
super
|
180
|
-
@collection = macro.in?([:has_many, :has_and_belongs_to_many])
|
181
|
-
end
|
182
|
-
|
183
|
-
# Returns a new, unsaved instance of the associated class. +options+ will
|
184
|
-
# be passed to the class's constructor.
|
185
|
-
def build_association(*options, &block)
|
186
|
-
klass.new(*options, &block)
|
187
|
-
end
|
188
|
-
|
189
|
-
def table_name
|
190
|
-
@table_name ||= klass.table_name
|
191
|
-
end
|
192
|
-
|
193
|
-
def quoted_table_name
|
194
|
-
@quoted_table_name ||= klass.quoted_table_name
|
195
|
-
end
|
196
|
-
|
197
|
-
def foreign_key
|
198
|
-
@foreign_key ||= options[:foreign_key] || derive_foreign_key
|
199
|
-
end
|
200
|
-
|
201
|
-
def foreign_type
|
202
|
-
@foreign_type ||= options[:foreign_type] || "#{name}_type"
|
203
|
-
end
|
204
|
-
|
205
|
-
def type
|
206
|
-
@type ||= options[:as] && "#{options[:as]}_type"
|
207
|
-
end
|
208
|
-
|
209
|
-
def primary_key_column
|
210
|
-
@primary_key_column ||= klass.columns.find { |c| c.name == klass.primary_key }
|
211
|
-
end
|
212
|
-
|
213
|
-
def association_foreign_key
|
214
|
-
@association_foreign_key ||= options[:association_foreign_key] || class_name.foreign_key
|
215
|
-
end
|
216
|
-
|
217
|
-
# klass option is necessary to support loading polymorphic associations
|
218
|
-
def association_primary_key(klass = nil)
|
219
|
-
options[:primary_key] || primary_key(klass || self.klass)
|
220
|
-
end
|
221
|
-
|
222
|
-
def active_record_primary_key
|
223
|
-
@active_record_primary_key ||= options[:primary_key] || primary_key(active_record)
|
224
|
-
end
|
225
|
-
|
226
|
-
def counter_cache_column
|
227
|
-
if options[:counter_cache] == true
|
228
|
-
"#{active_record.name.demodulize.underscore.pluralize}_count"
|
229
|
-
elsif options[:counter_cache]
|
230
|
-
options[:counter_cache].to_s
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
|
-
def columns(tbl_name, log_msg)
|
235
|
-
@columns ||= klass.connection.columns(tbl_name, log_msg)
|
236
|
-
end
|
237
|
-
|
238
|
-
def reset_column_information
|
239
|
-
@columns = nil
|
240
|
-
end
|
241
|
-
|
242
|
-
def check_validity!
|
243
|
-
check_validity_of_inverse!
|
244
|
-
end
|
245
|
-
|
246
|
-
def check_validity_of_inverse!
|
247
|
-
unless options[:polymorphic]
|
248
|
-
if has_inverse? && inverse_of.nil?
|
249
|
-
raise InverseOfAssociationNotFoundError.new(self)
|
250
|
-
end
|
251
|
-
end
|
252
|
-
end
|
253
|
-
|
254
|
-
def through_reflection
|
255
|
-
nil
|
256
|
-
end
|
257
|
-
|
258
|
-
def source_reflection
|
259
|
-
nil
|
260
|
-
end
|
261
|
-
|
262
|
-
# A chain of reflections from this one back to the owner. For more see the explanation in
|
263
|
-
# ThroughReflection.
|
264
|
-
def chain
|
265
|
-
[self]
|
266
|
-
end
|
267
|
-
|
268
|
-
def nested?
|
269
|
-
false
|
270
|
-
end
|
271
|
-
|
272
|
-
# An array of arrays of conditions. Each item in the outside array corresponds to a reflection
|
273
|
-
# in the #chain. The inside arrays are simply conditions (and each condition may itself be
|
274
|
-
# a hash, array, arel predicate, etc...)
|
275
|
-
def conditions
|
276
|
-
[[options[:conditions]].compact]
|
277
|
-
end
|
278
|
-
|
279
|
-
alias :source_macro :macro
|
280
|
-
|
281
|
-
def has_inverse?
|
282
|
-
@options[:inverse_of]
|
283
|
-
end
|
284
|
-
|
285
|
-
def inverse_of
|
286
|
-
if has_inverse?
|
287
|
-
@inverse_of ||= klass.reflect_on_association(options[:inverse_of])
|
288
|
-
end
|
289
|
-
end
|
290
|
-
|
291
|
-
def polymorphic_inverse_of(associated_class)
|
292
|
-
if has_inverse?
|
293
|
-
if inverse_relationship = associated_class.reflect_on_association(options[:inverse_of])
|
294
|
-
inverse_relationship
|
295
|
-
else
|
296
|
-
raise InverseOfAssociationNotFoundError.new(self, associated_class)
|
297
|
-
end
|
298
|
-
end
|
299
|
-
end
|
300
|
-
|
301
|
-
# Returns whether or not this association reflection is for a collection
|
302
|
-
# association. Returns +true+ if the +macro+ is either +has_many+ or
|
303
|
-
# +has_and_belongs_to_many+, +false+ otherwise.
|
304
|
-
def collection?
|
305
|
-
@collection
|
306
|
-
end
|
307
|
-
|
308
|
-
# Returns whether or not the association should be validated as part of
|
309
|
-
# the parent's validation.
|
310
|
-
#
|
311
|
-
# Unless you explicitly disable validation with
|
312
|
-
# <tt>:validate => false</tt>, validation will take place when:
|
313
|
-
#
|
314
|
-
# * you explicitly enable validation; <tt>:validate => true</tt>
|
315
|
-
# * you use autosave; <tt>:autosave => true</tt>
|
316
|
-
# * the association is a +has_many+ association
|
317
|
-
def validate?
|
318
|
-
!options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
|
319
|
-
end
|
320
|
-
|
321
|
-
# Returns +true+ if +self+ is a +belongs_to+ reflection.
|
322
|
-
def belongs_to?
|
323
|
-
macro == :belongs_to
|
324
|
-
end
|
325
|
-
|
326
|
-
def association_class
|
327
|
-
case macro
|
328
|
-
when :belongs_to
|
329
|
-
if options[:polymorphic]
|
330
|
-
Associations::BelongsToPolymorphicAssociation
|
331
|
-
else
|
332
|
-
Associations::BelongsToAssociation
|
333
|
-
end
|
334
|
-
when :has_and_belongs_to_many
|
335
|
-
Associations::HasAndBelongsToManyAssociation
|
336
|
-
when :has_many
|
337
|
-
if options[:through]
|
338
|
-
Associations::HasManyThroughAssociation
|
339
|
-
else
|
340
|
-
Associations::HasManyAssociation
|
341
|
-
end
|
342
|
-
when :has_one
|
343
|
-
if options[:through]
|
344
|
-
Associations::HasOneThroughAssociation
|
345
|
-
else
|
346
|
-
Associations::HasOneAssociation
|
347
|
-
end
|
348
|
-
end
|
349
|
-
end
|
350
|
-
|
351
|
-
private
|
352
|
-
def derive_class_name
|
353
|
-
class_name = name.to_s.camelize
|
354
|
-
class_name = class_name.singularize if collection?
|
355
|
-
class_name
|
356
|
-
end
|
357
|
-
|
358
|
-
def derive_foreign_key
|
359
|
-
if belongs_to?
|
360
|
-
"#{name}_id"
|
361
|
-
elsif options[:as]
|
362
|
-
"#{options[:as]}_id"
|
363
|
-
else
|
364
|
-
active_record.name.foreign_key
|
365
|
-
end
|
366
|
-
end
|
367
|
-
|
368
|
-
def primary_key(klass)
|
369
|
-
klass.primary_key || raise(UnknownPrimaryKey.new(klass))
|
370
|
-
end
|
371
|
-
end
|
143
|
+
#class AssociationReflection < MacroReflection #:nodoc: #NOTE totally overriden in Ooor
|
372
144
|
|
373
145
|
# Holds all the meta-data about a :through association as it was specified
|
374
146
|
# in the Active Record class.
|
375
|
-
class ThroughReflection < AssociationReflection #:nodoc:
|
376
|
-
# delegate :foreign_key, :foreign_type, :association_foreign_key, #FIXME hacked for OOOR
|
377
|
-
# :active_record_primary_key, :type, :to => :source_reflection
|
378
|
-
|
379
|
-
# Gets the source of the through reflection. It checks both a singularized
|
380
|
-
# and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
|
381
|
-
#
|
382
|
-
# class Post < ActiveRecord::Base
|
383
|
-
# has_many :taggings
|
384
|
-
# has_many :tags, :through => :taggings
|
385
|
-
# end
|
386
|
-
#
|
387
|
-
def source_reflection
|
388
|
-
@source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first
|
389
|
-
end
|
390
|
-
|
391
|
-
# Returns the AssociationReflection object specified in the <tt>:through</tt> option
|
392
|
-
# of a HasManyThrough or HasOneThrough association.
|
393
|
-
#
|
394
|
-
# class Post < ActiveRecord::Base
|
395
|
-
# has_many :taggings
|
396
|
-
# has_many :tags, :through => :taggings
|
397
|
-
# end
|
398
|
-
#
|
399
|
-
# tags_reflection = Post.reflect_on_association(:tags)
|
400
|
-
# taggings_reflection = tags_reflection.through_reflection
|
401
|
-
#
|
402
|
-
def through_reflection
|
403
|
-
@through_reflection ||= active_record.reflect_on_association(options[:through])
|
404
|
-
end
|
405
|
-
|
406
|
-
# Returns an array of reflections which are involved in this association. Each item in the
|
407
|
-
# array corresponds to a table which will be part of the query for this association.
|
408
|
-
#
|
409
|
-
# The chain is built by recursively calling #chain on the source reflection and the through
|
410
|
-
# reflection. The base case for the recursion is a normal association, which just returns
|
411
|
-
# [self] as its #chain.
|
412
|
-
def chain
|
413
|
-
@chain ||= begin
|
414
|
-
chain = source_reflection.chain + through_reflection.chain
|
415
|
-
chain[0] = self # Use self so we don't lose the information from :source_type
|
416
|
-
chain
|
417
|
-
end
|
418
|
-
end
|
419
|
-
|
420
|
-
# Consider the following example:
|
421
|
-
#
|
422
|
-
# class Person
|
423
|
-
# has_many :articles
|
424
|
-
# has_many :comment_tags, :through => :articles
|
425
|
-
# end
|
426
|
-
#
|
427
|
-
# class Article
|
428
|
-
# has_many :comments
|
429
|
-
# has_many :comment_tags, :through => :comments, :source => :tags
|
430
|
-
# end
|
431
|
-
#
|
432
|
-
# class Comment
|
433
|
-
# has_many :tags
|
434
|
-
# end
|
435
|
-
#
|
436
|
-
# There may be conditions on Person.comment_tags, Article.comment_tags and/or Comment.tags,
|
437
|
-
# but only Comment.tags will be represented in the #chain. So this method creates an array
|
438
|
-
# of conditions corresponding to the chain. Each item in the #conditions array corresponds
|
439
|
-
# to an item in the #chain, and is itself an array of conditions from an arbitrary number
|
440
|
-
# of relevant reflections, plus any :source_type or polymorphic :as constraints.
|
441
|
-
def conditions
|
442
|
-
@conditions ||= begin
|
443
|
-
conditions = source_reflection.conditions.map { |c| c.dup }
|
444
|
-
|
445
|
-
# Add to it the conditions from this reflection if necessary.
|
446
|
-
conditions.first << options[:conditions] if options[:conditions]
|
447
|
-
|
448
|
-
through_conditions = through_reflection.conditions
|
449
|
-
|
450
|
-
if options[:source_type]
|
451
|
-
through_conditions.first << { foreign_type => options[:source_type] }
|
452
|
-
end
|
453
|
-
|
454
|
-
# Recursively fill out the rest of the array from the through reflection
|
455
|
-
conditions += through_conditions
|
456
|
-
|
457
|
-
# And return
|
458
|
-
conditions
|
459
|
-
end
|
460
|
-
end
|
461
|
-
|
462
|
-
# The macro used by the source association
|
463
|
-
def source_macro
|
464
|
-
source_reflection.source_macro
|
465
|
-
end
|
466
|
-
|
467
|
-
# A through association is nested if there would be more than one join table
|
468
|
-
def nested?
|
469
|
-
chain.length > 2 || through_reflection.macro == :has_and_belongs_to_many
|
470
|
-
end
|
471
|
-
|
472
|
-
# We want to use the klass from this reflection, rather than just delegate straight to
|
473
|
-
# the source_reflection, because the source_reflection may be polymorphic. We still
|
474
|
-
# need to respect the source_reflection's :primary_key option, though.
|
475
|
-
def association_primary_key(klass = nil)
|
476
|
-
# Get the "actual" source reflection if the immediate source reflection has a
|
477
|
-
# source reflection itself
|
478
|
-
source_reflection = self.source_reflection
|
479
|
-
while source_reflection.source_reflection
|
480
|
-
source_reflection = source_reflection.source_reflection
|
481
|
-
end
|
482
|
-
|
483
|
-
source_reflection.options[:primary_key] || primary_key(klass || self.klass)
|
484
|
-
end
|
485
|
-
|
486
|
-
# Gets an array of possible <tt>:through</tt> source reflection names:
|
487
|
-
#
|
488
|
-
# [:singularized, :pluralized]
|
489
|
-
#
|
490
|
-
def source_reflection_names
|
491
|
-
@source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }
|
492
|
-
end
|
493
|
-
|
494
|
-
def source_options
|
495
|
-
source_reflection.options
|
496
|
-
end
|
497
|
-
|
498
|
-
def through_options
|
499
|
-
through_reflection.options
|
500
|
-
end
|
501
|
-
|
502
|
-
def check_validity!
|
503
|
-
if through_reflection.nil?
|
504
|
-
raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
|
505
|
-
end
|
506
|
-
|
507
|
-
if through_reflection.options[:polymorphic]
|
508
|
-
raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self)
|
509
|
-
end
|
510
|
-
|
511
|
-
if source_reflection.nil?
|
512
|
-
raise HasManyThroughSourceAssociationNotFoundError.new(self)
|
513
|
-
end
|
514
|
-
|
515
|
-
if options[:source_type] && source_reflection.options[:polymorphic].nil?
|
516
|
-
raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
|
517
|
-
end
|
518
|
-
|
519
|
-
if source_reflection.options[:polymorphic] && options[:source_type].nil?
|
520
|
-
raise HasManyThroughAssociationPolymorphicSourceError.new(active_record.name, self, source_reflection)
|
521
|
-
end
|
522
|
-
|
523
|
-
if macro == :has_one && through_reflection.collection?
|
524
|
-
raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection)
|
525
|
-
end
|
526
|
-
|
527
|
-
check_validity_of_inverse!
|
528
|
-
end
|
529
|
-
|
530
|
-
private
|
531
|
-
def derive_class_name
|
532
|
-
# get the class_name of the belongs_to association of the through reflection
|
533
|
-
options[:source_type] || source_reflection.class_name
|
534
|
-
end
|
535
|
-
end
|
147
|
+
#class ThroughReflection < AssociationReflection #:nodoc: #NOTE commented out because not used in Ooor
|
536
148
|
end
|
537
149
|
end
|