├── .gitignore ├── Gemfile ├── Gemfile.lock ├── MIT-LICENSE ├── README.md ├── Rakefile ├── app ├── assets │ ├── images │ │ └── ransack_advanced_search │ │ │ └── .gitkeep │ ├── javascripts │ │ └── ransack_advanced_search │ │ │ ├── application.js │ │ │ └── ransack_advanced_search.js │ └── stylesheets │ │ └── ransack_advanced_search │ │ └── application.css ├── controllers │ ├── concerns │ │ └── ransack_advanced_search │ │ │ └── saved_search_utils.rb │ ├── ransack_advanced_search │ │ └── application_controller.rb │ └── saved_searches_controller.rb ├── helpers │ └── ransack_advanced_search_helper.rb ├── models │ └── saved_search.rb └── views │ └── ransack_advanced_search │ ├── _advanced_search.html.erb │ ├── _attribute_fields.erb │ ├── _condition_fields.erb │ ├── _grouping_fields.erb │ ├── _quick_search.html.erb │ ├── _saved_searches_list.erb │ ├── _sort_fields.erb │ └── _value_fields.erb ├── config ├── initializers │ └── ransack_advanced_search_setup.rb ├── locales │ ├── ransack.en.yml │ └── ransack.pt-BR.yml └── routes.rb ├── lib ├── generators │ ├── ransack_advanced_search │ │ ├── install_generator.rb │ │ └── saved_search_generator.rb │ └── templates │ │ ├── create_ransack_advanced_search_saved_search.rb │ │ └── ransack_advanced_search.rb ├── ransack_advanced_search.rb ├── ransack_advanced_search │ ├── engine.rb │ ├── helpers │ │ └── configuration.rb │ └── version.rb └── tasks │ └── ransack_advanced_search_tasks.rake ├── ransack_advanced_search.gemspec ├── script └── rails └── test ├── dummy ├── README.rdoc ├── Rakefile ├── app │ ├── assets │ │ ├── javascripts │ │ │ └── application.js │ │ └── stylesheets │ │ │ └── application.css │ ├── controllers │ │ └── application_controller.rb │ ├── helpers │ │ └── application_helper.rb │ ├── mailers │ │ └── .gitkeep │ ├── models │ │ └── .gitkeep │ └── views │ │ └── layouts │ │ └── application.html.erb ├── config.ru ├── config │ ├── application.rb │ ├── boot.rb │ ├── database.yml │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers │ │ ├── backtrace_silencers.rb │ │ ├── inflections.rb │ │ ├── mime_types.rb │ │ ├── secret_token.rb │ │ ├── session_store.rb │ │ └── wrap_parameters.rb │ ├── locales │ │ └── en.yml │ └── routes.rb ├── lib │ └── assets │ │ └── .gitkeep ├── log │ └── .gitkeep ├── public │ ├── 404.html │ ├── 422.html │ ├── 500.html │ └── favicon.ico └── script │ └── rails ├── integration └── navigation_test.rb ├── ransack_advanced_search_test.rb └── test_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle/ 2 | log/*.log 3 | pkg/ 4 | test/dummy/db/*.sqlite3 5 | test/dummy/log/*.log 6 | test/dummy/tmp/ 7 | test/dummy/.sass-cache 8 | *.gem 9 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # Declare your gem's dependencies in ransack_advanced_search.gemspec. 4 | # Bundler will treat runtime dependencies like base dependencies, and 5 | # development dependencies will be added by default to the :development group. 6 | gemspec 7 | 8 | # jquery-rails is used by the dummy application 9 | gem "jquery-rails" 10 | 11 | # Declare any dependencies that are still in development here instead of in 12 | # your gemspec. These might include edge Rails or gems from your path or 13 | # Git. Remember to move these dependencies to your gemspec before releasing 14 | # your gem to rubygems.org. 15 | 16 | # To use debugger 17 | # gem 'debugger' 18 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | ransack_advanced_search (0.1.5) 5 | rails (>= 3.2.6, < 5) 6 | ransack (~> 1.7.0, >= 1.7.0) 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | actionmailer (3.2.22.2) 12 | actionpack (= 3.2.22.2) 13 | mail (~> 2.5.4) 14 | actionpack (3.2.22.2) 15 | activemodel (= 3.2.22.2) 16 | activesupport (= 3.2.22.2) 17 | builder (~> 3.0.0) 18 | erubis (~> 2.7.0) 19 | journey (~> 1.0.4) 20 | rack (~> 1.4.5) 21 | rack-cache (~> 1.2) 22 | rack-test (~> 0.6.1) 23 | sprockets (~> 2.2.1) 24 | activemodel (3.2.22.2) 25 | activesupport (= 3.2.22.2) 26 | builder (~> 3.0.0) 27 | activerecord (3.2.22.2) 28 | activemodel (= 3.2.22.2) 29 | activesupport (= 3.2.22.2) 30 | arel (~> 3.0.2) 31 | tzinfo (~> 0.3.29) 32 | activeresource (3.2.22.2) 33 | activemodel (= 3.2.22.2) 34 | activesupport (= 3.2.22.2) 35 | activesupport (3.2.22.2) 36 | i18n (~> 0.6, >= 0.6.4) 37 | multi_json (~> 1.0) 38 | arel (3.0.3) 39 | builder (3.0.4) 40 | coderay (1.1.1) 41 | erubis (2.7.0) 42 | hike (1.2.3) 43 | i18n (0.7.0) 44 | journey (1.0.4) 45 | jquery-rails (3.1.4) 46 | railties (>= 3.0, < 5.0) 47 | thor (>= 0.14, < 2.0) 48 | json (1.8.3) 49 | mail (2.5.4) 50 | mime-types (~> 1.16) 51 | treetop (~> 1.4.8) 52 | method_source (0.8.2) 53 | mime-types (1.25.1) 54 | multi_json (1.11.3) 55 | polyamorous (1.3.0) 56 | activerecord (>= 3.0) 57 | polyglot (0.3.5) 58 | pry (0.10.3) 59 | coderay (~> 1.1.0) 60 | method_source (~> 0.8.1) 61 | slop (~> 3.4) 62 | rack (1.4.7) 63 | rack-cache (1.6.1) 64 | rack (>= 0.4) 65 | rack-ssl (1.3.4) 66 | rack 67 | rack-test (0.6.3) 68 | rack (>= 1.0) 69 | rails (3.2.22.2) 70 | actionmailer (= 3.2.22.2) 71 | actionpack (= 3.2.22.2) 72 | activerecord (= 3.2.22.2) 73 | activeresource (= 3.2.22.2) 74 | activesupport (= 3.2.22.2) 75 | bundler (~> 1.0) 76 | railties (= 3.2.22.2) 77 | railties (3.2.22.2) 78 | actionpack (= 3.2.22.2) 79 | activesupport (= 3.2.22.2) 80 | rack-ssl (~> 1.3.2) 81 | rake (>= 0.8.7) 82 | rdoc (~> 3.4) 83 | thor (>= 0.14.6, < 2.0) 84 | rake (11.1.2) 85 | ransack (1.7.0) 86 | actionpack (>= 3.0) 87 | activerecord (>= 3.0) 88 | activesupport (>= 3.0) 89 | i18n 90 | polyamorous (~> 1.2) 91 | rdoc (3.12.2) 92 | json (~> 1.4) 93 | slop (3.6.0) 94 | sprockets (2.2.3) 95 | hike (~> 1.2) 96 | multi_json (~> 1.0) 97 | rack (~> 1.0) 98 | tilt (~> 1.1, != 1.3.0) 99 | thor (0.19.1) 100 | tilt (1.4.1) 101 | treetop (1.4.15) 102 | polyglot 103 | polyglot (>= 0.3.1) 104 | tzinfo (0.3.49) 105 | 106 | PLATFORMS 107 | ruby 108 | 109 | DEPENDENCIES 110 | jquery-rails 111 | pry (~> 0.10) 112 | ransack_advanced_search! 113 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016 David Brusius 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Gem Version](https://badge.fury.io/rb/ransack_advanced_search.svg)](https://badge.fury.io/rb/ransack_advanced_search) 2 | 3 | # Ransack Advanced Search 4 | 5 | [ransack](https://github.com/activerecord-hackery/ransack) is a Object-based searching for Rails. 6 | 7 | The `ransack_advanced_search` gem provides Bootstrap based templates for the [ransack](https://github.com/activerecord-hackery/ransack) Advanced query mode. This gem also provides some additional features to make search experience better and easy to use. 8 | 9 | ## Features 10 | * Full [ransack](https://github.com/activerecord-hackery/ransack) compatibility (you can still use the default [ransack](https://github.com/activerecord-hackery/ransack) features); 11 | * Ransack integrated Advanced Search mode with helpers and views - extracted from [ransack-demo](https://github.com/activerecord-hackery/ransack_demo) project; 12 | * Saved Searches, scoped by contexts, to persist search params and use it in the future; 13 | * Custom search value inputs based on data type; 14 | * TODO: scope by current user or current account (abstract scope). 15 | 16 | ## Installation 17 | 18 | Remove any entry of `ransack` gem from your application's Gemfile. The `ransack_advanced_search` gem will include the `ransack` gem as a dependency. 19 | 20 | Add this line to your application's Gemfile: 21 | 22 | ```ruby 23 | gem 'ransack_advanced_search' 24 | ``` 25 | 26 | Execute: 27 | 28 | $ bundle 29 | 30 | Or install it yourself as: 31 | 32 | $ gem install ransack_advanced_search 33 | 34 | Run the generator to install the gem initializer, this will create the file `config/initializers/ransack_advanced_search.rb`: 35 | 36 | $ rails generate ransack_advanced_search:install 37 | 38 | For while we don't need to change this file. 39 | 40 | ## Usage 41 | 42 | First, in your controller action that you will use for search, include the following: 43 | 44 | ```ruby 45 | # GET /your_models 46 | # GET /your_models.json 47 | # POST /your_models/advanced_search 48 | def index 49 | # The ransack search must be in the @search instance variable, because the advanced search will use it to build the search form. You must provide associations you will use in the includes method. 50 | @search = YourModel.search(params[:q]) 51 | @results = @search.result().includes(:association1, :association2) 52 | # or, if the above doesn't work 53 | @search = YourModel.ransack(params[:q]) 54 | @results = @search.result(:association1, :association2) 55 | end 56 | ``` 57 | 58 | To use the Advanced Search with associtions you must provide a method in your model to tell what are the associations for that model 59 | ```ruby 60 | class YourModel < ActiveRecord::Base 61 | # Associations to be included in the search attributes 62 | def self.ransackable_associations(*) 63 | %w( association1 association2 ) 64 | end 65 | end 66 | ``` 67 | 68 | By default ransack will provide all your model fields to the avaliable field to search. You can restrict what fields will be included in the Advanced Search by defining it in your model, like this: 69 | ```ruby 70 | class YourModel < ActiveRecord::Base 71 | # Fields that will be included in ransack advanced search 72 | def self.ransackable_attributes(*) 73 | %w( name description other_fields_names ) + _ransackers.keys 74 | end 75 | end 76 | ``` 77 | 78 | This rule applies to each model included in the search, even in the associations you can restrict fields to search. 79 | 80 | 81 | Now, we have to create a POST route to this action, in your `config/routes.rb` provide a POST route to this controller/action: 82 | 83 | ```ruby 84 | # For example 85 | resources :your_models do 86 | collection do 87 | match :advanced_search, to: 'your_models#index', via: :post 88 | end 89 | end 90 | ``` 91 | 92 | We have a ransack search well configured, from this step we will include the Advanced Search query mode in our views. 93 | 94 | In your application layout `app/views/layouts/application.erb`, include a yield in the head section to load ransack advanced search dependencies: 95 | ```html 96 | 97 | 98 | 99 | <%= yield(:ransack_advanced_search_setup) %> 100 | 101 | 102 | 103 | 104 | ``` 105 | In the view that you want the advanced search views you can choose betwenn quick_search and advanced_search views, so insert the following for advanced_search: 106 | 107 | ```ruby 108 | <%= render partial: 'ransack_advanced_search/advanced_search', 109 | locals: { 110 | search_url: advanced_search_your_models_path, # POST route we created above 111 | redirect_path: your_models_path # GET redirect path, to return after some actions 112 | } 113 | %> 114 | ``` 115 | 116 | Or, the following for quick_search: 117 | 118 | ```ruby 119 | <%= render partial: 'ransack_advanced_search/quick_search', 120 | locals: { 121 | search_url: advanced_search_your_models_path, # POST route we created above 122 | redirect_path: your_models_path # GET redirect path, to return after some actions 123 | } 124 | %> 125 | ``` 126 | IMPORTANT: If you are using saved searches and you want to change from quick search to advanced search views or vice versa, you must provide a new context for this saved searches or delete all saved search for that context before changing the view. 127 | 128 | All done! Enjoy the search! 129 | 130 | ## Saving Searches 131 | 132 | If you want to provide the feature to Save ransack searches, follow these steps. 133 | 134 | Enable Saved Searches configuration in `config/initializers/ransack_advanced_search.rb`: 135 | 136 | ```ruby 137 | config.enable_saved_searches = true 138 | ``` 139 | 140 | Run this command to generate the Saved Search Migration: 141 | 142 | $ rails generate ransack_advanced_search:saved_search 143 | 144 | Execute: 145 | 146 | $ rake db:migrate 147 | 148 | In each controller action with the Advanced Search: 149 | 150 | * Include the Saved Search Utils methods: 151 | ```ruby 152 | include RansackAdvancedSearch::SavedSearchUtils 153 | ``` 154 | 155 | * Insert this line before creating the search: 156 | ```ruby 157 | # GET /your_models 158 | # GET /your_models.json 159 | # POST /your_models/advanced_search 160 | def index 161 | # Call this methods passing a context(to scope the saved searches, can be any symbol) and the params variable 162 | params[:q] = perform_saved_searches_actions(:your_models_index, params) 163 | @search = YourModel.search(params[:q]) 164 | @results = @search.result() 165 | end 166 | ``` 167 | IMPORTANT: if you use custom inflections settings, you can receive this error: 168 | ``` 169 | Table 'calendario_development.saved_searchs' doesn't exist 170 | ``` 171 | To avoid this you will have to include an irregular inflection: 172 | ```ruby 173 | inflect.irregular 'saved_search', 'saved_searches' 174 | ``` 175 | 176 | ## i18n Support 177 | 178 | This gem was built using i18n translation supports, and has bult-in support for English (en) and Brazilian Portuguese (pt-BR). If you want to translate to your specific language, add a new locale file in your `config/locales` and translate the values to your language. You can get one of the locales of this project to make it easier to translate to your language. 179 | 180 | 181 | ## Contributing 182 | 183 | 1. Fork it ( https://github.com/davidbrusius/ransack_advanced_search/fork ) 184 | 2. Create your feature branch (`git checkout -b my-new-feature`) 185 | 3. Commit your changes (`git commit -am 'Add some feature'`) 186 | 4. Push to the branch (`git push origin my-new-feature`) 187 | 5. Create a new Pull Request 188 | 189 | This project uses MIT-LICENSE. 190 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | begin 3 | require 'bundler/setup' 4 | rescue LoadError 5 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks' 6 | end 7 | begin 8 | require 'rdoc/task' 9 | rescue LoadError 10 | require 'rdoc/rdoc' 11 | require 'rake/rdoctask' 12 | RDoc::Task = Rake::RDocTask 13 | end 14 | 15 | RDoc::Task.new(:rdoc) do |rdoc| 16 | rdoc.rdoc_dir = 'rdoc' 17 | rdoc.title = 'RansackAdvancedSearch' 18 | rdoc.options << '--line-numbers' 19 | rdoc.rdoc_files.include('lib/**/*.rb') 20 | end 21 | 22 | APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__) 23 | load 'rails/tasks/engine.rake' 24 | 25 | 26 | 27 | Bundler::GemHelper.install_tasks 28 | 29 | require 'rake/testtask' 30 | 31 | Rake::TestTask.new(:test) do |t| 32 | t.libs << 'lib' 33 | t.libs << 'test' 34 | t.pattern = 'test/**/*_test.rb' 35 | t.verbose = false 36 | end 37 | 38 | 39 | task :default => :test 40 | -------------------------------------------------------------------------------- /app/assets/images/ransack_advanced_search/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcfoxlegacy/ransack_advanced_search/1e757ad118aa02bd9064fa02fca48d32163fcd87/app/assets/images/ransack_advanced_search/.gitkeep -------------------------------------------------------------------------------- /app/assets/javascripts/ransack_advanced_search/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // the compiled file. 9 | // 10 | // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD 11 | // GO AFTER THE REQUIRES BELOW. 12 | // 13 | //= require_tree . 14 | -------------------------------------------------------------------------------- /app/assets/javascripts/ransack_advanced_search/ransack_advanced_search.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | this.Search = (function() { 3 | function Search(templates) { 4 | this.templates = templates != null ? templates : {}; 5 | } 6 | 7 | Search.prototype.remove_fields = function(button) { 8 | return $(button).closest('.fields').remove(); 9 | }; 10 | 11 | Search.prototype.add_fields = function(button, type, content) { 12 | var new_id, regexp; 13 | new_id = new Date().getTime(); 14 | regexp = new RegExp('new_' + type, 'g'); 15 | return $(button).before(content.replace(regexp, new_id)); 16 | }; 17 | 18 | Search.prototype.nest_fields = function(button, type) { 19 | var id_regexp, new_id, object_name, sanitized_object_name, template; 20 | new_id = new Date().getTime(); 21 | id_regexp = new RegExp('new_' + type, 'g'); 22 | template = this.templates[type]; 23 | object_name = $(button).closest('.fields').attr('data-object-name'); 24 | sanitized_object_name = object_name.replace(/\]\[|[^-a-zA-Z0-9:.]/g, '_').replace(/_$/, ''); 25 | template = template.replace(/new_object_name\[/g, object_name + "["); 26 | template = template.replace(/new_object_name_/, sanitized_object_name + '_'); 27 | return $(button).before(template.replace(id_regexp, new_id)); 28 | }; 29 | 30 | Search.prototype.convertFieldType = function (fieldType) { 31 | var fieldTypeToHtmlType = { 32 | 'default': 'text', 33 | 'integer': 'number', 34 | 'date' : 'date', 35 | 'datetime' : 'date' 36 | }; 37 | return (fieldTypeToHtmlType[fieldType] || fieldTypeToHtmlType['default']); 38 | }; 39 | 40 | Search.prototype.changeValueInputsType = function(element, fieldName, search) { 41 | fieldType = search.fieldsType[fieldName]; 42 | conditionValueInputs = $(element).parents('.ransack-condition-field').find('.ransack-attribute-value'); 43 | conditionValueInputs.attr('type', search.convertFieldType(fieldType)); 44 | }; 45 | 46 | return Search; 47 | 48 | })(); 49 | 50 | }).call(this); 51 | -------------------------------------------------------------------------------- /app/assets/stylesheets/ransack_advanced_search/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the top of the 9 | * compiled file, but it's generally better to create a new file per style scope. 10 | * 11 | *= require_self 12 | *= require_tree . 13 | */ 14 | 15 | .ransack-advanced-search i.remove_fields, 16 | .ransack-advanced-search i.add_fields { 17 | cursor: pointer; 18 | } 19 | 20 | .ransack-advanced-search .ransack-condition-field { 21 | margin-bottom: 5px; 22 | margin-top: 5px; 23 | } 24 | 25 | .ransack-advanced-search .ransack-action-buttons { 26 | margin-top: 10px; 27 | } 28 | 29 | .ransack-advanced-search .ransack-saved-search-delete { 30 | margin-top: 1px; 31 | } 32 | -------------------------------------------------------------------------------- /app/controllers/concerns/ransack_advanced_search/saved_search_utils.rb: -------------------------------------------------------------------------------- 1 | module RansackAdvancedSearch 2 | module SavedSearchUtils 3 | extend ActiveSupport::Concern 4 | 5 | # Perform saved searches actions to provide full functionality with one method 6 | def perform_saved_searches_actions(context, params={}) 7 | get_saved_searches(context) 8 | save_or_update_saved_search(params.merge(context: context)) 9 | get_params_to_search(context) 10 | end 11 | 12 | # Get list of Saved Search by context 13 | def get_saved_searches(context) 14 | @saved_searches = SavedSearch.where(context: context) 15 | end 16 | 17 | # Return params of Saved Search or search form params 18 | def get_params_to_search(context) 19 | if params[:saved_search].present? 20 | @saved_search = SavedSearch.find_by(id: params[:saved_search], context: context) 21 | end 22 | return params[:q] if params[:use_search_params].present? 23 | params[:q] = @saved_search.try(:search_params) || params[:q] 24 | end 25 | 26 | # Save or update Saved Search 27 | def save_or_update_saved_search(params) 28 | if params[:save_new_search].present? || params[:save_search].present? 29 | if params[:save_new_search].present? 30 | @saved_search = new_saved_search(params) 31 | elsif params[:save_search].present? && params[:saved_search].present? 32 | @saved_search = update_saved_search(params) 33 | elsif params[:save_search].present? 34 | @saved_search = new_saved_search(params) 35 | end 36 | 37 | if @saved_search.save 38 | flash[:notice] = t('ransack.saved_search.save.success') 39 | else 40 | flash[:error] = t('ransack.saved_search.save.error') 41 | end 42 | end 43 | end 44 | 45 | def new_saved_search(params) 46 | SavedSearch.new( 47 | context: params[:context], description: params[:description], 48 | search_params: params[:q] 49 | ) 50 | end 51 | 52 | def update_saved_search(params) 53 | saved_search = SavedSearch.find_by(id: params[:saved_search], context: params[:context]) 54 | saved_search.description = params[:description] 55 | saved_search.search_params = params[:q] 56 | saved_search 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /app/controllers/ransack_advanced_search/application_controller.rb: -------------------------------------------------------------------------------- 1 | module RansackAdvancedSearch 2 | class ApplicationController < ActionController::Base 3 | protect_from_forgery :with => :exception 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/controllers/saved_searches_controller.rb: -------------------------------------------------------------------------------- 1 | class SavedSearchesController < ApplicationController 2 | before_action :set_saved_search, only: [:destroy] 3 | 4 | # DELETE /saved_search/1 5 | # DELETE /saved_search/1.json 6 | def destroy 7 | @saved_search.destroy 8 | respond_to do |format| 9 | redirect_path = params[:redirect_to] || root_path 10 | format.html { redirect_to redirect_path, notice: t('ransack.saved_search.delete.success') } 11 | format.json { head :no_content } 12 | end 13 | end 14 | 15 | private 16 | # Use callbacks to share common setup or constraints between actions. 17 | def set_saved_search 18 | @saved_search = SavedSearch.find(params[:id]) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/helpers/ransack_advanced_search_helper.rb: -------------------------------------------------------------------------------- 1 | module RansackAdvancedSearchHelper 2 | def setup_search_form(builder, search_object) 3 | fields = builder.grouping_fields builder.object.new_grouping, 4 | object_name: 'new_object_name', child_index: "new_grouping" do |f| 5 | render('ransack_advanced_search/grouping_fields', f: f) 6 | end 7 | content_for :ransack_setup, %Q{ 8 | var search = new Search({grouping: "#{escape_javascript(fields)}"}); 9 | search.fieldsType = #{get_fields_data_type(search_object).to_json.html_safe} 10 | $('select.ransack-attribute-select').each(function(e) { 11 | fieldName = $(this).find('option:selected')[0].value; 12 | search.changeValueInputsType(this, fieldName, search); 13 | }); 14 | $(document).on("click", "i.add_fields", function() { 15 | search.add_fields(this, $(this).data('fieldType'), $(this).data('content')); 16 | if($(this).hasClass('ransack-add-attribute')) { 17 | fieldName = $(this).parents('.ransack-condition-field').find('select.ransack-attribute-select').find('option:selected')[0].value; 18 | search.changeValueInputsType(this, fieldName, search); 19 | } 20 | return false; 21 | }); 22 | $(document).on('change', 'select.ransack-attribute-select', function(e) { 23 | fieldName = $(this).find('option:selected')[0].value; 24 | search.changeValueInputsType(this, fieldName, search); 25 | }); 26 | $(document).on("click", "i.remove_fields", function() { 27 | search.remove_fields(this); 28 | return false; 29 | }); 30 | $(document).on("click", "button.nest_fields", function() { 31 | search.nest_fields(this, $(this).data('fieldType')); 32 | return false; 33 | }); 34 | }.html_safe 35 | end 36 | 37 | def get_fields_data_type(search) 38 | bases = [''] + search.klass.ransackable_associations 39 | fields_type = Hash.new 40 | bases.each do |model| 41 | model_name = model.present? ? "#{model}_" : "" 42 | search.context.traverse(model).columns_hash.each do |field, attributes| 43 | fields_type["#{model_name}#{field}"] = attributes.type 44 | end 45 | end 46 | fields_type 47 | end 48 | 49 | def button_to_remove_fields 50 | content_tag :i, nil, class: 'remove_fields glyphicon glyphicon-minus-sign text-danger' 51 | end 52 | 53 | def button_to_add_fields(name, f, type, custom_class='') 54 | new_object = f.object.send "build_#{type}" 55 | fields = f.send("#{type}_fields", new_object, child_index: "new_#{type}") do |builder| 56 | render('ransack_advanced_search/' + type.to_s + "_fields", f: builder) 57 | end 58 | content_tag :i, name, :class => custom_class + ' add_fields glyphicon glyphicon-plus-sign text-success', :type => 'button', 'data-field-type' => type, 'data-content' => "#{fields}" 59 | end 60 | 61 | def button_to_nest_fields(name, type) 62 | content_tag :button, name, :class => 'nest_fields', 'data-field-type' => type 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /app/models/saved_search.rb: -------------------------------------------------------------------------------- 1 | class SavedSearch < ActiveRecord::Base 2 | validates_presence_of :context, :description, :search_params 3 | 4 | serialize :search_params 5 | end 6 | -------------------------------------------------------------------------------- /app/views/ransack_advanced_search/_advanced_search.html.erb: -------------------------------------------------------------------------------- 1 |