Code Monkey home page Code Monkey logo

by_star's Introduction

ByStar

Build Status Code Climate

ByStar (by_*) allows you easily and reliably query ActiveRecord and Mongoid objects based on time.

Examples

Post.by_year(2013)                           # all posts in 2013
Post.before(Date.today)                      # all posts for before today
Post.yesterday                               # all posts for yesterday
Post.between_times(Time.zone.now - 3.hours,  # all posts in last 3 hours
                   Time.zone.now)
@post.next                                   # next post after a given post

Installation

Install this gem by adding this to your Gemfile:

gem 'by_star', git: 'https://github.com/radar/by_star'

Then run bundle install

If you are using ActiveRecord, you're done!

Mongoid users, please include the Mongoid::ByStar module for each model you wish to use the functionality. This is the convention among Mongoid plugins.

class MyModel
  include Mongoid::Document
  include Mongoid::ByStar

Finder Methods

Base Scopes

ByStar adds the following finder scopes (class methods) to your model to query time ranges. These accept a Date, Time, or DateTime object as an argument, which defaults to Time.zone.now if not specified:

Scope Meaning
between_times(start_time, end_time) Finds all records occurring between two given times.
between_dates(start_date, end_date) Finds all records occurring between two given dates, from beginning of start_date until end of end_date.
before(end_time) Finds all records occurring before the given time.
after(start_time) Finds all records occurring after the given time.
at_time(time) Finds all records occurring exactly at the given time, or which overlap the time in the case of "timespan"-type object (see below)

between_times and between_dates supports alternate argument forms:

  • between_times(Range)
  • between_times(Array)
  • between_times(start_time, nil) - same as after(start_time)
  • between_times(nil, end_time) - same as before(end_time)

Time Range Scopes

ByStar adds additional shortcut scopes based on commonly used time ranges. See sections below for detailed argument usage of each:

Scope Meaning
by_day Query by a given date.
by_week Allows zero-based week value from 0 to 52.
by_cweek Allows one-based week value from 1 to 53.
by_weekend Saturday and Sunday only of the given week.
by_fortnight A two-week period, with the first fortnight of the year beginning on 1st January.
by_month Query by month. Allows integer arg, e.g. 11 for November.
by_calendar_month Month as it appears on a calendar; days form previous/following months which are part of the first/last weeks of the given month.
by_quarter 3-month intervals of the year.
by_year Query by year. Allows integer arg, e.g. 2017.

Relative Scopes

ByStar also adds scopes which are relative to the current time. Note the past_* and next_* methods represent a time distance from current time (Time.zone.now), and do not strictly end/begin evenly on a calendar week/month/year (unlike by_* methods which do.)

Scope Meaning
today Finds all occurrences on today's date.
yesterday Finds all occurrences on yesterday's date.
tomorrow Finds all occurrences on tomorrow's date.
past_day Prior 24-hour period from current time.
past_week Prior 7-day period from current time.
past_fortnight Prior 14-day period from current time.
past_month Prior 30-day period from current time.
past_year Prior 365-day period from current time.
next_day Subsequent 24-hour period from current time.
next_week Subsequent 7-day period from current time.
next_fortnight Subsequent 14-day period from current time.
next_month Subsequent 30-day period from current time.
next_year Subsequent 365-day period from current time.

Superlative Finders

Find the oldest or newest records. Returns an object instance (not a relation):

  • newest
  • oldest

Instance Methods

In addition, ByStar adds instance methods to return the next / previous record in the timewise sequence. Returns an object instance (not a relation):

  • object.next
  • object.previous

Kernel Extensions

ByStar extends the kernel Date, Time, and DateTime objects with the following instance methods, which mirror the ActiveSupport methods beginning_of_day, end_of_week, etc:

  • beginning_of_weekend
  • end_of_weekend
  • beginning_of_fortnight
  • end_of_fortnight
  • beginning_of_calendar_month
  • end_of_calendar_month

Lastly, ByStar aliases Rails 3 Date#to_time_in_current_zone to the Rails 4 syntax #in_time_zone, if it has not already been defined.

Usage

Setting the Query Field

By default, ByStar assumes you will use the created_at field to query objects by time. You may specify an alternate field on all query methods as follows:

Post.by_month("January", field: :updated_at)

Alternatively, you may set a default in your model using the by_star_field macro:

class Post < ActiveRecord::Base
  by_star_field :updated_at
end

Scoping the Query

All ByStar methods (except oldest, newest, previous, next) return an ActiveRecord::Relation (or Mongoid::Criteria) which can be daisy-chained with other scopes/finder methods:

Post.by_month.your_scope
Post.by_month(1).include(:tags).where("tags.name" => "ruby")

Want to count records? Simple:

Post.by_month.count

Timezone Handling

ByStar date-range finders will use value of Time.zone to evaluate the args. This may cause unexpected behavior when use Time values in timezones other than Time.zone.

Time.zone = 'Australia/Sydney'
Post.by_day('2020-04-05 18:00:00 EST')
#=> Returns Apr 6th, 0:00 until Apr 6th, 23:59 in Sydney timezone.

:offset Option

All ByStar finders support an :offset option which is applied to time period of the query condition. This is useful in cases where the daily cycle occurs at a time other than midnight.

For example, if you'd like to find all Posts from 9:00 on 2014-03-05 until 8:59:59.999 on 2014-03-06, you can do:

Post.by_day('2014-03-05', offset: 9.hours)

Note: When passing offset in date finders, it will set the hour, minute, and second on the queried date in order to properly handle DST transitions. Example:

Time.zone = 'Australia/Sydney'
Post.by_day('2020-04-05', offset: 9.hours)
#=> Returns Apr 5th, 09:00 until Apr 6th, 08:59

Timespan Objects

If your object has both a start and end time, you may pass both params to by_star_field:

by_star_field :start_time, :end_time

By default, ByStar queries will return all objects whose range has any overlap within the desired period (permissive):

MultiDayEvent.by_month("January")
#=> returns MultiDayEvents that overlap in January,
#   even if they start in December and/or end in February

Timespan Objects: #at_time

To find all instances of a timespan object which contain a specific time:

Post.at_time(time)

This can be useful to find all currently active instances. Note that object instances which start exactly at the given time will be included in the result, but instances that end exactly at the given time will not be.

Timespan Objects: :strict Option

If you'd like to confine results to only those both starting and ending within the given range, use the :strict option:

MultiDayEvent.by_month("January", :strict => true)
#=> returns MultiDayEvents that both start AND end in January

Timespan Objects: Database Indexing and :index_scope Option

In order to ensure query performance on large dataset, you must add an index to the query field (e.g. "created_at") be indexed. ByStar does not define indexes automatically.

Database indexes require querying a range query on a single field, i.e. start_time >= X and start_time <= Y. If we use a single-sided query, the database will iterate through all items either from the beginning or until the end of time. This poses a challenge for timespan-type objects which have two fields, i.e. start_time and end_time. There are two cases to consider:

  1. Timespan with :strict option, e.g. start_time >= X and end_time <= Y.

Given that this gem requires start_time >= end_time, we add the converse constraint start_time <= Y and end_time >= X to ensure both fields are double-sided, i.e. an index can be used on either field.

  1. Timespan without :strict option, e.g. "start_time < Y and end_time > X".

Here we need to add a condition start_time >= X to ensure start_time is bounded on both sides. To achieve this, we allow an :index_scope option which is the minimum "strict" bound on the querying range, in other words, it is an assumption about the maximum timespan of objects.

:index_scope supports multiple value types:

:index_scope Value Meaning
nil or false No constraint set; query will be one-sided (default, but not recommended)
Date or Time, etc. A fixed point in time
ActiveSupport::Duration (e.g. 1.month) The duration value will be subtracted from the start of the range. In other words, a value of 1.month would imply the longest possible object in the database is no longer than 1.month.
Numeric Will be converted to seconds, then handled the same as ActiveSupport::Duration
:beginning_of_day (Symbol literal)
Proc<Range, Hash(options)> A proc which evaluates to one of the above types. Args are (start_time, end_time, options)

An example settings of :index_scope:

# The maximum possible object length is 5 hours.
by_star index_scope: 5.hours

# Objects are guaranteed to start within the same month, with some offset.
by_star index_scope: ->(start_time, end_time, options){ start_time.beginning_of_month + (options[:offset] || 0) }

# The maximum possible object length half the range being queried.
by_star index_scope: ->(start_time, end_time, options){ ((start_time - end_time)*0.5).seconds }

Chronic Support

If Chronic gem is present, it will be used to parse natural-language date/time strings in all ByStar finder methods. Otherwise, the Ruby Time.parse kernel method will be used as a fallback.

As of ByStar 2.2.0, you must explicitly include gem 'chronic' into your Gemfile in order to use Chronic.

Advanced Usage

between_times

To find records between two times:

Post.between_times(time1, time2)

You use a Range like so:

Post.between_times(time1..time2)

Also works with dates - WARNING: there are currently some caveats see Issue #49:

Post.between_times(date1, date2)

It will query records from date1 (00:00:00 Hrs) until date2 (23:59:59 Hrs).

before and after

To find all posts before / after the current time:

Post.before
Post.after

To find all posts before certain time or date:

Post.before(Date.today + 2)
Post.after(Time.now + 5.days)

You can also pass a string:

Post.before("next tuesday")

For Time-Range type objects, only the start time is considered for before and after.

previous and next

To find the prior/subsequent record to a model instance, previous/next on it:

Post.last.previous
Post.first.next

You can specify a field also:

Post.last.previous(field: "published_at")
Post.first.next(field: "published_at")

For Time-Range type objects, only the start time is considered for previous and next.

by_year

To find records from the current year, simply call the method without any arguments:

Post.by_year

To find records based on a year you can pass it a two or four digit number:

Post.by_year(09)

This will return all posts in 2009, whereas:

Post.by_year(99)

will return all the posts in the year 1999.

You can also specify the full year:

Post.by_year(2009)
Post.by_year(1999)

by_month

If you know the number of the month you want:

Post.by_month(1)

This will return all posts in the first month (January) of the current year.

If you like being verbose:

Post.by_month("January")

This will return all posts created in January of the current year.

If you want to find all posts in January of last year just do

Post.by_month(1, year: 2007)

or

Post.by_month("January", year: 2007)

This will perform a find using the column you've specified.

If you have a Time object you can use it to find the posts:

Post.by_month(Time.local(2012, 11, 24))

This will find all the posts in November 2012.

by_calendar_month

Finds records for a given month as shown on a calendar. Includes all the results of by_month, plus any results which fall in the same week as the first and last of the month. Useful for working with UI calendars which show rows of weeks.

Post.by_calendar_month

Parameter behavior is otherwise the same as by_month. Also, :start_day option is supported to specify the start day of the week (:monday, :tuesday, etc.)

by_fortnight

Fortnight numbering starts at 0. The beginning of a fortnight is Monday, 12am.

To find records from the current fortnight:

Post.by_fortnight

To find records based on a fortnight, you can pass in a number (representing the fortnight number) or a time object:

Post.by_fortnight(18)

This will return all posts in the 18th fortnight of the current year.

Post.by_fortnight(18, year: 2012)

This will return all posts in the 18th fortnight week of 2012.

Post.by_fortnight(Time.local(2012,1,1))

This will return all posts from the first fortnight of 2012.

by_week and by_cweek

Week numbering starts at 0, and cweek numbering starts at 1 (same as Date#cweek). The beginning of a week is as defined in ActiveSupport#beginning_of_week, which can be configured.

To find records from the current week:

Post.by_week
Post.by_cweek  # same result

This will return all posts in the 37th week of the current year (remember week numbering starts at 0):

Post.by_week(36)
Post.by_cweek(37)  # same result

This will return all posts in the 37th week of 2012:

Post.by_week(36, year: 2012)
Post.by_cweek(37, year: 2012)  # same result

This will return all posts in the week which contains Jan 1, 2012:

Post.by_week(Time.local(2012,1,1))
Post.by_cweek(Time.local(2012,1,1))  # same result

You may pass in a :start_day option (:monday, :tuesday, etc.) to specify the starting day of the week. This may also be configured in Rails.

by_weekend

If the time passed in (or the time now is a weekend) it will return posts from 0:00 Saturday to 23:59:59 Sunday. If the time is a week day, it will show all posts for the coming weekend.

Post.by_weekend(Time.now)

by_day and today

To find records for today:

Post.by_day
Post.today

To find records for a certain day:

Post.by_day(Time.local(2012, 1, 1))

You can also pass a string:

Post.by_day("next tuesday")

This will return all posts for the given day.

by_quarter

Finds records by 3-month quarterly period of year. Quarter numbering starts at 1. The four quarters of the year begin on Jan 1, Apr 1, Jul 1, and Oct 1 respectively.

To find records from the current quarter:

Post.by_quarter

To find records based on a quarter, you can pass in a number (representing the quarter number) or a time object:

Post.by_quarter(4)

This will return all posts in the 4th quarter of the current year.

Post.by_quarter(2, year: 2012)

This will return all posts in the 2nd quarter of 2012.

Post.by_week(Time.local(2012,1,1))

This will return all posts from the first quarter of 2012.

Version Support

ByStar is tested against the following versions:

  • Ruby 2.0.0+
  • Rails/ActiveRecord 3.2+
  • Mongoid 3.1+

Note that ByStar automatically adds the following version compatibility shims:

  • ActiveSupport 3.x: Add Time/Date/DateTime#in_time_zone (as an alias to #to_time_in_current_zone) for compatibility with Rails 4+.
  • Mongoid 3.x: Adds Criteria#reorder method from Mongoid 4.

Testing

Test Setup

Specify a database by supplying a DB environmental variable:

bundle exec rake spec DB=sqlite

You can also take an ORM-specific test task for a ride:

bundle exec rake spec:active_record

Have an Active Record or Mongoid version in mind? Set the environment variables ACTIVE_RECORD_VERSION and MONGOID_VERSION to a version of your choice. A version number provided will translate to ~> VERSION, and the string master will grab the latest from Github.

# Update your bundle appropriately...
ACTIVE_RECORD_VERSION=4.0.0 MONGOID_VERSION=master bundle update

# ...then run the specs
ACTIVE_RECORD_VERSION=4.0.0 MONGOID_VERSION=master bundle exec rpsec spec

Test Implementation

ByStar tests use TimeCop to lock the system Time.now at Jan 01, 2014, and seed objects with fixed dates according to spec/fixtures/shared/seeds.rb. Note that the timezone is randomized on each run to shake-out timezone related quirks.

Collaborators

ByStar is actively maintained by Ryan Bigg (radar) and Johnny Shields (johnnyshields)

Thank you to the following people:

  • Thomas Sinclair for the original bump for implementing ByStar
  • Ruby on Rails for their support
  • Mislav Marohnic
  • August Lilleas (leethal)
  • gte351s
  • Sam Elliott (lenary)
  • The creators of the Chronic gem
  • Erik Fonselius
  • Johnny Shields (johnnyshields)

by_star's People

Contributors

anathematic avatar davegudge avatar fny avatar jcypret avatar jnajera avatar johnnyshields avatar knoopx avatar lengarvey avatar lxneng avatar mislav avatar nhocki avatar olleolleolle avatar pnomolos avatar radar avatar rgioia avatar timothyjb avatar zacharywelch avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

by_star's Issues

Merging by_star scopes, best practice?

Given the following scope:

ShippingItem.joins(:shipment => :shipment_booking).
merge(ShipmentBooking.by_year(@year, field: 'shipment_bookings.etd'))

I'm not sure what is best: fully-qualifying the database field (as above, in the controller) or letting by_star fully-qualifying it.
The issue I see with the latter is that if do complex queries with renaming/aliasing, you might get stuck.

How to query between two fields?

I need to query a table using the fields start_at and end_at.
Is it possible to do that using the gem?

.where("start_at >= ? and end_at <= ?", start_date, end_date)

Best practice for frontend

Hi Ryan,

Very nice gem! Do you have a recommended way of connecting these finders to the frontend? Right now I have a dropdown menu that links to the current page with a range parameter, referencing scopes named this_month, last_month,last_7_days, etc.

It would be nice if I could just call something like People.by_star(params[:range]). As of right now, it seems I'd need to still keep my own scopes, but instead using my own where conditions, I'd call a scope provided by ByStar. Not as DRY as I would like, but it works.

Do you have any other recommendations?

Test suite: 1 failure

Hi Ryan,

I have just attempted to run the active_record test suite and I received 1 failure:

$ rake spec:active_record
/usr/local/var/rbenv/versions/2.0.0-p195/bin/ruby -S rspec ./spec/integration/active_record//active_record_spec.rb ./spec/unit//kernel_time_spec.rb ./spec/unit//normalization_spec.rb
Running specs in (GMT-05:00) America/New_York timezone...
..........................................................................................DEPRECATION WARNING: ByStar `between` method will be removed in v3.0.0. Please use `between_times`. (called from between at /Users/dave/workspace/by_star/lib/by_star/orm/active_record/by_star.rb:22)
...................F......................................................................................

Failures:

  1) Time fortnight middle of year beginning_of_fortnight should eq 2014-06-04 00:00:00 +0100
     Failure/Error: its(:beginning_of_fortnight){ should eq Time.parse('2014-06-04') }

       expected: 2014-06-04 00:00:00 +0100
            got: 2014-06-04 01:00:00 +0100

       (compared using ==)

       Diff:
       @@ -1,2 +1,2 @@
       -2014-06-04 00:00:00 +0100
       +2014-06-04 01:00:00 +0100
     # ./spec/unit/kernel_time_spec.rb:32:in `block (4 levels) in <top (required)>'

Finished in 0.41676 seconds
196 examples, 1 failure

Failed examples:

rspec ./spec/unit/kernel_time_spec.rb:32 # Time fortnight middle of year beginning_of_fortnight should eq 2014-06-04 00:00:00 +0100
/usr/local/var/rbenv/versions/2.0.0-p195/bin/ruby -S rspec ./spec/integration/active_record//active_record_spec.rb ./spec/unit//kernel_time_spec.rb ./spec/unit//normalization_spec.rb failed

I'm not 100% how to check the spec:mongoid tests as I haven't used mongoid before:

 MOPED: Retrying connection to primary for replica set <Moped::Cluster nodes=[<Moped::Node resolved_address="127.0.0.1:27017">]>

Kind regards,
Dave

between_dates(nil, nil) or between_times(nil, nil) clears method chaining.

I found that between_dates(nil, nil) or between_times(nil, nil) clears all method chaining. Is this an expected behabior?

For example, I have User and Post models. I tried the following on rails console.

irb(main):002:0> user = User.first
  User Load (0.6ms)  SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
=> #<User id: 1, email: ... >
irb(main):003:0> user.posts.count
   (0.7ms)  SELECT COUNT(*) FROM `posts` WHERE `posts`.`user_id` = 1
=> 41
irb(main):004:0> user.posts.between_dates(Date.new(2000, 1), nil).count
   (0.9ms)  SELECT COUNT(*) FROM `posts` WHERE `posts`.`user_id` = 1 AND (posts.created_at >= '1999-12-31 15:00:00')
=> 41
irb(main):005:0> user.posts.between_dates(nil, Date.today).count
   (0.7ms)  SELECT COUNT(*) FROM `posts` WHERE `posts`.`user_id` = 1 AND (posts.created_at <= '2020-06-01 14:59:59')
=> 41
irb(main):006:0> user.posts.between_dates(nil, nil).count # See the generated SQL below
   (0.7ms)  SELECT COUNT(*) FROM `posts`
=> 1326

I expected it to work as follows:

irb(main):006:0> user.posts.between_dates(nil, nil).count
   (0.7ms)  SELECT COUNT(*) FROM `posts` WHERE `posts`.`user_id` = 1
=> 41

Checked with:

  • by_star 4.0.0 3b53809
  • rails 6.0.2.2
  • ruby 2.6.5

by_week broken

tacking this date: 2010-3-29
Gives weeknumber 13 via strftime(%W).to_i

Using the 13 (weeknumber) and the year 2010
((shifts.start >= '2010-04-02 00:00:00' AND shifts.start <= '2010-04-09 00:00:00')
But that timespan includes the ending of week 13 and the start of week 14.

My only conclusion is that the function is broken.

My controller function is:
@Shifts = @user.shifts.by_week(params[:report][:week].to_date.strftime('%W').to_i, :field => :start, :year => params[:report][:week].to_date.strftime('%Y').to_i)

NoMethodError: undefined method `users.created_at' for #<User:0x007fb548e8c128>

Hi

I am having trouble with next and previous methods. I get the following error:

1.9.3p125 :014 > User.last.next
  User Load (0.5ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT 1
NoMethodError: undefined method `users.created_at' for #<User:0x007fb548e8c128>

This happens with both next and previous

Gem Version: by_star (2.1.0.beta2) from git://github.com/radar/by_star (at master)

Rails version 3.2.11.

What am I doing wrong?

Edit: Also posted on StackOverflow: http://stackoverflow.com/questions/16829493/nomethoderror-undefined-method-users-created-at-for-user0x007fb548e8c128

Using by_star outside Rails, by_quarter error

I'm using by_star in a ruby script using also Activerecord.

When I use Model.by_quarter(1), I'm getting

NoMethodError: undefined method `in?' for 1:Fixnum

The culprit is line 68 at normalization.rb, method method quarter_fixnum wich checks if the month passed is 1..4

        raise ParseError, 'Quarter number must be between 1 and 4' unless value.in?(1..4)

I'm using extensively by_quarter(1) on some Rails projects that work perfectly

thanks,

by_direction calculates field different

by_direction sets the field to:

field = options.delete(:field) || by_star_field"

However, by_star uses this from conditions_for_range:

field = table_name << '.' << field.to_s if field
field ||= by_star_field

This causes an inconsitency between how the methods 'past' and 'future' work compared with 'by_year', 'by_month' and 'by_day'.

by_direction should specify the table_name just as by_star does.

Scoping the Find Example

Scoping the Find

All ByStar methods (except oldest, newest, previous, next) return ActiveRecord::Relation and/or Mongoid::Criteria objects, which can be daisy-chained with other scopes/finder methods:

Post.by_month.your_scope
Post.by_month(1).include(:tags).where("tags.name" => "ruby")

I was wondering how to do the reverse
Tag.include(:posts). # WHERE Post.by_month('May')

TZ/DST issues for models using a strict date field

Hi, I'm having issues using all by_* fields with a Garde model which uses a Date type field to model all day events only. Time zone is set at 'Paris' (UTC +1), btw.

When I want to load all garde records for year 2015 with: Garde.by_year(15).to_sql, I'm getting:

"SELECT \"gardes\".* FROM \"gardes\" WHERE (date >= '2014-12-31 23:00:00.000000' AND date <= '2015-12-31 22:59:59.999999')"

If I issue a 1 hour offset with Garde.by_year(15, offset: 1.hour) I get the correct query:

"SELECT \"gardes\".* FROM \"gardes\" WHERE (date >= '2015-01-01 00:00:00.000000' AND date <= '2016-01-01 23:59:59.999999')"

When I try to load records in a specific month which is in a non-DST time, for example october, to get the correct query I need to use a 2 hours offset with Garde.by_month(10, offset: 2.hours).to_sql:

`"SELECT "gardes".* FROM "gardes" WHERE (date >= '2014-10-01 00:00:00.000000' AND date <= '2014-11-01 00:59:59.999999')"``

I don't know if the gem is working as intended, but using a custom class method such as:

def self.by_year_month(year, month)
  offset = Date.new(2014,month,1).beginning_of_month.to_time.gmt_offset
  by_month(month, year: year, offset: (offset / 3600).hours)
end

seems awkward to me.

Date field type not supported?

Am I correct in assuming that the :field option is not intended to support a Date column? In my test below, the March 1st appointment shows up in both the February and March by_month queries.

describe Appointment, '#by_month' do

  it 'returns appointments within the month' do
    Appointment.create date: "Feb 1, 2014"
    Appointment.create date: "Feb 28, 2014"
    Appointment.create date: "Mar 1, 2014"

    february_query = Appointment.by_month(2, year: 2014, field: :date)
    expect(february_query.collect {|a| a.date.to_s}).to eq [
      "2014-02-01",
      "2014-02-28"
    ] # fails, because it includes "2014-03-01"

    march_query = Appointment.by_month(3, year: 2014, field: :date)
    expect(march_query.collect {|a| a.date.to_s}).to eq [
      "2014-03-01"
    ]
  end

end

Never mind these being in a date column is not ideal in the first place, but just wanting to make sure that this is a known and intended behavior. Thanks.

More intelligent between_times

Where either or both of the start and finish timestamps could be absent, it is often necessary to have logic such as the following.

relation = run_some_query
relation = if start.present? && finish.present?
  relation.between_times(start, finish)
elsif start.present?
  relation.after(start)
elsif finish.present?
  relation.before(finish)
else
  relation
end

I'm wondering whether either of the following would be acceptable.

  1. Add a new method to encapsulate this logic; for example between_optional_times
  2. Add an option to the existing between_times, to switch the behaviour into this "optional timestamps" mode of operation; for example between_times(start, finish, optional: true)

I'm happy to code this up if there are no objections to the addition of this feature.

Make Specs Pass for v2.1.0

Per discussion in #29, we've gotta make these specs pass. 😄 Below is a breakdown of what needs to get done.

  • Unshared ActiveRecord
  • Unshared Mongoid
  • by_day.rb
  • by_direction.rb
  • by_fortnight.rb
  • by_quarter.rb
  • by_week.rb
  • by_weekend.rb
  • by_year.rb

We should branch from master until Ryan decides what to merge from my two pull requests (#26, #27).

Scope Offsets Issues

Are there any reasons why the default scope macro wouldn't take any effect?

When i add the offset option to the query, I'm getting the right offset:
Post.by_day('2014-03-05', offset: 9.hours)

But when I set it on the model level, there is no offset:

class Post < ActiveRecord::Base
     by_star_field offset: 9.hours
end

Support by_hour, by_minute, and other times

Currently there is no way to query for hour, minute and so on. The only scopes available are between times. Do you plan to implement those in the future? Do you accept pull requests?

I have made this in order to accomplish my goals tested in PostgreSQL (you may want to use this as well):

scope :by_created_at_hour, -> (hour) { where("date_part('hour', created_at) = ?", hour) }

scope :by_created_at_minute, -> (minute) {where("date_part('minute', created_at) = ?", minute) }

Not using created_at? No worries!

Hi,

Maybe If posible to convert the field to a string in the shared.rb:3 line before adding the field name to the table_name.

In the README your example use a symbol (:something_else) but it doesn't work for now.

Great work with this gem!! (Excuse my english please)

Previous and next for base_class if STI present

I've got problem with previous and next, if model's got a base class:
users = User.today               # User::Client, User::Admin...
previous = users.first.previous  # searches with type=User::Client
next = users.last.next           # searches with type=User::Admin

I believe it supposed to search in context of base class.
Workaround that i've found:
previous = users.first.becomes(User).previous
next = users.first.becomes(User).next

Change how :scope works for next/previous methods

I know this is a tricky one since the next and previous methods operate differently than all the other finders because they are based off a single base record rather than a scope.

However, it'd be really nice if you could do something like the following:

# post.rb
by_star_field :publish_date, scope: ->(record) { where(customer_id: record.customer_id) }

This way, when you do some_post.next you can get the next post (ordered by publish_date) but constrained to having the same customer_id as some_post.

Right now, when used in a singular context both self and record evaluate to the Class:Post rather than the instance of the post.

I know there's no easy way to solve this, unfortunately. Perhaps we need the ability to have two different scoping methods depending on whether you're using the collection filters or the next and previous methods?

Next_week does not work

Would you like to check where i make mistake
here is my code

Past week
http://prntscr.com/ciortp
where my past week task

Current Week i don't by this
Task.between_times(Date.current.beginning_of_week, Date.current.end_of_week)
also in current week i cant see my future task list too :(
http://prntscr.com/cips6f

but when i try next_week it does not work
http://prntscr.com/cipsht

here is my table info for task

Table name: tasks

id :integer not null, primary key

title :string

description :text

status :integer default("defualt")

reviewer_id :integer

project_id :integer

created_at :datetime not null

updated_at :datetime not null

position :integer

working_hours :integer default(0)

deadline :datetime

start_day :datetime

*.by_calendar_month('May') looks wrong

I just installed the gem and was playing with it but I just did

Consumers.by_calendar_month('May').to_sql and I got back
SELECT "consumers".* FROM "consumers" WHERE "consumers"."account_id" = 1 AND (consumers.created_at >= '2015-04-27 00:00:00.000000' AND consumers.created_at <= '2015-05-31 23:59:59.999999')"

End date is perfect but start daye seems like its to far back

before(nil) and after(nil)

Similar to #60 and #63, wondering if there's a way for .before and .after to return scope (do nothing) when the argument is nil, similar to .between_times.

between_times(start_time, nil) - same as after(start_time)
between_times(nil, end_time) - same as before(end_time)

Current behavior is to default base and time range scopes to Time.zone.now, but would prefer that only apply to the time range scopes 😅

Current behavior

Event.after(nil) #=> returns all events after the current time 

Desired behavior

Event.after(nil) #=> returns all events

Multiple fields for ordering by when using previous, next

Right now you can only set a single field for ordering by when using previous and next. Most of the time this isn't an issue, but I've run into an issue where things are breaking down for me.

Here's the code for previous for reference:

    def previous(options={})
      field = self.class.by_star_start_field
      value = self.send(field.split(".").last)
      self.class.by_star_scope(options.merge(scope_args: self)).where("#{field} < ?", value).reorder("#{field} DESC").first
    end

Part of my issue is the use of where("#{field} < ?", value) rather than something like where("#{field} <= ? AND id != ?", value, self.id) because my field is month-based (and can't be changed) and on very rare occasions I have two values with the same month, so previous/next will always skip to a different month. I do realize that my proposed fix is over-simplified and falls apart when dealing with legacy schemas, composite PKs, etc, but it's for the sake of example.

Following up from that is then the inability to order records by some criteria within the same month, should the previous issue be fixable.

I don't know if this is something that you want to deal with, or if it's too niche to be a problem. I'm more than willing to create a full pull request should you be OK with trying to solve it, and would welcome any suggestions, criticism, or thoughts.

Thanks!

Between the same DateTime behavior

user.orders.between_times(Time.zone.now, Time.zone.now)

Result is empty, it shouldn't like this, i guess. What happened?

SELECT * FROM `orders` WHERE created_at BETWEEN "now" AND "now";

'field' parameter ignored in master ('2.2.0.rc1')

My tests are failing with this version. Seems that the field parameter is completely ignored by by_star.

ShipmentBooking.by_month(field: 'shipment_bookings.etd').to_sql

master (wrong, reverts to created_at):

SELECT "shipments".* FROM "shipments" INNER JOIN "shipment_bookings" ON "shipment_bookings"."id" = "shipments"."shipment_booking_id" WHERE ((shipments.processing_flags & 256 = 0)) AND (shipment_bookings.created_at >= '2014-02-28 16:00:00.000000' AND shipment_bookings.created_at <= '2014-03-31 15:59:59.999999')

2.1.0beta (right):

SELECT "shipments".* FROM "shipments" INNER JOIN "shipment_bookings" ON "shipment_bookings"."id" = "shipments"."shipment_booking_id" WHERE ((shipments.processing_flags & 256 = 0)) AND (shipment_bookings.etd >= '2014-02-28 16:00:00.000000' AND shipment_bookings.etd <= '2014-03-31 15:59:59.999999')

Setting by_star_field 'shipment_booking.etd' in the model class works as expected.

The weird thing is that you have a test for that (I didn't manage to run your tests since I don't have mysql or pg installed on my machine but a basic app fails too)

Env: Rails 3.0.20

by_star_field with :scope doesn't work properly for class methods (ex. newest)

I have a model like this:

class TodoList < ActiveRecord::Base
  ## Associations
  belongs_to :chat_room

  ## Validations
  validates_presence_of :chat_room_id
  validates_presence_of :date
  validates_associated :chat_room
  validates_uniqueness_of :chat_room_id, {scope: :date}

  ## ByStar Settings
  by_star_field :date
  by_star_field scope: ->(record) { where(chat_room: record.chat_room) }
  ...

here at the last row, I want ByStar to look separately between TodoLists of different ChatRooms.
This does work for instance methods like next previous

TodoList.last.previous
#   TodoList Load (0.9ms)  SELECT  `todo_lists`.* FROM `todo_lists`  ORDER BY `todo_lists`.`id` DESC LIMIT 1
#   ChatRoom Load (0.9ms)  SELECT  `chat_rooms`.* FROM `chat_rooms` WHERE `chat_rooms`.`id` = 1 LIMIT 1
#   TodoList Load (0.7ms)  SELECT  `todo_lists`.* FROM `todo_lists` WHERE `todo_lists`.`chat_room_id` = 1 AND (date < '2017-05-18')  ORDER BY date DESC LIMIT 1
# => #<TodoList:0x0055845f838b58
#  id: 1,
#  date: Wed, 17 May 2017,
#  comment: "Test",
#  chat_room_id: 1,
#  created_at: Wed, 17 May 2017 12:45:42 UTC +00:00,
#  updated_at: Wed, 17 May 2017 12:45:42 UTC +00:00>

but not for class methods:

TodoList.newest
# RuntimeError: ByStar :scope does not accept arguments

as for #39, I thought class methods and instance methods should differentiate scopes. I think this is not working well.

Possible to specify timezone?

It looks like by_star operates only on UTC. What if I wanted to work in EST or some other timezone?

Like right now I have to make named scopes using conditions like this where I'd love to use by_star:

  :conditions => ['created_at >= ?', Time.parse('2010-01-01 00:00:00 EST').utc]

How hard would that be to add?

What time is the by_* will support the Rails4?

I already used the Rails 4, but the by_star can not work in the Rails 4.
When I "bundle install", the error like this.

Bundler could not find compatible versions for gem "activerecord":
  In Gemfile:
    by_star (>= 0) ruby depends on
      activerecord (~> 3.0) ruby

    rails (= 4.0.0.rc1) ruby depends on
      activerecord (4.0.0.rc1)

by_star_field on an association field

Typically, eg for a order item, the date we want to select on is part of a belong_to/has_many association (eg Order#date).
Currently, by_star_field only accept a symbol. You can (and I do :o/) pass the association field and join manually but it doesn't feel 'proper' to me.

The best would be to be able to pass an association field so by_star would join in the table automatically, renaming the association field if needed.

That's a suggestion.

previous(field => :somthing_else) doesn't work

Hi,

Thanks you for your gem, it's really useful and easy to use :)
But I think there is a problem with previous(field => :somthing_else)
Here is a sample of code demonstrating the problem :

previous = raw_receipts.last.previous(:field => :affected_at) do
  { :joins => :user, :conditions => ["colocation_id = ? AND is_validated = ?",self.colocation.id,0], :order => "affected_at DESC" }
end

I get the error :

NoMethodError: undefined method `{:field=>:affected_at}' for #<Receipt:0x007fb1d4b05fa8>

To solve this issue I add by_star_field :affected_at to my Receipt model.
Now the generated SQL request is correct :

Receipt Load (0.3ms)  SELECT "receipts".* FROM "receipts" WHERE (affected_at < '2012-02-17 15:10:01.935885') ORDER BY affected_at DESC LIMIT 1

stack level too deep when bundle install

Gemfile:
gem 'by_star', git: 'git://github.com/radar/by_star
Got:

SystemStackError: stack level too deep
An error occurred while installing by_star (2.2.1), and Bundler cannot continue.

Ruby 2.2.0, Rails 4.2.4, OSX Yosemite.

Verbose bundle install show looping on this block till it died on stack level:

SystemStackError: stack level too deep
/Users/alexey/.rvm/rubies/ruby-2.2.0/lib/ruby/2.2.0/psych/deprecated.rb:19:in `call'
/Users/alexey/.rvm/rubies/ruby-2.2.0/lib/ruby/2.2.0/psych/deprecated.rb:19:in `block in quick_emit'
/Users/alexey/.rvm/rubies/ruby-2.2.0/lib/ruby/site_ruby/2.2.0/rubygems/specification.rb:2312:in `block (2 levels) in to_yaml'
/Users/alexey/.rvm/rubies/ruby-2.2.0/lib/ruby/2.2.0/psych/coder.rb:36:in `map'
/Users/alexey/.rvm/rubies/ruby-2.2.0/lib/ruby/site_ruby/2.2.0/rubygems/specification.rb:2311:in `block in to_yaml'

by_month picking up the first day of the next month

I am using by_month and the results are picking up records from the first day if the next month.
So if I pass in "2011-10-1" the sql that's generated looks like this;
expenses.trx_date >= ''2011-10-01 00:00:00.000'' AND expenses.trx_date <= ''2011-10-30 23:59:59.999''

The thru date picks up dates that look like this;

"2011-11-01 00:00:00.000"

I have tested and if just one of the 9's is dropped from the precision it doesn't pickup the next day.

Is there anyway to adjust this?

Thanks for your work on this gem!

undefined method `at_time`

Using by_star v3.0.0 in Rails 4.1.8

at_time is undefined. between_times works perfectly.

class Job < ActiveRecord::Base
  by_star_field :job_start, :job_end
end
Job.at_time(Time.now)
> NoMethodError: undefined method `at_time` for ...

by_day doesn't accept DateTime objects

Accepts only Date or Time objects.

On a DateTime object raise: undefined method `by_day_DateTime' error.

I've worked around this by using to_time on my calls, but I think it should happen implicitly.

Handling of Week 53

Some years have 53 weeks like 2015 which Date.today.cweek returns corrected, however an Error ByStar::ParseError: Week number must be between 0 and 52 is thrown by by_star when it encounters week 53.

Many Thanks

between_times( nil, Time.now) fails when column contains nil

I am attempting to use by_star on a column that may have a nil date value.

MyObject.between_times( nil, Time.now )

fails with

NoMethodError:
  undefined method `+' for nil:NilClass
  # /var/lib/gems/2.3.0/gems/by_star-2.2.1/lib/by_star/between.rb:7:in `between_times'

Conflict with paper_trail?

Adding this gem to my app makes it crash on boot:

/Users/gamov/.rvm/gems/ruby-1.9.3-p327@ector/gems/activerecord-3.0.19/lib/active_record/named_scope.rb:132:in `valid_scope_name?': private method `warn' called for nil:NilClass (NoMethodError)
    from /Users/gamov/.rvm/gems/ruby-1.9.3-p327@ector/gems/activerecord-3.0.19/lib/active_record/named_scope.rb:102:in `scope'
    from /Users/gamov/.rvm/gems/ruby-1.9.3-p327@ector/gems/paper_trail-2.6.3/lib/paper_trail/version.rb:36:in `<class:Version>'
    from /Users/gamov/.rvm/gems/ruby-1.9.3-p327@ector/gems/paper_trail-2.6.3/lib/paper_trail/version.rb:1:in `<top (required)>'

It happens on the line:

  scope :between, lambda { |start_time, end_time|
    where(["#{PaperTrail.timestamp_field} > ? AND #{PaperTrail.timestamp_field} < ?", start_time, end_time ]).
      order("#{PaperTrail.timestamp_field} ASC, #{self.primary_key} ASC")
  }

because it tries to define another scope called :between.

What would be the best way to de-activate by_star in the application context? Maybe an initializer where we can specify which classes we want to include/exclude by_star scopes?

Update: I've created a blank 3.0 app and installed only by_star & paper_trail and I don't get the exception... mmm here comes gem dependencies nightmare...

Remove miliseconds on sql generated

hi @radar , Thanks for the great gem.
I use it on production and now since the data is large, i need improve a bit.
The example generated date on sql query is 2016-09-30 16:59:59.999999. There is any option for remove the seconds and timestamp?

Thanks.

Any way to simulate by_days

So, I have a installment with due date at '2016-01-10', and I want to notify the user x days before this date. I case I want choose how many days that can be 0 days (the same day of due date) and 3 days before. Like so:

first notification -> '2016-01-07'
second notification -> '2016-01-10'
due_date -> '2016-01-10'

The method could be like that:

.by_dates([Time.zone.local(2016, 1, 7), Time.zone.local(2016, 1, 10)])

Or something crazy like the following:

.by_left(base: Time.current, days: [0, 3], field: :due_date)

Maybe a IN with utc.to_day or something like that. Is it possible?

Thank you and congrats by the great gem. (:

cant use by_month if january

if its january 2015, you cant use do queries like:
.by_year('2014').by_month('december')
or
.by_month('december') #<--- this is should go to the past month, not future month.

how could you do this with just by_month?
Im trying to use by_star to pass parameters directly to the url which then get passed to by_star.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.