acts_as_data_table 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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