acts_as_data_table 0.0.1

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.
Files changed (33) hide show
  1. data/.gitignore +18 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +55 -0
  5. data/Rakefile +11 -0
  6. data/acts_as_data_table.gemspec +25 -0
  7. data/app/coffeescripts/acts_as_data_table.coffee +47 -0
  8. data/app/helpers/acts_as_data_table_helper.rb +170 -0
  9. data/config/locales/acts_as_data_table.en.yml +11 -0
  10. data/generators/acts_as_data_table/acts_as_data_table_generator.rb +13 -0
  11. data/generators/acts_as_data_table/templates/assets/js/acts_as_data_table.js +37 -0
  12. data/init.rb +1 -0
  13. data/lib/acts_as_data_table/multi_column_scopes.rb +116 -0
  14. data/lib/acts_as_data_table/scope_filters/action_controller.rb +139 -0
  15. data/lib/acts_as_data_table/scope_filters/active_record.rb +264 -0
  16. data/lib/acts_as_data_table/scope_filters/form_helper.rb +67 -0
  17. data/lib/acts_as_data_table/scope_filters/validator.rb +144 -0
  18. data/lib/acts_as_data_table/session_helper.rb +193 -0
  19. data/lib/acts_as_data_table/shared/action_controller.rb +25 -0
  20. data/lib/acts_as_data_table/shared/session.rb +312 -0
  21. data/lib/acts_as_data_table/sortable_columns/action_controller.rb +111 -0
  22. data/lib/acts_as_data_table/sortable_columns/active_record.rb +34 -0
  23. data/lib/acts_as_data_table/sortable_columns/renderers/bootstrap2.rb +17 -0
  24. data/lib/acts_as_data_table/sortable_columns/renderers/default.rb +82 -0
  25. data/lib/acts_as_data_table/version.rb +3 -0
  26. data/lib/acts_as_data_table.rb +71 -0
  27. data/lib/acts_as_data_table_helper.rb.bak +165 -0
  28. data/lib/named_scope_filters.rb +273 -0
  29. data/lib/tasks/acts_as_data_table.rake +4 -0
  30. data/rails/init.rb +4 -0
  31. data/test/acts_as_searchable_test.rb +8 -0
  32. data/test/test_helper.rb +4 -0
  33. metadata +142 -0
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+
16
+ .idea/
17
+ .ruby-gemset
18
+ .ruby-version
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in acts_as_data_table.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Stefan Exner
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,55 @@
1
+ # ActsAsDataTable
2
+
3
+ This gem adds automatic filtering and sorting to models and controllers in Rails applications.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'acts_as_data_table'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install acts_as_data_table
20
+
21
+ The `sortable_columns` helper will also need a javascript file if you'd like to
22
+ use the `CTRL + Click` way of adding new sorting columns.
23
+ You can easily generated it by using
24
+
25
+ $ ruby script/generate acts_as_data_table js
26
+
27
+ Please note that this javascript addon requires jQuery.
28
+
29
+ ## Usage
30
+
31
+ The gem consists of 3 parts:
32
+
33
+ 1. Multi Column Queries, e.g. a full text search over several table columns
34
+ 2. Automatic filtering based on (named) scopes defined in the model
35
+ 3. Automatic sorting (`ORDER BY`) by multiple columns
36
+
37
+ ### Multi Column Queries
38
+
39
+ TODO
40
+
41
+ ### Scope Filters
42
+
43
+ TODO
44
+
45
+ ### Sortable Columns
46
+
47
+ TODO
48
+
49
+ ## Contributing
50
+
51
+ 1. Fork it ( https://github.com/stex/acts_as_data_table/fork )
52
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
53
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
54
+ 4. Push to the branch (`git push origin my-new-feature`)
55
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ desc "Compiles the plugin's coffeescript to a js file"
4
+ task :make do |t|
5
+ input_files = Dir['./app/coffeescripts/**/*.coffee']
6
+ output_directory = './generators/acts_as_data_table/templates/assets/js'
7
+
8
+ input_files.each do |file|
9
+ `coffee -c --no-header -b -o #{output_directory} #{file}`
10
+ end
11
+ end
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'acts_as_data_table/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "acts_as_data_table"
8
+ spec.version = ActsAsDataTable::VERSION
9
+ spec.authors = ["Stefan Exner"]
10
+ spec.email = ["stex@sterex.de"]
11
+ spec.summary = %q{Adds automatic scope based filtering and column sorting to controllers and models.}
12
+ spec.description = %q{Adds methods to models and controllers to perform automatic filtering, sorting and multi-column-queries without having to worry about the implementation.}
13
+ spec.homepage = 'https://www.github.com/stex/acts_as_data_table'
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+
24
+ spec.add_dependency 'rails', '~> 2.3'
25
+ end
@@ -0,0 +1,47 @@
1
+ #
2
+ # Handles clicks on table column headers and adds the functionality
3
+ # for CTRL+Click. The different actions are triggered as follows:
4
+ #
5
+ # Normal click: The column will be the only sorting column
6
+ # CTRL + click on inactive column: Column will be added to sorting list
7
+ # CTRL + click on active column: Column will be removed from sorting list
8
+ #
9
+ jQuery(document).ready () ->
10
+ jQuery(document).on "click", '[data-init=sortable-column]', (e) ->
11
+ #Keep the browser from jumping to the top due to the href='#'
12
+ e.preventDefault()
13
+
14
+ urlToggle = jQuery(@).data('urlToggle')
15
+ urlSetBase = jQuery(@).data('urlSetBase')
16
+
17
+ remote = jQuery(@).data('remote')
18
+ active = jQuery(@).data('active')
19
+
20
+ url = urlSetBase
21
+
22
+ if e.ctrlKey || e.metaKey
23
+ url = urlToggle
24
+
25
+ if remote
26
+ jQuery.ajax
27
+ 'url': url,
28
+ dataType: 'script'
29
+ else
30
+ window.location.href = url
31
+
32
+ return false
33
+
34
+ jQuery(document).on "click", "[data-init=sortable-column-direction]", (e) ->
35
+ e.preventDefault()
36
+
37
+ url = jQuery(@).data('urlChangeDirection')
38
+ remote = jQuery(@).data('remote')
39
+
40
+ if remote
41
+ jQuery.ajax
42
+ 'url': url,
43
+ dataType: 'script'
44
+ else
45
+ window.location.href = url
46
+
47
+
@@ -0,0 +1,170 @@
1
+ module ActsAsDataTableHelper
2
+ require 'ostruct'
3
+
4
+ #
5
+ # Generates a link to add/remove a certain scope filter
6
+ #
7
+ # @param [Hash] options
8
+ # Options to customize the generated link
9
+ #
10
+ # @option options [String, Symbol] :scope
11
+ # The scope within the given +group+ to be added/removed
12
+ #
13
+ # @option options [TrueClass, FalseClass] :toggle (false)
14
+ # If set to +true+, the link will automatically remove the filter
15
+ # if it's currently active without having to explicitly set +:remove+
16
+ #
17
+ # @option options [TrueClass, FalseClass] :remove (false)
18
+ # If set to +true+, the link will always attempt to remove
19
+ # the given +:scope+ within the given +group+ and cannot be used
20
+ # to add it.
21
+ #
22
+ def scope_filter_link(group, scope, options = {})
23
+ caption = options.delete(:caption) || scope_filter_caption(group, scope, options[:args])
24
+ surrounding_tag = options.delete(:surrounding)
25
+ remote = options.delete(:remote)
26
+ args = options[:args]
27
+ url = scope_filter_link_url(group, scope, options)
28
+
29
+ classes = options[:class].try(:split, ' ') || []
30
+ classes << 'active' if scope && acts_as_data_table_session.active_filter?(group, scope, args)
31
+
32
+ options[:class] = classes.join(' ')
33
+
34
+ if remote
35
+ link = link_to_remote caption, :url => url, :method => :get, :html => options
36
+ else
37
+ link = link_to caption, url, options
38
+ end
39
+
40
+ surrounding_tag ? content_tag(surrounding_tag, link, :class => options[:class]) : link
41
+ end
42
+
43
+ #
44
+ # Generates a URL to add/remove/toggle a given scope filter
45
+ #
46
+ # @see #scope_filter_link for arguments
47
+ #
48
+ # @return [String] The generated URL
49
+ #
50
+ def scope_filter_link_url(group, scope, options)
51
+ args = options.delete(:args)
52
+ toggle = options.delete(:toggle)
53
+ auto_remove = scope && toggle && acts_as_data_table_session.active_filter?(group, scope, args)
54
+ remove = options.delete(:remove) || auto_remove
55
+
56
+ if remove
57
+ url_for({:scope_filters => {:action => 'remove', :group => group}})
58
+ else
59
+ url_for({:scope_filters => {:action => 'add', :group => group, :scope => scope, :args => args}})
60
+ end
61
+ end
62
+
63
+ #
64
+ # Generates a URL to be used as form action for filters which require
65
+ # dynamic arguments
66
+ #
67
+ # @return [String] the generated URL
68
+ #
69
+ # @example Using the rails form helper with a scope filter url
70
+ #
71
+ # - form_tag(scope_filter_form_url) do
72
+ # ...
73
+ #
74
+ def scope_filter_form_url(group, scope)
75
+ url_for({:scope_filters => {:action => 'add', :group => group, :scope => scope}})
76
+ end
77
+
78
+
79
+ def scope_filter_form(group, scope, options = {}, &proc)
80
+ content = capture(Acts::DataTable::ScopeFilters::FormHelper.new(self, group, scope), &proc)
81
+ method = options[:method] || :get
82
+ if options.delete(:remote)
83
+ options.delete(:method)
84
+ form_remote_tag :url => scope_filter_form_url(group, scope), :method => method, :html => options do
85
+ concat(content)
86
+ end
87
+ else
88
+ form_tag scope_filter_form_url(group, scope), options do
89
+ concat(content)
90
+ end
91
+ end
92
+ end
93
+
94
+ #
95
+ # @return [String] URL to remove all active scope filters for the current action
96
+ #
97
+ def scope_filter_reset_url
98
+ url_for({:scope_filters => {:action => :reset}})
99
+ end
100
+
101
+ #
102
+ # Looks up a set argument for a given filter, e.g. to highlight it
103
+ # in the search results
104
+ #
105
+ # @return [Object, NilClass] the argument used for the given scope
106
+ # or +nil+ if the filter is currently not active
107
+ #
108
+ def scope_filter_arg(group, scope, name)
109
+ Acts::DataTable.lookup_nested_hash(acts_as_data_table_session.active_filters, group.to_s, scope.to_s, name.to_s)
110
+ end
111
+
112
+ #
113
+ # @return [Hash] Arguments used for the given filter
114
+ #
115
+ def scope_filter_args(group, scope)
116
+ Acts::DataTable.lookup_nested_hash(acts_as_data_table_session.active_filters, group.to_s, scope.to_s) || {}
117
+ end
118
+
119
+ #
120
+ # Generates a scope filter caption
121
+ #
122
+ def scope_filter_caption(group, scope, args = {})
123
+ model = Acts::DataTable::ScopeFilters::ActionController.get_request_model
124
+ Acts::DataTable::ScopeFilters::ActiveRecord.scope_filter_caption(model, group, scope, args)
125
+ end
126
+
127
+ def active_scope_filter(group)
128
+ acts_as_data_table_session.active_filter(group)
129
+ end
130
+
131
+ #----------------------------------------------------------------
132
+ # Sortable Columns
133
+ #----------------------------------------------------------------
134
+
135
+ def sortable_column(model, column, caption, options = {}, &proc)
136
+ renderer_class = options.delete(:renderer) || Acts::DataTable::SortableColumns::Renderers.default_renderer
137
+
138
+ sortable = sortable_column_data(model, column)
139
+ sortable[:html_options] = options
140
+ sortable[:caption] = caption
141
+ sortable[:remote] = options.delete(:remote)
142
+ sortable[:remote] = true if sortable[:remote].blank?
143
+ sortable = OpenStruct.new(sortable)
144
+
145
+ #If a block is given, we let the user handle the content of the table
146
+ #header himself. Otherwise, we'll generate the default links.
147
+ if block_given?
148
+ yield sortable
149
+ else
150
+ renderer = renderer_class.constantize.new(sortable, self)
151
+ renderer.to_html
152
+ end
153
+ end
154
+
155
+ def sortable_column_data(model, column)
156
+ sortable = {}
157
+
158
+ sortable[:direction] = acts_as_data_table_session.sorting_direction(model, column)
159
+ sortable[:active] = acts_as_data_table_session.active_column?(model, column)
160
+
161
+ urls = {}
162
+ urls[:toggle] = url_for({:sortable_columns => {:action => :toggle, :model => model, :column => column}})
163
+ urls[:change_direction] = url_for({:sortable_columns => {:action => :change_direction, :model => model, :column => column}})
164
+ urls[:set_base] = url_for({:sortable_columns => {:action => :set_base, :model => model, :column => column}})
165
+ sortable[:urls] = OpenStruct.new(urls)
166
+
167
+ sortable
168
+ end
169
+
170
+ end
@@ -0,0 +1,11 @@
1
+ en:
2
+ acts_as_data_table:
3
+ scope_filters:
4
+ add_filter:
5
+ filter_not_registered: "The filter '%{scope_name}' in group '%{group}' was not properly registered in model '%{model}'"
6
+ non_matching_arity: "The filter '%{scope_name}' in group '%{group}' registered in model '%{model}' did not get the required amount of arguments"
7
+
8
+ validations:
9
+ general_error: "The scope filter could not be applied successfully"
10
+ invalid_date: "%{arg_name} ('%{arg_value}') is not a valid date"
11
+ invalid_record: "The filter argument '%{arg_name}' did not match any valid record in the system."
@@ -0,0 +1,13 @@
1
+ class ActsAsDataTableGenerator < Rails::Generator::NamedBase
2
+ def manifest
3
+ record do |m|
4
+ m.file File.join('assets', 'js', 'acts_as_data_table.js'), File.join('public', 'javascripts', 'acts_as_data_table.js')
5
+ end
6
+ end
7
+
8
+ protected
9
+
10
+ def banner
11
+ "Usage: #{$0} acts_as_data_table js"
12
+ end
13
+ end
@@ -0,0 +1,37 @@
1
+ jQuery(document).ready(function() {
2
+ jQuery(document).on("click", '[data-init=sortable-column]', function(e) {
3
+ var active, remote, url, urlSetBase, urlToggle;
4
+ e.preventDefault();
5
+ urlToggle = jQuery(this).data('urlToggle');
6
+ urlSetBase = jQuery(this).data('urlSetBase');
7
+ remote = jQuery(this).data('remote');
8
+ active = jQuery(this).data('active');
9
+ url = urlSetBase;
10
+ if (e.ctrlKey || e.metaKey) {
11
+ url = urlToggle;
12
+ }
13
+ if (remote) {
14
+ jQuery.ajax({
15
+ 'url': url,
16
+ dataType: 'script'
17
+ });
18
+ } else {
19
+ window.location.href = url;
20
+ }
21
+ return false;
22
+ });
23
+ return jQuery(document).on("click", "[data-init=sortable-column-direction]", function(e) {
24
+ var remote, url;
25
+ e.preventDefault();
26
+ url = jQuery(this).data('urlChangeDirection');
27
+ remote = jQuery(this).data('remote');
28
+ if (remote) {
29
+ return jQuery.ajax({
30
+ 'url': url,
31
+ dataType: 'script'
32
+ });
33
+ } else {
34
+ return window.location.href = url;
35
+ }
36
+ });
37
+ });
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + '/rails/init'
@@ -0,0 +1,116 @@
1
+ module Acts
2
+ module DataTable
3
+ module MultiColumnScopes
4
+ def self.included(base)
5
+ base.send :extend, ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ #
10
+ # Generates a scope to search for a given string in multiple
11
+ # columns at once. Columns may either be in the own table
12
+ # or in associated tables.
13
+ # It also automatically generates concat queries to enable
14
+ # full name searches, e.g. for a users table with
15
+ # separate columns for first and last name (see examples below)
16
+ #
17
+ # The result can be used further to chain the query like
18
+ # every other scope
19
+ #
20
+ # @param [String, Symbol] scope_name
21
+ # The newly generated scope's name, e.g. :full_text
22
+ #
23
+ # If the last argument is a Hash, it will be used as options.
24
+ #
25
+ # @option options [Boolean] :downcase (true)
26
+ # If set to +true+, the both searched query and
27
+ # database values will be converted to lowercase to support
28
+ # case insensitivity
29
+ #
30
+ # @example Basic usage, searching in two columns
31
+ # has_multi_column_scope :email_or_name, :email, :name
32
+ #
33
+ # @example Searching for a full name by concatenating two columns
34
+ # has_multi_column_scope :email_or_name, :email, [:first_name, :last_name]
35
+ #
36
+ # @example Including an association named :title (belongs_to :title)
37
+ # has_multi_column_scope :name_or_title, [:first_name, :last_name], {:title => :name}, {}
38
+ # #The empty has at the end is necessary as otherwise the title-hash would
39
+ # #be taken as options.
40
+ #
41
+ # The method does currently not support chained includes, this
42
+ # will be added in the future (e.g. :user_rooms => {:room => :number})
43
+ #
44
+ def has_multi_column_scope(scope_name, *args)
45
+ options = {:downcase => true}
46
+ options.merge!(args.pop) if args.size > 1 && args.last.is_a?(Hash)
47
+
48
+ include_chain = []
49
+
50
+ fields = args.map {|arg| Acts::DataTable::MultiColumnScopes.process_column_arg(arg, include_chain, self)}
51
+ fields = fields.flatten.map {|f| Acts::DataTable::MultiColumnScopes.database_cast(f)}
52
+ fields = fields.map {|f| "LOWER(#{f})"} if options[:downcase]
53
+
54
+ conditions = fields.map {|f| "#{f} LIKE ?"}.join(' OR ')
55
+ include_chain = include_chain.uniq.compact
56
+
57
+ named_scope scope_name, lambda {|search|
58
+ if search.present?
59
+ s = "%#{search}%"
60
+ s = s.downcase if options[:downcase]
61
+ { :include => include_chain, :conditions => [conditions] + Array.new(fields.size, s) }
62
+ else
63
+ {}
64
+ end
65
+ }
66
+ end
67
+ end
68
+
69
+ #
70
+ # Processes a single argument for has_multi_column_scope.
71
+ # Handles
72
+ # - simple values (e.g. :first_name),
73
+ # - arrays which will be concatenated with a space character (e.g. [:first_name, :last_name])
74
+ # - hashes which represent associations on the main model (e.g. :student => [:mat_num])
75
+ #
76
+ def self.process_column_arg(arg, includes, model = self)
77
+ if arg.is_a?(Hash)
78
+ res = []
79
+ arg.each do |association, columns|
80
+ includes << association
81
+ Array(columns).each do |column|
82
+ res << process_column_arg(column, includes, association.to_s.singularize.classify.constantize)
83
+ end
84
+ end
85
+ res
86
+ elsif arg.is_a?(Array)
87
+ columns = arg.map {|a| process_column_arg(a, includes, model)}
88
+ columns = columns.map {|c| "TRIM(#{c})"}
89
+ "CONCAT(#{columns.join(", ' ', ")})"
90
+ else
91
+ if model.column_names.include?(arg.to_s)
92
+ [model.table_name, arg.to_s].join('.')
93
+ else
94
+ raise ArgumentError.new "The table '#{model.table_name}' does not have a column named '#{arg}'"
95
+ end
96
+ end
97
+ end
98
+
99
+ #
100
+ # Performs a cast to text-like for various database types
101
+ #
102
+ def self.database_cast(content)
103
+ case ActiveRecord::Base.connection.adapter_name
104
+ when 'MySQL'
105
+ "CAST(#{content} AS CHAR(10000) CHARACTER SET utf8)"
106
+ else
107
+ "CAST(#{content} AS TEXT)"
108
+ end
109
+ end
110
+
111
+ def self.build_scope_name(*args)
112
+ args.join('_').to_sym
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,139 @@
1
+ module Acts
2
+ module DataTable
3
+ module ScopeFilters
4
+ module ActionController
5
+
6
+ def self.included(base)
7
+ base.send :extend, ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ #
12
+ # Adds scope filter support to this controller (or parts of it)
13
+ #
14
+ # @param [Hash] options
15
+ # Options to customize the behaviour
16
+ #
17
+ # @option options [Array<Symbol>] :only
18
+ # If given, filters are only applied to actions which are in the given array
19
+ #
20
+ # @option options [Array<Symbol>] :except
21
+ # If given, filters are only applied to actions which _not_ in the given array
22
+ #
23
+ # @option options [String, Symbol] :model
24
+ # The model name which will be used as filter base.
25
+ # If not given, the name is inferred from the controller name
26
+ #
27
+ def scope_filters(options = {})
28
+ #Include on-demand methods
29
+ include Acts::DataTable::Shared::ActionController::OnDemand
30
+
31
+ #Add helper methods to this controller's views
32
+ helper :acts_as_data_table
33
+
34
+ model_name = (options.delete(:model) || self.name.underscore.split('/').last.sub('_controller', '')).to_s.camelize.singularize
35
+
36
+ #Create a custom before filter
37
+ around_filter(options) do |controller, block|
38
+
39
+ sf_params = controller.request.params[:scope_filters]
40
+
41
+ begin
42
+
43
+ #Set current filters before adding/removing based on the current request
44
+ #This is needed for certain actions within the session
45
+ Acts::DataTable::ScopeFilters::ActionController.set_request_filters!(model_name, controller.acts_as_data_table_session.active_filters)
46
+
47
+ #Ensure that any scope filter related params are given and
48
+ #that the given action is valid (add a filter, remove a filter, remove all filters)
49
+ if sf_params.present? && %w(add remove reset).include?(sf_params[:action])
50
+ case sf_action = sf_params[:action].to_s
51
+ when 'add'
52
+ #Ensure that a group and a scope name are given
53
+ if [:group, :scope].all? { |p| sf_params[p].present? }
54
+ unless controller.acts_as_data_table_session.add_filter(sf_params[:group], sf_params[:scope], sf_params[:args])
55
+ #TODO: add error message if the validation failed or the filter was not available
56
+ end
57
+ end
58
+ when 'remove'
59
+ #Ensure that a group and a filter name are given
60
+ if sf_params[:group].present?
61
+ controller.acts_as_data_table_session.remove_filter!(sf_params[:group])
62
+ end
63
+ when 'reset'
64
+ controller.acts_as_data_table_session.remove_all_filters!
65
+ else
66
+ raise ArgumentError.new "Invalid scope filter action '#{sf_action}' was given."
67
+ end
68
+ end
69
+
70
+ #Set the updated filters
71
+ Acts::DataTable::ScopeFilters::ActionController.set_request_filters!(model_name, controller.acts_as_data_table_session.active_filters)
72
+ block.call
73
+ ensure
74
+ Acts::DataTable::ScopeFilters::ActionController.clear_request_filters!
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ #
81
+ # Returns the currently active scope filters
82
+ #
83
+ # This function should only be used when the automatic scope +with_scope_filters+
84
+ # is not working due to a different execution time or thread, e.g. a background worker.
85
+ #
86
+ def current_scope_filters
87
+ Acts::DataTable::ScopeFilters::ActionController.get_request_filters
88
+ end
89
+
90
+ #----------------------------------------------------------------
91
+ # Module Methods
92
+ #----------------------------------------------------------------
93
+
94
+ #
95
+ # Saves the current request's active filters to the thread space
96
+ #
97
+ def self.set_request_filters!(model, filters)
98
+ Acts::DataTable.ensure_nested_hash!(Thread.current, :scope_filters)
99
+
100
+ current_scopes = filters.inject({}) do |h, (group, scope)|
101
+ h[group] = [scope.keys.first, scope[scope.keys.first]]
102
+ h
103
+ end
104
+
105
+ Thread.current[:scope_filters] = {:model => model.to_s, :filters => current_scopes}
106
+ end
107
+
108
+ #
109
+ # Fetches the current request's filter data from the thread space
110
+ #
111
+ def self.get_request_data
112
+ Acts::DataTable.lookup_nested_hash(Thread.current, :scope_filters)
113
+ end
114
+
115
+ #
116
+ # Fetches the current request's active filters from the thread space
117
+ #
118
+ def self.get_request_filters
119
+ self.get_request_data[:filters]
120
+ end
121
+
122
+ #
123
+ # @return [ActiveRecord::Base] the model used for filtering in the current request
124
+ #
125
+ def self.get_request_model
126
+ model = self.get_request_data[:model]
127
+ model.camelize.constantize
128
+ end
129
+
130
+ #
131
+ # Clears all active filters from the thread space.
132
+ #
133
+ def self.clear_request_filters!
134
+ Thread.current[:scope_filters] = nil
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end