datagrid 0.0.3
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/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +26 -0
- data/Gemfile.lock +111 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +49 -0
- data/Readme.md +88 -0
- data/VERSION +1 -0
- data/datagrid.gemspec +106 -0
- data/lib/datagrid.rb +37 -0
- data/lib/datagrid/columns.rb +105 -0
- data/lib/datagrid/columns/column.rb +46 -0
- data/lib/datagrid/core.rb +99 -0
- data/lib/datagrid/filters.rb +150 -0
- data/lib/datagrid/filters/base_filter.rb +33 -0
- data/lib/datagrid/filters/boolean_enum_filter.rb +21 -0
- data/lib/datagrid/filters/boolean_filter.rb +7 -0
- data/lib/datagrid/filters/composite_filters.rb +41 -0
- data/lib/datagrid/filters/date_filter.rb +10 -0
- data/lib/datagrid/filters/default_filter.rb +5 -0
- data/lib/datagrid/filters/enum_filter.rb +32 -0
- data/lib/datagrid/filters/filter_eval.rb +25 -0
- data/lib/datagrid/filters/integer_filter.rb +7 -0
- data/lib/datagrid/form_builder.rb +56 -0
- data/lib/datagrid/helper.rb +80 -0
- data/lib/datagrid/rspec.rb +68 -0
- data/spec/datagrid/form_builder_spec.rb +68 -0
- data/spec/datagrid/helper_spec.rb +46 -0
- data/spec/datagrid_spec.rb +56 -0
- data/spec/spec_helper.rb +39 -0
- data/spec/support/equal_to_dom.rb +42 -0
- data/spec/support/schema.rb +33 -0
- data/spec/support/simple_report.rb +28 -0
- metadata +278 -0
@@ -0,0 +1,105 @@
|
|
1
|
+
module Datagrid
|
2
|
+
|
3
|
+
class OrderUnsupported < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
module Columns
|
7
|
+
require "datagrid/columns/column"
|
8
|
+
|
9
|
+
def self.included(base)
|
10
|
+
base.extend ClassMethods
|
11
|
+
base.class_eval do
|
12
|
+
|
13
|
+
include Datagrid::Core
|
14
|
+
|
15
|
+
datagrid_attribute :order
|
16
|
+
datagrid_attribute :reverse
|
17
|
+
|
18
|
+
|
19
|
+
end
|
20
|
+
base.send :include, InstanceMethods
|
21
|
+
end # self.included
|
22
|
+
|
23
|
+
module ClassMethods
|
24
|
+
|
25
|
+
def columns
|
26
|
+
@columns ||= []
|
27
|
+
end
|
28
|
+
|
29
|
+
def column(name, options = {}, &block)
|
30
|
+
block ||= lambda do
|
31
|
+
self.send(name)
|
32
|
+
end
|
33
|
+
self.columns << Datagrid::Columns::Column.new(self, name, options, &block)
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
end # ClassMethods
|
38
|
+
|
39
|
+
module InstanceMethods
|
40
|
+
|
41
|
+
def header
|
42
|
+
self.class.columns.map(&:header)
|
43
|
+
end
|
44
|
+
|
45
|
+
def row_for(asset)
|
46
|
+
self.class.columns.map do |column|
|
47
|
+
column.value(asset)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def hash_for(asset)
|
52
|
+
result = {}
|
53
|
+
self.class.columns.each do |column|
|
54
|
+
result[column.name] = column.value(asset)
|
55
|
+
end
|
56
|
+
result
|
57
|
+
end
|
58
|
+
|
59
|
+
def data
|
60
|
+
self.assets.map do |asset|
|
61
|
+
self.row_for(asset)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def data_hash
|
66
|
+
self.assets.map do |asset|
|
67
|
+
hash_for(asset)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def assets
|
72
|
+
result = super
|
73
|
+
if self.order
|
74
|
+
column = column_by_name(self.order)
|
75
|
+
raise Datagrid::OrderUnsupported, "Can not sort #{self.inspect} by #{name.inspect}" unless column
|
76
|
+
result = result.order(self.reverse ? column.order : column.desc_order)
|
77
|
+
end
|
78
|
+
result
|
79
|
+
end
|
80
|
+
|
81
|
+
def to_csv(options = {})
|
82
|
+
require "fastercsv"
|
83
|
+
FasterCSV.generate(
|
84
|
+
{:headers => self.header, :write_headers => true}.merge(options)
|
85
|
+
) do |csv|
|
86
|
+
self.data.each do |row|
|
87
|
+
csv << row
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def columns
|
93
|
+
self.class.columns
|
94
|
+
end
|
95
|
+
|
96
|
+
def column_by_name(name)
|
97
|
+
self.columns.find do |col|
|
98
|
+
col.name.to_sym == name.to_sym
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
end # InstanceMethods
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
|
2
|
+
class Datagrid::Columns::Column
|
3
|
+
|
4
|
+
attr_accessor :grid, :options, :block, :name
|
5
|
+
|
6
|
+
def initialize(grid, name, options = {}, &block)
|
7
|
+
self.grid = grid
|
8
|
+
self.name = name
|
9
|
+
self.options = options
|
10
|
+
self.block = block
|
11
|
+
end
|
12
|
+
|
13
|
+
def value(object)
|
14
|
+
value_for(object)
|
15
|
+
end
|
16
|
+
|
17
|
+
def value_for(object)
|
18
|
+
object.instance_eval(&self.block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def format
|
22
|
+
self.options[:format]
|
23
|
+
end
|
24
|
+
|
25
|
+
def label
|
26
|
+
self.options[:label]
|
27
|
+
end
|
28
|
+
|
29
|
+
def header
|
30
|
+
self.options[:header] ||
|
31
|
+
I18n.translate(self.name, :scope => "reports.#{self.grid}.columns", :default => self.name.to_s.humanize )
|
32
|
+
end
|
33
|
+
|
34
|
+
def order
|
35
|
+
if options.has_key?(:order)
|
36
|
+
self.options[:order]
|
37
|
+
else
|
38
|
+
grid.scope.column_names.include?(name.to_s) ? [grid.scope.table_name, name].join(".") : nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def desc_order
|
43
|
+
order ? order.to_s + " DESC" : nil
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Datagrid
|
2
|
+
module Core
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
|
+
base.class_eval do
|
7
|
+
|
8
|
+
end
|
9
|
+
base.send :include, InstanceMethods
|
10
|
+
end # self.included
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
|
14
|
+
def datagrid_attribute(name, &block)
|
15
|
+
unless datagrid_attributes.include?(name)
|
16
|
+
block ||= lambda do |value|
|
17
|
+
value
|
18
|
+
end
|
19
|
+
datagrid_attributes << name
|
20
|
+
define_method name do
|
21
|
+
instance_variable_get("@#{name}")
|
22
|
+
end
|
23
|
+
|
24
|
+
define_method :"#{name}=" do |value|
|
25
|
+
instance_variable_set("@#{name}", block.call(value))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def datagrid_attributes
|
31
|
+
@datagrid_attributes ||= []
|
32
|
+
end
|
33
|
+
|
34
|
+
def scope(&block)
|
35
|
+
if block
|
36
|
+
@scope = block
|
37
|
+
else
|
38
|
+
raise(Datagrid::ConfigurationError, "Scope not defined") unless @scope
|
39
|
+
@scope.call
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def param_name
|
44
|
+
self.to_s.underscore.split('/').last
|
45
|
+
end
|
46
|
+
|
47
|
+
end # ClassMethods
|
48
|
+
|
49
|
+
module InstanceMethods
|
50
|
+
|
51
|
+
def initialize(attributes = nil)
|
52
|
+
super()
|
53
|
+
|
54
|
+
if attributes
|
55
|
+
self.attributes = attributes
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def attributes
|
60
|
+
result = {}
|
61
|
+
self.class.datagrid_attributes.each do |name|
|
62
|
+
result[name] = self[name]
|
63
|
+
end
|
64
|
+
result
|
65
|
+
end
|
66
|
+
|
67
|
+
def [](attribute)
|
68
|
+
self.send(attribute)
|
69
|
+
end
|
70
|
+
|
71
|
+
def []=(attribute, value)
|
72
|
+
self.send(:"#{attribute}=", value)
|
73
|
+
end
|
74
|
+
|
75
|
+
def assets
|
76
|
+
scope.scoped({})
|
77
|
+
end
|
78
|
+
|
79
|
+
def attributes=(attributes)
|
80
|
+
attributes.each do |name, value|
|
81
|
+
self[name] = value
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def paginate(*args, &block)
|
86
|
+
self.assets.paginate(*args, &block)
|
87
|
+
end
|
88
|
+
|
89
|
+
def scope
|
90
|
+
self.class.scope
|
91
|
+
end
|
92
|
+
|
93
|
+
def param_name
|
94
|
+
self.class.param_name
|
95
|
+
end
|
96
|
+
|
97
|
+
end # InstanceMethods
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
module Datagrid
|
2
|
+
module Filters
|
3
|
+
|
4
|
+
require "datagrid/filters/base_filter"
|
5
|
+
require "datagrid/filters/enum_filter"
|
6
|
+
require "datagrid/filters/boolean_enum_filter"
|
7
|
+
require "datagrid/filters/boolean_filter"
|
8
|
+
require "datagrid/filters/date_filter"
|
9
|
+
require "datagrid/filters/default_filter"
|
10
|
+
require "datagrid/filters/filter_eval"
|
11
|
+
require "datagrid/filters/integer_filter"
|
12
|
+
require "datagrid/filters/composite_filters"
|
13
|
+
|
14
|
+
FILTER_TYPES = {
|
15
|
+
:date => Filters::DateFilter,
|
16
|
+
:string => Filters::DefaultFilter,
|
17
|
+
:default => Filters::DefaultFilter,
|
18
|
+
:eboolean => Filters::BooleanEnumFilter ,
|
19
|
+
:boolean => Filters::BooleanFilter ,
|
20
|
+
:integer => Filters::IntegerFilter,
|
21
|
+
:enum => Filters::EnumFilter,
|
22
|
+
}
|
23
|
+
|
24
|
+
def self.included(base)
|
25
|
+
base.extend ClassMethods
|
26
|
+
base.class_eval do
|
27
|
+
|
28
|
+
include Datagrid::Core
|
29
|
+
include Datagrid::Filters::CompositeFilters
|
30
|
+
|
31
|
+
end
|
32
|
+
base.send :include, InstanceMethods
|
33
|
+
end # self.included
|
34
|
+
|
35
|
+
module ClassMethods
|
36
|
+
|
37
|
+
def filters
|
38
|
+
@filters ||= []
|
39
|
+
end
|
40
|
+
|
41
|
+
def filter_by_name(attribute)
|
42
|
+
self.filters.find do |filter|
|
43
|
+
filter.name.to_sym == attribute.to_sym
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
# Defines the accessible attribute that is used to filter
|
49
|
+
# scope by the specified value with specified code.
|
50
|
+
#
|
51
|
+
# Example:
|
52
|
+
#
|
53
|
+
# class UserGrid
|
54
|
+
# include Datagrid
|
55
|
+
#
|
56
|
+
# filter(:name)
|
57
|
+
# filter(:posts_count, :integer) do |value|
|
58
|
+
# self.where(["posts_count >= ?", value])
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# scope do
|
62
|
+
# User.order("users.created_at desc")
|
63
|
+
# end
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# Each filter becomes grid atttribute.
|
67
|
+
# In order to create grid that display all users with name 'John' that have more than zero posts:
|
68
|
+
#
|
69
|
+
# grid = UserGrid.new(:posts_count => 1, :name => "John")
|
70
|
+
# grid.assets # SELECT * FROM users WHERE users.posts_count > 1 AND name = 'John'
|
71
|
+
#
|
72
|
+
# Important! Take care about non-breaking the filter chain and force objects loading in filter.
|
73
|
+
# The filter block should always return a <tt>ActiveRecord::Scope</tt> rather than <tt>Array</tt>
|
74
|
+
#
|
75
|
+
# = Default filter block
|
76
|
+
#
|
77
|
+
# If no block given filter is generated automatically as simple select by filter name from scope.
|
78
|
+
#
|
79
|
+
# = Filter types
|
80
|
+
#
|
81
|
+
# Filter does types conversion automatically.
|
82
|
+
# The following filter types are supported:
|
83
|
+
#
|
84
|
+
# * <tt>:string</tt> (default) - converts value to string
|
85
|
+
# * <tt>:date</tt> - converts value to date using date parser
|
86
|
+
# * <tt>:enum</tt> - designed to be collection select. Additional options for easy form generation:
|
87
|
+
# * <tt>:select</tt> (required) - collection of values to match agains.
|
88
|
+
# * <tt>:multiple</tt> - if true multiple values can be assigned to this filter. Default: false.
|
89
|
+
# * <tt>:eboolean</tt> - subtype of enum filter that provides select of "Yes", "No" and "Any". Could be useful.
|
90
|
+
# * <tt>:integer</tt> - converts given value to integer.
|
91
|
+
#
|
92
|
+
#
|
93
|
+
def filter(attribute, type = :string, options = {}, &block)
|
94
|
+
|
95
|
+
klass = type.is_a?(Class) ? type : FILTER_TYPES[type]
|
96
|
+
raise ConfigurationError, "filter class not found" unless klass
|
97
|
+
|
98
|
+
block ||= default_filter(attribute)
|
99
|
+
|
100
|
+
filter = klass.new(self, attribute, options, &block)
|
101
|
+
self.filters << filter
|
102
|
+
|
103
|
+
datagrid_attribute(attribute) do |value|
|
104
|
+
filter.format(value)
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
protected
|
110
|
+
def default_filter(attribute)
|
111
|
+
if self.scope.column_names.include?(attribute.to_s)
|
112
|
+
lambda do |value|
|
113
|
+
self.scoped(:conditions => {attribute => value})
|
114
|
+
end
|
115
|
+
else
|
116
|
+
raise ConfigurationError, "Not able to generate default filter. No column '#{attribute}' in #{self.scope.table_name}."
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
end # ClassMethods
|
121
|
+
|
122
|
+
module InstanceMethods
|
123
|
+
|
124
|
+
def initialize(*args, &block)
|
125
|
+
self.filters.each do |filter|
|
126
|
+
self[filter.name] = filter.default
|
127
|
+
end
|
128
|
+
super(*args, &block)
|
129
|
+
end
|
130
|
+
|
131
|
+
def assets
|
132
|
+
result = super
|
133
|
+
self.class.filters.each do |filter|
|
134
|
+
result = filter.apply(result, filter_value(filter))
|
135
|
+
end
|
136
|
+
result
|
137
|
+
end
|
138
|
+
|
139
|
+
def filters
|
140
|
+
self.class.filters
|
141
|
+
end
|
142
|
+
|
143
|
+
def filter_value(filter)
|
144
|
+
self[filter.name]
|
145
|
+
end
|
146
|
+
|
147
|
+
end # InstanceMethods
|
148
|
+
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "datagrid/filters/filter_eval"
|
2
|
+
|
3
|
+
class Datagrid::Filters::BaseFilter
|
4
|
+
|
5
|
+
attr_accessor :grid, :options, :block, :name
|
6
|
+
|
7
|
+
def initialize(grid, name, options = {}, &block)
|
8
|
+
self.grid = grid
|
9
|
+
self.name = name
|
10
|
+
self.options = options
|
11
|
+
self.block = block
|
12
|
+
end
|
13
|
+
|
14
|
+
def apply(scope, value)
|
15
|
+
return scope if value.nil? && !options[:allow_nil]
|
16
|
+
return scope if value.blank? && !options[:allow_blank]
|
17
|
+
::Datagrid::Filters::FilterEval.new(self, scope, value).run
|
18
|
+
end
|
19
|
+
|
20
|
+
def format(value)
|
21
|
+
raise NotImplementedError, "#format(value) suppose to be overwritten"
|
22
|
+
end
|
23
|
+
|
24
|
+
def header
|
25
|
+
I18n.translate(self.name, :scope => "datagrid.#{grid.class.to_s.underscore.split("/").last}.filters", :default => self.name.to_s.humanize)
|
26
|
+
end
|
27
|
+
|
28
|
+
def default
|
29
|
+
self.options[:default]
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Datagrid::Filters::BooleanEnumFilter < Datagrid::Filters::EnumFilter
|
2
|
+
|
3
|
+
YES = "YES"
|
4
|
+
NO = "NO"
|
5
|
+
VALUES = {YES => true, NO => false}
|
6
|
+
|
7
|
+
def initialize(report, attribute, options = {}, &block)
|
8
|
+
options[:allow_blank] = true unless options.has_key?(:allow_blank)
|
9
|
+
options[:select] = VALUES.keys
|
10
|
+
super(report, attribute, options, &block)
|
11
|
+
end
|
12
|
+
|
13
|
+
def apply(scope, value)
|
14
|
+
super(scope, to_boolean(value))
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_boolean(value)
|
18
|
+
VALUES[value]
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|