datagrid 1.8.1 → 2.0.0.pre.alpha

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -7
  3. data/{Readme.markdown → README.md} +46 -29
  4. data/app/assets/stylesheets/datagrid.css +145 -0
  5. data/app/views/datagrid/_enum_checkboxes.html.erb +5 -3
  6. data/app/views/datagrid/_form.html.erb +4 -5
  7. data/app/views/datagrid/_head.html.erb +26 -3
  8. data/app/views/datagrid/_range_filter.html.erb +5 -3
  9. data/app/views/datagrid/_row.html.erb +12 -1
  10. data/app/views/datagrid/_table.html.erb +4 -4
  11. data/datagrid.gemspec +8 -8
  12. data/lib/datagrid/active_model.rb +9 -17
  13. data/lib/datagrid/base.rb +39 -0
  14. data/lib/datagrid/column_names_attribute.rb +12 -12
  15. data/lib/datagrid/columns/column.rb +155 -133
  16. data/lib/datagrid/columns.rb +495 -282
  17. data/lib/datagrid/configuration.rb +23 -10
  18. data/lib/datagrid/core.rb +184 -150
  19. data/lib/datagrid/deprecated_object.rb +20 -0
  20. data/lib/datagrid/drivers/abstract_driver.rb +13 -25
  21. data/lib/datagrid/drivers/active_record.rb +24 -26
  22. data/lib/datagrid/drivers/array.rb +26 -17
  23. data/lib/datagrid/drivers/mongo_mapper.rb +15 -14
  24. data/lib/datagrid/drivers/mongoid.rb +16 -18
  25. data/lib/datagrid/drivers/sequel.rb +14 -19
  26. data/lib/datagrid/drivers.rb +2 -1
  27. data/lib/datagrid/engine.rb +11 -3
  28. data/lib/datagrid/filters/base_filter.rb +166 -142
  29. data/lib/datagrid/filters/boolean_filter.rb +19 -5
  30. data/lib/datagrid/filters/date_filter.rb +33 -35
  31. data/lib/datagrid/filters/date_time_filter.rb +24 -16
  32. data/lib/datagrid/filters/default_filter.rb +9 -3
  33. data/lib/datagrid/filters/dynamic_filter.rb +151 -105
  34. data/lib/datagrid/filters/enum_filter.rb +43 -19
  35. data/lib/datagrid/filters/extended_boolean_filter.rb +39 -27
  36. data/lib/datagrid/filters/float_filter.rb +16 -5
  37. data/lib/datagrid/filters/integer_filter.rb +21 -10
  38. data/lib/datagrid/filters/ranged_filter.rb +66 -45
  39. data/lib/datagrid/filters/select_options.rb +58 -49
  40. data/lib/datagrid/filters/string_filter.rb +9 -4
  41. data/lib/datagrid/filters.rb +234 -106
  42. data/lib/datagrid/form_builder.rb +116 -128
  43. data/lib/datagrid/generators/scaffold.rb +185 -0
  44. data/lib/datagrid/generators/views.rb +20 -0
  45. data/lib/datagrid/helper.rb +397 -22
  46. data/lib/datagrid/ordering.rb +81 -87
  47. data/lib/datagrid/rspec.rb +8 -12
  48. data/lib/datagrid/utils.rb +42 -38
  49. data/lib/datagrid/version.rb +3 -1
  50. data/lib/datagrid.rb +18 -28
  51. data/templates/base.rb.erb +33 -7
  52. data/templates/grid.rb.erb +1 -1
  53. metadata +18 -19
  54. data/app/assets/stylesheets/datagrid.sass +0 -134
  55. data/lib/datagrid/filters/composite_filters.rb +0 -49
  56. data/lib/datagrid/renderer.rb +0 -157
  57. data/lib/datagrid/scaffold.rb +0 -129
  58. data/lib/tasks/datagrid_tasks.rake +0 -15
  59. data/templates/controller.rb.erb +0 -6
  60. data/templates/index.html.erb +0 -5
@@ -1,132 +1,126 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "datagrid/columns"
2
4
 
3
5
  module Datagrid
4
6
  # Raised when grid order value is incorrect
5
7
  class OrderUnsupported < StandardError
6
8
  end
7
- module Ordering
8
9
 
10
+ # Module adds support for ordering by defined columns for Datagrid.
11
+ module Ordering
9
12
  # @!visibility private
10
13
  def self.included(base)
11
- base.extend ClassMethods
14
+ base.extend ClassMethods
12
15
  base.class_eval do
13
16
  include Datagrid::Columns
14
17
 
15
18
  datagrid_attribute :order do |value|
16
- if value.present?
17
- value.to_sym
18
- else
19
- nil
20
- end
21
-
19
+ value.to_sym if value.present?
22
20
  end
23
21
 
24
22
  datagrid_attribute :descending do |value|
25
23
  Datagrid::Utils.booleanize(value)
26
24
  end
27
- alias descending? descending
28
-
25
+ alias_method :descending?, :descending
29
26
  end
30
- base.include InstanceMethods
31
27
  end
32
28
 
33
29
  # @!visibility private
34
30
  module ClassMethods
35
31
  def order_unsupported(name, reason)
36
- raise Datagrid::OrderUnsupported, "Can not sort #{self.inspect} by ##{name}: #{reason}"
32
+ raise Datagrid::OrderUnsupported, "Can not sort #{inspect} by ##{name}: #{reason}"
37
33
  end
38
34
  end
39
35
 
40
- module InstanceMethods
36
+ # @!visibility private
37
+ def assets
38
+ check_order_valid!
39
+ apply_order(super)
40
+ end
41
41
 
42
- # @!visibility private
43
- def assets
44
- check_order_valid!
45
- apply_order(super)
46
- end
42
+ # @return [Datagrid::Columns::Column, nil] a column definition that is currently used to order assets
43
+ # @example
44
+ # class MyGrid
45
+ # scope { Model }
46
+ # column(:id)
47
+ # column(:name)
48
+ # end
49
+ # MyGrid.new(order: "name").order_column # => #<Column name: "name", ...>
50
+ def order_column
51
+ order ? column_by_name(order) : nil
52
+ end
47
53
 
48
- # @return [Datagrid::Columns::Column, nil] a column definition that is currently used to order assets
49
- # @example
50
- # class MyGrid
51
- # scope { Model }
52
- # column(:id)
53
- # column(:name)
54
- # end
55
- # MyGrid.new(order: "name").order_column # => #<Column name: "name", ...>
56
- def order_column
57
- order ? column_by_name(order) : nil
58
- end
54
+ # @param column [String, Datagrid::Columns::Column]
55
+ # @param desc [nil, Boolean] confirm order direction as well if specified
56
+ # @return [Boolean] true if given grid is ordered by given column.
57
+ def ordered_by?(column, desc = nil)
58
+ order_column == column_by_name(column) &&
59
+ (desc.nil? || (desc ? descending? : !descending?))
60
+ end
59
61
 
60
- # @param column [String, Datagrid::Columns::Column]
61
- # @return [Boolean] true if given grid is ordered by given column.
62
- def ordered_by?(column)
63
- order_column == column_by_name(column)
64
- end
62
+ private
65
63
 
66
- private
64
+ def apply_order(assets)
65
+ return assets unless order
67
66
 
68
- def apply_order(assets)
69
- return assets unless order
70
- if order_column.order_by_value?
71
- assets = assets.sort_by do |asset|
72
- order_column.order_by_value(asset, self)
73
- end
74
- descending? ? assets.reverse : assets
67
+ if order_column.order_by_value?
68
+ assets = assets.sort_by do |asset|
69
+ order_column.order_by_value(asset, self)
70
+ end
71
+ descending? ? assets.reverse : assets
72
+ elsif descending?
73
+ if order_column.order_desc
74
+ apply_asc_order(assets, order_column.order_desc)
75
75
  else
76
- if descending?
77
- if order_column.order_desc
78
- apply_asc_order(assets, order_column.order_desc)
79
- else
80
- apply_desc_order(assets, order_column.order)
81
- end
82
- else
83
- apply_asc_order(assets, order_column.order)
84
- end
76
+ apply_desc_order(assets, order_column.order)
85
77
  end
78
+ else
79
+ apply_asc_order(assets, order_column.order)
86
80
  end
81
+ end
87
82
 
88
- def check_order_valid!
89
- return unless order
90
- column = column_by_name(order)
91
- unless column
92
- self.class.order_unsupported(order, "no column #{order} in #{self.class}")
93
- end
94
- unless column.supports_order?
95
- self.class.order_unsupported(column.name, "column don't support order" )
96
- end
97
- end
83
+ def check_order_valid!
84
+ return unless order
98
85
 
99
- def apply_asc_order(assets, order)
100
- if order.respond_to?(:call)
101
- apply_block_order(assets, order)
102
- else
103
- driver.asc(assets, order)
104
- end
105
- end
86
+ column = column_by_name(order)
87
+ self.class.order_unsupported(order, "no column #{order} in #{self.class}") unless column
88
+ return if column.supports_order?
106
89
 
107
- def apply_desc_order(assets, order)
108
- if order.respond_to?(:call)
109
- reverse_order(apply_asc_order(assets, order))
110
- else
111
- driver.desc(assets, order)
112
- end
90
+ self.class.order_unsupported(column.name, "column don't support order")
91
+ end
92
+
93
+ def apply_asc_order(assets, order)
94
+ if order.respond_to?(:call)
95
+ apply_block_order(assets, order)
96
+ else
97
+ driver.asc(assets, order)
113
98
  end
99
+ end
114
100
 
115
- def reverse_order(assets)
116
- driver.reverse_order(assets)
117
- rescue NotImplementedError
118
- self.class.order_unsupported(order_column.name, "Your ORM do not support reverse order: please specify :order_desc option manually")
101
+ def apply_desc_order(assets, order)
102
+ if order.respond_to?(:call)
103
+ reverse_order(apply_asc_order(assets, order))
104
+ else
105
+ driver.desc(assets, order)
119
106
  end
107
+ end
120
108
 
121
- def apply_block_order(assets, order)
122
- case order.arity
123
- when -1, 0
124
- assets.instance_eval(&order)
125
- when 1
126
- order.call(assets)
127
- else
128
- self.class.order_unsupported(order_column.name, "Order option proc can not handle more than one argument")
129
- end
109
+ def reverse_order(assets)
110
+ driver.reverse_order(assets)
111
+ rescue NotImplementedError
112
+ self.class.order_unsupported(order_column.name,
113
+ "Your ORM do not support reverse order: please specify :order_desc option manually",)
114
+ end
115
+
116
+ def apply_block_order(assets, order)
117
+ case order.arity
118
+ when -1, 0
119
+ assets.instance_eval(&order)
120
+ when 1
121
+ order.call(assets)
122
+ else
123
+ self.class.order_unsupported(order_column.name, "Order option proc can not handle more than one argument")
130
124
  end
131
125
  end
132
126
  end
@@ -1,16 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "datagrid"
2
4
 
3
- #TODO: refactor this experimental shit
5
+ # TODO: refactor this experimental shit
4
6
  shared_examples_for "Datagrid" do
5
7
  describe "as Datagrid" do
6
-
7
8
  it "should have at least one entry if assets" do
8
9
  subject.assets.should_not be_empty
9
10
  end
10
11
 
11
- described_class.columns(:data => true).each do |column|
12
+ described_class.columns(data: true).each do |column|
12
13
  describe "column ##{column.name}" do
13
-
14
14
  it "should has value in #data_hash" do
15
15
  subject.data_hash.first.should have_key(column.name)
16
16
  end
@@ -25,14 +25,11 @@ shared_examples_for "Datagrid" do
25
25
  subject.assets.first.should_not be_nil
26
26
  end
27
27
  end
28
-
29
28
  end
30
29
 
31
30
  described_class.filters.each do |filter|
32
31
  describe "filter ##{filter.name}" do
33
-
34
32
  let(:filter_value) do
35
-
36
33
  case Datagrid::Filters::FILTER_TYPES.invert[filter.class]
37
34
  when :default, :string
38
35
  "text"
@@ -46,23 +43,22 @@ shared_examples_for "Datagrid" do
46
43
  1
47
44
  when :enum
48
45
  select = subject.select_options(filter)
49
- select.first.try(:last)
46
+ select.first&.last
50
47
  else
51
48
  raise "unknown filter type: #{filter.class}"
52
49
  end.to_s
53
50
  end
54
51
 
55
52
  before(:each) do
56
- subject.attributes = {filter.name => filter_value}
57
- subject.send(filter.name).should_not be_nil
53
+ subject.attributes = { filter.name => filter_value }
54
+ subject.public_send(filter.name).should_not be_nil
58
55
  end
59
56
 
60
57
  it "should be supported" do
61
58
  subject.assets.should_not be_nil
62
- #TODO: better matcher.
59
+ # TODO: better matcher.
63
60
  end
64
61
  end
65
62
  end
66
-
67
63
  end
68
64
  end
@@ -1,52 +1,54 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Datagrid
2
4
  # @!visibility private
3
- module Utils # :nodoc:
5
+ module Utils
4
6
  class << self
5
-
6
-
7
- TRUTH = [true, 1, "1", "true", "yes", "on"]
7
+ TRUTH = [true, 1, "1", "true", "yes", "on"].freeze
8
8
 
9
9
  def booleanize(value)
10
- if value.respond_to?(:downcase)
11
- value = value.downcase
12
- end
10
+ value = value.downcase if value.respond_to?(:downcase)
13
11
  TRUTH.include?(value)
14
12
  end
15
13
 
16
14
  def translate_from_namespace(namespace, grid_class, key)
17
-
18
15
  lookups = []
19
16
  namespaced_key = "#{namespace}.#{key}"
20
17
 
21
18
  grid_class.ancestors.each do |ancestor|
22
- if ancestor.respond_to?(:model_name)
23
- lookups << :"datagrid.#{ancestor.model_name.i18n_key}.#{namespaced_key}"
24
- end
19
+ lookups << :"datagrid.#{ancestor.model_name.i18n_key}.#{namespaced_key}" if ancestor.respond_to?(:model_name)
25
20
  end
26
21
  lookups << :"datagrid.defaults.#{namespaced_key}"
27
22
  lookups << key.to_s.humanize
28
23
  I18n.t(lookups.shift, default: lookups).presence
29
24
  end
30
25
 
26
+ def deprecator
27
+ if defined?(Rails) && Rails.version >= "7.1.0"
28
+ Rails.deprecator
29
+ else
30
+ ActiveSupport::Deprecation
31
+ end
32
+ end
33
+
31
34
  def warn_once(message, delay = 5)
32
35
  @warnings ||= {}
33
36
  timestamp = @warnings[message]
34
37
  return false if timestamp && timestamp >= Time.now - delay
35
- warn message
38
+
39
+ deprecator.warn(message)
36
40
  @warnings[message] = Time.now
37
41
  true
38
42
  end
39
43
 
40
44
  def add_html_classes(options, *classes)
45
+ return options if classes.empty?
46
+
41
47
  options = options.clone
42
- options[:class] ||= ""
43
- if options[:class].is_a?(Array)
44
- options[:class] += classes
45
- else
46
- # suppose that it is a String
47
- options[:class] += " " unless options[:class].blank?
48
- options[:class] += classes.join(" ")
49
- end
48
+ options[:class] ||= []
49
+ array = options[:class].is_a?(Array)
50
+ value = [*options[:class], *classes]
51
+ options[:class] = array ? value : value.join(" ")
50
52
  options
51
53
  end
52
54
 
@@ -55,41 +57,42 @@ module Datagrid
55
57
  end
56
58
 
57
59
  def extract_position_from_options(array, options)
58
- before, after = options[:before], options[:after]
59
- if before && after
60
- raise Datagrid::ConfigurationError, "Options :before and :after can not be used together"
61
- end
60
+ before = options[:before]
61
+ after = options[:after]
62
+ raise Datagrid::ConfigurationError, "Options :before and :after can not be used together" if before && after
62
63
  # Consider as before all
63
64
  return 0 if before == true
65
+
64
66
  if before
65
67
  before = before.to_sym
66
- array.index {|c| c.name.to_sym == before }
68
+ array.index { |c| c.name.to_sym == before }
67
69
  elsif after
68
70
  after = after.to_sym
69
- array.index {|c| c.name.to_sym == after } + 1
71
+ array.index { |c| c.name.to_sym == after } + 1
70
72
  else
71
73
  -1
72
74
  end
73
75
  end
74
76
 
75
77
  def apply_args(*args, &block)
76
- size = block.arity < 0 ? args.size : block.arity
78
+ size = block.arity.negative? ? args.size : block.arity
77
79
  block.call(*args.slice(0, size))
78
80
  end
79
81
 
80
82
  def parse_date(value)
81
83
  return nil if value.blank?
82
84
  return value if value.is_a?(Range)
85
+
83
86
  if value.is_a?(String)
84
87
  Array(Datagrid.configuration.date_formats).each do |format|
85
- begin
86
- return Date.strptime(value, format)
87
- rescue ::ArgumentError
88
- end
88
+ return Date.strptime(value, format)
89
+ rescue ::ArgumentError
90
+ nil
89
91
  end
90
92
  end
91
93
  return Date.parse(value) if value.is_a?(String)
92
94
  return value.to_date if value.respond_to?(:to_date)
95
+
93
96
  value
94
97
  rescue ::ArgumentError
95
98
  nil
@@ -98,16 +101,18 @@ module Datagrid
98
101
  def parse_datetime(value)
99
102
  return nil if value.blank?
100
103
  return value if value.is_a?(Range)
104
+ return value if defined?(ActiveSupport::TimeWithZone) && value.is_a?(ActiveSupport::TimeWithZone)
105
+
101
106
  if value.is_a?(String)
102
107
  Array(Datagrid.configuration.datetime_formats).each do |format|
103
- begin
104
- return Time.strptime(value, format)
105
- rescue ::ArgumentError
106
- end
108
+ return Time.strptime(value, format)
109
+ rescue ::ArgumentError
110
+ nil
107
111
  end
108
112
  end
109
113
  return Time.parse(value) if value.is_a?(String)
110
114
  return value.to_time if value.respond_to?(:to_time)
115
+
111
116
  value
112
117
  rescue ::ArgumentError
113
118
  nil
@@ -116,10 +121,8 @@ module Datagrid
116
121
  def format_date_as_timestamp(value)
117
122
  if !value
118
123
  value
119
- elsif value.is_a?(Array)
120
- [value.first.try(:beginning_of_day), value.last.try(:end_of_day)]
121
124
  elsif value.is_a?(Range)
122
- (value.first.beginning_of_day..value.last.end_of_day)
125
+ value.begin&.beginning_of_day..value.end&.end_of_day
123
126
  else
124
127
  value.beginning_of_day..value.end_of_day
125
128
  end
@@ -135,6 +138,7 @@ module Datagrid
135
138
  end
136
139
 
137
140
  protected
141
+
138
142
  def property_availability(grid, option, default)
139
143
  case option
140
144
  when nil
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Datagrid
2
- VERSION = "1.8.1"
4
+ VERSION = "2.0.0-alpha"
3
5
  end
data/lib/datagrid.rb CHANGED
@@ -1,49 +1,39 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "action_view"
2
4
  require "datagrid/configuration"
5
+ require "datagrid/engine"
3
6
 
7
+ # @main README.md
4
8
  module Datagrid
5
-
6
- extend ActiveSupport::Autoload
7
-
8
- autoload :Core
9
- autoload :ActiveModel
10
- autoload :Filters
11
- autoload :Columns
12
- autoload :ColumnNamesAttribute
13
- autoload :Ordering
14
- autoload :Configuration
15
-
16
- autoload :Helper
17
- ActionView::Base.send(:include, Datagrid::Helper)
18
-
19
- autoload :FormBuilder
20
- ActionView::Helpers::FormBuilder.send(:include, Datagrid::FormBuilder)
21
-
22
- autoload :Renderer
23
-
24
- autoload :Engine
25
-
26
9
  # @!visibility private
27
10
  def self.included(base)
11
+ Utils.warn_once("Including Datagrid is deprecated. Inherit Datagrid::Base instead.")
28
12
  base.class_eval do
29
-
30
13
  include ::Datagrid::Core
31
14
  include ::Datagrid::ActiveModel
32
15
  include ::Datagrid::Filters
33
16
  include ::Datagrid::Columns
34
17
  include ::Datagrid::ColumnNamesAttribute
35
18
  include ::Datagrid::Ordering
36
-
37
19
  end
38
20
  end
39
21
 
22
+ def self.configuration
23
+ @configuration ||= Configuration.new
24
+ end
25
+
26
+ # Configure
27
+ def self.configure(&block)
28
+ block.call(configuration)
29
+ end
30
+
40
31
  class ConfigurationError < StandardError; end
41
32
  class ArgumentError < ::ArgumentError; end
42
33
  class ColumnUnavailableError < StandardError; end
43
-
44
34
  end
45
35
 
46
- require "datagrid/scaffold"
47
- I18n.load_path << File.expand_path('../datagrid/locale/en.yml', __FILE__)
48
-
49
-
36
+ require "datagrid/base"
37
+ require "datagrid/generators/scaffold"
38
+ require "datagrid/generators/views"
39
+ I18n.load_path << File.expand_path("datagrid/locale/en.yml", __dir__)
@@ -1,7 +1,4 @@
1
- class BaseGrid
2
-
3
- include Datagrid
4
-
1
+ class ApplicationGrid < Datagrid::Base
5
2
  self.default_column_options = {
6
3
  # Uncomment to disable the default order
7
4
  # order: false,
@@ -11,11 +8,40 @@ class BaseGrid
11
8
  # Enable forbidden attributes protection
12
9
  # self.forbidden_attributes_protection = true
13
10
 
14
- def self.date_column(name, *args)
11
+ # Makes a date column
12
+ # @param name [Symbol] Column name
13
+ # @param args [Array] Other column helper arguments
14
+ # @example
15
+ # date_column(:created_at)
16
+ # date_column(:owner_registered_at) do |model|
17
+ # model.owner.registered_at
18
+ # end
19
+ def self.date_column(name, *args, &block)
15
20
  column(name, *args) do |model|
16
- format(block_given? ? yield : model.send(name)) do |date|
17
- date.strftime("%m/%d/%Y")
21
+ format(block ? block.call(model) : model.public_send(name)) do |date|
22
+ date&.strftime("%m/%d/%Y") || "&mdash;".html_safe
18
23
  end
19
24
  end
20
25
  end
26
+
27
+ # Makes a boolean YES/NO column
28
+ # @param name [Symbol] Column name
29
+ # @param args [Array] Other column helper arguments
30
+ # @example
31
+ # boolean_column(:approved)
32
+ # boolean_column(:has_tasks, preload: :tasks) do |model|
33
+ # model.tasks.unfinished.any?
34
+ # end
35
+ def self.boolean_column(name, *args, &block)
36
+ column(name, *args) do |model|
37
+ value = block ? block.call(model) : model.public_send(name)
38
+ value ? "Yes" : "No"
39
+ end
40
+ end
41
+
42
+ # Uncomment to shorten URL query string for all grids.
43
+ # May cause collisions if multiple grids are used on the same page.
44
+ # def param_name
45
+ # 'grid'
46
+ # end
21
47
  end
@@ -1,4 +1,4 @@
1
- class <%= grid_class_name %> < BaseGrid
1
+ class <%= grid_class_name %> < <%= grid_base_class %>
2
2
 
3
3
  scope do
4
4
  <%= grid_model_name %>