Permalink
Browse files

Merge pull request #85 from iwiznia/master

Add support for datetime filter
  • Loading branch information...
2 parents 039f35d + cdceab7 commit c34fd4924ece58ec6f1622df7a94e7131a2a0071 @bogdan committed Jan 14, 2014
View
@@ -61,13 +61,13 @@ Basic grid api:
``` ruby
report = SimpleReport.new(
- :group_id => [1,2], :from_logins_count => 1,
+ :group_id => [1,2], :from_logins_count => 1,
:category => "first",
:order => :group,
:descending => true
)
-report.assets # => Array of User instances:
+report.assets # => Array of User instances:
# SELECT * FROM users WHERE users.group_id in (1,2) AND users.logins_count >= 1 AND users.category = 'first' ORDER BY groups.name DESC
report.header # => ["Name", "Group", "Activated"]
@@ -118,6 +118,7 @@ Datagrid supports different type of filters including:
* integer
* float
* date
+* datetime
* boolean
* eboolean - the select of "yes", "no" and any
* enum
@@ -137,10 +138,10 @@ column(:activated, :header => "Active", :order => "activated", :after => :name)
end
```
-Some formatting options are also available.
+Some formatting options are also available.
Each column is sortable.
-[More about columns](https://github.com/bogdan/datagrid/wiki/Columns)
+[More about columns](https://github.com/bogdan/datagrid/wiki/Columns)
### Front end
@@ -178,8 +179,8 @@ All advanced frontend things are described in:
## Self-Promotion
-Like datagrid?
+Like datagrid?
-Follow the repository on [GitHub](https://github.com/bogdan/datagrid).
+Follow the repository on [GitHub](https://github.com/bogdan/datagrid).
Read [author blog](http://gusiev.com).
@@ -8,6 +8,6 @@ def self.configure
yield(configuration)
end
- class Configuration < Struct.new(:date_formats)
+ class Configuration < Struct.new(:date_formats, :datetime_formats)
end
end
View
@@ -8,6 +8,7 @@ module Filters
require "datagrid/filters/boolean_enum_filter"
require "datagrid/filters/boolean_filter"
require "datagrid/filters/date_filter"
+ require "datagrid/filters/date_time_filter"
require "datagrid/filters/default_filter"
require "datagrid/filters/integer_filter"
require "datagrid/filters/composite_filters"
@@ -17,6 +18,7 @@ module Filters
FILTER_TYPES = {
:date => Filters::DateFilter,
+ :datetime => Filters::DateTimeFilter,
:string => Filters::StringFilter,
:default => Filters::DefaultFilter,
:eboolean => Filters::BooleanEnumFilter ,
@@ -52,7 +54,7 @@ def filter_by_name(attribute)
# Defines new datagrid filter.
# This method automatically generates <tt>attr_accessor</tt> for filter name
# and adds it to the list of datagrid attributes.
- #
+ #
# Arguments:
#
# * <tt>name</tt> - filter name
@@ -61,19 +63,19 @@ def filter_by_name(attribute)
# * <tt>block</tt> - proc to apply the filter
#
# Available options:
- #
+ #
# * <tt>:header</tt> - determines the header of the filter
# * <tt>:default</tt> - the default filter value. Able to accept a <tt>Proc</tt> in case default should be recalculated
- # * <tt>:multiple</tt> - if true multiple values can be assigned to this filter.
- # By default multiple values are parsed from string using `,` separator.
+ # * <tt>:multiple</tt> - if true multiple values can be assigned to this filter.
+ # By default multiple values are parsed from string using `,` separator.
# But you can specify a different separator as option value. Default: false.
# * <tt>:allow_nil</tt> - determines if the value can be nil
# * <tt>:allow_blank</tt> - determines if the value can be blank
- # * <tt>:before</tt> - determines the position of this filter,
+ # * <tt>:before</tt> - determines the position of this filter,
# by adding it before the filter passed here (when using datagrid_form_for helper)
- # * <tt>:after</tt> - determines the position of this filter,
+ # * <tt>:after</tt> - determines the position of this filter,
# by adding it after the filter passed here (when using datagrid_form_for helper)
- # * <tt>:dummy</tt> - if true, this filter will not be applied automatically
+ # * <tt>:dummy</tt> - if true, this filter will not be applied automatically
# and will be just displayed in form. In case you may want to apply it manually.
#
# See: https://github.com/bogdan/datagrid/wiki/Filters for examples
@@ -0,0 +1,25 @@
+require "datagrid/filters/ranged_filter"
+
+class Datagrid::Filters::DateTimeFilter < Datagrid::Filters::BaseFilter
+
+ include RangedFilter
+
+ def parse(value)
+ Datagrid::Utils.parse_datetime(value)
+ end
+
+ def format(value)
+ if formats.any? && value
+ value.strftime(formats.first)
+ else
+ super
+ end
+ end
+
+ protected
+
+ def formats
+ Array(Datagrid.configuration.datetime_formats)
+ end
+end
+
@@ -29,6 +29,10 @@ def datagrid_date_filter(attribute_or_filter, options = {})
datagrid_range_filter(:date, attribute_or_filter, options)
end
+ def datagrid_date_time_filter(attribute_or_filter, options = {})
+ datagrid_range_filter(:datetime, attribute_or_filter, options)
+ end
+
def datagrid_default_filter(attribute_or_filter, options = {})
filter = datagrid_get_filter(attribute_or_filter)
text_field filter.name, options.reverse_merge(:value => object.filter_value_as_string(filter))
@@ -16,6 +16,9 @@ en:
date:
range_separator:
"<span class=\"separator date\"> - </span>"
+ datetime:
+ range_separator:
+ "<span class=\"separator datetime\"> - </span>"
eboolean:
"yes": "Yes"
View
@@ -1,4 +1,4 @@
-module Datagrid
+module Datagrid
module Utils # :nodoc:
class << self
@@ -73,6 +73,24 @@ def parse_date(value)
nil
end
+ def parse_datetime(value)
+ return nil if value.blank?
+ return value if value.is_a?(Range)
+ if value.is_a?(String)
+ Array(Datagrid.configuration.datetime_formats).each do |format|
+ begin
+ return DateTime.strptime(value, format)
+ rescue ::ArgumentError
+ end
+ end
+ end
+ return DateTime.parse(value) if value.is_a?(String)
+ return value.to_datetime if value.respond_to?(:to_datetime)
+ value
+ rescue ::ArgumentError
+ nil
+ end
+
def format_date_as_timestamp(value)
if !value
value
@@ -0,0 +1,150 @@
+require 'spec_helper'
+
+describe Datagrid::Filters::DateTimeFilter do
+ {:active_record => Entry, :mongoid => MongoidEntry}.each do |orm, klass|
+ describe "with orm #{orm}" do
+ describe "timestamp to timestamp conversion" do
+ let(:klass) { klass }
+ subject do
+ test_report(:created_at => _created_at) do
+ scope { klass }
+ filter(:created_at, :datetime, :range => true)
+ end.assets.to_a
+ end
+
+ def entry_dated(date)
+ klass.create(:created_at => date)
+ end
+
+ context "when single datetime paramter given" do
+ let(:_created_at) { DateTime.now }
+ it { should include(entry_dated(_created_at))}
+ it { should_not include(entry_dated(_created_at - 1.second))}
+ it { should_not include(entry_dated(_created_at + 1.second))}
+ end
+
+ context "when range datetime range given" do
+ let(:_created_at) { [DateTime.now.beginning_of_day, DateTime.now.end_of_day] }
+ it { should include(entry_dated(1.second.ago))}
+ it { should include(entry_dated(Date.today.to_datetime))}
+ it { should include(entry_dated(Date.today.end_of_day.to_datetime))}
+ it { should_not include(entry_dated(Date.yesterday.end_of_day))}
+ it { should_not include(entry_dated(Date.tomorrow.beginning_of_day))}
+ end
+ end
+
+ end
+ end
+
+ it "should support datetime range given as array argument" do
+ e1 = Entry.create!(:created_at => DateTime.new(2013, 1, 1, 1, 0))
+ e2 = Entry.create!(:created_at => DateTime.new(2013, 1, 1, 2, 0))
+ e3 = Entry.create!(:created_at => DateTime.new(2013, 1, 1, 3, 0))
+ report = test_report(:created_at => [DateTime.new(2013, 1, 1, 1, 30).to_s, DateTime.new(2013, 1, 1, 2, 30).to_s]) do
+ scope { Entry }
+ filter(:created_at, :datetime, :range => true)
+ end
+ report.assets.should_not include(e1)
+ report.assets.should include(e2)
+ report.assets.should_not include(e3)
+ end
+
+ it "should support minimum datetime argument" do
+ e1 = Entry.create!(:created_at => DateTime.new(2013, 1, 1, 1, 0))
+ e2 = Entry.create!(:created_at => DateTime.new(2013, 1, 1, 2, 0))
+ e3 = Entry.create!(:created_at => DateTime.new(2013, 1, 1, 3, 0))
+ report = test_report(:created_at => [DateTime.new(2013, 1, 1, 1, 30).to_s, nil]) do
+ scope { Entry }
+ filter(:created_at, :datetime, :range => true)
+ end
+ report.assets.should_not include(e1)
+ report.assets.should include(e2)
+ report.assets.should include(e3)
+ end
+
+ it "should support maximum datetime argument" do
+ e1 = Entry.create!(:created_at => DateTime.new(2013, 1, 1, 1, 0))
+ e2 = Entry.create!(:created_at => DateTime.new(2013, 1, 1, 2, 0))
+ e3 = Entry.create!(:created_at => DateTime.new(2013, 1, 1, 3, 0))
+ report = test_report(:created_at => [nil, DateTime.new(2013, 1, 1, 2, 30).to_s]) do
+ scope { Entry }
+ filter(:created_at, :datetime, :range => true)
+ end
+ report.assets.should include(e1)
+ report.assets.should include(e2)
+ report.assets.should_not include(e3)
+ end
+
+ it "should find something in one second interval" do
+
+ e1 = Entry.create!(:created_at => DateTime.new(2013, 1, 1, 1, 0))
+ e2 = Entry.create!(:created_at => DateTime.new(2013, 1, 1, 2, 0))
+ e3 = Entry.create!(:created_at => DateTime.new(2013, 1, 1, 3, 0))
+ report = test_report(:created_at => (DateTime.new(2013, 1, 1, 2, 0)..DateTime.new(2013, 1, 1, 2, 0))) do
+ scope { Entry }
+ filter(:created_at, :datetime, :range => true)
+ end
+ report.assets.should_not include(e1)
+ report.assets.should include(e2)
+ report.assets.should_not include(e3)
+ end
+ it "should support invalid range" do
+
+ e1 = Entry.create!(:created_at => DateTime.new(2013, 1, 1, 1, 0))
+ e2 = Entry.create!(:created_at => DateTime.new(2013, 1, 1, 2, 0))
+ e3 = Entry.create!(:created_at => DateTime.new(2013, 1, 1, 3, 0))
+ report = test_report(:created_at => (DateTime.new(2013, 1, 1, 3, 0)..DateTime.new(2013, 1, 1, 1, 0))) do
+ scope { Entry }
+ filter(:created_at, :datetime, :range => true)
+ end
+ report.assets.should_not include(e1)
+ report.assets.should_not include(e2)
+ report.assets.should_not include(e3)
+ end
+
+
+ it "should support block" do
+ report = test_report(:created_at => DateTime.now) do
+ scope { Entry }
+ filter(:created_at, :datetime, :range => true) do |value|
+ where("created_at >= ?", value)
+ end
+ end
+ report.assets.should_not include(Entry.create!(:created_at => 1.day.ago))
+ report.assets.should include(Entry.create!(:created_at => DateTime.tomorrow))
+ end
+
+
+ context "when datetime format is configured" do
+ around(:each) do |example|
+ with_datetime_format(format = "%m/%d/%Y %H:%M") do
+ example.run
+ end
+ end
+
+ it "should have configurable datetime format" do
+ report = test_report(:created_at => "10/01/2013 01:00") do
+ scope {Entry}
+ filter(:created_at, :datetime)
+ end
+ report.created_at.should == DateTime.new(2013,10,01,1,0)
+ end
+
+ it "should support default explicit datetime" do
+ report = test_report(:created_at => DateTime.parse("2013-10-01 01:00")) do
+ scope {Entry}
+ filter(:created_at, :datetime)
+ end
+ report.created_at.should == DateTime.new(2013,10,01,1,0)
+ end
+ end
+
+
+ it "should automatically reverse Array if first more than last" do
+ report = test_report(:created_at => ["2013-01-01 01:00", "2012-01-01 01:00"]) do
+ scope {Entry}
+ filter(:created_at, :datetime, :range => true)
+ end
+ report.created_at.should == [DateTime.new(2012, 01, 01, 1, 0), DateTime.new(2013, 01, 01, 1, 0)]
+ end
+end
@@ -11,3 +11,17 @@ def with_date_format(format = "%m/%d/%Y")
end
end
end
+
+def with_datetime_format(format = "%m/%d/%Y")
+ begin
+ old_format = Datagrid.configuration.datetime_formats
+ Datagrid.configure do |config|
+ config.datetime_formats = format
+ end
+ yield
+ ensure
+ Datagrid.configure do |config|
+ config.datetime_formats = old_format
+ end
+ end
+end

0 comments on commit c34fd49

Please sign in to comment.