datagrid 0.0.3

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