datagrid 1.6.3 → 1.7.0

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.
data/lib/datagrid/core.rb CHANGED
@@ -4,6 +4,7 @@ require "active_support/core_ext/class/attribute"
4
4
  module Datagrid
5
5
  module Core
6
6
 
7
+ # @!visibility private
7
8
  def self.included(base)
8
9
  base.extend ClassMethods
9
10
  base.class_eval do
@@ -12,7 +13,7 @@ module Datagrid
12
13
  class_attribute :datagrid_attributes, instance_writer: false
13
14
  self.datagrid_attributes = []
14
15
 
15
- class_attribute :dynamic_block, :instance_writer => false
16
+ class_attribute :dynamic_block, instance_writer: false
16
17
  class_attribute :forbidden_attributes_protection, instance_writer: false
17
18
  self.forbidden_attributes_protection = false
18
19
  if defined?(::ActiveModel::AttributeAssignment)
@@ -20,11 +21,12 @@ module Datagrid
20
21
  end
21
22
  end
22
23
  base.send :include, InstanceMethods
23
- end # self.included
24
+ end
24
25
 
25
26
  module ClassMethods
26
27
 
27
- def datagrid_attribute(name, &block) #:nodoc:
28
+ # @!visibility private
29
+ def datagrid_attribute(name, &block)
28
30
  unless datagrid_attributes.include?(name)
29
31
  block ||= lambda do |value|
30
32
  value
@@ -41,6 +43,11 @@ module Datagrid
41
43
  end
42
44
 
43
45
  # Defines a scope at class level
46
+ # @return [void]
47
+ # @example
48
+ # scope { User }
49
+ # scope { Project.where(deleted: false) }
50
+ # scope { Project.preload(:stages) }
44
51
  def scope(&block)
45
52
  if block
46
53
  current_scope = scope_value
@@ -54,12 +61,17 @@ module Datagrid
54
61
  end
55
62
  end
56
63
 
57
- def driver #:nodoc:
64
+ # @!visibility private
65
+ def driver
58
66
  @driver ||= Drivers::AbstractDriver.guess_driver(scope).new
59
67
  end
60
68
 
61
69
  # Allows dynamic columns definition, that could not be defined at class level
62
- #
70
+ # Columns that depend on the database state or third party service
71
+ # can be defined this way.
72
+ # @param block [Proc] block that defines dynamic columns
73
+ # @return [void]
74
+ # @example
63
75
  # class MerchantsGrid
64
76
  #
65
77
  # scope { Merchant }
@@ -69,12 +81,15 @@ module Datagrid
69
81
  # dynamic do
70
82
  # PurchaseCategory.all.each do |category|
71
83
  # column(:"#{category.name.underscore}_sales") do |merchant|
72
- # merchant.purchases.where(:category_id => category.id).count
84
+ # merchant.purchases.where(category_id: category.id).count
73
85
  # end
74
86
  # end
75
87
  # end
76
88
  # end
77
89
  #
90
+ # ProductCategory.create!(name: 'Swimwear')
91
+ # ProductCategory.create!(name: 'Sportswear')
92
+ #
78
93
  # grid = MerchantsGrid.new
79
94
  # grid.data # => [
80
95
  # # [ "Name", "Swimwear Sales", "Sportswear Sales", ... ]
@@ -95,6 +110,7 @@ module Datagrid
95
110
  end
96
111
 
97
112
  protected
113
+
98
114
  def check_scope_defined!(message = nil)
99
115
  message ||= "#{self}.scope is not defined"
100
116
  raise(Datagrid::ConfigurationError, message) unless scope_value
@@ -105,7 +121,7 @@ module Datagrid
105
121
  child_class.datagrid_attributes = self.datagrid_attributes.clone
106
122
  end
107
123
 
108
- end # ClassMethods
124
+ end
109
125
 
110
126
  module InstanceMethods
111
127
 
@@ -122,8 +138,7 @@ module Datagrid
122
138
  end
123
139
  end
124
140
 
125
- # Returns a hash of grid attributes including filter values
126
- # and ordering values
141
+ # @return [Hash<Symbol, Object>] grid attributes including filter values and ordering values
127
142
  def attributes
128
143
  result = {}
129
144
  self.datagrid_attributes.each do |name|
@@ -132,21 +147,24 @@ module Datagrid
132
147
  result
133
148
  end
134
149
 
135
- # Alias for <tt>send</tt> method
150
+ # @return [Object] Any datagrid attribute value
136
151
  def [](attribute)
137
152
  self.send(attribute)
138
153
  end
139
154
 
155
+ # Assigns any datagrid attribute
156
+ # @param attribute [Symbol, String] Datagrid attribute name
157
+ # @param value [Object] Datagrid attribute value
158
+ # @return [void]
140
159
  def []=(attribute, value)
141
160
  self.send(:"#{attribute}=", value)
142
161
  end
143
162
 
144
- # Returns a scope(e.g ActiveRecord::Relation) with all applied filters
163
+ # @return [Object] a scope relation (e.g ActiveRecord::Relation) with all applied filters
145
164
  def assets
146
165
  driver.to_scope(scope)
147
166
  end
148
167
 
149
-
150
168
  # Updates datagrid attributes with a passed hash argument
151
169
  def attributes=(attributes)
152
170
  if respond_to?(:assign_attributes)
@@ -163,7 +181,7 @@ module Datagrid
163
181
  end
164
182
 
165
183
  # Returns serializable query arguments skipping all nil values
166
- #
184
+ # @example
167
185
  # grid = ProductsGrid.new(category: 'dresses', available: true)
168
186
  # grid.as_query # => {category: 'dresses', available: true}
169
187
  def as_query
@@ -174,8 +192,8 @@ module Datagrid
174
192
  attributes
175
193
  end
176
194
 
177
- # Returns query parameters to link this grid from a page
178
- #
195
+ # @return [Hash<Symbol, Hash<Symbol, Object>>] query parameters to link this grid from a page
196
+ # @example
179
197
  # grid = ProductsGrid.new(category: 'dresses', available: true)
180
198
  # Rails.application.routes.url_helpers.products_path(grid.query_params)
181
199
  # # => "/products?products_grid[category]=dresses&products_grid[available]=true"
@@ -184,19 +202,18 @@ module Datagrid
184
202
  end
185
203
 
186
204
  # Redefines scope at instance level
187
- #
205
+ # @example
188
206
  # class MyGrid
189
207
  # scope { Article.order('created_at desc') }
190
208
  # end
191
209
  #
192
210
  # grid = MyGrid.new
193
211
  # grid.scope do |scope|
194
- # scope.where(:author_id => current_user.id)
212
+ # scope.where(author_id: current_user.id)
195
213
  # end
196
214
  # grid.assets
197
215
  # # => SELECT * FROM articles WHERE author_id = ?
198
216
  # # ORDER BY created_at desc
199
- #
200
217
  def scope(&block)
201
218
  if block_given?
202
219
  current_scope = scope
@@ -211,23 +228,27 @@ module Datagrid
211
228
  end
212
229
 
213
230
  # Resets current instance scope to default scope defined in a class
231
+ # @return [void]
214
232
  def reset_scope
215
233
  self.scope_value = self.class.scope_value
216
234
  end
217
235
 
218
- # Returns true if the scope was redefined for this instance of grid object
236
+ # @return [Boolean] true if the scope was redefined for this instance of grid object
219
237
  def redefined_scope?
220
238
  self.class.scope_value != scope_value
221
239
  end
222
240
 
223
- def driver #:nodoc:
241
+ # @!visibility private
242
+ def driver
224
243
  self.class.driver
225
244
  end
226
245
 
227
- def check_scope_defined!(message = nil) #:nodoc:
246
+ # @!visibility private
247
+ def check_scope_defined!(message = nil)
228
248
  self.class.send :check_scope_defined!, message
229
249
  end
230
250
 
251
+ # @return [String] a datagrid attributes and their values in inspection form
231
252
  def inspect
232
253
  attrs = attributes.map do |key, value|
233
254
  "#{key}: #{value.inspect}"
@@ -240,6 +261,6 @@ module Datagrid
240
261
  attributes == other.attributes &&
241
262
  scope == other.scope
242
263
  end
243
- end # InstanceMethods
264
+ end
244
265
  end
245
266
  end
@@ -1,6 +1,7 @@
1
1
  module Datagrid
2
2
  module Drivers
3
- class AbstractDriver #:nodoc:
3
+ # @!visibility private
4
+ class AbstractDriver
4
5
 
5
6
  TIMESTAMP_CLASSES = [DateTime, Time, ActiveSupport::TimeWithZone]
6
7
 
@@ -1,6 +1,7 @@
1
1
  module Datagrid
2
2
  module Drivers
3
- class ActiveRecord < AbstractDriver #:nodoc:
3
+ # @!visibility private
4
+ class ActiveRecord < AbstractDriver
4
5
 
5
6
  def self.match?(scope)
6
7
  return false unless defined?(::ActiveRecord)
@@ -1,6 +1,7 @@
1
1
  module Datagrid
2
2
  module Drivers
3
- class Array < AbstractDriver #:nodoc:
3
+ # @!visibility private
4
+ class Array < AbstractDriver
4
5
 
5
6
  def self.match?(scope)
6
7
  !Datagrid::Drivers::ActiveRecord.match?(scope) && (scope.is_a?(::Array) || scope.is_a?(Enumerator))
@@ -1,6 +1,7 @@
1
1
  module Datagrid
2
2
  module Drivers
3
- class MongoMapper < AbstractDriver #:nodoc:
3
+ # @!visibility private
4
+ class MongoMapper < AbstractDriver
4
5
 
5
6
  def self.match?(scope)
6
7
  return false unless defined?(::MongoMapper)
@@ -1,6 +1,7 @@
1
1
  module Datagrid
2
2
  module Drivers
3
- class Mongoid < AbstractDriver #:nodoc:
3
+ # @!visibility private
4
+ class Mongoid < AbstractDriver
4
5
 
5
6
  def self.match?(scope)
6
7
  return false unless defined?(::Mongoid)
@@ -63,7 +64,7 @@ module Datagrid
63
64
  return nil unless type
64
65
  {
65
66
  [BigDecimal , String, Symbol, Range, Array, Hash, ] => :string,
66
- [Boolean] => :boolean,
67
+ [::Mongoid::Boolean] => :boolean,
67
68
 
68
69
  [Date] => :date,
69
70
 
@@ -1,6 +1,7 @@
1
1
  module Datagrid
2
2
  module Drivers
3
- class Sequel < AbstractDriver #:nodoc:
3
+ # @!visibility private
4
+ class Sequel < AbstractDriver
4
5
 
5
6
  def self.match?(scope)
6
7
  return false unless defined?(::Sequel)
@@ -7,6 +7,7 @@ require "datagrid/drivers/sequel"
7
7
 
8
8
 
9
9
  module Datagrid
10
- module Drivers # :nodoc:
10
+ # @!visibility private
11
+ module Drivers
11
12
  end
12
13
  end
@@ -1,12 +1,13 @@
1
1
  require "rails/engine"
2
2
 
3
3
  module Datagrid
4
+ # @!private
4
5
  class Engine < ::Rails::Engine
5
6
  initializer "datagrid.helpers" do
6
7
  #TODO: check why it doesn't work
7
8
  ActiveSupport.on_load :action_view do
8
9
  include Datagrid::Helper
9
- end
10
- end
10
+ end
11
+ end
11
12
  end
12
13
  end
@@ -1,7 +1,10 @@
1
+ # An error raise when datagrid filter is defined incorrectly and
2
+ # causes filtering chain to be broken
1
3
  class Datagrid::FilteringError < StandardError
2
4
  end
3
5
 
4
- class Datagrid::Filters::BaseFilter #:nodoc:
6
+ # @!visibility private
7
+ class Datagrid::Filters::BaseFilter
5
8
 
6
9
  attr_accessor :grid_class, :options, :block, :name
7
10
 
@@ -26,7 +29,7 @@ class Datagrid::Filters::BaseFilter #:nodoc:
26
29
  result = execute(value, scope, grid_object)
27
30
 
28
31
  return scope unless result
29
- if result.is_a?(Datagrid::Filters::DefaultFilterScope)
32
+ if result == Datagrid::Filters::DEFAULT_FILTER_BLOCK
30
33
  result = default_filter(value, scope, grid_object)
31
34
  end
32
35
  unless grid_object.driver.match?(result)
@@ -1,17 +1,19 @@
1
- class Datagrid::Filters::BooleanEnumFilter < Datagrid::Filters::EnumFilter #:nodoc:
1
+ # @!visibility private
2
+ class Datagrid::Filters::BooleanEnumFilter < Datagrid::Filters::EnumFilter
2
3
 
3
4
  YES = "YES"
4
5
  NO = "NO"
5
6
 
6
7
  def initialize(report, attribute, options = {}, &block)
8
+ Datagrid::Utils.warn_once("Datagrid eboolean filter is deprecated in favor of xboolean filter")
7
9
  options[:select] = [YES, NO].map do |key, value|
8
- [I18n.t("datagrid.filters.eboolean.#{key.downcase}", :default => key.downcase.capitalize), key]
10
+ [I18n.t("datagrid.filters.eboolean.#{key.downcase}", default: key.downcase.capitalize), key]
9
11
  end
10
12
  super(report, attribute, options, &block)
11
13
  end
12
14
 
13
15
 
14
16
  def checkbox_id(value)
15
- [object_name, name, value].join('_').underscore
17
+ [object_name, name, value].join('_').underscore
16
18
  end
17
19
  end
@@ -1,5 +1,6 @@
1
1
  require "datagrid/utils"
2
- class Datagrid::Filters::BooleanFilter < Datagrid::Filters::BaseFilter #:nodoc:
2
+ # @!visibility private
3
+ class Datagrid::Filters::BooleanFilter < Datagrid::Filters::BaseFilter
3
4
 
4
5
  def parse(value)
5
6
  Datagrid::Utils.booleanize(value)
@@ -1,15 +1,15 @@
1
1
  module Datagrid
2
2
  module Filters
3
- module CompositeFilters #:nodoc:
3
+ # @!visibility private
4
+ module CompositeFilters
4
5
 
5
6
  def self.included(base)
6
7
  base.extend ClassMethods
7
8
  base.class_eval do
8
-
9
9
  end
10
- base.send :include, InstanceMethods
11
- end # self.included
10
+ end
12
11
 
12
+ # @!visibility private
13
13
  module ClassMethods
14
14
 
15
15
  def date_range_filters(field, from_options = {}, to_options = {})
@@ -41,13 +41,7 @@ module Datagrid
41
41
  end
42
42
  options
43
43
  end
44
- end # ClassMethods
45
-
46
- module InstanceMethods
47
-
48
-
49
- end # InstanceMethods
50
-
44
+ end
51
45
  end
52
46
  end
53
47
  end
@@ -1,4 +1,5 @@
1
- class Datagrid::Filters::ExtendedBooleanFilter < Datagrid::Filters::EnumFilter #:nodoc:
1
+ # @!visibility private
2
+ class Datagrid::Filters::ExtendedBooleanFilter < Datagrid::Filters::EnumFilter
2
3
 
3
4
  YES = "YES"
4
5
  NO = "NO"
@@ -1,5 +1,4 @@
1
1
  module Datagrid::Filters::SelectOptions
2
-
3
2
  def select(object)
4
3
  select = self.options[:select]
5
4
  if select.is_a?(Symbol)
@@ -11,6 +10,18 @@ module Datagrid::Filters::SelectOptions
11
10
  end
12
11
  end
13
12
 
13
+ def select_values(object)
14
+ options = select(object)
15
+ groups_used = grouped_choices?(options)
16
+ options.map do |option|
17
+ if groups_used
18
+ option[1].map {|o| option_text_and_value(o)}
19
+ else
20
+ option_text_and_value(option)
21
+ end
22
+ end.map(&:last)
23
+ end
24
+
14
25
  def include_blank
15
26
  unless prompt
16
27
  options.has_key?(:include_blank) ?
@@ -21,4 +32,26 @@ module Datagrid::Filters::SelectOptions
21
32
  def prompt
22
33
  options.has_key?(:prompt) ? Datagrid::Utils.callable(options[:prompt]) : false
23
34
  end
35
+
36
+ protected
37
+
38
+ # Rails built-in method:
39
+ # https://github.com/rails/rails/blob/94e80269e36caf7b2d22a7ab68e6898d3a824122/actionview/lib/action_view/helpers/form_options_helper.rb#L789
40
+ # Code reuse is difficult, so it is easier to duplicate it
41
+ def option_text_and_value(option)
42
+ # Options are [text, value] pairs or strings used for both.
43
+ if !option.is_a?(String) && option.respond_to?(:first) && option.respond_to?(:last)
44
+ option = option.reject { |e| Hash === e } if Array === option
45
+ [option.first, option.last]
46
+ else
47
+ [option, option]
48
+ end
49
+ end
50
+
51
+ # Rails built-in method:
52
+ # https://github.com/rails/rails/blob/f95c0b7e96eb36bc3efc0c5beffbb9e84ea664e4/actionview/lib/action_view/helpers/tags/select.rb#L36
53
+ # Code reuse is difficult, so it is easier to duplicate it
54
+ def grouped_choices?(choices)
55
+ !choices.blank? && choices.first.respond_to?(:last) && Array === choices.first.last
56
+ end
24
57
  end
@@ -31,10 +31,10 @@ module Datagrid
31
31
  :dynamic => Filters::DynamicFilter
32
32
  }
33
33
 
34
- class DefaultFilterScope
35
- end
34
+ DEFAULT_FILTER_BLOCK = Object.new
36
35
 
37
- def self.included(base) #:nodoc:
36
+ # @!visibility private
37
+ def self.included(base)
38
38
  base.extend ClassMethods
39
39
  base.class_eval do
40
40
 
@@ -45,13 +45,18 @@ module Datagrid
45
45
 
46
46
  end
47
47
  base.send :include, InstanceMethods
48
- end # self.included
48
+ end
49
49
 
50
50
  module ClassMethods
51
51
 
52
52
  # Returns filter definition object by name
53
53
  def filter_by_name(attribute)
54
- return attribute if attribute.is_a?(Datagrid::Filters::BaseFilter)
54
+ if attribute.is_a?(Datagrid::Filters::BaseFilter)
55
+ unless ancestors.include?(attribute.grid_class)
56
+ raise "#{attribute.grid_class}##{attribute.name} filter doen't belong to #{self.class}"
57
+ end
58
+ return attribute
59
+ end
55
60
  self.filters.find do |filter|
56
61
  filter.name == attribute.to_sym
57
62
  end
@@ -108,7 +113,7 @@ module Datagrid
108
113
  end
109
114
 
110
115
  def default_filter
111
- DefaultFilterScope.new
116
+ DEFAULT_FILTER_BLOCK
112
117
  end
113
118
 
114
119
  def inspect
@@ -132,11 +137,12 @@ module Datagrid
132
137
  "#{filter.name}: #{filter.type}"
133
138
  end.join(", ")
134
139
  end
135
- end # ClassMethods
140
+ end
136
141
 
137
142
  module InstanceMethods
138
143
 
139
- def initialize(*args, &block) # :nodoc:
144
+ # @!visibility private
145
+ def initialize(*args, &block)
140
146
  self.filters_array = self.class.filters_array.clone
141
147
  self.filters_array.each do |filter|
142
148
  self[filter.name] = filter.default(self)
@@ -144,7 +150,8 @@ module Datagrid
144
150
  super(*args, &block)
145
151
  end
146
152
 
147
- def assets # :nodoc:
153
+ # @!visibility private
154
+ def assets
148
155
  apply_filters(super, filters)
149
156
  end
150
157
 
@@ -178,11 +185,18 @@ module Datagrid
178
185
  # Returns select options for specific filter or filter name
179
186
  # If given filter doesn't support select options raises `ArgumentError`
180
187
  def select_options(filter)
181
- filter = filter_by_name(filter)
182
- unless filter.class.included_modules.include?(::Datagrid::Filters::SelectOptions)
183
- raise ::Datagrid::ArgumentError, "#{filter.name} with type #{FILTER_TYPES.invert[filter.class].inspect} can not have select options"
184
- end
185
- filter.select(self)
188
+ find_select_filter(filter).select(self)
189
+ end
190
+
191
+ # Sets all options as selected for a filter that has options
192
+ def select_all(filter)
193
+ filter = find_select_filter(filter)
194
+ self[filter.name] = select_values(filter)
195
+ end
196
+
197
+ # Returns all values that can be set to a filter with select options
198
+ def select_values(filter)
199
+ find_select_filter(filter).select_values(self)
186
200
  end
187
201
 
188
202
  def default_filter
@@ -198,12 +212,21 @@ module Datagrid
198
212
 
199
213
  protected
200
214
 
215
+ def find_select_filter(filter)
216
+ filter = filter_by_name(filter)
217
+ unless filter.class.included_modules.include?(::Datagrid::Filters::SelectOptions)
218
+ raise ::Datagrid::ArgumentError,
219
+ "#{self.class.name}##{filter.name} with type #{FILTER_TYPES.invert[filter.class].inspect} can not have select options"
220
+ end
221
+ filter
222
+ end
223
+
201
224
  def apply_filters(current_scope, filters)
202
225
  filters.inject(current_scope) do |result, filter|
203
226
  filter.apply(self, result, filter_value(filter))
204
227
  end
205
228
  end
206
- end # InstanceMethods
229
+ end
207
230
 
208
231
  end
209
232
  end
@@ -2,8 +2,12 @@ require "action_view"
2
2
 
3
3
  module Datagrid
4
4
  module FormBuilder
5
-
6
- # Returns a form input html for the corresponding filter name
5
+ # @param filter_or_attribute [Datagrid::Filters::BaseFilter, String, Symbol] filter object or filter name
6
+ # @param options [Hash] options of rails form input helper
7
+ # @return [String] a form input html for the corresponding filter name
8
+ # * <tt>select</tt> for enum, xboolean filter types
9
+ # * <tt>check_box</tt> for boolean filter type
10
+ # * <tt>text_field</tt> for other filter types
7
11
  def datagrid_filter(filter_or_attribute, options = {}, &block)
8
12
  filter = datagrid_get_filter(filter_or_attribute)
9
13
  options = {
@@ -15,7 +19,10 @@ module Datagrid
15
19
  self.send(filter.form_builder_helper_name, filter, options, &block)
16
20
  end
17
21
 
18
- # Returns a form label html for the corresponding filter name
22
+ # @param filter_or_attribute [Datagrid::Filters::BaseFilter, String, Symbol] filter object or filter name
23
+ # @param text [String, nil] label text, defaults to <tt>filter.header</tt>
24
+ # @param options [Hash] options of rails <tt>label</tt> helper
25
+ # @return [String] a form label html for the corresponding filter name
19
26
  def datagrid_label(filter_or_attribute, text = nil, **options, &block)
20
27
  filter = datagrid_get_filter(filter_or_attribute)
21
28
  label(filter.name, text || filter.header, **filter.label_options, **options, &block)