ta_by_star 4.0.0

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 (61) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +59 -0
  3. data/Gemfile +18 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +616 -0
  6. data/Rakefile +18 -0
  7. data/UPGRADING +4 -0
  8. data/by_star.gemspec +34 -0
  9. data/cleaner.rb +25 -0
  10. data/lib/by_star/base.rb +76 -0
  11. data/lib/by_star/between.rb +190 -0
  12. data/lib/by_star/directional.rb +35 -0
  13. data/lib/by_star/kernel/date.rb +41 -0
  14. data/lib/by_star/kernel/in_time_zone.rb +20 -0
  15. data/lib/by_star/kernel/time.rb +41 -0
  16. data/lib/by_star/normalization.rb +156 -0
  17. data/lib/by_star/orm/active_record/by_star.rb +75 -0
  18. data/lib/by_star/orm/mongoid/by_star.rb +90 -0
  19. data/lib/by_star/orm/mongoid/reorder.rb +23 -0
  20. data/lib/by_star/version.rb +3 -0
  21. data/lib/by_star.rb +18 -0
  22. data/spec/database.yml +15 -0
  23. data/spec/fixtures/active_record/models.rb +12 -0
  24. data/spec/fixtures/active_record/schema.rb +19 -0
  25. data/spec/fixtures/mongoid/models.rb +31 -0
  26. data/spec/fixtures/shared/seeds.rb +36 -0
  27. data/spec/gemfiles/Gemfile.rails +5 -0
  28. data/spec/gemfiles/Gemfile.rails32 +7 -0
  29. data/spec/gemfiles/Gemfile.rails40 +7 -0
  30. data/spec/gemfiles/Gemfile.rails41 +7 -0
  31. data/spec/gemfiles/Gemfile.rails42 +7 -0
  32. data/spec/gemfiles/Gemfile.rails50 +7 -0
  33. data/spec/gemfiles/Gemfile.rails51 +7 -0
  34. data/spec/gemfiles/Gemfile.rails52 +7 -0
  35. data/spec/gemfiles/Gemfile.rails60 +7 -0
  36. data/spec/gemfiles/Gemfile.rails61 +7 -0
  37. data/spec/integration/active_record/active_record_spec.rb +41 -0
  38. data/spec/integration/mongoid/mongoid_spec.rb +39 -0
  39. data/spec/integration/shared/at_time.rb +53 -0
  40. data/spec/integration/shared/between_dates.rb +99 -0
  41. data/spec/integration/shared/between_times.rb +99 -0
  42. data/spec/integration/shared/by_calendar_month.rb +55 -0
  43. data/spec/integration/shared/by_cweek.rb +54 -0
  44. data/spec/integration/shared/by_day.rb +120 -0
  45. data/spec/integration/shared/by_direction.rb +126 -0
  46. data/spec/integration/shared/by_fortnight.rb +48 -0
  47. data/spec/integration/shared/by_month.rb +50 -0
  48. data/spec/integration/shared/by_quarter.rb +49 -0
  49. data/spec/integration/shared/by_week.rb +54 -0
  50. data/spec/integration/shared/by_weekend.rb +49 -0
  51. data/spec/integration/shared/by_year.rb +48 -0
  52. data/spec/integration/shared/index_scope_parameter.rb +111 -0
  53. data/spec/integration/shared/offset_parameter.rb +32 -0
  54. data/spec/integration/shared/order_parameter.rb +36 -0
  55. data/spec/integration/shared/relative.rb +174 -0
  56. data/spec/spec_helper.rb +33 -0
  57. data/spec/unit/kernel_date_spec.rb +113 -0
  58. data/spec/unit/kernel_time_spec.rb +57 -0
  59. data/spec/unit/normalization_spec.rb +384 -0
  60. data/tmp/.gitignore +1 -0
  61. metadata +298 -0
@@ -0,0 +1,76 @@
1
+ module ByStar
2
+
3
+ module Base
4
+
5
+ include ByStar::Between
6
+ include ByStar::Directional
7
+
8
+ def by_star_field(*args)
9
+ options = args.extract_options!
10
+ @by_star_start_field ||= args[0]
11
+ @by_star_end_field ||= args[1]
12
+ @by_star_offset ||= options[:offset]
13
+ @by_star_scope ||= options[:scope]
14
+ @by_star_index_scope ||= options[:index_scope]
15
+ @by_star_field_type ||= options[:field_type]
16
+ end
17
+
18
+ def by_star_offset(options = {})
19
+ (options[:offset] || @by_star_offset || 0).seconds
20
+ end
21
+
22
+ def by_star_start_field(options={})
23
+ field = options[:field] ||
24
+ options[:start_field] ||
25
+ @by_star_start_field ||
26
+ by_star_default_field
27
+ field.to_s
28
+ end
29
+
30
+ def by_star_end_field(options={})
31
+ field = options[:field] ||
32
+ options[:end_field] ||
33
+ @by_star_end_field ||
34
+ by_star_start_field
35
+ field.to_s
36
+ end
37
+
38
+ def by_star_field_type(options={})
39
+ field = options[:field_type] ||
40
+ @by_star_field_type
41
+ field.to_s
42
+ end
43
+
44
+ protected
45
+
46
+ # Wrapper function which extracts time and options for each by_star query.
47
+ # Note the following syntax examples are valid:
48
+ #
49
+ # Post.by_month # defaults to current time
50
+ # Post.by_month(2, year: 2004) # February, 2004
51
+ # Post.by_month(Time.now)
52
+ # Post.by_month(Time.now, field: "published_at")
53
+ # Post.by_month(field: "published_at")
54
+ #
55
+ def with_by_star_options(*args, &block)
56
+ options = args.extract_options!.symbolize_keys!
57
+ time = args.first || Time.zone.now
58
+ block.call(time, options)
59
+ end
60
+
61
+ def by_star_eval_index_scope(start_time, end_time, options)
62
+ value = options[:index_scope] || @by_star_index_scope
63
+ value = value.call(start_time, end_time, options) if value.is_a?(Proc)
64
+ case value
65
+ when nil, false then nil
66
+ when Time, DateTime, Date then value.in_time_zone
67
+ when ActiveSupport::Duration then start_time - value
68
+ when Numeric then start_time - value.seconds
69
+ when :beginning_of_day
70
+ offset = options[:offset] || 0
71
+ (start_time - offset).beginning_of_day + offset
72
+ else raise 'ByStar :index_scope option value is not a supported type.'
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,190 @@
1
+ module ByStar
2
+
3
+ module Between
4
+
5
+ def between_times(*args)
6
+ options = args.extract_options!.symbolize_keys!
7
+
8
+ start_time, end_time = ByStar::Normalization.extract_range(args)
9
+ offset = options[:offset] || 0
10
+ field_type = by_star_field_type(options)
11
+
12
+ if start_time.is_a?(Date)
13
+ start_time = field_type == 'date' ?
14
+ start_time :
15
+ ByStar::Normalization.apply_offset_start(start_time.in_time_zone, offset)
16
+ elsif start_time
17
+ start_time += offset.seconds
18
+ end
19
+
20
+ if end_time.is_a?(Date)
21
+ end_time = field_type == 'date' ?
22
+ end_time :
23
+ ByStar::Normalization.apply_offset_end(end_time.in_time_zone, offset)
24
+ elsif end_time
25
+ end_time += offset.seconds
26
+ end
27
+
28
+ start_field = by_star_start_field(options)
29
+ end_field = by_star_end_field(options)
30
+
31
+ scope = self
32
+ scope = if !start_time && !end_time
33
+ scope # do nothing
34
+ elsif !end_time
35
+ by_star_after_query(scope, start_field, start_time)
36
+ elsif !start_time
37
+ by_star_before_query(scope, start_field, end_time)
38
+ elsif start_field == end_field
39
+ by_star_point_query(scope, start_field, start_time, end_time)
40
+ elsif options[:strict]
41
+ by_star_span_strict_query(scope, start_field, end_field, start_time, end_time)
42
+ else
43
+ by_star_span_loose_query(scope, start_field, end_field, start_time, end_time, options)
44
+ end
45
+
46
+ scope = by_star_order(scope, options[:order]) if options[:order]
47
+ scope
48
+ end
49
+
50
+ def between_dates(*args)
51
+ options = args.extract_options!
52
+ start_date, end_date = ByStar::Normalization.extract_range(args)
53
+ start_date = ByStar::Normalization.date(start_date)
54
+ end_date = ByStar::Normalization.date(end_date)
55
+ between_times(start_date, end_date, options)
56
+ end
57
+
58
+ def at_time(*args)
59
+ with_by_star_options(*args) do |time, options|
60
+ start_field = by_star_start_field(options)
61
+ end_field = by_star_end_field(options)
62
+
63
+ scope = self
64
+ scope = if start_field == end_field
65
+ by_star_point_overlap_query(scope, start_field, time)
66
+ else
67
+ by_star_span_overlap_query(scope, start_field, end_field, time, options)
68
+ end
69
+ scope = by_star_order(scope, options[:order]) if options[:order]
70
+ scope
71
+ end
72
+ end
73
+
74
+ def by_day(*args)
75
+ with_by_star_options(*args) do |time, options|
76
+ date = ByStar::Normalization.date(time)
77
+ between_dates(date, date, options)
78
+ end
79
+ end
80
+
81
+ def by_week(*args)
82
+ with_by_star_options(*args) do |time, options|
83
+ date = ByStar::Normalization.week(time, options)
84
+ start_day = Array(options[:start_day])
85
+ between_dates(date.beginning_of_week(*start_day), date.end_of_week(*start_day), options)
86
+ end
87
+ end
88
+
89
+ def by_cweek(*args)
90
+ with_by_star_options(*args) do |time, options|
91
+ by_week(ByStar::Normalization.cweek(time, options), options)
92
+ end
93
+ end
94
+
95
+ def by_weekend(*args)
96
+ with_by_star_options(*args) do |time, options|
97
+ date = ByStar::Normalization.week(time, options)
98
+ between_dates(date.beginning_of_weekend, date.end_of_weekend, options)
99
+ end
100
+ end
101
+
102
+ def by_fortnight(*args)
103
+ with_by_star_options(*args) do |time, options|
104
+ date = ByStar::Normalization.fortnight(time, options)
105
+ between_dates(date.beginning_of_fortnight, date.end_of_fortnight, options)
106
+ end
107
+ end
108
+
109
+ def by_month(*args)
110
+ with_by_star_options(*args) do |time, options|
111
+ date = ByStar::Normalization.month(time, options)
112
+ between_dates(date.beginning_of_month, date.end_of_month, options)
113
+ end
114
+ end
115
+
116
+ def by_calendar_month(*args)
117
+ with_by_star_options(*args) do |time, options|
118
+ date = ByStar::Normalization.month(time, options)
119
+ start_day = Array(options[:start_day])
120
+ between_dates(date.beginning_of_calendar_month(*start_day), date.end_of_calendar_month(*start_day), options)
121
+ end
122
+ end
123
+
124
+ def by_quarter(*args)
125
+ with_by_star_options(*args) do |time, options|
126
+ date = ByStar::Normalization.quarter(time, options)
127
+ between_dates(date.beginning_of_quarter, date.end_of_quarter, options)
128
+ end
129
+ end
130
+
131
+ def by_year(*args)
132
+ with_by_star_options(*args) do |time, options|
133
+ date = ByStar::Normalization.year(time, options)
134
+ between_dates(date.beginning_of_year, date.to_date.end_of_year, options)
135
+ end
136
+ end
137
+
138
+ def today(options = {})
139
+ by_day(Date.current, options)
140
+ end
141
+
142
+ def yesterday(options = {})
143
+ by_day(Date.yesterday, options)
144
+ end
145
+
146
+ def tomorrow(options = {})
147
+ by_day(Date.tomorrow, options)
148
+ end
149
+
150
+ def past_day(options = {})
151
+ between_times(Time.current - 1.day, Time.current, options)
152
+ end
153
+
154
+ def past_week(options = {})
155
+ between_times(Time.current - 1.week, Time.current, options)
156
+ end
157
+
158
+ def past_fortnight(options = {})
159
+ between_times(Time.current - 2.weeks, Time.current, options)
160
+ end
161
+
162
+ def past_month(options = {})
163
+ between_times(Time.current - 1.month, Time.current, options)
164
+ end
165
+
166
+ def past_year(options = {})
167
+ between_times(Time.current - 1.year, Time.current, options)
168
+ end
169
+
170
+ def next_day(options = {})
171
+ between_times(Time.current, Time.current + 1.day, options)
172
+ end
173
+
174
+ def next_week(options = {})
175
+ between_times(Time.current, Time.current + 1.week, options)
176
+ end
177
+
178
+ def next_fortnight(options = {})
179
+ between_times(Time.current, Time.current + 2.weeks, options)
180
+ end
181
+
182
+ def next_month(options = {})
183
+ between_times(Time.current, Time.current + 1.month, options)
184
+ end
185
+
186
+ def next_year(options = {})
187
+ between_times(Time.current, Time.current + 1.year, options)
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,35 @@
1
+ module ByStar
2
+
3
+ module Directional
4
+
5
+ def before(*args)
6
+ with_by_star_options(*args) do |time, options|
7
+ field = by_star_start_field(options)
8
+ time = ByStar::Normalization.time(time)
9
+ by_star_before_query(self, field, time)
10
+ end
11
+ end
12
+ alias_method :before_now, :before
13
+
14
+ def after(*args)
15
+ with_by_star_options(*args) do |time, options|
16
+ field = by_star_start_field(options)
17
+ time = ByStar::Normalization.time(time)
18
+ by_star_after_query(self, field, time)
19
+ end
20
+ end
21
+ alias_method :after_now, :after
22
+
23
+ def oldest(*args)
24
+ with_by_star_options(*args) do |time, options|
25
+ oldest_query(options)
26
+ end
27
+ end
28
+
29
+ def newest(*args)
30
+ with_by_star_options(*args) do |time, options|
31
+ newest_query(options)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,41 @@
1
+ module ByStar
2
+
3
+ module Kernel
4
+
5
+ module Date
6
+
7
+ # A "Weekend" is defined as beginning of Saturday to end of Sunday.
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: 5)
12
+ end
13
+
14
+ def end_of_weekend
15
+ beginning_of_weekend + 1
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 + 14 * ((self - beginning_of_year) / 14).to_i
22
+ end
23
+
24
+ def end_of_fortnight
25
+ beginning_of_fortnight + 13
26
+ end
27
+
28
+ # A "Calendar Month" is defined as a month as it appears on a calendar, including days form
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
+ ::Date.__send__(:include, ByStar::Kernel::Date)
@@ -0,0 +1,20 @@
1
+ module ByStar
2
+
3
+ module Kernel
4
+
5
+ module InTimeZone
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ if method_defined?(:to_time_in_current_zone) && !method_defined?(:in_time_zone)
10
+ alias_method :in_time_zone, :to_time_in_current_zone
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ ::Date.__send__(:include, ByStar::Kernel::InTimeZone)
18
+ ::Time.__send__(:include, ByStar::Kernel::InTimeZone)
19
+ ::DateTime.__send__(:include, ByStar::Kernel::InTimeZone)
20
+ ::ActiveSupport::TimeWithZone.__send__(:include, ByStar::Kernel::InTimeZone)
@@ -0,0 +1,41 @@
1
+ module ByStar
2
+
3
+ module Kernel
4
+
5
+ module Time
6
+
7
+ # A "Weekend" is defined as beginning of Saturday to end of Sunday.
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: 5)
12
+ end
13
+
14
+ def end_of_weekend
15
+ (beginning_of_weekend + 47.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 + 1.fortnight * ((self - beginning_of_year) / 1.fortnight).to_i).beginning_of_day
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 form
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,156 @@
1
+ module ByStar
2
+
3
+ class ParseError < StandardError; end
4
+
5
+ module Normalization
6
+
7
+ class << self
8
+
9
+ def date(value)
10
+ value = parse_time(value) if value.is_a?(String)
11
+ value = value.try(:in_time_zone) unless value.is_a?(Date)
12
+ value.try(:to_date)
13
+ end
14
+
15
+ def time(value)
16
+ value = parse_time(value) if value.is_a?(String)
17
+ value.try(:in_time_zone)
18
+ end
19
+
20
+ def week(value, options={})
21
+ value = try_string_to_int(value)
22
+ case value
23
+ when Integer then week_integer(value, options)
24
+ else date(value)
25
+ end
26
+ end
27
+
28
+ def week_integer(value, options={})
29
+ raise ParseError, 'Week number must be between 0 and 52' unless value.in?(0..52)
30
+ time = Time.zone.local(options[:year] || Time.zone.now.year)
31
+ time.beginning_of_year + value.to_i.weeks
32
+ end
33
+
34
+ def cweek(value, options={})
35
+ _value = value
36
+ if _value.is_a?(Integer)
37
+ raise ParseError, 'cweek number must be between 1 and 53' unless value.in?(1..53)
38
+ _value -= 1
39
+ end
40
+ week(_value, options)
41
+ end
42
+
43
+ def fortnight(value, options={})
44
+ value = try_string_to_int(value)
45
+ case value
46
+ when Integer then fortnight_integer(value, options)
47
+ else date(value)
48
+ end
49
+ end
50
+
51
+ def fortnight_integer(value, options={})
52
+ raise ParseError, 'Fortnight number must be between 0 and 26' unless value.in?(0..26)
53
+ time = Time.zone.local(options[:year] || Time.zone.now.year)
54
+ time + (value * 2).weeks
55
+ end
56
+
57
+ def quarter(value, options={})
58
+ value = try_string_to_int(value)
59
+ case value
60
+ when Integer then quarter_integer(value, options)
61
+ else date(value)
62
+ end
63
+ end
64
+
65
+ def quarter_integer(value, options={})
66
+ raise ParseError, 'Quarter number must be between 1 and 4' unless value.in?(1..4)
67
+ time = Time.zone.local(options[:year] || Time.zone.now.year)
68
+ time.beginning_of_year + ((value - 1) * 3).months
69
+ end
70
+
71
+ def month(value, options={})
72
+ value = try_string_to_int(value)
73
+ case value
74
+ when Integer, String then month_integer(value, options)
75
+ else date(value)
76
+ end
77
+ end
78
+
79
+ def month_integer(value, options={})
80
+ year = options[:year] || Time.zone.now.year
81
+ Time.zone.parse "#{year}-#{value}-01"
82
+ rescue
83
+ raise ParseError, 'Month must be a number between 1 and 12 or a month name'
84
+ end
85
+
86
+ def year(value, options={})
87
+ value = try_string_to_int(value)
88
+ case value
89
+ when Integer then year_integer(value)
90
+ else date(value)
91
+ end
92
+ end
93
+
94
+ def year_integer(value)
95
+ Time.zone.local(extrapolate_year(value))
96
+ end
97
+
98
+ def extrapolate_year(value)
99
+ case value.to_i
100
+ when 0..69
101
+ 2000 + value
102
+ when 70..99
103
+ 1900 + value
104
+ else
105
+ value.to_i
106
+ end
107
+ end
108
+
109
+ def try_string_to_int(value)
110
+ value.is_a?(String) ? Integer(value) : value
111
+ rescue
112
+ value
113
+ end
114
+
115
+ def time_in_units(seconds)
116
+ days = seconds / 1.day
117
+ time = Time.at(seconds).utc
118
+ { days: days, hour: time.hour, min: time.min, sec: time.sec }
119
+ end
120
+
121
+ def apply_offset_start(time, offset)
122
+ units = time_in_units(offset)
123
+ time += units.delete(:days).days
124
+ time.change(units)
125
+ end
126
+
127
+ def apply_offset_end(time, offset)
128
+ units = time_in_units(offset)
129
+ time += units.delete(:days).days
130
+ (time + 1.day).change(units) - 1.second
131
+ end
132
+
133
+ def extract_range(args)
134
+ case args[0]
135
+ when Array, Range then [args[0].first, args[0].last]
136
+ else args[0..1]
137
+ end
138
+ end
139
+
140
+ private
141
+
142
+ def parse_time(value)
143
+ defined?(Chronic) ? parse_time_chronic(value) : parse_time_fallback(value)
144
+ end
145
+
146
+ def parse_time_chronic(value)
147
+ Chronic.time_class = Time.zone
148
+ Chronic.parse(value) || raise(ByStar::ParseError, "Chronic could not parse String #{value.inspect}")
149
+ end
150
+
151
+ def parse_time_fallback(value)
152
+ Time.zone.parse(value) || raise(ByStar::ParseError, "Cannot parse String #{value.inspect}")
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,75 @@
1
+ module ByStar
2
+ module ActiveRecord
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ include ::ByStar::Base
7
+
8
+ protected
9
+
10
+ def by_star_default_field
11
+ "#{self.table_name}.created_at"
12
+ end
13
+
14
+ def by_star_point_query(scope, field, start_time, end_time)
15
+ scope.where("#{field} >= ? AND #{field} <= ?", start_time, end_time)
16
+ end
17
+
18
+ def by_star_span_strict_query(scope, start_field, end_field, start_time, end_time)
19
+ scope.where("#{start_field} >= ? AND #{start_field} <= ? AND #{end_field} >= ? AND #{end_field} <= ?", start_time, end_time, start_time, end_time)
20
+ end
21
+
22
+ def by_star_span_loose_query(scope, start_field, end_field, start_time, end_time, options)
23
+ index_scope = by_star_eval_index_scope(start_time, end_time, options)
24
+ scope = scope.where("#{end_field} > ? AND #{start_field} < ?", start_time, end_time)
25
+ scope = scope.where("#{start_field} >= ?", index_scope) if index_scope
26
+ scope
27
+ end
28
+
29
+ def by_star_point_overlap_query(scope, field, time)
30
+ scope.where("#{field} = ?", time)
31
+ end
32
+
33
+ def by_star_span_overlap_query(scope, start_field, end_field, time, options)
34
+ index_scope = by_star_eval_index_scope(time, time, options)
35
+ scope = scope.where("#{end_field} > ? AND #{start_field} <= ?", time, time)
36
+ scope = scope.where("#{start_field} >= ?", index_scope) if index_scope
37
+ scope
38
+ end
39
+
40
+ def by_star_before_query(scope, field, time)
41
+ scope.where("#{field} <= ?", time)
42
+ end
43
+
44
+ def by_star_after_query(scope, field, time)
45
+ scope.where("#{field} >= ?", time)
46
+ end
47
+
48
+ def by_star_order(scope, order)
49
+ scope.order(order)
50
+ end
51
+
52
+ def oldest_query(options={})
53
+ field = by_star_start_field(options)
54
+ reorder("#{field} ASC").first
55
+ end
56
+
57
+ def newest_query(options={})
58
+ field = by_star_start_field(options)
59
+ reorder("#{field} DESC").first
60
+ end
61
+ end
62
+
63
+ def previous(options={})
64
+ field = self.class.by_star_start_field(options)
65
+ value = self.send(field.split(".").last)
66
+ self.class.where("#{field} < ?", value).reorder("#{field} DESC").first
67
+ end
68
+
69
+ def next(options={})
70
+ field = self.class.by_star_start_field(options)
71
+ value = self.send(field.split(".").last)
72
+ self.class.where("#{field} > ?", value).reorder("#{field} ASC").first
73
+ end
74
+ end
75
+ end