by_star 1.0.1 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
data/by_star.gemspec CHANGED
@@ -15,6 +15,12 @@ Gem::Specification.new do |s|
15
15
  s.rubyforge_project = "by_star"
16
16
 
17
17
  s.add_development_dependency "bundler", ">= 1.0.0"
18
+ s.add_development_dependency "sqlite3"
19
+ s.add_development_dependency "pg"
20
+ s.add_development_dependency "mysql2"
21
+ s.add_development_dependency "rspec-rails", "~> 2.8"
22
+ s.add_development_dependency "timecop", "~> 0.3"
23
+
18
24
  s.add_dependency "activerecord", ">= 2.0.0"
19
25
  s.add_dependency "chronic"
20
26
 
data/lib/by_star.rb CHANGED
@@ -1,43 +1,63 @@
1
1
  require 'chronic'
2
2
 
3
- require 'by_star/shared'
4
- require 'by_star/range_calculations'
5
3
  require 'by_star/time_ext'
6
- require 'by_star/vanilla'
7
- require 'by_star/neighbours'
4
+ require 'by_star/instance_methods'
8
5
 
9
- require 'by_star/calculations/count'
10
- require 'by_star/calculations/sum'
11
-
12
- require 'by_star/calculations'
6
+ require 'by_star/by_direction'
7
+ require 'by_star/by_year'
8
+ require 'by_star/by_month'
9
+ require 'by_star/by_fortnight'
10
+ require 'by_star/by_week'
11
+ require 'by_star/by_weekend'
12
+ require 'by_star/by_day'
13
13
 
14
14
  module ByStar
15
15
 
16
- def self.included(base)
17
- base.extend ClassMethods
18
- base.send(:include, InstanceMethods)
19
- base.class_eval do
20
- def self.by_star_field(value=nil)
21
- @by_star_field ||= value
22
- @by_star_field || "#{self.table_name}.created_at"
23
- end
24
- end
16
+ def by_star_field(field=nil)
17
+ @by_star_field ||= field
18
+ @by_star_field || "created_at"
19
+ end
20
+
21
+ include ByDirection
22
+ include ByYear
23
+ include ByMonth
24
+ include ByFortnight
25
+ include ByWeek
26
+ include ByWeekend
27
+ include ByDay
28
+
29
+ class ParseError < StandardError
30
+
25
31
  end
26
32
 
27
- module ClassMethods
28
- include RangeCalculations
29
- include Shared
30
- include Vanilla
31
- include Calculations
33
+ # Returns all records between a given start and finish time.
34
+ #
35
+ # Currently only supports Time objects.
36
+ def between(start, finish, options={})
37
+ field = options[:field] || by_star_field
38
+ scope = where("#{field} >= ? AND #{field} <= ?",
39
+ start, finish)
40
+ scope = scope.order(options[:order]) if options[:order]
41
+ scope
32
42
  end
33
43
 
34
- module InstanceMethods
35
- include Neighbours
44
+ private
45
+
46
+ # Used inside the by_* methods to determine what kind of object "time" is.
47
+ # These methods take the result of the time_klass method, and call other methods
48
+ # using it, such as by_year_Time and by_year_String.
49
+ def time_klass(time)
50
+ case time
51
+ when ActiveSupport::TimeWithZone
52
+ Time
53
+ else
54
+ time.class
55
+ end
36
56
  end
37
57
 
38
- class ParseError < Exception; end
39
- class MonthNotFound < Exception; end
40
58
  end
41
59
 
42
- ActiveRecord::Base.send :include, ByStar
43
- ActiveRecord::Relation.send :include, ByStar if ActiveRecord.const_defined?("Relation")
60
+ ActiveRecord::Base.send :extend, ByStar
61
+ ActiveRecord::Relation.send :extend, ByStar
62
+
63
+ ActiveRecord::Base.send :include, ByStar::InstanceMethods
@@ -0,0 +1,30 @@
1
+ module ByStar
2
+ module ByDay
3
+ def by_day(*args)
4
+ options = args.extract_options!.symbolize_keys!
5
+ time = args.first
6
+ time ||= Time.zone.local(options[:year], 1, 1) if options[:year]
7
+ time ||= Time.zone.now
8
+ send("by_day_#{time_klass(time)}", time, options)
9
+ end
10
+
11
+ def today(options={})
12
+ by_day_Time(Time.zone.now, options)
13
+ end
14
+
15
+ def yesterday(options={})
16
+ by_day_Time(Time.zone.now.yesterday, options)
17
+ end
18
+
19
+ def tomorrow(options={})
20
+ by_day_Time(Time.zone.now.tomorrow, options)
21
+ end
22
+
23
+ private
24
+
25
+ def by_day_Time(time, options)
26
+ between(time.beginning_of_day, time.end_of_day, options)
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,52 @@
1
+ module ByStar
2
+ module ByDirection
3
+ def before(*args)
4
+ options = args.extract_options!.symbolize_keys!
5
+ time = args.first || Time.zone.now
6
+ send("before_#{time_klass(time)}", time, options)
7
+ end
8
+ alias_method :before_now, :before
9
+
10
+ def after(*args)
11
+ options = args.extract_options!.symbolize_keys!
12
+ time = args.first || Time.zone.now
13
+ send("after_#{time_klass(time)}", time, options)
14
+ end
15
+ alias_method :after_now, :after
16
+
17
+ private
18
+
19
+ def before_Time_or_Date(time_or_date, options={})
20
+ field = options[:field] || by_star_field
21
+ where("#{field} <= ?", time_or_date)
22
+ end
23
+ alias_method :before_Time, :before_Time_or_Date
24
+ alias_method :before_Date, :before_Time_or_Date
25
+
26
+ def before_String(string, options={})
27
+ field = options[:field] || by_star_field
28
+ if time = Chronic.parse(string)
29
+ where("#{field} <= ?", time)
30
+ else
31
+ raise ParseError, "Chronic couldn't understand #{string.inspect}. Please try something else."
32
+ end
33
+ end
34
+
35
+ def after_Time_or_Date(time_or_date, options={})
36
+ field = options[:field] || by_star_field
37
+ where("#{field} >= ?", time_or_date)
38
+ end
39
+ alias_method :after_Time, :after_Time_or_Date
40
+ alias_method :after_Date, :after_Time_or_Date
41
+
42
+ def after_String(string, options={})
43
+ field = options[:field] || by_star_field
44
+ if time = Chronic.parse(string)
45
+ where("#{field} >= ?", time)
46
+ else
47
+ raise ParseError, "Chronic couldn't understand #{string.inspect}. Please try something else."
48
+ end
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,54 @@
1
+ module ByStar
2
+ module ByFortnight
3
+ # For reasoning why I use *args rather than variables here,
4
+ # please see the by_year method comments in lib/by_star/by_year.rb
5
+
6
+ def by_fortnight(*args)
7
+ options = args.extract_options!.symbolize_keys!
8
+ time = args.first
9
+ time ||= Time.local(options[:year], 1, 1) if options[:year]
10
+ time ||= Time.zone.now
11
+ send("by_fortnight_#{time_klass(time)}", time, options)
12
+ end
13
+
14
+ private
15
+
16
+ def by_fortnight_Time(time, options={})
17
+ # We want to get the current fortnight and so...
18
+ # We need to find the current week number and take one from it,
19
+ # so that we are correctly offset from the start of the year.
20
+ # The first fortnight of the year should of course start on the 1st January,
21
+ # and not the beginning of that week.
22
+ start_time = time.beginning_of_year + (time.strftime("%U").to_i - 1).weeks
23
+ between(start_time, (start_time + 2.weeks).end_of_day, options)
24
+ end
25
+
26
+ def by_fortnight_String_or_Fixnum(weeks, options={})
27
+ weeks = weeks.to_i
28
+ current_time = Time.zone.local(options[:year] || Time.zone.now.year)
29
+ if weeks <= 26
30
+ start_time = current_time + (weeks * 2).weeks
31
+ between(start_time, (start_time + 2.weeks).end_of_day, options)
32
+ else
33
+ raise ParseError, "by_fortnight takes only a Time, Date or a Fixnum (less than or equal to 26)."
34
+ end
35
+ end
36
+ alias_method :by_fortnight_String, :by_fortnight_String_or_Fixnum
37
+ alias_method :by_fortnight_Fixnum, :by_fortnight_String_or_Fixnum
38
+
39
+
40
+
41
+ # def omg
42
+ # time.beginning_of_year + (time.strftime("%U").to_i).weeks
43
+ # if time.is_a?(Numeric) && time <= 26
44
+ # Time.utc(year, 1, 1) + ((time.to_i) * 2).weeks
45
+ # else
46
+ # raise ParseError, "by_fortnight takes only a Time or Date object, a Fixnum (less than or equal to 26) or a Chronicable string."
47
+ # end
48
+
49
+ # between(start_time.beginning_of_week, start_time + 2.weeks)
50
+ # end
51
+
52
+
53
+ end
54
+ end
@@ -0,0 +1,35 @@
1
+ module ByStar
2
+ module ByMonth
3
+ # For reasoning why I use *args rather than variables here,
4
+ # please see the by_year method comments in lib/by_star/by_year.rb
5
+
6
+ def by_month(*args)
7
+ options = args.extract_options!
8
+ time = args.first || Time.zone.now
9
+ send("by_month_#{time_klass(time)}", time, options)
10
+ end
11
+
12
+ private
13
+
14
+ def by_month_Time(time, options={})
15
+ between(time.beginning_of_month, time.end_of_month, options)
16
+ end
17
+
18
+ def by_month_String_or_Fixnum(month, options={})
19
+ if valid_month?(month)
20
+ year = options[:year] || Time.zone.now.year
21
+ by_month_Time("#{year}-#{month}-01".to_time, options)
22
+ else
23
+ raise ParseError, "Month must be a number between 1 and 12 or the full month name (e.g. 'January', 'Feburary', etc.)"
24
+ end
25
+ end
26
+ alias_method :by_month_String, :by_month_String_or_Fixnum
27
+ alias_method :by_month_Fixnum, :by_month_String_or_Fixnum
28
+
29
+
30
+ def valid_month?(month)
31
+ (1..12).include?(month) || Date::MONTHNAMES.include?(month)
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,30 @@
1
+ module ByStar
2
+ module ByWeek
3
+
4
+ # For reasoning why I use *args rather than variables here,
5
+ # please see the by_year method comments in lib/by_star/by_year.rb
6
+
7
+ def by_week(*args)
8
+ options = args.extract_options!.symbolize_keys!
9
+ time = args.first
10
+ time ||= Time.zone.local(options[:year], 1, 1) if options[:year]
11
+ time ||= Time.zone.now
12
+ send("by_week_#{time_klass(time)}", time, options)
13
+ end
14
+
15
+ private
16
+
17
+ def by_week_Time_or_Date(time, options={})
18
+ between(time.beginning_of_week, time.end_of_week, options)
19
+ end
20
+ alias_method :by_week_Time, :by_week_Time_or_Date
21
+ alias_method :by_week_Date, :by_week_Time_or_Date
22
+
23
+ def by_week_Fixnum(week, options={})
24
+ time = Time.zone.local(options[:year], 1, 1) if options[:year]
25
+ time ||= Time.zone.now
26
+ start_time = time.beginning_of_year + week.to_i.weeks
27
+ between(start_time, (start_time + 1.week).end_of_day, options)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,17 @@
1
+ module ByStar
2
+ module ByWeekend
3
+ def by_weekend(*args)
4
+ options = args.extract_options!.symbolize_keys!
5
+ time = args.first
6
+ time ||= Time.zone.now
7
+ send("by_weekend_#{time_klass(time)}", time, options)
8
+ end
9
+
10
+ private
11
+
12
+ def by_weekend_Time(time, options={})
13
+ between(time.beginning_of_weekend, time.end_of_weekend)
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,60 @@
1
+ module ByStar
2
+ module ByYear
3
+
4
+ # NOTE: We would define this as this:
5
+ #
6
+ # def by_year(time=Time.zone.now, options={})
7
+ #
8
+ # But, there's a potential situation where people want to just pass options.
9
+ # Like this:
10
+ #
11
+ # Post.by_year(:field => "published_at")
12
+ #
13
+ # And so, we support any number of arguments and just parse them as necessary.
14
+ # By doing it this way, we can support *both* this:
15
+ #
16
+ # Post.by_year(2012, :field => "published_at")
17
+ #
18
+ # And this:
19
+ #
20
+ # Post.by_year(:field => "published_at")
21
+ #
22
+ # This is because the time variable is going to be defaulting to the current time.
23
+
24
+ def by_year(*args)
25
+ options = args.extract_options!.symbolize_keys!
26
+ time = args.first || Time.zone.now
27
+
28
+ send("by_year_#{time_klass(time)}", time, options)
29
+ end
30
+
31
+ private
32
+
33
+ def by_year_Time_or_Date(time, options={})
34
+ between(time.beginning_of_year, time.end_of_year, options)
35
+ end
36
+ alias_method :by_year_Time, :by_year_Time_or_Date
37
+ alias_method :by_year_Date, :by_year_Time_or_Date
38
+
39
+ def by_year_String_or_Fixnum(year, options={})
40
+ by_year_Time("#{work_out_year(year)}-01-01".to_time, options)
41
+ end
42
+ alias_method :by_year_String, :by_year_String_or_Fixnum
43
+ alias_method :by_year_Fixnum, :by_year_String_or_Fixnum
44
+
45
+ def work_out_year(value)
46
+ case value.to_i
47
+ when 0..39
48
+ 2000 + value
49
+ when 40..99
50
+ 1900 + value
51
+ when nil
52
+ Time.zone.now.year
53
+ else
54
+ # We may be passed something that's not a straight out integer
55
+ # These things include: BigDecimals, Floats and Strings.
56
+ value.to_i
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,13 @@
1
+ module ByStar
2
+ module InstanceMethods
3
+ def previous(options={})
4
+ field = options[:field] || self.class.by_star_field
5
+ self.class.where("#{field} < ?", self.send(field)).reorder("#{field} DESC").first
6
+ end
7
+
8
+ def next(options={})
9
+ field = options[:field] || self.class.by_star_field
10
+ self.class.where("#{field} > ?", self.send(field)).reorder("#{field} ASC").first
11
+ end
12
+ end
13
+ end
@@ -17,4 +17,5 @@ class Time
17
17
  # LOL I CHEATED.
18
18
  beginning_of_weekend + 3.days - 12.hours
19
19
  end
20
- end
20
+ end
21
+
@@ -1,3 +1,3 @@
1
1
  module ByStar
2
- VERSION = "1.0.1"
2
+ VERSION = "2.0.0.beta1"
3
3
  end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ describe "by day" do
4
+ def posts_count(*args)
5
+ Post.by_day(*args).count
6
+ end
7
+
8
+ it "should be able to find a post for today" do
9
+ posts_count.should eql(4)
10
+ end
11
+
12
+ it "should be able to find a post by a given date in last year" do
13
+ posts_count(:year => Time.zone.now.year - 1).should eql(1)
14
+ end
15
+
16
+ it "should be able to use an alternative field" do
17
+ Event.by_day(Time.now.yesterday, :field => "start_time").size.should eql(1)
18
+ end
19
+ end
20
+
21
+ describe "today" do
22
+ it "should show the post for today" do
23
+ Post.today.map(&:text).should include("Today's post")
24
+ end
25
+
26
+ it "should be able to use an alternative field" do
27
+ # Test may occur on an event day.
28
+ Event.today(:field => "start_time").size.should eql(1)
29
+ end
30
+ end
31
+
32
+ describe "yesterday" do
33
+
34
+ it "should show the post for yesterday" do
35
+ Post.yesterday.map(&:text).should include("Yesterday's post")
36
+ end
37
+
38
+ it "should be able to use an alternative field" do
39
+ Event.yesterday(:field => "start_time").size.should eql(1)
40
+ end
41
+
42
+ end
43
+
44
+ describe "tomorrow" do
45
+ it "should show the post for tomorrow" do
46
+ Post.tomorrow.map(&:text).should include("Tomorrow's post")
47
+ end
48
+
49
+ it "should be able to use an alternative field" do
50
+ Event.tomorrow(:field => "start_time").size.should eql(1)
51
+ end
52
+ end