by_star 2.1.0.beta2 → 2.2.0.rc1

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