Skip to content

Commit 6a93fa0

Browse files
committed
Sorting on globalized attributes of associations breaks when joining translations (#1614)
1 parent af55622 commit 6a93fa0

File tree

4 files changed

+114
-37
lines changed

4 files changed

+114
-37
lines changed

docs/docs/getting-started/sorting.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,41 @@ class PostsController < ActionController::Base
6969
end
7070
end
7171
```
72+
73+
## Sorting on Association Attributes
74+
75+
You can sort on attributes of associated models by using the association name followed by the attribute name:
76+
77+
```ruby
78+
# Sort by the name of the associated category
79+
@q = Post.ransack(s: 'category_name asc')
80+
@posts = @q.result
81+
82+
# Sort by attributes of nested associations
83+
@q = Post.ransack(s: 'category_section_title desc')
84+
@posts = @q.result
85+
```
86+
87+
### Sorting on Globalized/Translated Attributes
88+
89+
When working with internationalized models (like those using the Globalize gem), special care is needed when sorting on translated attributes of associations. The simplest approach is to use the `sort_link` helper directly with the translation attribute:
90+
91+
```erb
92+
<!-- This works perfectly for sorting on translated attributes -->
93+
<%= sort_link @q, :translations_name %>
94+
<%= sort_link @q, :category_translations_name %>
95+
```
96+
97+
For programmatic sorting, let Ransack handle the joins first:
98+
99+
```ruby
100+
# Let Ransack establish the necessary joins for sorting
101+
@q = Book.ransack(s: 'category_translations_name asc')
102+
@books = @q.result.joins(:translations)
103+
104+
# For complex scenarios with multiple translations
105+
@q = Book.ransack(s: 'category_translations_name asc')
106+
@books = @q.result.includes(:translations, category: :translations)
107+
```
108+
109+
This ensures that Ransack properly handles the join dependencies between your main model's translations and the associated model's translations.

docs/docs/going-further/i18n.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,46 @@ en:
5151
namespace_article:
5252
title: Old Ransack Namespaced Title
5353
```
54+
55+
## Working with Globalized Attributes
56+
57+
If you're using the [Globalize gem](https://github.com/globalize/globalize) for internationalized model attributes, you may encounter issues when sorting on translated attributes of associations while also joining the main model's translations.
58+
59+
For example, if you have a `Book` model with translated `title` and a `Category` model with translated `name`, sorting on the category's translated name while joining the book's translations may not work as expected:
60+
61+
```ruby
62+
# This may not work correctly:
63+
Book.joins(:translations).ransack({ s: ['category_translations_name asc'] }).result
64+
```
65+
66+
### Workaround for Globalized Attributes Sorting
67+
68+
When working with globalized attributes and you need to sort on translated fields of associations, the simplest and most effective approach is to use the `sort_link` helper with the translation attribute directly:
69+
70+
```erb
71+
<!-- This works perfectly for sorting on translated attributes -->
72+
<%= sort_link @search, :translations_name %>
73+
<%= sort_link @search, :category_translations_name %>
74+
```
75+
76+
For programmatic sorting, let Ransack handle the joins first:
77+
78+
```ruby
79+
# Instead of joining translations first, let Ransack handle the joins:
80+
search = Book.ransack({ s: ['category_translations_name asc'] })
81+
results = search.result.joins(:translations)
82+
83+
# Or use the includes method to ensure all necessary translations are loaded:
84+
search = Book.ransack({ s: ['category_translations_name asc'] })
85+
results = search.result.includes(:translations, category: :translations)
86+
87+
# For more complex scenarios, you can manually specify the joins:
88+
search = Book.ransack({ s: ['category_translations_name asc'] })
89+
results = search.result
90+
.joins(:translations)
91+
.joins(category: :translations)
92+
```
93+
94+
The key is to let Ransack establish the necessary joins for sorting first, then add any additional joins you need for the query.
95+
96+
This approach ensures that Ransack properly handles the complex join dependencies between your main model's translations and the associated model's translations.

docs/docs/going-further/other-notes.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,39 @@ def index
116116
end
117117
```
118118

119+
### Problem with Globalized Attributes and Sorting
120+
121+
When using internationalization gems like [Globalize](https://github.com/globalize/globalize), you may encounter issues when trying to sort on translated attributes of associations while also having pre-existing joins to translation tables.
122+
123+
**Problem scenario:**
124+
```ruby
125+
# This may fail to generate proper joins:
126+
Book.joins(:translations).ransack({ s: ['category_translations_name asc'] }).result
127+
```
128+
129+
**Solution:**
130+
The simplest and most effective approach is to use the `sort_link` helper directly with the translation attribute:
131+
132+
```erb
133+
<!-- This works perfectly for sorting on translated attributes -->
134+
<%= sort_link @search, :translations_name %>
135+
<%= sort_link @search, :category_translations_name %>
136+
```
137+
138+
For programmatic sorting, let Ransack establish the sorting joins first, then add your additional joins:
139+
140+
```ruby
141+
# Let Ransack handle the sorting joins first
142+
search = Book.ransack({ s: ['category_translations_name asc'] })
143+
results = search.result.joins(:translations)
144+
145+
# Or use includes for complex scenarios
146+
search = Book.ransack({ s: ['category_translations_name asc'] })
147+
results = search.result.includes(:translations, category: :translations)
148+
```
149+
150+
This ensures that Ransack properly handles the join dependencies between your main model's translations and the associated model's translations.
151+
119152
#### `PG::UndefinedFunction: ERROR: could not identify an equality operator for type json`
120153

121154
If you get the above error while using `distinct: true` that means that

spec/ransack/search_spec.rb

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -147,43 +147,6 @@ module Ransack
147147
expect(s.result.to_sql).to include 'published'
148148
end
149149

150-
# The failure/oversight in Ransack::Nodes::Condition#arel_predicate or deeper is beyond my understanding of the structures
151-
it 'preserves (inverts) default scope and conditions for negative subqueries' do
152-
# the positive case (published_articles_title_eq) is
153-
# SELECT "people".* FROM "people"
154-
# LEFT OUTER JOIN "articles" ON "articles"."person_id" = "people"."id"
155-
# AND "articles"."published" = 't'
156-
# AND ('default_scope' = 'default_scope')
157-
# WHERE "articles"."title" = 'Test' ORDER BY "people"."id" DESC
158-
#
159-
# negative case was
160-
# SELECT "people".* FROM "people" WHERE "people"."id" NOT IN (
161-
# SELECT "articles"."person_id" FROM "articles"
162-
# WHERE "articles"."person_id" = "people"."id"
163-
# AND NOT ("articles"."title" != 'Test')
164-
# ) ORDER BY "people"."id" DESC
165-
#
166-
# Should have been like
167-
# SELECT "people".* FROM "people" WHERE "people"."id" NOT IN (
168-
# SELECT "articles"."person_id" FROM "articles"
169-
# WHERE "articles"."person_id" = "people"."id"
170-
# AND "articles"."title" = 'Test' AND "articles"."published" = 't' AND ('default_scope' = 'default_scope')
171-
# ) ORDER BY "people"."id" DESC
172-
#
173-
# With tenanting (eg default_scope with column reference), NOT IN should be like
174-
# SELECT "people".* FROM "people" WHERE "people"."tenant_id" = 'tenant_id' AND "people"."id" NOT IN (
175-
# SELECT "articles"."person_id" FROM "articles"
176-
# WHERE "articles"."person_id" = "people"."id"
177-
# AND "articles"."tenant_id" = 'tenant_id'
178-
# AND "articles"."title" = 'Test' AND "articles"."published" = 't' AND ('default_scope' = 'default_scope')
179-
# ) ORDER BY "people"."id" DESC
180-
181-
pending("spec should pass, but I do not know how/where to fix lib code")
182-
s = Search.new(Person, published_articles_title_not_eq: 'Test')
183-
expect(s.result.to_sql).to include 'default_scope'
184-
expect(s.result.to_sql).to include 'published'
185-
end
186-
187150
it 'discards empty conditions' do
188151
s = Search.new(Person, children_name_eq: '')
189152
condition = s.base[:children_name_eq]

0 commit comments

Comments
 (0)