forked from norman/friendly_id
/
relation.rb
176 lines (152 loc) · 5.83 KB
/
relation.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
module FriendlyId
module ActiveRecordAdapter
module Relation
class Find
extend Forwardable
attr :relation
attr :ids
alias id ids
def_delegators :relation, :arel, :arel_table, :klass, :limit_value, :offset_value, :where
def_delegators :klass, :connection, :friendly_id_config
alias fc friendly_id_config
def initialize(relation, ids)
@relation = relation
@ids = ids
end
def find_one
if fc.cache_column?
find_one_with_cached_slug
elsif fc.use_slugs?
find_one_with_slug
else
find_one_without_slug
end
end
def find_some
ids = @ids.compact.uniq.map {|id| id.respond_to?(:friendly_id_config) ? id.id.to_i : id}
friendly_ids, unfriendly_ids = ids.partition {|id| id.friendly_id?}
return if friendly_ids.empty?
records = friendly_records(friendly_ids, unfriendly_ids).each do |record|
record.friendly_id_status.name = ids
end
validate_expected_size!(ids, records)
end
private
def assign_status
return unless @result
name, seq = id.to_s.parse_friendly_id
@result.friendly_id_status.name = name
@result.friendly_id_status.sequence = seq if fc.use_slugs?
@result
end
def find_one_without_slug
@result = where(fc.column => id).first
assign_status
end
def find_one_with_cached_slug
@result = where(fc.cache_column => id).first
assign_status or find_one_with_slug
end
def find_one_with_slug
sluggable_ids = sluggable_ids_for([id])
if sluggable_ids.size > 1 && fc.scope?
return relation.where(relation.primary_key.in(sluggable_ids)).first
end
sluggable_id = sluggable_ids.first
if sluggable_id
name, seq = id.to_s.parse_friendly_id
record = relation.send(:find_one_without_friendly, sluggable_id)
record.friendly_id_status.name = name
record.friendly_id_status.sequence = seq
record
else
relation.send(:find_one_without_friendly, id)
end
end
def friendly_records(friendly_ids, unfriendly_ids)
use_slugs_table = fc.use_slugs? && (!fc.cache_column?)
return find_some_using_slug(friendly_ids, unfriendly_ids) if use_slugs_table
column = fc.cache_column || fc.column
friendly = arel_table[column].in(friendly_ids)
unfriendly = arel_table[relation.primary_key.name].in unfriendly_ids
if friendly_ids.present? && unfriendly_ids.present?
where(friendly.or(unfriendly))
else
where(friendly)
end
end
def find_some_using_slug(friendly_ids, unfriendly_ids)
ids = [unfriendly_ids + sluggable_ids_for(friendly_ids)].flatten.uniq
where(arel_table[relation.primary_key.name].in(ids))
end
def sluggable_ids_for(ids)
return [] if ids.empty?
fragment = "(slugs.sluggable_type = %s AND slugs.name = %s AND slugs.sequence = %d)"
conditions = ids.inject(nil) do |clause, id|
name, seq = id.parse_friendly_id
string = fragment % [connection.quote(klass.base_class.name), connection.quote(name), seq]
clause ? clause + " OR #{string}" : string
end
if fc.scope?
conditions = if friendly_id_scope
scope = connection.quote(friendly_id_scope)
"slugs.scope = %s AND (%s)" % [scope, conditions]
else
"slugs.scope IS NULL AND (%s)" % [conditions]
end
end
# TODO mal refactorn in arel
locale = I18n.locale
conditions = "slugs.locale = %s AND (%s)" % [connection.quote(locale), conditions]
sql = "SELECT sluggable_id FROM slugs WHERE (%s)" % conditions
connection.select_values sql
end
def validate_expected_size!(ids, result)
expected_size =
if limit_value && ids.size > limit_value
limit_value
else
ids.size
end
# 11 ids with limit 3, offset 9 should give 2 results.
if offset_value && (ids.size - offset_value < expected_size)
expected_size = ids.size - offset_value
end
if result.size == expected_size
result
else
conditions = arel.send(:where_clauses).join(', ')
conditions = " [WHERE #{conditions}]" if conditions.present?
error = "Couldn't find all #{klass.name.pluralize} with IDs "
error << "(#{ids.join(", ")})#{conditions} (found #{result.size} results, but was looking for #{expected_size})"
raise ActiveRecord::RecordNotFound, error
end
end
end
def apply_finder_options(options)
if options[:scope]
raise "The :scope finder option has been removed from FriendlyId 3.2.0 " +
"https://github.com/norman/friendly_id/issues#issue/88"
end
super
end
protected
def find_one(id)
begin
return super if !klass.uses_friendly_id? or id.unfriendly_id?
find = Find.new(self, id)
find.find_one or super
end
end
def find_some(ids)
return super unless klass.uses_friendly_id?
Find.new(self, ids).find_some or begin
# A change in Arel 2.0.x causes find_some to fail with arrays of instances; not sure why.
# This is an emergency, temporary fix.
ids = ids.map {|id| (id.respond_to?(:friendly_id_config) ? id.id : id).to_i}
super
end
end
end
end
end