ransack 0.1.0 → 0.2.0
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.
- data/lib/ransack.rb +1 -1
- data/lib/ransack/adapters/active_record.rb +18 -2
- data/lib/ransack/adapters/active_record/3.0/base.rb +34 -0
- data/lib/ransack/adapters/active_record/3.0/compat.rb +23 -0
- data/lib/ransack/adapters/active_record/3.0/context.rb +168 -0
- data/lib/ransack/adapters/active_record/3.0/join_association.rb +44 -0
- data/lib/ransack/adapters/active_record/3.0/join_dependency.rb +63 -0
- data/lib/ransack/adapters/active_record/base.rb +19 -2
- data/lib/ransack/adapters/active_record/context.rb +45 -33
- data/lib/ransack/adapters/active_record/join_association.rb +44 -0
- data/lib/ransack/adapters/active_record/join_dependency.rb +63 -0
- data/lib/ransack/context.rb +25 -65
- data/lib/ransack/helpers/form_builder.rb +10 -4
- data/lib/ransack/helpers/form_helper.rb +1 -0
- data/lib/ransack/locale/en.yml +1 -0
- data/lib/ransack/nodes.rb +1 -0
- data/lib/ransack/nodes/attribute.rb +21 -4
- data/lib/ransack/nodes/bindable.rb +29 -0
- data/lib/ransack/nodes/condition.rb +30 -28
- data/lib/ransack/nodes/sort.rb +5 -3
- data/lib/ransack/nodes/value.rb +84 -100
- data/lib/ransack/predicate.rb +1 -11
- data/lib/ransack/ransacker.rb +26 -0
- data/lib/ransack/search.rb +3 -2
- data/lib/ransack/version.rb +1 -1
- data/lib/ransack/visitor.rb +64 -0
- data/ransack.gemspec +3 -3
- data/spec/console.rb +1 -2
- data/spec/ransack/adapters/active_record/base_spec.rb +18 -0
- data/spec/ransack/adapters/active_record/context_spec.rb +2 -2
- data/spec/ransack/helpers/form_builder_spec.rb +4 -0
- data/spec/ransack/search_spec.rb +25 -2
- data/spec/spec_helper.rb +2 -3
- data/spec/support/schema.rb +8 -0
- metadata +16 -8
data/lib/ransack.rb
CHANGED
@@ -16,9 +16,9 @@ end
|
|
16
16
|
|
17
17
|
require 'ransack/translate'
|
18
18
|
require 'ransack/search'
|
19
|
+
require 'ransack/ransacker'
|
19
20
|
require 'ransack/adapters/active_record'
|
20
21
|
require 'ransack/helpers'
|
21
22
|
require 'action_controller'
|
22
23
|
|
23
|
-
ActiveRecord::Base.extend Ransack::Adapters::ActiveRecord::Base
|
24
24
|
ActionController::Base.helper Ransack::Helpers::FormHelper
|
@@ -1,2 +1,18 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
case ActiveRecord::VERSION::STRING
|
2
|
+
when /^3\.0\./
|
3
|
+
require 'ransack/adapters/active_record/3.0/base'
|
4
|
+
require 'ransack/adapters/active_record/3.0/join_dependency'
|
5
|
+
require 'ransack/adapters/active_record/3.0/join_association'
|
6
|
+
require 'ransack/adapters/active_record/3.0/context'
|
7
|
+
|
8
|
+
ActiveRecord::Base.extend Ransack::Adapters::ActiveRecord::Base
|
9
|
+
ActiveRecord::Associations::ClassMethods::JoinDependency.send :include, Ransack::Adapters::ActiveRecord::JoinDependency
|
10
|
+
else
|
11
|
+
require 'ransack/adapters/active_record/base'
|
12
|
+
require 'ransack/adapters/active_record/join_dependency'
|
13
|
+
require 'ransack/adapters/active_record/join_association'
|
14
|
+
require 'ransack/adapters/active_record/context'
|
15
|
+
|
16
|
+
ActiveRecord::Base.extend Ransack::Adapters::ActiveRecord::Base
|
17
|
+
ActiveRecord::Associations::JoinDependency.send :include, Ransack::Adapters::ActiveRecord::JoinDependency
|
18
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Ransack
|
2
|
+
module Adapters
|
3
|
+
module ActiveRecord
|
4
|
+
module Base
|
5
|
+
|
6
|
+
def self.extended(base)
|
7
|
+
alias :search :ransack unless base.method_defined? :search
|
8
|
+
base.instance_eval do
|
9
|
+
class_attribute :_ransackers
|
10
|
+
self._ransackers ||= {}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def ransack(params = {}, options = {})
|
15
|
+
Search.new(self, params, options)
|
16
|
+
end
|
17
|
+
|
18
|
+
def ransacker(name, opts = {}, &block)
|
19
|
+
Ransacker.new(self, name, opts, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
# TODO: Let's actually do some authorization. Whitelist-only.
|
23
|
+
def ransackable_attributes(auth_object)
|
24
|
+
column_names + _ransackers.keys
|
25
|
+
end
|
26
|
+
|
27
|
+
def ransackable_associations(auth_object)
|
28
|
+
reflect_on_all_associations.map {|a| a.name.to_s}
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# UGLY, UGLY MONKEY PATCHES FOR BACKWARDS COMPAT!!! AVERT YOUR EYES!!
|
2
|
+
if Arel::Nodes::And < Arel::Nodes::Binary
|
3
|
+
class Ransack::Visitor
|
4
|
+
def visit_Ransack_Nodes_And(object)
|
5
|
+
nodes = object.values.map {|o| accept(o)}.compact
|
6
|
+
return nil unless nodes.size > 0
|
7
|
+
|
8
|
+
if nodes.size > 1
|
9
|
+
nodes.inject(&:and)
|
10
|
+
else
|
11
|
+
nodes.first
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class ::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinBase
|
18
|
+
def table
|
19
|
+
Arel::Table.new(table_name, :as => aliased_table_name,
|
20
|
+
:engine => active_record.arel_engine,
|
21
|
+
:columns => active_record.columns)
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'ransack/context'
|
2
|
+
require 'active_record'
|
3
|
+
require 'ransack/adapters/active_record/3.0/compat'
|
4
|
+
|
5
|
+
module Ransack
|
6
|
+
|
7
|
+
module Adapters
|
8
|
+
module ActiveRecord
|
9
|
+
class Context < ::Ransack::Context
|
10
|
+
# Because the AR::Associations namespace is insane
|
11
|
+
JoinDependency = ::ActiveRecord::Associations::ClassMethods::JoinDependency
|
12
|
+
JoinBase = JoinDependency::JoinBase
|
13
|
+
|
14
|
+
def evaluate(search, opts = {})
|
15
|
+
viz = Visitor.new
|
16
|
+
relation = @object.where(viz.accept(search.base)).order(viz.accept(search.sorts))
|
17
|
+
opts[:distinct] ? relation.select("DISTINCT #{@klass.quoted_table_name}.*") : relation
|
18
|
+
end
|
19
|
+
|
20
|
+
def attribute_method?(str, klass = @klass)
|
21
|
+
exists = false
|
22
|
+
|
23
|
+
if ransackable_attribute?(str, klass)
|
24
|
+
exists = true
|
25
|
+
elsif (segments = str.split(/_/)).size > 1
|
26
|
+
remainder = []
|
27
|
+
found_assoc = nil
|
28
|
+
while !found_assoc && remainder.unshift(segments.pop) && segments.size > 0 do
|
29
|
+
assoc, poly_class = unpolymorphize_association(segments.join('_'))
|
30
|
+
if found_assoc = get_association(assoc, klass)
|
31
|
+
exists = attribute_method?(remainder.join('_'), poly_class || found_assoc.klass)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
exists
|
37
|
+
end
|
38
|
+
|
39
|
+
def table_for(parent)
|
40
|
+
parent.table
|
41
|
+
end
|
42
|
+
|
43
|
+
def klassify(obj)
|
44
|
+
if Class === obj && ::ActiveRecord::Base > obj
|
45
|
+
obj
|
46
|
+
elsif obj.respond_to? :klass
|
47
|
+
obj.klass
|
48
|
+
elsif obj.respond_to? :active_record
|
49
|
+
obj.active_record
|
50
|
+
else
|
51
|
+
raise ArgumentError, "Don't know how to klassify #{obj}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def type_for(attr)
|
56
|
+
return nil unless attr
|
57
|
+
name = attr.name.to_s
|
58
|
+
table = attr.relation.name
|
59
|
+
|
60
|
+
unless @engine.connection.table_exists?(table)
|
61
|
+
raise "No table named #{table} exists"
|
62
|
+
end
|
63
|
+
|
64
|
+
# TODO: optimize
|
65
|
+
@engine.connection.columns(table).detect {|c| c.name == name}.type
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def get_parent_and_attribute_name(str, parent = @base)
|
71
|
+
attr_name = nil
|
72
|
+
|
73
|
+
if ransackable_attribute?(str, klassify(parent))
|
74
|
+
attr_name = str
|
75
|
+
elsif (segments = str.split(/_/)).size > 1
|
76
|
+
remainder = []
|
77
|
+
found_assoc = nil
|
78
|
+
while remainder.unshift(segments.pop) && segments.size > 0 && !found_assoc do
|
79
|
+
assoc, klass = unpolymorphize_association(segments.join('_'))
|
80
|
+
if ransackable_association?(assoc, klassify(parent))
|
81
|
+
found_assoc = get_association(assoc, parent)
|
82
|
+
join = build_or_find_association(found_assoc.name, parent, klass)
|
83
|
+
parent, attr_name = get_parent_and_attribute_name(remainder.join('_'), join)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
[parent, attr_name]
|
89
|
+
end
|
90
|
+
|
91
|
+
def ransackable_attribute?(str, klass)
|
92
|
+
klass.ransackable_attributes(auth_object).include? str
|
93
|
+
end
|
94
|
+
|
95
|
+
def ransackable_association?(str, klass)
|
96
|
+
klass.ransackable_associations(auth_object).include? str
|
97
|
+
end
|
98
|
+
|
99
|
+
def get_association(str, parent = @base)
|
100
|
+
klassify(parent).reflect_on_all_associations.detect {|a| a.name.to_s == str}
|
101
|
+
end
|
102
|
+
|
103
|
+
def join_dependency(relation)
|
104
|
+
if relation.respond_to?(:join_dependency) # Squeel will enable this
|
105
|
+
relation.join_dependency
|
106
|
+
else
|
107
|
+
build_join_dependency(relation)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def build_join_dependency(relation)
|
112
|
+
buckets = relation.joins_values.group_by do |join|
|
113
|
+
case join
|
114
|
+
when String
|
115
|
+
'string_join'
|
116
|
+
when Hash, Symbol, Array
|
117
|
+
'association_join'
|
118
|
+
when ::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation
|
119
|
+
'stashed_join'
|
120
|
+
when Arel::Nodes::Join
|
121
|
+
'join_node'
|
122
|
+
else
|
123
|
+
raise 'unknown class: %s' % join.class.name
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
association_joins = buckets['association_join'] || []
|
128
|
+
stashed_association_joins = buckets['stashed_join'] || []
|
129
|
+
join_nodes = buckets['join_node'] || []
|
130
|
+
string_joins = (buckets['string_join'] || []).map { |x|
|
131
|
+
x.strip
|
132
|
+
}.uniq
|
133
|
+
|
134
|
+
join_list = relation.send :custom_join_sql, (string_joins + join_nodes)
|
135
|
+
|
136
|
+
join_dependency = JoinDependency.new(
|
137
|
+
relation.klass,
|
138
|
+
association_joins,
|
139
|
+
join_list
|
140
|
+
)
|
141
|
+
|
142
|
+
join_nodes.each do |join|
|
143
|
+
join_dependency.table_aliases[join.left.name.downcase] = 1
|
144
|
+
end
|
145
|
+
|
146
|
+
join_dependency.graft(*stashed_association_joins)
|
147
|
+
end
|
148
|
+
|
149
|
+
def build_or_find_association(name, parent = @base, klass = nil)
|
150
|
+
found_association = @join_dependency.join_associations.detect do |assoc|
|
151
|
+
assoc.reflection.name == name &&
|
152
|
+
assoc.parent == parent &&
|
153
|
+
(!klass || assoc.klass == klass)
|
154
|
+
end
|
155
|
+
unless found_association
|
156
|
+
@join_dependency.send(:build_polymorphic, name.to_sym, parent, Arel::Nodes::OuterJoin, klass)
|
157
|
+
found_association = @join_dependency.join_associations.last
|
158
|
+
# Leverage the stashed association functionality in AR
|
159
|
+
@object = @object.joins(found_association)
|
160
|
+
end
|
161
|
+
|
162
|
+
found_association
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module Ransack
|
4
|
+
module Adapters
|
5
|
+
module ActiveRecord
|
6
|
+
class JoinAssociation < ::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation
|
7
|
+
|
8
|
+
def initialize(reflection, join_dependency, parent = nil, polymorphic_class = nil)
|
9
|
+
if polymorphic_class && ::ActiveRecord::Base > polymorphic_class
|
10
|
+
swapping_reflection_klass(reflection, polymorphic_class) do |reflection|
|
11
|
+
super(reflection, join_dependency, parent)
|
12
|
+
end
|
13
|
+
else
|
14
|
+
super(reflection, join_dependency, parent)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def swapping_reflection_klass(reflection, klass)
|
19
|
+
reflection = reflection.clone
|
20
|
+
original_polymorphic = reflection.options.delete(:polymorphic)
|
21
|
+
reflection.instance_variable_set(:@klass, klass)
|
22
|
+
yield reflection
|
23
|
+
ensure
|
24
|
+
reflection.options[:polymorphic] = original_polymorphic
|
25
|
+
end
|
26
|
+
|
27
|
+
def ==(other)
|
28
|
+
super && active_record == other.active_record
|
29
|
+
end
|
30
|
+
|
31
|
+
def build_constraint(reflection, table, key, foreign_table, foreign_key)
|
32
|
+
if reflection.options[:polymorphic]
|
33
|
+
super.and(
|
34
|
+
foreign_table[reflection.foreign_type].eq(reflection.klass.name)
|
35
|
+
)
|
36
|
+
else
|
37
|
+
super
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module Ransack
|
4
|
+
module Adapters
|
5
|
+
module ActiveRecord
|
6
|
+
module JoinDependency
|
7
|
+
|
8
|
+
# Yes, I'm using alias_method_chain here. No, I don't feel too
|
9
|
+
# bad about it. JoinDependency, or, to call it by its full proper
|
10
|
+
# name, ::ActiveRecord::Associations::JoinDependency, is one of the
|
11
|
+
# most "for internal use only" chunks of ActiveRecord.
|
12
|
+
def self.included(base)
|
13
|
+
base.class_eval do
|
14
|
+
alias_method_chain :graft, :ransack
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def graft_with_ransack(*associations)
|
19
|
+
associations.each do |association|
|
20
|
+
join_associations.detect {|a| association == a} ||
|
21
|
+
build_polymorphic(association.reflection.name, association.find_parent_in(self) || join_base, association.join_type, association.reflection.klass)
|
22
|
+
end
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
# Should only be called by Ransack, and only with a single association name
|
27
|
+
def build_polymorphic(association, parent = nil, join_type = Arel::OuterJoin, klass = nil)
|
28
|
+
parent ||= joins.last
|
29
|
+
reflection = parent.reflections[association] or
|
30
|
+
raise ::ActiveRecord::ConfigurationError, "Association named '#{ association }' was not found; perhaps you misspelled it?"
|
31
|
+
unless join_association = find_join_association_respecting_polymorphism(reflection, parent, klass)
|
32
|
+
@reflections << reflection
|
33
|
+
join_association = build_join_association_respecting_polymorphism(reflection, parent, klass)
|
34
|
+
join_association.join_type = join_type
|
35
|
+
@joins << join_association
|
36
|
+
cache_joined_association(join_association)
|
37
|
+
end
|
38
|
+
|
39
|
+
join_association
|
40
|
+
end
|
41
|
+
|
42
|
+
def find_join_association_respecting_polymorphism(reflection, parent, klass)
|
43
|
+
if association = find_join_association(reflection, parent)
|
44
|
+
unless reflection.options[:polymorphic]
|
45
|
+
association
|
46
|
+
else
|
47
|
+
association if association.active_record == klass
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def build_join_association_respecting_polymorphism(reflection, parent, klass = nil)
|
53
|
+
if reflection.options[:polymorphic] && klass
|
54
|
+
JoinAssociation.new(reflection, self, parent, klass)
|
55
|
+
else
|
56
|
+
JoinAssociation.new(reflection, self, parent)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -5,10 +5,27 @@ module Ransack
|
|
5
5
|
|
6
6
|
def self.extended(base)
|
7
7
|
alias :search :ransack unless base.method_defined? :search
|
8
|
+
base.instance_eval do
|
9
|
+
class_attribute :_ransackers
|
10
|
+
self._ransackers ||= {}
|
11
|
+
end
|
8
12
|
end
|
9
13
|
|
10
|
-
def ransack(params = {})
|
11
|
-
Search.new(self, params)
|
14
|
+
def ransack(params = {}, options = {})
|
15
|
+
Search.new(self, params, options)
|
16
|
+
end
|
17
|
+
|
18
|
+
def ransacker(name, opts = {}, &block)
|
19
|
+
Ransacker.new(self, name, opts, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
# TODO: Let's actually do some authorization. Whitelist-only.
|
23
|
+
def ransackable_attributes(auth_object)
|
24
|
+
column_names + _ransackers.keys
|
25
|
+
end
|
26
|
+
|
27
|
+
def ransackable_associations(auth_object)
|
28
|
+
reflect_on_all_associations.map {|a| a.name.to_s}
|
12
29
|
end
|
13
30
|
|
14
31
|
end
|
@@ -8,24 +8,25 @@ module Ransack
|
|
8
8
|
# Because the AR::Associations namespace is insane
|
9
9
|
JoinDependency = ::ActiveRecord::Associations::JoinDependency
|
10
10
|
JoinPart = JoinDependency::JoinPart
|
11
|
-
JoinAssociation = JoinDependency::JoinAssociation
|
12
11
|
|
13
12
|
def evaluate(search, opts = {})
|
14
|
-
|
15
|
-
|
13
|
+
viz = Visitor.new
|
14
|
+
relation = @object.where(viz.accept(search.base)).order(viz.accept(search.sorts))
|
15
|
+
opts[:distinct] ? relation.select("DISTINCT #{@klass.quoted_table_name}.*") : relation
|
16
16
|
end
|
17
17
|
|
18
18
|
def attribute_method?(str, klass = @klass)
|
19
19
|
exists = false
|
20
20
|
|
21
|
-
if
|
21
|
+
if ransackable_attribute?(str, klass)
|
22
22
|
exists = true
|
23
23
|
elsif (segments = str.split(/_/)).size > 1
|
24
24
|
remainder = []
|
25
25
|
found_assoc = nil
|
26
26
|
while !found_assoc && remainder.unshift(segments.pop) && segments.size > 0 do
|
27
|
-
|
28
|
-
|
27
|
+
assoc, poly_class = unpolymorphize_association(segments.join('_'))
|
28
|
+
if found_assoc = get_association(assoc, klass)
|
29
|
+
exists = attribute_method?(remainder.join('_'), poly_class || found_assoc.klass)
|
29
30
|
end
|
30
31
|
end
|
31
32
|
end
|
@@ -33,20 +34,10 @@ module Ransack
|
|
33
34
|
exists
|
34
35
|
end
|
35
36
|
|
36
|
-
def
|
37
|
-
|
38
|
-
name = attr.name.to_s
|
39
|
-
table = attr.relation.table_name
|
40
|
-
|
41
|
-
unless @engine.connection_pool.table_exists?(table)
|
42
|
-
raise "No table named #{table} exists"
|
43
|
-
end
|
44
|
-
|
45
|
-
@engine.connection_pool.columns_hash[table][name].type
|
37
|
+
def table_for(parent)
|
38
|
+
parent.table
|
46
39
|
end
|
47
40
|
|
48
|
-
private
|
49
|
-
|
50
41
|
def klassify(obj)
|
51
42
|
if Class === obj && ::ActiveRecord::Base > obj
|
52
43
|
obj
|
@@ -59,27 +50,47 @@ module Ransack
|
|
59
50
|
end
|
60
51
|
end
|
61
52
|
|
62
|
-
def
|
63
|
-
|
53
|
+
def type_for(attr)
|
54
|
+
return nil unless attr
|
55
|
+
name = attr.name.to_s
|
56
|
+
table = attr.relation.table_name
|
57
|
+
|
58
|
+
unless @engine.connection_pool.table_exists?(table)
|
59
|
+
raise "No table named #{table} exists"
|
60
|
+
end
|
61
|
+
|
62
|
+
@engine.connection_pool.columns_hash[table][name].type
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
64
66
|
|
65
|
-
|
66
|
-
|
67
|
+
def get_parent_and_attribute_name(str, parent = @base)
|
68
|
+
attr_name = nil
|
69
|
+
|
70
|
+
if ransackable_attribute?(str, klassify(parent))
|
71
|
+
attr_name = str
|
67
72
|
elsif (segments = str.split(/_/)).size > 1
|
68
73
|
remainder = []
|
69
74
|
found_assoc = nil
|
70
75
|
while remainder.unshift(segments.pop) && segments.size > 0 && !found_assoc do
|
71
|
-
|
72
|
-
|
73
|
-
|
76
|
+
assoc, klass = unpolymorphize_association(segments.join('_'))
|
77
|
+
if ransackable_association?(assoc, klassify(parent))
|
78
|
+
found_assoc = get_association(assoc, parent)
|
79
|
+
join = build_or_find_association(found_assoc.name, parent, klass)
|
80
|
+
parent, attr_name = get_parent_and_attribute_name(remainder.join('_'), join)
|
74
81
|
end
|
75
82
|
end
|
76
83
|
end
|
77
84
|
|
78
|
-
|
85
|
+
[parent, attr_name]
|
86
|
+
end
|
87
|
+
|
88
|
+
def ransackable_attribute?(str, klass)
|
89
|
+
klass.ransackable_attributes(auth_object).include? str
|
79
90
|
end
|
80
91
|
|
81
|
-
def
|
82
|
-
|
92
|
+
def ransackable_association?(str, klass)
|
93
|
+
klass.ransackable_associations(auth_object).include? str
|
83
94
|
end
|
84
95
|
|
85
96
|
def get_association(str, parent = @base)
|
@@ -87,7 +98,7 @@ module Ransack
|
|
87
98
|
end
|
88
99
|
|
89
100
|
def join_dependency(relation)
|
90
|
-
if relation.respond_to?(:join_dependency) #
|
101
|
+
if relation.respond_to?(:join_dependency) # Squeel will enable this
|
91
102
|
relation.join_dependency
|
92
103
|
else
|
93
104
|
build_join_dependency(relation)
|
@@ -101,7 +112,7 @@ module Ransack
|
|
101
112
|
'string_join'
|
102
113
|
when Hash, Symbol, Array
|
103
114
|
'association_join'
|
104
|
-
when ActiveRecord::Associations::JoinDependency::JoinAssociation
|
115
|
+
when ::ActiveRecord::Associations::JoinDependency::JoinAssociation
|
105
116
|
'stashed_join'
|
106
117
|
when Arel::Nodes::Join
|
107
118
|
'join_node'
|
@@ -132,13 +143,14 @@ module Ransack
|
|
132
143
|
join_dependency.graft(*stashed_association_joins)
|
133
144
|
end
|
134
145
|
|
135
|
-
def build_or_find_association(name, parent = @base)
|
146
|
+
def build_or_find_association(name, parent = @base, klass = nil)
|
136
147
|
found_association = @join_dependency.join_associations.detect do |assoc|
|
137
148
|
assoc.reflection.name == name &&
|
138
|
-
assoc.parent == parent
|
149
|
+
assoc.parent == parent &&
|
150
|
+
(!klass || assoc.klass == klass)
|
139
151
|
end
|
140
152
|
unless found_association
|
141
|
-
@join_dependency.send(:
|
153
|
+
@join_dependency.send(:build_polymorphic, name.to_sym, parent, Arel::Nodes::OuterJoin, klass)
|
142
154
|
found_association = @join_dependency.join_associations.last
|
143
155
|
# Leverage the stashed association functionality in AR
|
144
156
|
@object = @object.joins(found_association)
|