pick_a_record 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f9b03aee74dc4e3304fa9b8b35147024fa7b71a7
4
+ data.tar.gz: 65be33658d3bb0173b1bd38f8e44e132ebba422f
5
+ SHA512:
6
+ metadata.gz: 59a54cfe12be9a8fef8aaca5be2ce317affe0c8c8a848c6de84d8339d9096266259212ea58df097fdd832c423fb2b145511cc1304cff9baf04c9ad034e0faf95
7
+ data.tar.gz: c9cdb739e2b2fed82568285f3b4f8225169a4bdef02fa38bb47189b7111882e5bfd99c431fcd6be16fa75a32961f501dc93608306e70d25737eaea67fed809d3
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
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.
data/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # PickARecord
2
+
3
+ Very alpha. Wow. Such risk of API breakage.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'pick_a_record'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install pick_a_record
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+
24
+ ## Contributing
25
+
26
+ 1. Fork it
27
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
28
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
29
+ 4. Push to the branch (`git push origin my-new-feature`)
30
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'PickARecord'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
24
+ load 'rails/tasks/engine.rake'
25
+
26
+
27
+
28
+ Bundler::GemHelper.install_tasks
29
+
@@ -0,0 +1,15 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // the compiled file.
9
+ //
10
+ // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
11
+ // GO AFTER THE REQUIRES BELOW.
12
+ //
13
+ //= require jquery
14
+ //= require jquery_ujs
15
+ //= require_tree .
@@ -0,0 +1,13 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the top of the
9
+ * compiled file, but it's generally better to create a new file per style scope.
10
+ *
11
+ *= require_self
12
+ *= require_tree .
13
+ */
@@ -0,0 +1,4 @@
1
+ module PickARecord
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module PickARecord
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>PickARecord</title>
5
+ <%= stylesheet_link_tag "pick_a_record/application", :media => "all" %>
6
+ <%= javascript_include_tag "pick_a_record/application" %>
7
+ <%= csrf_meta_tags %>
8
+ </head>
9
+ <body>
10
+
11
+ <%= yield %>
12
+
13
+ </body>
14
+ </html>
data/config/routes.rb ADDED
@@ -0,0 +1,2 @@
1
+ PickARecord::Engine.routes.draw do
2
+ end
@@ -0,0 +1,18 @@
1
+ module PickARecord
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace PickARecord
4
+
5
+ initializer 'pick_a_record.time_stub' do
6
+ Time.send(:include, PickARecord::TimeExtensions)
7
+ end
8
+
9
+ initializer 'pick_a_record.active_record_integration' do
10
+ ActiveSupport.on_load(:active_record) do
11
+ ::ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:include, PickARecord::RandomFunction)
12
+
13
+ include PickARecord::InRandomOrder
14
+ include PickARecord::SelectorHelpers
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ module PickARecord::InRandomOrder
2
+ extend ActiveSupport::Concern
3
+
4
+ module ClassMethods
5
+ # Return the first `n` record(s) in random order.
6
+ # @param [Integer] n
7
+ # @return [ActiveRecord::Relation]
8
+ def first_random(n = 1)
9
+ n = n.to_i.nonzero? || 1
10
+
11
+ limit(n).in_random_order
12
+ end
13
+
14
+ # @return [ActiveRecord::Relation]
15
+ def in_random_order
16
+ reorder(connection.random_function)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ # Mixin for ActiveRecord::ConnectionAdapters::AbstractAdapter
2
+ module PickARecord::RandomFunction
3
+ # The SQL Standard function for randomization
4
+ # @return [String]
5
+ STANDARD_RANDOM = 'RANDOM()'
6
+
7
+ # MySQL's abbreviated function for randomization
8
+ # @return [String]
9
+ MYSQL_RANDOM = 'RAND()'
10
+
11
+ # @return [String]
12
+ def random_function
13
+ @random_function ||= adapter_name =~ /mysql/i ? MYSQL_RANDOM : STANDARD_RANDOM
14
+ end
15
+ end
@@ -0,0 +1,164 @@
1
+ # Module subclass for generating a cached random record.
2
+ class PickARecord::Selector < Module
3
+ HOUR = 1.hour
4
+ DAY = 1.day
5
+ WEEK = 1.week
6
+
7
+ include Calculi::HasFunctionSet
8
+
9
+ calculi_string :name
10
+ calculi_string :prefix, default: 'random'
11
+ calculi_int :store_last, default: 5
12
+ calculi_duration :duration, default: 1.day
13
+
14
+ # @!attribute [rw] source_scope
15
+ # The source to draw a random record from.
16
+ # @return [Proc, Symbol]
17
+ calculi_procable :source_scope
18
+
19
+ calculi_computed :full_name do
20
+ [prefix, name].compact.join('_').to_sym
21
+ end
22
+
23
+ options! :name, prefix: 'random', store_last: 5, duration: 1.day
24
+
25
+ # @!group Duration attributes
26
+
27
+ # @return {Boolean}
28
+ def hourly?
29
+ duration == HOUR
30
+ end
31
+
32
+ # @return [Boolean]
33
+ def daily?
34
+ duration == DAY
35
+ end
36
+
37
+ # @return [Boolean]
38
+ def weekly?
39
+ duration == WEEK
40
+ end
41
+ # @!endgroup
42
+
43
+ calculi_functions do
44
+ function :cache_key do
45
+ name { "#{full_name}_cache_key" }
46
+
47
+ body do |target, fn, args|
48
+ [self, "#{target.full_name}"]
49
+ end
50
+ end
51
+
52
+ function :last_records_cache_key do
53
+ name { "last_#{full_name}_cache_key" }
54
+
55
+ body do |target, fn, args|
56
+ "#{self}.last_#{target.full_name}_ids"
57
+ end
58
+ end
59
+
60
+ function :last_records_list, memoize: true do
61
+ name { "last_#{full_name}_ids" }
62
+
63
+ requires :last_records_cache_key
64
+
65
+ body do |target, fn, args|
66
+ ::Redis::List.new(fn[:last_records_cache_key], max_length: target.store_last)
67
+ end
68
+ end
69
+
70
+ function :without_last_records do
71
+ name { "without_last_#{full_name}_ids" }
72
+
73
+ requires :last_records_list
74
+
75
+ body do |target, fn, args|
76
+ last_ids = Array(fn[:last_records_list])
77
+
78
+ last_ids.present? ? where('id NOT IN (?)', last_ids) : scoped
79
+ end
80
+ end
81
+
82
+ function :last_records_clear do
83
+ name { "clear_last_#{full_name}_ids!" }
84
+
85
+ requires :last_records_list
86
+
87
+ body do |target, fn, args|
88
+ fn[:last_records_list].clear
89
+ end
90
+ end
91
+
92
+ function :expires_in do
93
+ name { "#{full_name}_expires_in" }
94
+
95
+ if target.daily?
96
+ body { Time.now.seconds_until_end_of_day }
97
+ elsif target.weekly?
98
+ body { Time.now.seconds_until_end_of_week }
99
+ else
100
+ body do |target, fn, args|
101
+ target.duration
102
+ end
103
+ end
104
+ end
105
+
106
+ function :scope do
107
+ name { "scope_for_#{full_name}" }
108
+
109
+ if procable? target.source_scope
110
+ body do |target, fn, args|
111
+ instance_eval(&target.source_scope)
112
+ end
113
+ else
114
+ body { scoped }
115
+ end
116
+ end
117
+
118
+ function :clear! do
119
+ name { "clear_#{full_name}!" }
120
+
121
+ requires :cache_key
122
+
123
+ body do |target, fn, args|
124
+ ::Rails.cache.delete fn[:cache_key]
125
+ end
126
+ end
127
+
128
+ function :fetch_one do
129
+ name { "fetch_one_#{full_name}" }
130
+
131
+ requires :scope, :without_last_records
132
+
133
+ body do |target, fn, args|
134
+ fn.chain(:scope, :without_last_records).in_random_order.first
135
+ end
136
+ end
137
+
138
+ function :get do
139
+ name { "#{full_name}" }
140
+
141
+ requires :fetch_one, :remember, :cache_key, :expires_in
142
+
143
+ body do |target, fn, args|
144
+ ::Rails.cache.fetch fn[:cache_key], expires_in: fn[:expires_in], race_condition_ttl: 20 do
145
+ fn[:fetch_one].tap do |last_entry|
146
+ fn[:remember, last_entry]
147
+ end
148
+ end
149
+ end
150
+ end
151
+
152
+ function :remember do
153
+ name { "remember_#{full_name}" }
154
+
155
+ requires :last_records_list
156
+
157
+ body do |target, fn, args|
158
+ last_entry_id = args.first.try(:id)
159
+
160
+ fn[:last_records_list] << last_entry_id if last_entry_id.present?
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,27 @@
1
+ module PickARecord::SelectorHelpers
2
+ extend ActiveSupport::Concern
3
+
4
+ module ClassMethods
5
+ def pick_a_record(options = {}, &scope)
6
+ options.reverse_merge! name: model_name.singular, source_scope: scope
7
+
8
+ extend PickARecord::Selector.new options
9
+ end
10
+
11
+ def pick_a_daily(selector_name = nil, &scope)
12
+ options = { duration: 1.day, prefix: 'daily' }
13
+
14
+ options[:name] = selector_name if selector_name.present?
15
+
16
+ pick_a_record(options, &scope)
17
+ end
18
+
19
+ def pick_a_weekly(selector_name = nil, &scope)
20
+ options = { duration: 1.week, prefix: 'weekly' }
21
+
22
+ options[:name] = selector_name if selector_name.present?
23
+
24
+ pick_a_record(options, &scope)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,19 @@
1
+ # Extra methods for the {Time} class.
2
+ module PickARecord::TimeExtensions
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ alias_method :seconds_until_end_of_day, :as4_seconds_until_end_of_day unless method_defined?(:seconds_until_end_of_day)
7
+ end
8
+
9
+ # @note Backport of Time#seconds_until_end_of_day from ActiveSupport 4
10
+ # @return [Integer] number of the seconds until the end of the day
11
+ def as4_seconds_until_end_of_day
12
+ end_of_day.to_i - to_i
13
+ end
14
+
15
+ # @return [Integer] number of seconds until the end of the week
16
+ def seconds_until_end_of_week
17
+ end_of_week.to_i - to_i
18
+ end
19
+ end
@@ -0,0 +1,2 @@
1
+ module PickARecord::Utility
2
+ end
@@ -0,0 +1,3 @@
1
+ module PickARecord
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,14 @@
1
+ require 'calculi'
2
+ require 'redis-objects'
3
+
4
+ module PickARecord
5
+ # Extensions
6
+ require 'pick_a_record/in_random_order'
7
+ require 'pick_a_record/random_function'
8
+ require 'pick_a_record/time_extensions'
9
+
10
+ require 'pick_a_record/selector'
11
+ require 'pick_a_record/selector_helpers'
12
+
13
+ require 'pick_a_record/engine'
14
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :pick_a_record do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,174 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pick_a_record
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Alexa Grey
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-04-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: calculi
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 0.0.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 0.0.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: 3.2.13
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: 3.2.13
41
+ - !ruby/object:Gem::Dependency
42
+ name: redis-objects
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: 0.8.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: 0.8.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: sqlite3
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry-rails
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: faker
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: fabrication
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: timecop
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: Pick a random record and cache the result for display.
126
+ email:
127
+ - devel@mouse.vc
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - app/assets/javascripts/pick_a_record/application.js
133
+ - app/assets/stylesheets/pick_a_record/application.css
134
+ - app/controllers/pick_a_record/application_controller.rb
135
+ - app/helpers/pick_a_record/application_helper.rb
136
+ - app/views/layouts/pick_a_record/application.html.erb
137
+ - config/routes.rb
138
+ - lib/pick_a_record/engine.rb
139
+ - lib/pick_a_record/in_random_order.rb
140
+ - lib/pick_a_record/random_function.rb
141
+ - lib/pick_a_record/selector.rb
142
+ - lib/pick_a_record/selector_helpers.rb
143
+ - lib/pick_a_record/time_extensions.rb
144
+ - lib/pick_a_record/utility.rb
145
+ - lib/pick_a_record/version.rb
146
+ - lib/pick_a_record.rb
147
+ - lib/tasks/pick_a_record_tasks.rake
148
+ - MIT-LICENSE
149
+ - Rakefile
150
+ - README.md
151
+ homepage: https://github.com/scryptmouse/pick_a_record
152
+ licenses: []
153
+ metadata: {}
154
+ post_install_message:
155
+ rdoc_options: []
156
+ require_paths:
157
+ - lib
158
+ required_ruby_version: !ruby/object:Gem::Requirement
159
+ requirements:
160
+ - - '>='
161
+ - !ruby/object:Gem::Version
162
+ version: '0'
163
+ required_rubygems_version: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - '>='
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ requirements: []
169
+ rubyforge_project:
170
+ rubygems_version: 2.1.4
171
+ signing_key:
172
+ specification_version: 4
173
+ summary: Pick a random record and cache the result for display.
174
+ test_files: []