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.
@@ -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