by_star 2.1.0.beta2 → 2.2.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/.gitignore +5 -4
  2. data/.travis.yml +35 -25
  3. data/CHANGELOG.md +21 -0
  4. data/Gemfile +25 -4
  5. data/MIT-LICENSE +20 -20
  6. data/{README.markdown → README.md} +414 -348
  7. data/Rakefile +18 -2
  8. data/UPGRADING +10 -0
  9. data/by_star.gemspec +32 -31
  10. data/cleaner.rb +24 -24
  11. data/lib/by_star.rb +15 -71
  12. data/lib/by_star/base.rb +53 -0
  13. data/lib/by_star/between.rb +80 -0
  14. data/lib/by_star/directional.rb +21 -0
  15. data/lib/by_star/kernel.rb +41 -0
  16. data/lib/by_star/normalization.rb +118 -0
  17. data/lib/by_star/orm/active_record/by_star.rb +52 -0
  18. data/lib/by_star/orm/mongoid/by_star.rb +59 -0
  19. data/lib/by_star/version.rb +3 -3
  20. data/spec/database.yml +15 -15
  21. data/spec/fixtures/active_record/models.rb +6 -13
  22. data/spec/fixtures/active_record/schema.rb +11 -36
  23. data/spec/fixtures/mongoid/models.rb +15 -65
  24. data/spec/fixtures/shared/seeds.rb +16 -35
  25. data/spec/integration/active_record/active_record_spec.rb +50 -0
  26. data/spec/integration/mongoid/mongoid_spec.rb +43 -0
  27. data/spec/integration/shared/by_calendar_month.rb +55 -0
  28. data/spec/integration/shared/by_day.rb +84 -0
  29. data/spec/integration/shared/by_direction.rb +55 -0
  30. data/spec/integration/shared/by_fortnight.rb +48 -0
  31. data/spec/integration/shared/by_month.rb +50 -0
  32. data/spec/integration/shared/by_quarter.rb +49 -0
  33. data/spec/integration/shared/by_week.rb +54 -0
  34. data/spec/integration/shared/by_weekend.rb +49 -0
  35. data/spec/integration/shared/by_year.rb +48 -0
  36. data/spec/integration/shared/offset_parameter.rb +31 -0
  37. data/spec/spec_helper.rb +29 -35
  38. data/spec/unit/kernel_time_spec.rb +57 -0
  39. data/spec/unit/normalization_spec.rb +255 -0
  40. metadata +131 -56
  41. data/Gemfile.lock +0 -94
  42. data/lib/by_star/by_day.rb +0 -34
  43. data/lib/by_star/by_direction.rb +0 -52
  44. data/lib/by_star/by_fortnight.rb +0 -58
  45. data/lib/by_star/by_month.rb +0 -47
  46. data/lib/by_star/by_quarter.rb +0 -32
  47. data/lib/by_star/by_week.rb +0 -32
  48. data/lib/by_star/by_weekend.rb +0 -20
  49. data/lib/by_star/by_year.rb +0 -62
  50. data/lib/by_star/instance_methods.rb +0 -13
  51. data/lib/by_star/time_ext.rb +0 -21
  52. data/lib/mongoid/by_star.rb +0 -83
  53. data/spec/by_star/active_record/active_record_spec.rb +0 -50
  54. data/spec/by_star/mongoid/mongoid_spec.rb +0 -44
  55. data/spec/by_star/shared/by_day.rb +0 -62
  56. data/spec/by_star/shared/by_direction.rb +0 -85
  57. data/spec/by_star/shared/by_fortnight.rb +0 -47
  58. data/spec/by_star/shared/by_month.rb +0 -109
  59. data/spec/by_star/shared/by_quarter.rb +0 -33
  60. data/spec/by_star/shared/by_week.rb +0 -41
  61. data/spec/by_star/shared/by_weekend.rb +0 -13
  62. data/spec/by_star/shared/by_year.rb +0 -54
  63. data/spec/time_ext_spec.rb +0 -10
data/Rakefile CHANGED
@@ -1,2 +1,18 @@
1
- require 'bundler'
2
- Bundler::GemHelper.install_tasks
1
+ require 'bundler'
2
+ require 'rspec/core/rake_task'
3
+
4
+ Bundler::GemHelper.install_tasks
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ def orm_test(orm)
8
+ RSpec::Core::RakeTask.new(orm) do |task|
9
+ task.pattern = "./spec/{unit,integration/#{orm}}/{,/*/**}/*_spec.rb"
10
+ end
11
+ end
12
+
13
+ namespace :spec do
14
+ orm_test 'active_record'
15
+ orm_test 'mongoid'
16
+ end
17
+
18
+ task default: :spec
@@ -0,0 +1,10 @@
1
+ Upgrading to ByStar 2.2.0
2
+ -------------------------
3
+
4
+ * For Mongoid only, ByStar's `.between` method has been removed and replaced with `.between_times`
5
+ (previously `.between_times` was an alias to `.between`). Mongoid already provides a native `.between`
6
+ finder method that we do not want to mask. ActiveRecord users may continue to use either method.
7
+
8
+ * Chronic gem (used for time string parsing) has been removed as a hard dependency for ByStar,
9
+ however it is still supported. If you would like to use Chronic with ByStar, please explicitly
10
+ include `gem chronic` into your Gemfile.
@@ -1,31 +1,32 @@
1
- # -*- encoding: utf-8 -*-
2
- require File.expand_path("../lib/by_star/version", __FILE__)
3
-
4
- Gem::Specification.new do |s|
5
- s.name = "by_star"
6
- s.version = ByStar::VERSION
7
- s.platform = Gem::Platform::RUBY
8
- s.authors = ["Ryan Bigg"]
9
- s.email = ["radarlistener@gmail.com"]
10
- s.homepage = "http://github.com/radar/by_star"
11
- s.summary = "ActiveRecord extension for easier date scopes and time ranges"
12
- s.description = "ActiveRecord extension for easier date scopes and time ranges"
13
-
14
- s.required_rubygems_version = ">= 1.3.6"
15
- s.rubyforge_project = "by_star"
16
-
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
- s.add_development_dependency "mongoid", "~> 3.0" if Gem::Version.create(RUBY_VERSION.dup) >= Gem::Version.create('1.9.3')
24
-
25
- s.add_dependency "activerecord", "~> 3.0"
26
- s.add_dependency "chronic"
27
-
28
- s.files = `git ls-files`.split("\n")
29
- s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
30
- s.require_path = 'lib'
31
- end
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path("../lib/by_star/version", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "by_star"
6
+ s.version = ByStar::VERSION
7
+ s.authors = ["Ryan Bigg", "Johnny Shields"]
8
+ s.email = ["radarlistener@gmail.com"]
9
+ s.homepage = "http://github.com/radar/by_star"
10
+ s.summary = "ActiveRecord and Mongoid extension for easier date scopes and time ranges"
11
+ s.description = "ActiveRecord and Mongoid extension for easier date scopes and time ranges"
12
+
13
+ s.post_install_message = File.read('UPGRADING') if File.exists?('UPGRADING')
14
+
15
+ s.add_dependency "activesupport"
16
+
17
+ s.add_development_dependency "chronic"
18
+ s.add_development_dependency "bundler", ">= 1.0.0"
19
+ s.add_development_dependency "sqlite3"
20
+ s.add_development_dependency "activerecord"
21
+ s.add_development_dependency "mongoid"
22
+ s.add_development_dependency "pg"
23
+ s.add_development_dependency "mysql2"
24
+ s.add_development_dependency "rspec-rails", "~> 2.14"
25
+ s.add_development_dependency "timecop", "~> 0.3"
26
+ s.add_development_dependency "pry"
27
+
28
+ s.files = `git ls-files`.split($/)
29
+ s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
30
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
31
+ s.require_paths = ['lib']
32
+ end
data/cleaner.rb CHANGED
@@ -1,25 +1,25 @@
1
- files = Dir["**/*"]
2
- ignored_files = [
3
- /log\/.*/,
4
- ]
5
-
6
- files.delete_if do |file|
7
- if File.directory?(file)
8
- true
9
- else
10
- ignored_files.any? do |condition|
11
- if condition.is_a?(String)
12
- file == condition
13
- else
14
- condition.match(file)
15
- end
16
- end || false
17
- end
18
- end
19
-
20
- for file in files - ignored_files
21
- if File.file?(file)
22
- lines = File.readlines(file).map { |line| line.gsub(/^\s+$/, "\n") }
23
- File.open(file, "w+") { |f| f.write(lines.join) }
24
- end
1
+ files = Dir["**/*"]
2
+ ignored_files = [
3
+ /log\/.*/,
4
+ ]
5
+
6
+ files.delete_if do |file|
7
+ if File.directory?(file)
8
+ true
9
+ else
10
+ ignored_files.any? do |condition|
11
+ if condition.is_a?(String)
12
+ file == condition
13
+ else
14
+ condition.match(file)
15
+ end
16
+ end || false
17
+ end
18
+ end
19
+
20
+ for file in files - ignored_files
21
+ if File.file?(file)
22
+ lines = File.readlines(file).map { |line| line.gsub(/^\s+$/, "\n") }
23
+ File.open(file, "w+") { |f| f.write(lines.join) }
24
+ end
25
25
  end
@@ -1,71 +1,15 @@
1
- require 'chronic'
2
-
3
- require 'by_star/time_ext'
4
- require 'by_star/instance_methods'
5
-
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
- require 'by_star/by_quarter'
14
-
15
- module ByStar
16
-
17
- def by_star_field(field=nil)
18
- @by_star_field ||= field
19
- @by_star_field || "#{self.table_name}.created_at"
20
- end
21
-
22
- include ByDirection
23
- include ByYear
24
- include ByMonth
25
- include ByFortnight
26
- include ByWeek
27
- include ByWeekend
28
- include ByDay
29
- include ByQuarter
30
-
31
- class ParseError < StandardError
32
-
33
- end
34
-
35
- # Returns all records between a given start and finish time.
36
- #
37
- # Currently only supports Time objects.
38
- def between(start, finish, options={})
39
- field = options[:field] || by_star_field
40
- scope = where("#{field} >= ? AND #{field} <= ?",
41
- start, finish)
42
- scope = scope.order(options[:order]) if options[:order]
43
- scope
44
- end
45
- alias_method :between_times, :between
46
-
47
- private
48
-
49
- # Used inside the by_* methods to determine what kind of object "time" is.
50
- # These methods take the result of the time_klass method, and call other methods
51
- # using it, such as by_year_Time and by_year_String.
52
- def time_klass(time)
53
- case time
54
- when ActiveSupport::TimeWithZone
55
- Time
56
- else
57
- time.class
58
- end
59
- end
60
-
61
- end
62
-
63
- if defined?(ActiveRecord)
64
- ActiveRecord::Base.send :extend, ByStar
65
- ActiveRecord::Relation.send :extend, ByStar
66
- ActiveRecord::Base.send :include, ByStar::InstanceMethods
67
- end
68
-
69
- if defined?(Mongoid)
70
- require 'mongoid/by_star'
71
- end
1
+ require 'by_star/kernel'
2
+ require 'by_star/normalization'
3
+ require 'by_star/between'
4
+ require 'by_star/directional'
5
+ require 'by_star/base'
6
+
7
+ if defined?(ActiveRecord)
8
+ require 'by_star/orm/active_record/by_star'
9
+ ActiveRecord::Base.send :include, ByStar::ActiveRecord
10
+ ActiveRecord::Relation.send :extend, ByStar::ActiveRecord::ClassMethods
11
+ end
12
+
13
+ if defined?(Mongoid)
14
+ require 'by_star/orm/mongoid/by_star'
15
+ end
@@ -0,0 +1,53 @@
1
+ module ByStar
2
+
3
+ module Base
4
+
5
+ include ByStar::Between
6
+ include ByStar::Directional
7
+
8
+ def by_star_field(start_field = nil, end_field = nil, options = {})
9
+ @by_star_start_field ||= start_field
10
+ @by_star_end_field ||= end_field
11
+ @by_star_offset ||= options[:offset]
12
+ end
13
+
14
+ def by_star_offset(options = {})
15
+ (options[:offset] || @by_star_offset || 0).seconds
16
+ end
17
+
18
+ def by_star_start_field(options={})
19
+ field = options[:field] ||
20
+ options[:start_field] ||
21
+ @by_star_start_field ||
22
+ by_star_default_field
23
+ field.to_s
24
+ end
25
+
26
+ def by_star_end_field(options={})
27
+ field = options[:field] ||
28
+ options[:end_field] ||
29
+ @by_star_end_field ||
30
+ by_star_start_field
31
+ field.to_s
32
+ end
33
+
34
+ protected
35
+
36
+ # Wrapper function which extracts time and options for each by_star query.
37
+ # Note the following syntax examples are valid:
38
+ #
39
+ # Post.by_month # defaults to current time
40
+ # Post.by_month(2, :year => 2004) # February, 2004
41
+ # Post.by_month(Time.now)
42
+ # Post.by_month(Time.now, :field => "published_at")
43
+ # Post.by_month(:field => "published_at")
44
+ #
45
+ def with_by_star_options(*args, &block)
46
+ options = args.extract_options!.symbolize_keys!
47
+ time = args.first
48
+ time ||= Time.zone.local(options[:year]) if options[:year]
49
+ time ||= Time.zone.now
50
+ block.call(time, options)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,80 @@
1
+ module ByStar
2
+
3
+ module Between
4
+
5
+ def between_times(start, finish, options={})
6
+ offset = by_star_offset(options)
7
+ between_times_query(start + offset, finish + offset, options)
8
+ end
9
+
10
+ def by_day(*args)
11
+ with_by_star_options(*args) do |time, options|
12
+ time = ByStar::Normalization.time(time)
13
+ between_times(time.beginning_of_day, time.end_of_day, options)
14
+ end
15
+ end
16
+
17
+ def today(options={})
18
+ by_day(Time.zone.now, options)
19
+ end
20
+
21
+ def yesterday(options={})
22
+ by_day(Time.zone.now.yesterday, options)
23
+ end
24
+
25
+ def tomorrow(options={})
26
+ by_day(Time.zone.now.tomorrow, options)
27
+ end
28
+
29
+ def by_week(*args)
30
+ with_by_star_options(*args) do |time, options|
31
+ time = ByStar::Normalization.week(time, options)
32
+ start_day = Array(options[:start_day])
33
+ between_times(time.beginning_of_week(*start_day), time.end_of_week(*start_day), options)
34
+ end
35
+ end
36
+
37
+ def by_weekend(*args)
38
+ with_by_star_options(*args) do |time, options|
39
+ time = ByStar::Normalization.week(time, options)
40
+ between_times(time.beginning_of_weekend, time.end_of_weekend, options)
41
+ end
42
+ end
43
+
44
+ def by_fortnight(*args)
45
+ with_by_star_options(*args) do |time, options|
46
+ time = ByStar::Normalization.fortnight(time, options)
47
+ between_times(time.beginning_of_fortnight, time.end_of_fortnight, options)
48
+ end
49
+ end
50
+
51
+ def by_month(*args)
52
+ with_by_star_options(*args) do |time, options|
53
+ time = ByStar::Normalization.month(time, options)
54
+ between_times(time.beginning_of_month, time.end_of_month, options)
55
+ end
56
+ end
57
+
58
+ def by_calendar_month(*args)
59
+ with_by_star_options(*args) do |time, options|
60
+ time = ByStar::Normalization.month(time, options)
61
+ start_day = Array(options[:start_day])
62
+ between_times(time.beginning_of_calendar_month(*start_day), time.end_of_calendar_month(*start_day), options)
63
+ end
64
+ end
65
+
66
+ def by_quarter(*args)
67
+ with_by_star_options(*args) do |time, options|
68
+ time = ByStar::Normalization.quarter(time, options)
69
+ between_times(time.beginning_of_quarter, time.end_of_quarter, options)
70
+ end
71
+ end
72
+
73
+ def by_year(*args)
74
+ with_by_star_options(*args) do |time, options|
75
+ time = ByStar::Normalization.year(time, options)
76
+ between_times(time.beginning_of_year, time.end_of_year, options)
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,21 @@
1
+ module ByStar
2
+
3
+ module Directional
4
+
5
+ def before(*args)
6
+ with_by_star_options(*args) do |time, options|
7
+ time = ByStar::Normalization.time(time)
8
+ before_query(time, options)
9
+ end
10
+ end
11
+ alias_method :before_now, :before
12
+
13
+ def after(*args)
14
+ with_by_star_options(*args) do |time, options|
15
+ time = ByStar::Normalization.time(time)
16
+ after_query(time, options)
17
+ end
18
+ end
19
+ alias_method :after_now, :after
20
+ end
21
+ end
@@ -0,0 +1,41 @@
1
+ module ByStar
2
+
3
+ module Kernel
4
+
5
+ module Time
6
+
7
+ # A "Weekend" is defined as the 60-hour period from 15:00 Friday to 03:00 Monday.
8
+ # The weekend for a given date will be the the next weekend if the day Mon-Thurs,
9
+ # otherwise the current weekend if the day is Fri-Sun.
10
+ def beginning_of_weekend
11
+ beginning_of_week(:monday).advance(:days => 4) + 15.hours
12
+ end
13
+
14
+ def end_of_weekend
15
+ (beginning_of_weekend + 59.hours).end_of_hour
16
+ end
17
+
18
+ # A "Fortnight" is defined as a two week period, with the first fortnight of the
19
+ # year beginning on 1st January.
20
+ def beginning_of_fortnight
21
+ beginning_of_year + ((self - beginning_of_year) / 2.weeks).to_i * 2.weeks
22
+ end
23
+
24
+ def end_of_fortnight
25
+ (beginning_of_fortnight + 13.days).end_of_day
26
+ end
27
+
28
+ # A "Calendar Month" is defined as a month as it appears on a calendar, including days
29
+ # previous/following months which are part of the first/last weeks of the given month.
30
+ def beginning_of_calendar_month(*args)
31
+ beginning_of_month.beginning_of_week(*args)
32
+ end
33
+
34
+ def end_of_calendar_month(*args)
35
+ end_of_month.end_of_week(*args)
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ ::Time.__send__(:include, ByStar::Kernel::Time)
@@ -0,0 +1,118 @@
1
+ module ByStar
2
+
3
+ class ParseError < StandardError; end
4
+
5
+ module Normalization
6
+
7
+ class << self
8
+
9
+ def time(value)
10
+ case value
11
+ when String then time_string(value)
12
+ when DateTime then value.to_time
13
+ when Date then value.to_time_in_current_zone
14
+ else value
15
+ end
16
+ end
17
+
18
+ def time_string(value)
19
+ defined?(Chronic) ? time_string_chronic(value) : time_string_fallback(value)
20
+ end
21
+
22
+ def time_string_chronic(value)
23
+ Chronic.time_class = Time.zone
24
+ Chronic.parse(value) || raise(ByStar::ParseError, "Chronic could not parse String #{value.inspect}")
25
+ end
26
+
27
+ def time_string_fallback(value)
28
+ Time.zone.parse(value) || raise(ByStar::ParseError, "Cannot parse String #{value.inspect}")
29
+ end
30
+
31
+ def week(value, options={})
32
+ value = try_string_to_int(value)
33
+ case value
34
+ when Fixnum then week_fixnum(value, options)
35
+ else time(value)
36
+ end
37
+ end
38
+
39
+ def week_fixnum(value, options={})
40
+ raise ParseError, 'Week number must be between 0 and 52' unless value.in?(0..52)
41
+ time = Time.zone.local(options[:year] || Time.zone.now.year)
42
+ time.beginning_of_year + value.to_i.weeks
43
+ end
44
+
45
+ def fortnight(value, options={})
46
+ value = try_string_to_int(value)
47
+ case value
48
+ when Fixnum then fortnight_fixnum(value, options)
49
+ else time(value)
50
+ end
51
+ end
52
+
53
+ def fortnight_fixnum(value, options={})
54
+ raise ParseError, 'Fortnight number must be between 0 and 26' unless value.in?(0..26)
55
+ time = Time.zone.local(options[:year] || Time.zone.now.year)
56
+ time + (value * 2).weeks
57
+ end
58
+
59
+ def quarter(value, options={})
60
+ value = try_string_to_int(value)
61
+ case value
62
+ when Fixnum then quarter_fixnum(value, options)
63
+ else time(value)
64
+ end
65
+ end
66
+
67
+ def quarter_fixnum(value, options={})
68
+ raise ParseError, 'Quarter number must be between 1 and 4' unless value.in?(1..4)
69
+ time = Time.zone.local(options[:year] || Time.zone.now.year)
70
+ time.beginning_of_year + ((value - 1) * 3).months
71
+ end
72
+
73
+ def month(value, options={})
74
+ value = try_string_to_int(value)
75
+ case value
76
+ when Fixnum, String then month_fixnum(value, options)
77
+ else time(value)
78
+ end
79
+ end
80
+
81
+ def month_fixnum(value, options={})
82
+ year = options[:year] || Time.zone.now.year
83
+ Time.zone.parse "#{year}-#{value}-01"
84
+ rescue
85
+ raise ParseError, 'Month must be a number between 1 and 12 or a month name'
86
+ end
87
+
88
+ def year(value, options={})
89
+ value = try_string_to_int(value)
90
+ case value
91
+ when Fixnum then year_fixnum(value)
92
+ else time(value)
93
+ end
94
+ end
95
+
96
+ def year_fixnum(value)
97
+ Time.zone.local(extrapolate_year(value))
98
+ end
99
+
100
+ def extrapolate_year(value)
101
+ case value.to_i
102
+ when 0..69
103
+ 2000 + value
104
+ when 70..99
105
+ 1900 + value
106
+ else
107
+ value.to_i
108
+ end
109
+ end
110
+
111
+ def try_string_to_int(value)
112
+ value.is_a?(String) ? Integer(value) : value
113
+ rescue
114
+ value
115
+ end
116
+ end
117
+ end
118
+ end