by_star 1.0.1 → 2.0.0.beta1

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/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