Skip to content

Commit 458f22c

Browse files
authored
Merge pull request #462 from platanus/use-association-name-in-selects
Use association name in selects
2 parents 6871fb5 + 8d5b0c5 commit 458f22c

File tree

15 files changed

+259
-219
lines changed

15 files changed

+259
-219
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
66

77
#### Breaking changes
88
* Defines required ruby version to >=2.7.0 [#460](https://github.com/platanus/activeadmin_addons/pull/460)
9+
* Nested and search select now use the name of the association instead of the name of id [#462](https://github.com/platanus/activeadmin_addons/pull/462)
910

1011
### 2.0.0.beta-0
1112

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,11 +225,13 @@ filter :created_at, as: :date_time_picker_filter
225225
You can use the ajax select input to filter values on index view like this:
226226

227227
```ruby
228-
filter :category_id, as: :search_select_filter
228+
filter :category, as: :search_select_filter
229229
```
230230

231231
<img src="./docs/images/filter-search-select.png" height="160" />
232232

233+
[Read more!](docs/slim-select_search.md#filter-usage)
234+
233235
### Themes
234236

235237
#### NO Theme

app/inputs/nested_level_input.rb

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
class NestedLevelInput < ActiveAdminAddons::InputBase
1+
class NestedLevelInput < ActiveAdminAddons::SelectInputBase
22
include ActiveAdminAddons::SelectHelpers
33

44
def render_custom_input
@@ -38,15 +38,17 @@ def load_parent_data_options
3838
return unless @options[:parent_attribute]
3939

4040
load_data_attr(:parent, value: @options[:parent_attribute])
41-
load_data_attr(:parent_id, value: @object.send(@options[:parent_attribute]), default: -1)
41+
load_data_attr(
42+
:parent_id, value: @object.send(@options[:parent_id_attribute]), default: -1
43+
)
4244
end
4345

4446
def load_collection_data
4547
return unless @options[:collection]
4648

4749
collection_options = collection_to_select_options do |item, option|
48-
if !!@options[:parent_attribute]
49-
option[@options[:parent_attribute]] = item.send(@options[:parent_attribute])
50+
if @options[:parent_attribute].present?
51+
option[@options[:parent_id_attribute]] = item.send(@options[:parent_id_attribute])
5052
end
5153
end
5254

app/inputs/nested_select_input.rb

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -59,32 +59,38 @@ def set_parent_attributes(hierarchy)
5959
parent_level_data = hierarchy[next_idx] if hierarchy.count != next_idx
6060
if parent_level_data
6161
level_data[:parent_attribute] = parent_level_data[:attribute]
62+
level_data[:parent_id_attribute] = "#{level_data[:parent_attribute]}_id"
6263
set_parent_value(level_data)
6364
end
6465
end
6566
end
6667

6768
def set_parent_value(level_data)
68-
parent_attribute = level_data[:parent_attribute]
69-
build_virtual_attr(parent_attribute)
70-
instance = instance_from_attribute_name(level_data[:attribute])
71-
if instance&.respond_to?(parent_attribute)
72-
@object.send("#{parent_attribute}=", instance.send(parent_attribute))
69+
build_virtual_attr(level_data[:parent_attribute])
70+
build_virtual_attr(level_data[:parent_id_attribute])
71+
instance = instance_from_attribute_id("#{level_data[:attribute]}_id")
72+
if instance.respond_to?(level_data[:parent_id_attribute])
73+
@object.send(
74+
"#{level_data[:parent_attribute]}=", instance.send(level_data[:parent_attribute])
75+
)
76+
@object.send(
77+
"#{level_data[:parent_id_attribute]}=", instance.send(level_data[:parent_id_attribute])
78+
)
7379
end
7480
end
7581

76-
def instance_from_attribute_name(attribute)
77-
return unless attribute
82+
def instance_from_attribute_id(attribute_id)
83+
return unless attribute_id
7884

79-
attribute_value = @object.send(attribute)
80-
return unless attribute_value
85+
attribute_id_value = @object.send(attribute_id)
86+
return unless attribute_id_value
8187

82-
klass = class_from_attribute(attribute)
83-
klass.find_by(id: attribute_value)
88+
klass = class_from_attribute_id(attribute_id)
89+
klass.find_by(id: attribute_id_value)
8490
end
8591

86-
def class_from_attribute(attribute)
87-
association_name = attribute.to_s.chomp("_id")
92+
def class_from_attribute_id(attribute_id)
93+
association_name = attribute_id.to_s.chomp("_id")
8894
association_name.camelize.constantize
8995
rescue NameError
9096
object_class.reflect_on_association(association_name).klass

app/inputs/search_select_filter_input.rb

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,20 @@ def load_control_attributes
77
end
88

99
def input_method
10-
eq_input_name
10+
"#{input_name}_eq"
11+
end
12+
13+
def input_html_options_name
14+
"#{object_name}[#{input_method}]"
15+
end
16+
17+
def input_value
18+
result = valid_object.conditions.find do |condition|
19+
condition.attributes.map(&:name).include?(input_name.to_s)
20+
end
21+
22+
return unless result
23+
24+
result.values.first.value
1125
end
1226
end

app/inputs/search_select_input.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
class SearchSelectInput < ActiveAdminAddons::InputBase
1+
class SearchSelectInput < ActiveAdminAddons::SelectInputBase
22
include ActiveAdminAddons::SelectHelpers
33

44
def render_custom_input

app/javascript/activeadmin_addons/inputs/slim-select-nested.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ function ajaxSearch(search, currentData, args) {
1212
});
1313

1414
if (!!args.parent) {
15-
args.query.q[`${args.parent}_eq`] = args.parentId;
15+
args.query.q[`${args.parent}_id_eq`] = args.parentId;
1616
}
1717

1818
args.query.q = { ...args.query.q, ...args.filters };
@@ -48,7 +48,7 @@ function settings(el) {
4848
const { model, association } = el.dataset;
4949
const child = el.closest('.nested_level')
5050
.parentNode
51-
.querySelector(`.nested_level [data-model=${model}][data-parent=${association}_id]`);
51+
.querySelector(`.nested_level [data-model=${model}][data-parent=${association}]`);
5252

5353
if (child) {
5454
child.slim.setSelected();

docs/slim-select_nested_select.md

Lines changed: 63 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ end
3636
to enable nested select functionality, you we'll need to do the following:
3737

3838
```ruby
39-
f.input :city_id, as: :nested_select,
40-
level_1: { attribute: :country_id },
41-
level_2: { attribute: :region_id },
42-
level_3: { attribute: :city_id }
39+
f.input :city, as: :nested_select,
40+
level_1: { attribute: :country },
41+
level_2: { attribute: :region },
42+
level_3: { attribute: :city }
4343
```
4444

4545
<img src="./images/slim-select-nested-select.gif" />
@@ -48,39 +48,39 @@ By default, the nested select input uses the index action of the different level
4848
It's not mandatory to register the ActiveAdmin pages. But, if you don't, you'll need to pass the `url` attribute, on each level, to make it work.
4949

5050
```ruby
51-
f.input :city_id, as: :nested_select,
52-
level_1: {
53-
attribute: :country_id,
54-
url: '/api/countries'
55-
},
56-
level_2: {
57-
attribute: :region_id,
58-
url: '/api/regions'
59-
},
60-
level_3: {
61-
attribute: :city_id,
62-
url: '/api/cities'
63-
}
51+
f.input :city, as: :nested_select,
52+
level_1: {
53+
attribute: :country,
54+
url: '/api/countries'
55+
},
56+
level_2: {
57+
attribute: :region,
58+
url: '/api/regions'
59+
},
60+
level_3: {
61+
attribute: :city,
62+
url: '/api/cities'
63+
}
6464
```
6565

6666
> Remember: those custom endpoints need to work with Ransack filters.
6767
6868
Another option is to pass the `collection` attribute. If you set this, the `url` attribute will be ignored (no ajax request will be executed) and you will work with preloaded collections.
6969

7070
```ruby
71-
f.input :city_id, as: :nested_select,
72-
level_1: {
73-
attribute: :country_id,
74-
collection: Country.all
75-
},
76-
level_2: {
77-
attribute: :region_id,
78-
collection: Region.active
79-
},
80-
level_3: {
81-
attribute: :city_id,
82-
collection: City.all
83-
}
71+
f.input :city, as: :nested_select,
72+
level_1: {
73+
attribute: :country,
74+
collection: Country.all
75+
},
76+
level_2: {
77+
attribute: :region,
78+
collection: Region.active
79+
},
80+
level_3: {
81+
attribute: :city,
82+
collection: City.all
83+
}
8484
```
8585

8686
### Options
@@ -94,38 +94,38 @@ Nested select, allows you to customize the general behavior of the input:
9494
* `predicate`: **(optional)** You can change the default [ransack predicate](https://github.com/activerecord-hackery/ransack#search-matchers). It **defaults to** `contains`
9595

9696
```ruby
97-
f.input :city_id, as: :nested_select,
98-
fields: [:name, :id],
99-
display_name: :id,
100-
minimum_input_length: 1,
101-
level_1: { attribute: :country_id },
102-
level_2: { attribute: :region_id },
103-
level_3: { attribute: :city_id }
97+
f.input :city, as: :nested_select,
98+
fields: [:name, :id],
99+
display_name: :id,
100+
minimum_input_length: 1,
101+
level_1: { attribute: :country },
102+
level_2: { attribute: :region },
103+
level_3: { attribute: :city }
104104
```
105105

106106
<img src="./images/slim-select-nested-select-general-options.gif" />
107107

108108
Also, you can redefine general options on each level.
109109

110110
```ruby
111-
f.input :city_id, as: :nested_select,
112-
fields: [:name],
113-
display_name: :name,
114-
minimum_input_length: 0,
115-
level_1: {
116-
attribute: :country_id,
117-
minimum_input_length: 3,
118-
url: '/api/countries',
119-
response_root: 'paises'
120-
},
121-
level_2: {
122-
attribute: :region_id,
123-
display_name: :id,
124-
},
125-
level_3: {
126-
attribute: :city_id,
127-
fields: [:name, :information]
128-
}
111+
f.input :city, as: :nested_select,
112+
fields: [:name],
113+
display_name: :name,
114+
minimum_input_length: 0,
115+
level_1: {
116+
attribute: :country,
117+
minimum_input_length: 3,
118+
url: '/api/countries',
119+
response_root: 'paises'
120+
},
121+
level_2: {
122+
attribute: :region,
123+
display_name: :id,
124+
},
125+
level_3: {
126+
attribute: :city,
127+
fields: [:name, :information]
128+
}
129129
```
130130

131131
> `response_root` is not valid as general configuration. You need to define this attribute by level.
@@ -135,16 +135,16 @@ f.input :city_id, as: :nested_select,
135135
If you are using ajax search, you can define custom filters. For example, if you have:
136136

137137
```ruby
138-
f.input :city_id, as: :nested_select,
139-
level_1: { attribute: :country_id },
140-
level_2: {
141-
attribute: :region_id,
142-
filters: { name_contains: "Cuy", id_gt: 22 }
143-
},
144-
level_3: { attribute: :city_id }
138+
f.input :city, as: :nested_select,
139+
level_1: { attribute: :country },
140+
level_2: {
141+
attribute: :region,
142+
filters: { name_contains: "Cuy", id_gt: 22 }
143+
},
144+
level_3: { attribute: :city }
145145
```
146146

147-
After select a country, the regions will be filtered by:
147+
After selecting a country, the regions will be filtered by:
148148

149149
* The selected country id.
150150
* The text entered in the region's input.

docs/slim-select_search.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
To enable Slim Select ajax search functionality you need to do the following:
66

77
```ruby
8-
f.input :category_id, as: :search_select, url: admin_categories_path,
8+
f.input :category, as: :search_select, url: admin_categories_path,
99
fields: [:name, :description], display_name: 'name', minimum_input_length: 2,
1010
order_by: 'description_asc'
1111
```
@@ -18,7 +18,7 @@ To use on filters you need to add `as: :search_select_filter` with same options.
1818
If you want to use url helpers, use a `proc` like on the example
1919

2020
```ruby
21-
filter :category_id, as: :search_select_filter, url: proc { admin_categories_path },
21+
filter :category, as: :search_select_filter, url: proc { admin_categories_path },
2222
fields: [:name, :description], display_name: 'name', minimum_input_length: 2,
2323
order_by: 'description_asc'
2424
```
@@ -30,10 +30,10 @@ In case you need to filter with an attribute of another table you need to includ
3030
fields: [:name, :email], display_name: 'email', minimum_input_length: 2,
3131
order_by: 'description_asc', method_model: User
3232
```
33+
Note that in this case you need to use the `id` instead of the name of the association, just like you would do in a normal filter that uses an attribute of an association.
3334

3435
### Options
3536

36-
* `category_id`: Notice we're using the relation field name, not the relation itself, so you can't use `f.input :category`.
3737
* `url`: This is the URL where to get the results. This URL expects an activeadmin collection Url (like the index action) or anything that uses [ransack](https://github.com/activerecord-hackery/ransack) search gem.
3838
* `response_root`: **(optional)** If a request to `url` responds with root, you can indicate the name of that root with this attribute. By default, the gem will try to infer the root from url. For example: if `url` is `GET /admin/categories`, the root will be `categories`. If you have a rootless api, you don't need to worry about this attribute.
3939
* `fields`: an array of field names where to search for matches in the related model (`Category` in this example). If we give many fields, they will be searched with an OR condition.

lib/activeadmin_addons/support/input_base.rb

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,47 +7,5 @@ class InputBase < Formtastic::Inputs::StringInput
77
include InputOptionsHandler
88
include InputMethods
99
include InputHtmlHelpers
10-
11-
def to_html
12-
load_input_class
13-
load_control_attributes
14-
render_custom_input
15-
if parts.any?
16-
input_wrapping { parts_to_html }
17-
else
18-
super
19-
end
20-
end
21-
22-
def input_html_options
23-
# maxwidth and size are added by Formtastic::Inputs::StringInput
24-
# but according to the HTML standard these are not valid attributes
25-
# on the inputs provided by this module
26-
super.except(:maxlength, :size).merge(control_attributes)
27-
end
28-
29-
def parts_to_html
30-
parts.flatten.join("\n").html_safe
31-
end
32-
33-
def load_input_class
34-
load_class(self.class.to_s.underscore.dasherize)
35-
end
36-
37-
def load_control_attributes
38-
# Override on child classes if needed
39-
end
40-
41-
def render_custom_input
42-
# Override on child classes if needed
43-
end
44-
45-
def parts
46-
@parts ||= []
47-
end
48-
49-
def concat(part)
50-
parts << part
51-
end
5210
end
5311
end

0 commit comments

Comments
 (0)