datagrid 1.8.1 → 2.0.0.pre.alpha

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