├── spec └── dummy │ ├── app │ └── models │ │ └── user.rb │ ├── config │ ├── routes.rb │ ├── environment.rb │ ├── boot.rb │ ├── initializers │ │ └── rails_init.rb │ ├── database.yml │ ├── secrets.yml │ ├── application.rb │ └── environments │ │ ├── development.rb │ │ └── test.rb │ ├── spec │ ├── spec_helper.rb │ ├── models │ │ ├── scopes_spec.rb │ │ └── test_spec.rb │ ├── rails_helper.rb │ ├── groupdate_output.rb │ ├── dateslices_output.rb │ └── tester.rb │ ├── .rspec │ ├── bin │ ├── rake │ ├── bundle │ └── rails │ ├── config.ru │ ├── db │ ├── migrate │ │ └── 20140719190922_create_users.rb │ └── schema.rb │ └── Rakefile ├── lib ├── dateslices │ ├── version.rb │ ├── postgresql.rb │ ├── sqlite.rb │ ├── mysql.rb │ └── scopes.rb ├── tasks │ └── dateslices_tasks.rake └── dateslices.rb ├── .gitignore ├── CHANGELOG.md ├── Rakefile ├── Gemfile ├── MIT-LICENSE ├── dateslices.gemspec ├── README.md └── Gemfile.lock /spec/dummy/app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ActiveRecord::Base 2 | end 3 | -------------------------------------------------------------------------------- /spec/dummy/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | end 3 | -------------------------------------------------------------------------------- /spec/dummy/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | end 3 | -------------------------------------------------------------------------------- /lib/dateslices/version.rb: -------------------------------------------------------------------------------- 1 | module Dateslices 2 | VERSION = '0.0.4' 3 | end 4 | -------------------------------------------------------------------------------- /spec/dummy/.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --warnings 3 | --require spec_helper 4 | --format documentation 5 | 6 | -------------------------------------------------------------------------------- /spec/dummy/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /lib/tasks/dateslices_tasks.rake: -------------------------------------------------------------------------------- 1 | # desc "Explaining what the task does" 2 | # task :dateslices do 3 | # # Task goes here 4 | # end 5 | -------------------------------------------------------------------------------- /spec/dummy/bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /spec/dummy/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../../config/application', __FILE__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle/ 2 | log/*.log 3 | pkg/ 4 | spec/dummy/db/*.sqlite3 5 | spec/dummy/db/*.sqlite3-journal 6 | spec/dummy/log/*.log 7 | spec/dummy/tmp/ 8 | spec/dummy/.sass-cache 9 | .idea/ -------------------------------------------------------------------------------- /spec/dummy/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /spec/dummy/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /spec/dummy/db/migrate/20140719190922_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration 2 | def change 3 | create_table :users do |t| 4 | t.string :email 5 | 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.0.1 2 | 3 | Initial release by Will Schenk 4 | 5 | # 0.0.2 6 | 7 | Added different aggregation types 8 | 9 | # 0.0.3 10 | 11 | Changed default output format to be compatible with Chartkick (@mbrookes) 12 | 13 | # 0.0.4 14 | 15 | Rails 5 fixes -------------------------------------------------------------------------------- /spec/dummy/config/boot.rb: -------------------------------------------------------------------------------- 1 | # Set up gems listed in the Gemfile. 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__) 3 | 4 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) 5 | $LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__) 6 | -------------------------------------------------------------------------------- /spec/dummy/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /spec/dummy/spec/models/scopes_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe "Scopes", :type => :model do 4 | Dateslices::METHODS.each do |f| 5 | it "should respond to #{f}" do 6 | expect( User.respond_to?( f ) ).to be(true) 7 | end 8 | 9 | it "should implement #{f}" do 10 | User.send f.to_sym 11 | end 12 | end 13 | end -------------------------------------------------------------------------------- /spec/dummy/spec/rails_helper.rb: -------------------------------------------------------------------------------- 1 | # This file is copied to spec/ when you run 'rails generate rspec:install' 2 | ENV['RAILS_ENV'] ||= 'test' 3 | require 'spec_helper' 4 | require File.expand_path('../../config/environment', __FILE__) 5 | require 'rspec/rails' 6 | 7 | Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } 8 | 9 | RSpec.configure do |config| 10 | config.infer_spec_type_from_file_location! 11 | end 12 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | begin 2 | require 'bundler/setup' 3 | rescue LoadError 4 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks' 5 | end 6 | 7 | require 'rdoc/task' 8 | 9 | RDoc::Task.new(:rdoc) do |rdoc| 10 | rdoc.rdoc_dir = 'rdoc' 11 | rdoc.title = 'Dateslices' 12 | rdoc.options << '--line-numbers' 13 | rdoc.rdoc_files.include('README.rdoc') 14 | rdoc.rdoc_files.include('lib/**/*.rb') 15 | end 16 | 17 | 18 | 19 | 20 | Bundler::GemHelper.install_tasks 21 | 22 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/rails_init.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.assets.version = '1.0' 4 | Rails.application.config.action_dispatch.cookies_serializer = :json 5 | Rails.application.config.filter_parameters += [:password] 6 | Rails.application.config.session_store :cookie_store, key: '_dummy_session' 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # Declare your gem's dependencies in dateslices.gemspec. 4 | # Bundler will treat runtime dependencies like base dependencies, and 5 | # development dependencies will be added by default to the :development group. 6 | gemspec 7 | 8 | # Declare any dependencies that are still in development here instead of in 9 | # your gemspec. These might include edge Rails or gems from your path or 10 | # Git. Remember to move these dependencies to your gemspec before releasing 11 | # your gem to rubygems.org. 12 | 13 | # To use debugger 14 | # gem 'debugger' 15 | 16 | -------------------------------------------------------------------------------- /spec/dummy/spec/models/test_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | require 'tester' 3 | 4 | databases = [{ :adapter => 'mysql', :database => 'dateslice_test', :user => 'root'}, 5 | { :adapter => 'postgresql', :database => 'dateslice_test'}, 6 | { :adapter => 'sqlite3', :database => 'db/test.sqlite3'}] 7 | 8 | formats = ['groupdate', 'dateslices'] 9 | 10 | databases.each do |database| 11 | formats.each do |format| 12 | RSpec.describe "#{database[:adapter].titleize} #{format}", :type => :model do 13 | include_examples format, database 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/dummy/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: 5 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: db/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: db/test.sqlite3 22 | 23 | production: 24 | <<: *default 25 | database: db/production.sqlite3 26 | -------------------------------------------------------------------------------- /lib/dateslices.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/core_ext/module/attribute_accessors' 2 | require 'active_support/time' 3 | 4 | module Dateslices 5 | FIELDS = [:second, :minute, :hour, :day, :week, :month, :year, :hour_of_day, :day_of_week, :day_of_month, :month_of_year ] 6 | METHODS = FIELDS.map{|v| :"group_by_#{v}" } 7 | 8 | mattr_accessor :output_format 9 | 10 | self.output_format = :groupdate 11 | end 12 | 13 | begin 14 | require 'active_record' 15 | rescue LoadError 16 | # do nothing 17 | end 18 | 19 | require 'dateslices/sqlite' 20 | require 'dateslices/postgresql' 21 | require 'dateslices/mysql' 22 | require 'dateslices/scopes' 23 | 24 | ActiveRecord::Base.send(:extend, Dateslices::Scopes) 25 | -------------------------------------------------------------------------------- /lib/dateslices/postgresql.rb: -------------------------------------------------------------------------------- 1 | module Dateslices 2 | module Postgresql 3 | 4 | def self.time_filter(column, field) 5 | case field 6 | when :hour_of_day 7 | "EXTRACT(HOUR from #{column})" 8 | when :day_of_week 9 | "EXTRACT(DOW from #{column})" 10 | when :day_of_month 11 | "EXTRACT(DAY from #{column})" 12 | when :month_of_year 13 | "EXTRACT(MONTH from #{column})" 14 | when :week # Postgres weeks start on monday 15 | "(DATE_TRUNC( 'week', #{column} + INTERVAL '1 day' ) - INTERVAL '1 day')" 16 | else 17 | "DATE_TRUNC( '#{field.to_s}' , #{column} )" 18 | end 19 | end 20 | 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/dummy/db/schema.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # This file is auto-generated from the current state of the database. Instead 3 | # of editing this file, please use the migrations feature of Active Record to 4 | # incrementally modify your database, and then regenerate this schema definition. 5 | # 6 | # Note that this schema.rb definition is the authoritative source for your 7 | # database schema. If you need to create the application database on another 8 | # system, you should be using db:schema:load, not running all the migrations 9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 10 | # you'll amass, the slower it'll run and the greater likelihood for issues). 11 | # 12 | # It's strongly recommended that you check this file into your version control system. 13 | 14 | ActiveRecord::Schema.define(version: 20140719190922) do 15 | 16 | create_table "users", force: true do |t| 17 | t.string "email" 18 | t.datetime "created_at" 19 | t.datetime "updated_at" 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /spec/dummy/config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: 501dd8e0bf4cda19228a993dad6df4f872f4e6d810fe5256064db47fc7d32b000596e43918a5eeb565c413b73ff9885c21b7bbb6a64061da2b41ca478f30a9e6 15 | 16 | test: 17 | secret_key_base: 5f79628a86f1426a2c9e2bbac133caa52bd5b3760b4f7728e752ec0192b5fdbdafa675bcf38b72703ae13e974593d838c81b8e7cf114a330880e751e837f96f1 18 | 19 | # Do not keep production secrets in the repository, 20 | # instead read values from the environment. 21 | production: 22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 23 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014 YOURNAME 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /dateslices.gemspec: -------------------------------------------------------------------------------- 1 | $:.push File.expand_path('../lib', __FILE__) 2 | 3 | # Maintain your gem's version: 4 | require 'dateslices/version' 5 | 6 | # Describe your gem and declare its dependencies: 7 | Gem::Specification.new do |s| 8 | s.name = 'dateslices' 9 | s.version = Dateslices::VERSION 10 | s.authors = ['Will Schenk'] 11 | s.email = ['will@happyfuncorp.com'] 12 | s.homepage = 'https://github.com/sublimeguile/dateslices' 13 | s.summary = 'A Rails 4 ActiveRecord plugin that adds group_by_day, group_by_month, etc.' 14 | s.description = 'A Rails 4 ActiveRecord plugin that adds group_by_day, group_by_month, etc. Not timezone aware, but supports sqlite.' 15 | s.license = 'MIT' 16 | 17 | s.files = Dir['{app,config,db,lib}/**/*', 'MIT-LICENSE', 'Rakefile', 'README.md'] 18 | 19 | s.add_dependency 'rails', '> 4' 20 | 21 | s.add_development_dependency 'sqlite3' 22 | s.add_development_dependency 'pg' 23 | s.add_development_dependency 'mysql' 24 | s.add_development_dependency 'rspec-rails' 25 | s.add_development_dependency 'rspec-autotest' 26 | s.add_development_dependency 'autotest-rails' 27 | end 28 | -------------------------------------------------------------------------------- /lib/dateslices/sqlite.rb: -------------------------------------------------------------------------------- 1 | module Dateslices 2 | module Sqlite 3 | 4 | def self.time_filter(column, field) 5 | case field 6 | when :second 7 | "strftime( \"%Y-%m-%d %H:%M:%S UTC\", #{column} )" 8 | when :minute 9 | "strftime( \"%Y-%m-%d %H:%M:00 UTC\", #{column} )" 10 | when :hour 11 | "strftime( \"%Y-%m-%d %H:00:00 UTC\", #{column} )" 12 | when :day 13 | "strftime( \"%Y-%m-%d 00:00:00 UTC\", #{column} )" 14 | when :week 15 | "strftime('%Y-%m-%d 00:00:00 UTC', #{column}, '-6 days', 'weekday 0')" 16 | when :month 17 | "strftime( \"%Y-%m-01 00:00:00 UTC\", #{column} )" 18 | when :year 19 | "strftime( \"%Y-01-01 00:00:00 UTC\", #{column} )" 20 | when :hour_of_day 21 | "strftime( \"%H\", #{column} )" 22 | when :day_of_week 23 | "strftime( \"%w\", #{column} )" 24 | when :day_of_month 25 | "strftime( \"%d\", #{column} )" 26 | when :month_of_year 27 | "strftime( \"%m\", #{column} )" 28 | else 29 | throw "Unknown time filter #{field}" 30 | end 31 | end 32 | 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/dateslices/mysql.rb: -------------------------------------------------------------------------------- 1 | module Dateslices 2 | module Mysql 3 | 4 | def self.time_filter(column, field) 5 | case field 6 | when :hour_of_day 7 | "(EXTRACT(HOUR from #{column}))" 8 | when :day_of_week 9 | "(DAYOFWEEK(#{column}) - 1)" 10 | when :day_of_month 11 | "DAYOFMONTH(#{column})" 12 | when :month_of_year 13 | "MONTH(#{column})" 14 | when :second 15 | "DATE_FORMAT(#{column}, '%Y-%m-%d %H:%i:%S UTC')" 16 | when :minute 17 | "DATE_FORMAT(#{column}, '%Y-%m-%d %H:%i:00 UTC')" 18 | when :hour 19 | "DATE_FORMAT(#{column}, '%Y-%m-%d %H:00:00 UTC')" 20 | when :day 21 | "DATE_FORMAT(#{column}, '%Y-%m-%d 00:00:00 UTC')" 22 | when :month 23 | "DATE_FORMAT(#{column}, '%Y-%m-01 00:00:00 UTC')" 24 | when :year 25 | "DATE_FORMAT(#{column}, '%Y-01-01 00:00:00 UTC')" 26 | when :week # Sigh... 27 | "DATE_FORMAT( date_sub( #{column}, interval ((weekday( #{column} ) + 1)%7) day ), '%Y-%m-%d 00:00:00 UTC')" 28 | else 29 | throw "Unknown time filter #{field}" 30 | end 31 | end 32 | 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/dummy/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | # Pick the frameworks you want: 4 | require 'active_record/railtie' 5 | require 'action_controller/railtie' 6 | require 'action_mailer/railtie' 7 | require 'action_view/railtie' 8 | require 'sprockets/railtie' 9 | # require "rails/test_unit/railtie" 10 | 11 | Bundler.require(*Rails.groups) 12 | require 'dateslices' 13 | 14 | module Dummy 15 | class Application < Rails::Application 16 | # Settings in config/environments/* take precedence over those specified here. 17 | # Application configuration should go into files in config/initializers 18 | # -- all .rb files in that directory are automatically loaded. 19 | 20 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 21 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 22 | # config.time_zone = 'Central Time (US & Canada)' 23 | 24 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 25 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 26 | # config.i18n.default_locale = :de 27 | end 28 | end 29 | 30 | -------------------------------------------------------------------------------- /spec/dummy/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports and disable caching. 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send. 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger. 20 | config.active_support.deprecation = :log 21 | 22 | # Raise an error on page load if there are pending migrations. 23 | config.active_record.migration_error = :page_load 24 | 25 | # Debug mode disables concatenation and preprocessing of assets. 26 | # This option may cause significant delays in view rendering with a large 27 | # number of complex assets. 28 | config.assets.debug = true 29 | 30 | # Adds additional error checking when serving assets at runtime. 31 | # Checks for improperly declared sprockets dependencies. 32 | # Raises helpful error messages. 33 | config.assets.raise_runtime_errors = true 34 | 35 | # Raises error for missing translations 36 | # config.action_view.raise_on_missing_translations = true 37 | end 38 | -------------------------------------------------------------------------------- /lib/dateslices/scopes.rb: -------------------------------------------------------------------------------- 1 | module Dateslices 2 | module Scopes 3 | 4 | Dateslices::FIELDS.each do |field| 5 | define_method :"group_by_#{field}" do |*args| 6 | 7 | args = args.dup 8 | 9 | column = args[0].blank? ? 'created_at' : args[0] 10 | 11 | aggregation = args[1].blank? ? 'count' : args[1] 12 | 13 | aggregation_column = args[2].blank? ? '*' : args[2] 14 | 15 | sql = ["#{aggregation}(#{aggregation_column}) as count"] 16 | 17 | time_filter = case connection.adapter_name 18 | when 'SQLite' 19 | Dateslices::Sqlite.time_filter(column, field) 20 | when 'PostgreSQL', 'PostGIS' 21 | Dateslices::Postgresql.time_filter(column, field) 22 | when 'MySQL', 'Mysql2' 23 | Dateslices::Mysql.time_filter(column, field) 24 | else 25 | throw "Unknown database adaptor #{connection.adapter_name}" 26 | end 27 | 28 | sql << "#{time_filter} as date_slice" 29 | 30 | slices = select( sql.join(', ')).where.not(column => nil).group('date_slice').order('date_slice') 31 | 32 | if Dateslices.output_format == :groupdate 33 | slices = slices.collect do |c| 34 | [slice(c), c['count']] 35 | end 36 | 37 | Hash[slices] 38 | else 39 | slices.collect do |c| 40 | { date_slice: slice(c), aggregation.to_sym => c['count'] } 41 | end 42 | end 43 | end 44 | end 45 | 46 | def slice(c) 47 | slice = c['date_slice'] 48 | slice.is_a?(Float) ? slice.to_i.to_s : slice.to_s 49 | end 50 | 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/dummy/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure static asset server for tests with Cache-Control for performance. 16 | config.serve_static_assets = true 17 | config.static_cache_control = 'public, max-age=3600' 18 | 19 | # Show full error reports and disable caching. 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates. 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment. 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Tell Action Mailer not to deliver emails to the real world. 30 | # The :test delivery method accumulates sent emails in the 31 | # ActionMailer::Base.deliveries array. 32 | config.action_mailer.delivery_method = :test 33 | 34 | # Print deprecation notices to the stderr. 35 | config.active_support.deprecation = :stderr 36 | 37 | # Raises error for missing translations 38 | # config.action_view.raise_on_missing_translations = true 39 | end 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dateslices 2 | 3 | This project rocks and uses MIT-LICENSE. 4 | 5 | This project is based upon [groupdate](https://github.com/ankane/groupdate) and follows a similar API. 6 | 7 | The differences between the groupdate and dateslices are: 8 | - dateslices supports sqlite3in addition to PostgreSQL & MySQL. 9 | - dateslices has much less functionality. 10 | - dateslices ignores timezones. 11 | - dateslices is rails 4 only. 12 | - dateslices uses rspecs for tests. 13 | 14 | The reason that I wrote this is that I use sqlite in development, and wanted to make sure that I could test things locally. I didn't understand the test suite of groupdate, so I ended up rewriting it. 15 | 16 | ## Walkthrough 17 | 18 | A walkthrough of how this code is written is available on [my blog](http://willschenk.com/dateslice-writing-rails-extensions/) 19 | 20 | Usage instructions below. 21 | 22 | ## Example Usage 23 | 24 | ``` 25 | User.where( :created_at > 1.month.ago ).group_by_day 26 | ``` 27 | 28 | ## Summing up a column 29 | 30 | ``` 31 | User.group_by_day( :created_at, "sum", "karma" ) 32 | ``` 33 | 34 | ## Averaging a column 35 | 36 | ``` 37 | Post.group_by_week( :updated_at, "avg", "comment_count") 38 | ``` 39 | 40 | ## All find methods 41 | 42 | These methods take three optional arguments: 43 | 44 | 1. column to group by, normally "created_at" 45 | 2. sql function to run, normally "count". Also "sum", "avg" 46 | 3. column to do the function on, normally "*" 47 | 48 | - group_by_second 49 | - group_by_minute 50 | - group_by_hour 51 | - group_by_day 52 | - group_by_week 53 | - group_by_month 54 | - group_by_year 55 | 56 | - group_by_hour_of_day 57 | - group_by_day_of_week 58 | - group_by_day_of_month 59 | - group_by_month_of_year 60 | 61 | ## Configuration 62 | 63 | The output format defaults to a Chartkick compatible hash of dates and values. 64 | If you wish to use the dateslices format, please add `Dateslices.output_format = :dateslices` to an initializer. 65 | 66 | ## Tests 67 | 68 | Rspec tests need to be run out of the spec/dummy directory, and you'll need to have a postgres and a mysql database named "dateslice_test" for them to succeed. 69 | -------------------------------------------------------------------------------- /spec/dummy/spec/groupdate_output.rb: -------------------------------------------------------------------------------- 1 | def groupdate_output 2 | { 3 | second: { 4 | "2014-07-19 19:26:44 UTC" => 2, 5 | "2014-07-19 19:26:45 UTC" => 2, 6 | "2014-07-19 19:26:46 UTC" => 2, 7 | "2014-07-19 19:26:47 UTC" => 2, 8 | "2014-07-19 19:26:48 UTC" => 2 9 | }, 10 | minute: { 11 | "2014-07-19 19:23:00 UTC" => 1, 12 | "2014-07-19 19:24:00 UTC" => 3, 13 | "2014-07-19 19:25:00 UTC" => 3, 14 | "2014-07-19 19:26:00 UTC" => 3 15 | }, 16 | hour: { 17 | "2014-07-19 16:00:00 UTC" => 3, 18 | "2014-07-19 17:00:00 UTC" => 2, 19 | "2014-07-19 18:00:00 UTC" => 3, 20 | "2014-07-19 19:00:00 UTC" => 2 21 | }, 22 | day: { 23 | "2014-07-12 00:00:00 UTC" => 1, 24 | "2014-07-18 00:00:00 UTC" => 2, 25 | "2014-07-19 00:00:00 UTC" => 1 26 | }, 27 | week: { 28 | "2014-07-06 00:00:00 UTC" => 1, 29 | "2014-07-13 00:00:00 UTC" => 7 30 | }, 31 | month: { 32 | "2014-05-01 00:00:00 UTC" => 3, 33 | "2014-06-01 00:00:00 UTC" => 4, 34 | "2014-07-01 00:00:00 UTC" => 3 35 | }, 36 | year: { 37 | "2013-01-01 00:00:00 UTC" => 3, 38 | "2014-01-01 00:00:00 UTC" => 7 39 | }, 40 | hour_of_day: { 41 | "16" => 3, 42 | "17" => 2, 43 | "18" => 3, 44 | "19" => 2 45 | }, 46 | day_of_week: { 47 | "0" => 1, 48 | "1" => 1, 49 | "2" => 1, 50 | "3" => 1, 51 | "4" => 1, 52 | "5" => 1, 53 | "6" => 2 54 | }, 55 | day_of_month: { 56 | "10"=>2, 57 | "20"=>1, 58 | "30"=>1, 59 | "31"=>1 60 | }, 61 | month_of_year: { 62 | "10" => 3, 63 | "11" => 4, 64 | "12" => 4 65 | }, 66 | count: { 67 | "2014-07-19 16:00:00 UTC" => 3, 68 | "2014-07-19 17:00:00 UTC" => 2, 69 | "2014-07-19 18:00:00 UTC" => 3, 70 | "2014-07-19 19:00:00 UTC" => 2 71 | }, 72 | sum: { 73 | "2014-07-19 16:00:00 UTC" => 24, 74 | "2014-07-19 17:00:00 UTC" => 11, 75 | "2014-07-19 18:00:00 UTC" => 9, 76 | "2014-07-19 19:00:00 UTC" => 1 77 | }, 78 | average: { 79 | "2014-07-19 16:00:00 UTC" => 8.0, 80 | "2014-07-19 17:00:00 UTC" => 5.5, 81 | "2014-07-19 18:00:00 UTC" => 3.0, 82 | "2014-07-19 19:00:00 UTC" => 0.5 83 | }, 84 | range: { 85 | "2014-07-19 18:00:00 UTC" => 3, 86 | "2014-07-19 19:00:00 UTC" => 2 87 | }, 88 | default: { 89 | "2014-07-12 00:00:00 UTC" => 1, 90 | "2014-07-18 00:00:00 UTC" => 2, 91 | "2014-07-19 00:00:00 UTC" => 1 92 | }, 93 | where: { 94 | "2014-07-12 00:00:00 UTC" => 1, 95 | "2014-07-18 00:00:00 UTC" => 2 96 | }, 97 | updated_at: { 98 | "2014-07-18 00:00:00 UTC" => 2, 99 | "2014-07-19 00:00:00 UTC" => 2 100 | } 101 | } 102 | end 103 | -------------------------------------------------------------------------------- /spec/dummy/spec/dateslices_output.rb: -------------------------------------------------------------------------------- 1 | def dateslices_output 2 | { 3 | second: [ 4 | {:date_slice=>"2014-07-19 19:26:44 UTC", :count=>2}, 5 | {:date_slice=>"2014-07-19 19:26:45 UTC", :count=>2}, 6 | {:date_slice=>"2014-07-19 19:26:46 UTC", :count=>2}, 7 | {:date_slice=>"2014-07-19 19:26:47 UTC", :count=>2}, 8 | {:date_slice=>"2014-07-19 19:26:48 UTC", :count=>2}], 9 | minute: [ 10 | {:date_slice=>"2014-07-19 19:23:00 UTC", :count=>1}, 11 | {:date_slice=>"2014-07-19 19:24:00 UTC", :count=>3}, 12 | {:date_slice=>"2014-07-19 19:25:00 UTC", :count=>3}, 13 | {:date_slice=>"2014-07-19 19:26:00 UTC", :count=>3}], 14 | hour: [ 15 | {:date_slice=>"2014-07-19 16:00:00 UTC", :count=>3}, 16 | {:date_slice=>"2014-07-19 17:00:00 UTC", :count=>2}, 17 | {:date_slice=>"2014-07-19 18:00:00 UTC", :count=>3}, 18 | {:date_slice=>"2014-07-19 19:00:00 UTC", :count=>2}], 19 | day: [ 20 | {:date_slice=>"2014-07-12 00:00:00 UTC", :count=>1}, 21 | {:date_slice=>"2014-07-18 00:00:00 UTC", :count=>2}, 22 | {:date_slice=>"2014-07-19 00:00:00 UTC", :count=>1}], 23 | week: [ 24 | {:date_slice=>"2014-07-06 00:00:00 UTC", :count=>1}, 25 | {:date_slice=>"2014-07-13 00:00:00 UTC", :count=>7}], 26 | month: [ 27 | {:date_slice=>"2014-05-01 00:00:00 UTC", :count=>3}, 28 | {:date_slice=>"2014-06-01 00:00:00 UTC", :count=>4}, 29 | {:date_slice=>"2014-07-01 00:00:00 UTC", :count=>3}], 30 | year: [ 31 | {:date_slice=>"2013-01-01 00:00:00 UTC", :count=>3}, 32 | {:date_slice=>"2014-01-01 00:00:00 UTC", :count=>7}], 33 | hour_of_day: [ 34 | {:date_slice=>"16", :count=>3}, 35 | {:date_slice=>"17", :count=>2}, 36 | {:date_slice=>"18", :count=>3}, 37 | {:date_slice=>"19", :count=>2}], 38 | day_of_week: [ 39 | {:date_slice=>"0", :count=>1}, 40 | {:date_slice=>"1", :count=>1}, 41 | {:date_slice=>"2", :count=>1}, 42 | {:date_slice=>"3", :count=>1}, 43 | {:date_slice=>"4", :count=>1}, 44 | {:date_slice=>"5", :count=>1}, 45 | {:date_slice=>"6", :count=>2}], 46 | day_of_month: [ 47 | {:date_slice=>"10", :count=>2}, 48 | {:date_slice=>"20", :count=>1}, 49 | {:date_slice=>"30", :count=>1}, 50 | {:date_slice=>"31", :count=>1}], 51 | month_of_year: [ 52 | {:date_slice=>"10", :count=>3}, 53 | {:date_slice=>"11", :count=>4}, 54 | {:date_slice=>"12", :count=>4} 55 | ], 56 | count: [ 57 | {:date_slice=>"2014-07-19 16:00:00 UTC", :count=>3}, 58 | {:date_slice=>"2014-07-19 17:00:00 UTC", :count=>2}, 59 | {:date_slice=>"2014-07-19 18:00:00 UTC", :count=>3}, 60 | {:date_slice=>"2014-07-19 19:00:00 UTC", :count=>2} 61 | ], 62 | sum: [ 63 | {:date_slice=>"2014-07-19 16:00:00 UTC", :sum=>24}, 64 | {:date_slice=>"2014-07-19 17:00:00 UTC", :sum=>11}, 65 | {:date_slice=>"2014-07-19 18:00:00 UTC", :sum=>9}, 66 | {:date_slice=>"2014-07-19 19:00:00 UTC", :sum=>1} 67 | ], 68 | average: [ 69 | {:date_slice=>"2014-07-19 16:00:00 UTC", :avg=>8.0}, 70 | {:date_slice=>"2014-07-19 17:00:00 UTC", :avg=>5.5}, 71 | {:date_slice=>"2014-07-19 18:00:00 UTC", :avg=>3.0}, 72 | {:date_slice=>"2014-07-19 19:00:00 UTC", :avg=>0.5} 73 | ], 74 | range: [ 75 | {:date_slice=>"2014-07-19 18:00:00 UTC", :count=>3}, 76 | {:date_slice=>"2014-07-19 19:00:00 UTC", :count=>2} 77 | ], 78 | default: [ 79 | {:date_slice=>"2014-07-12 00:00:00 UTC", :count=>1}, 80 | {:date_slice=>"2014-07-18 00:00:00 UTC", :count=>2}, 81 | {:date_slice=>"2014-07-19 00:00:00 UTC", :count=>1} 82 | ], 83 | where: [ 84 | {:date_slice=>"2014-07-12 00:00:00 UTC", :count=>1}, 85 | {:date_slice=>"2014-07-18 00:00:00 UTC", :count=>2} 86 | ], 87 | updated_at: [ 88 | {:date_slice=>"2014-07-18 00:00:00 UTC", :count=>2}, 89 | {:date_slice=>"2014-07-19 00:00:00 UTC", :count=>2} 90 | ] 91 | } 92 | end 93 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | dateslices (0.0.4) 5 | rails (> 4) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | ZenTest (4.11.0) 11 | actionmailer (4.2.6) 12 | actionpack (= 4.2.6) 13 | actionview (= 4.2.6) 14 | activejob (= 4.2.6) 15 | mail (~> 2.5, >= 2.5.4) 16 | rails-dom-testing (~> 1.0, >= 1.0.5) 17 | actionpack (4.2.6) 18 | actionview (= 4.2.6) 19 | activesupport (= 4.2.6) 20 | rack (~> 1.6) 21 | rack-test (~> 0.6.2) 22 | rails-dom-testing (~> 1.0, >= 1.0.5) 23 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 24 | actionview (4.2.6) 25 | activesupport (= 4.2.6) 26 | builder (~> 3.1) 27 | erubis (~> 2.7.0) 28 | rails-dom-testing (~> 1.0, >= 1.0.5) 29 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 30 | activejob (4.2.6) 31 | activesupport (= 4.2.6) 32 | globalid (>= 0.3.0) 33 | activemodel (4.2.6) 34 | activesupport (= 4.2.6) 35 | builder (~> 3.1) 36 | activerecord (4.2.6) 37 | activemodel (= 4.2.6) 38 | activesupport (= 4.2.6) 39 | arel (~> 6.0) 40 | activesupport (4.2.6) 41 | i18n (~> 0.7) 42 | json (~> 1.7, >= 1.7.7) 43 | minitest (~> 5.1) 44 | thread_safe (~> 0.3, >= 0.3.4) 45 | tzinfo (~> 1.1) 46 | arel (6.0.3) 47 | autotest-rails (4.2.1) 48 | ZenTest (~> 4.5) 49 | builder (3.2.2) 50 | concurrent-ruby (1.0.1) 51 | diff-lcs (1.2.5) 52 | erubis (2.7.0) 53 | globalid (0.3.6) 54 | activesupport (>= 4.1.0) 55 | i18n (0.7.0) 56 | json (1.8.3) 57 | loofah (2.0.3) 58 | nokogiri (>= 1.5.9) 59 | mail (2.6.4) 60 | mime-types (>= 1.16, < 4) 61 | mime-types (3.0) 62 | mime-types-data (~> 3.2015) 63 | mime-types-data (3.2016.0221) 64 | mini_portile2 (2.0.0) 65 | minitest (5.8.4) 66 | mysql (2.9.1) 67 | nokogiri (1.6.7.2) 68 | mini_portile2 (~> 2.0.0.rc2) 69 | pg (0.18.4) 70 | rack (1.6.4) 71 | rack-test (0.6.3) 72 | rack (>= 1.0) 73 | rails (4.2.6) 74 | actionmailer (= 4.2.6) 75 | actionpack (= 4.2.6) 76 | actionview (= 4.2.6) 77 | activejob (= 4.2.6) 78 | activemodel (= 4.2.6) 79 | activerecord (= 4.2.6) 80 | activesupport (= 4.2.6) 81 | bundler (>= 1.3.0, < 2.0) 82 | railties (= 4.2.6) 83 | sprockets-rails 84 | rails-deprecated_sanitizer (1.0.3) 85 | activesupport (>= 4.2.0.alpha) 86 | rails-dom-testing (1.0.7) 87 | activesupport (>= 4.2.0.beta, < 5.0) 88 | nokogiri (~> 1.6.0) 89 | rails-deprecated_sanitizer (>= 1.0.1) 90 | rails-html-sanitizer (1.0.3) 91 | loofah (~> 2.0) 92 | railties (4.2.6) 93 | actionpack (= 4.2.6) 94 | activesupport (= 4.2.6) 95 | rake (>= 0.8.7) 96 | thor (>= 0.18.1, < 2.0) 97 | rake (11.1.1) 98 | rspec-autotest (1.0.0) 99 | rspec-core (>= 2.99.0.beta1, < 4.0.0) 100 | rspec-core (3.4.4) 101 | rspec-support (~> 3.4.0) 102 | rspec-expectations (3.4.0) 103 | diff-lcs (>= 1.2.0, < 2.0) 104 | rspec-support (~> 3.4.0) 105 | rspec-mocks (3.4.1) 106 | diff-lcs (>= 1.2.0, < 2.0) 107 | rspec-support (~> 3.4.0) 108 | rspec-rails (3.4.2) 109 | actionpack (>= 3.0, < 4.3) 110 | activesupport (>= 3.0, < 4.3) 111 | railties (>= 3.0, < 4.3) 112 | rspec-core (~> 3.4.0) 113 | rspec-expectations (~> 3.4.0) 114 | rspec-mocks (~> 3.4.0) 115 | rspec-support (~> 3.4.0) 116 | rspec-support (3.4.1) 117 | sprockets (3.5.2) 118 | concurrent-ruby (~> 1.0) 119 | rack (> 1, < 3) 120 | sprockets-rails (3.0.4) 121 | actionpack (>= 4.0) 122 | activesupport (>= 4.0) 123 | sprockets (>= 3.0.0) 124 | sqlite3 (1.3.11) 125 | thor (0.19.1) 126 | thread_safe (0.3.5) 127 | tzinfo (1.2.2) 128 | thread_safe (~> 0.1) 129 | 130 | PLATFORMS 131 | ruby 132 | 133 | DEPENDENCIES 134 | autotest-rails 135 | dateslices! 136 | mysql 137 | pg 138 | rspec-autotest 139 | rspec-rails 140 | sqlite3 141 | 142 | BUNDLED WITH 143 | 1.10.6 144 | -------------------------------------------------------------------------------- /spec/dummy/spec/tester.rb: -------------------------------------------------------------------------------- 1 | require 'groupdate_output' 2 | require 'dateslices_output' 3 | 4 | formats = ['groupdate', 'dateslices'] 5 | 6 | formats.each do |format| 7 | output = send("#{format}_output") 8 | 9 | RSpec.shared_examples format do |config| 10 | 11 | before :suite do 12 | puts "Setting up #{config}" 13 | ActiveRecord::Base.establish_connection config 14 | 15 | puts "Trying to migrate" 16 | ActiveRecord::Migration.create_table :users, :force => true do |t| 17 | t.string :name 18 | t.integer :score 19 | t.timestamp :created_at 20 | t.timestamp :updated_at 21 | end 22 | end 23 | 24 | before :each do 25 | Dateslices.output_format = format.to_sym 26 | User.delete_all 27 | end 28 | 29 | context "groupings" do 30 | it "should return items grouped by second" do 31 | expect( User.count ).to eq(0) 32 | 33 | @t = Time.parse "2014-07-19 15:26:48 -0400" 34 | 35 | 10.times do |t| 36 | User.create created_at: @t - (t.seconds/2).to_i 37 | end 38 | 39 | expect( User.group_by_second ).to eq output[:second] 40 | end 41 | 42 | it "should return items grouped by minute" do 43 | expect( User.count ).to eq(0) 44 | 45 | @t = Time.parse "2014-07-19 15:26:48 -0400" 46 | 47 | 10.times do |t| 48 | User.create created_at: @t - (21.seconds * t) 49 | end 50 | 51 | expect( User.group_by_minute ).to eq output[:minute] 52 | end 53 | 54 | it "should return items grouped by hour" do 55 | expect( User.count ).to eq(0) 56 | 57 | @t = Time.parse "2014-07-19 15:26:48 -0400" 58 | 59 | 10.times do |t| 60 | User.create created_at: @t - (21.minutes * t) 61 | end 62 | 63 | expect( User.group_by_hour ).to eq output[:hour] 64 | end 65 | 66 | it "should return items grouped by day" do 67 | expect( User.count ).to eq(0) 68 | 69 | @initial_time = Time.parse "2014-07-19 15:26:48 -0400" 70 | 71 | User.create created_at: @initial_time 72 | User.create created_at: @initial_time - 1.day 73 | User.create created_at: @initial_time - 1.day 74 | User.create created_at: @initial_time - 1.week 75 | 76 | expect( User.count ).to eq( 4 ) 77 | 78 | res = User.group_by_day( :created_at ) 79 | 80 | expect(res).to eq output[:day] 81 | end 82 | 83 | it "should return items grouped by week" do 84 | expect( User.count ).to eq(0) 85 | 86 | @t = Time.parse "2014-07-19 15:26:48 -0000" 87 | 88 | 8.times do |t| 89 | User.create created_at: @t - t.day 90 | end 91 | 92 | expect( User.group_by_week ).to eq output[:week] 93 | end 94 | 95 | it "should return items grouped by month" do 96 | expect( User.count ).to eq(0) 97 | 98 | @t = Time.parse "2014-07-19 15:26:48 -0400" 99 | 100 | 10.times do |t| 101 | User.create created_at: @t - t.weeks 102 | end 103 | 104 | expect( User.group_by_month ).to eq output[:month] 105 | end 106 | 107 | it "should return items grouped by year" do 108 | expect( User.count ).to eq(0) 109 | 110 | @t = Time.parse "2014-07-19 15:26:48 -0400" 111 | 112 | 10.times do |t| 113 | User.create created_at: @t - t.months 114 | end 115 | 116 | expect( User.group_by_year ).to eq output[:year] 117 | end 118 | end 119 | 120 | context "collections" do 121 | it "should return items grouped by hour of day" do 122 | expect( User.count ).to eq(0) 123 | 124 | @t = Time.parse "2014-07-19 15:26:48 -0400" 125 | 126 | 10.times do |t| 127 | User.create created_at: @t - (21.minutes * t) 128 | end 129 | 130 | expect( User.group_by_hour_of_day ).to eq output[:hour_of_day] 131 | end 132 | 133 | it "should return items grouped by day of week" do 134 | expect( User.count ).to eq(0) 135 | 136 | @t = Time.parse "2014-07-19 15:26:48 -0400" 137 | 138 | 8.times do |t| 139 | User.create created_at: @t - t.day 140 | end 141 | 142 | expect( User.group_by_day_of_week ).to eq output[:day_of_week] 143 | end 144 | 145 | it "should return items grouped by day of month" do 146 | expect( User.count ).to eq(0) 147 | 148 | @t = Time.parse "2014-07-10 15:26:48 -0400" 149 | 150 | 5.times do |t| 151 | User.create created_at: @t - (t*10).days 152 | end 153 | 154 | expect( User.group_by_day_of_month ).to eq output[:day_of_month] 155 | end 156 | 157 | it "should return items grouped by month of year" do 158 | expect( User.count ).to eq(0) 159 | 160 | @t = Time.parse "2014-12-19 15:26:48 -0400" 161 | 162 | 10.times do |t| 163 | User.create created_at: @t - t.weeks 164 | end 165 | 166 | User.create created_at: @t - 12.months 167 | 168 | expect( User.group_by_month_of_year ).to eq output[:month_of_year] 169 | end 170 | end 171 | 172 | context "aggregations" do 173 | it "should be able to count" do 174 | expect( User.count ).to eq(0) 175 | 176 | @t = Time.parse "2014-07-19 15:26:48 -0400" 177 | 178 | 10.times do |t| 179 | User.create created_at: @t - (21.minutes * t), score: t 180 | end 181 | 182 | expect( User.group_by_hour( "created_at", "count") ).to eq output[:count] 183 | end 184 | 185 | it "should be able to sum" do 186 | expect( User.count ).to eq(0) 187 | 188 | @t = Time.parse "2014-07-19 15:26:48 -0400" 189 | 190 | 10.times do |t| 191 | User.create created_at: @t - (21.minutes * t), score: t 192 | end 193 | 194 | expect( User.group_by_hour( "created_at", "sum", "score") ).to eq output[:sum] 195 | end 196 | 197 | it "should be able to avergage" do 198 | expect( User.count ).to eq(0) 199 | 200 | @t = Time.parse "2014-07-19 15:26:48 -0400" 201 | 202 | 10.times do |t| 203 | User.create created_at: @t - (21.minutes * t), score: t 204 | end 205 | 206 | expect( User.group_by_hour( "created_at", "avg", "score") ).to eq output[:average] 207 | end 208 | end 209 | 210 | context "conditions" do 211 | it "should be able to limit the range" do 212 | expect( User.count ).to eq(0) 213 | 214 | @t = Time.parse "2014-07-19 15:26:48 -0400" 215 | 216 | 10.times do |t| 217 | User.create created_at: @t - (21.minutes * t) 218 | end 219 | 220 | expect( User.where( "created_at > ?", "2014-07-19 17:59:00 UTC").group_by_hour ).to eq output[:range] 221 | end 222 | 223 | 224 | it "should default to created_at" do 225 | expect( User.count ).to eq(0) 226 | 227 | @initial_time = Time.parse "2014-07-19 15:26:48 -0400" 228 | 229 | User.create created_at: @initial_time 230 | User.create created_at: @initial_time - 1.day 231 | User.create created_at: @initial_time - 1.day 232 | User.create created_at: @initial_time - 1.week 233 | 234 | expect( User.count ).to eq( 4 ) 235 | 236 | res = User.group_by_day 237 | 238 | expect(res).to eq output[:default] 239 | end 240 | 241 | it "should work with where clause" do 242 | expect( User.count ).to eq(0) 243 | 244 | @initial_time = Time.parse "2014-07-19 15:26:48 -0400" 245 | 246 | User.create created_at: @initial_time 247 | User.create created_at: @initial_time - 1.day 248 | User.create created_at: @initial_time - 1.day 249 | User.create created_at: @initial_time - 1.week 250 | 251 | expect( User.count ).to eq( 4 ) 252 | 253 | res = User.where( "created_at < ?", @initial_time ).group_by_day( :created_at ) 254 | 255 | expect( res ).to eq output[:where] 256 | end 257 | 258 | it "should work with updated_at" do 259 | expect( User.count ).to eq(0) 260 | 261 | @initial_time = Time.parse "2014-07-19 15:26:48 -0400" 262 | 263 | User.create created_at: @initial_time, updated_at: @initial_time 264 | User.create created_at: @initial_time - 1.day, updated_at: @initial_time - 1.day 265 | User.create created_at: @initial_time - 1.day, updated_at: @initial_time - 1.day 266 | User.create created_at: @initial_time - 1.week, updated_at: @initial_time 267 | 268 | expect( User.count ).to eq( 4 ) 269 | 270 | res = User.group_by_day( :updated_at ) 271 | 272 | expect(res).to eq output[:updated_at] 273 | end 274 | end 275 | 276 | end 277 | end 278 | --------------------------------------------------------------------------------