ohm-tallyable 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NGQyYTQzMTdhZGRjZjRmYWYwZWZkYTE4YzQ5YTEzNzEzOTFiMzAwMA==
5
+ data.tar.gz: !binary |-
6
+ YmYwMmFhYWJjMGRlZGI0NTY1YTY5Y2RlNTlmYTQzMWNjNTUxZmM4ZA==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ ODRjMDMxMjk2ZmY3ZWU5NDNmNDRlMzQwNDMzN2MwNDI1MjIyY2NjM2QwYjdi
10
+ YTJmNThmODFhZjdkNGRkM2ExZTkyODY2MTJkMTE1MWU1OGVhN2VlMzFiYTI3
11
+ NzUwMzNiMmM1ZmRjNGNiNjEyYzBmODllY2VlMjYwYjRiZThiZTY=
12
+ data.tar.gz: !binary |-
13
+ M2FiODBlMzBmM2VkYzNiNmFjMTg1ZjcwMmVhNjA1YjM4YmVlM2ZkMmQ2OTkw
14
+ MGZjZmVlYmQ5NjM0NDA3ZjczNjdlYzU1OTFkNjU2NmI0ODJmN2VlMzk1MGRh
15
+ ZDY4MDA0N2M1ZGRlNDgwOGY0NWQwZTFjOGIwMzllOTVlNTc4ODg=
data/README.md ADDED
@@ -0,0 +1,47 @@
1
+
2
+ ohm-tallyable
3
+ =============
4
+
5
+ A tally plugin for Ohm
6
+
7
+
8
+ Setup
9
+ -----
10
+
11
+ 1. Include the `Tallyable` module in your model:
12
+
13
+ include Ohm::Tallyable
14
+
15
+ 2. Add a tally to your model with the following line:
16
+
17
+ tally :category
18
+
19
+ You will need to resave every model if they already exist.
20
+
21
+ Usage
22
+ -----
23
+
24
+ To query the tallies, use the `leaderboard` class method.
25
+
26
+ Post.leaderboard(:category)
27
+
28
+
29
+ Advanced Usage
30
+ --------------
31
+
32
+ You can also partition the tally by a certain attribute:
33
+
34
+ tally :category, :by => :site_id
35
+
36
+ You will need to provide a value for this attribute every time you check the
37
+ leaderboard:
38
+
39
+ Post.leaderboard(:category, :site_id => 'ar')
40
+
41
+
42
+ Acknowledgements
43
+ ----------------
44
+
45
+ Many thanks to Damian Janowski (https://github.com/djanowski) who took care to
46
+ explain me the details of coding an Ohm plugin and providing many ideas on
47
+ how to handle certain cases.
data/Rakefile ADDED
@@ -0,0 +1,31 @@
1
+ require "rake/testtask"
2
+
3
+ REDIS_DIR = File.expand_path(File.join("..", "test"), __FILE__)
4
+ REDIS_CNF = File.join(REDIS_DIR, "test.conf")
5
+ REDIS_PID = File.join(REDIS_DIR, "db", "redis.pid")
6
+
7
+ task :default => :run
8
+
9
+ desc "Run tests and manage server start/stop"
10
+ task :run => [:start, :test, :stop]
11
+
12
+ desc "Start the Redis server"
13
+ task :start do
14
+ unless File.exists?(REDIS_PID)
15
+ system "redis-server #{REDIS_CNF}"
16
+ end
17
+ end
18
+
19
+ desc "Stop the Redis server"
20
+ task :stop do
21
+ if File.exists?(REDIS_PID)
22
+ system "kill #{File.read(REDIS_PID)}"
23
+ File.delete(REDIS_PID)
24
+ end
25
+ end
26
+
27
+ Rake::TestTask.new do |t|
28
+ t.libs << "test"
29
+ t.test_files = FileList['test/test*.rb']
30
+ t.verbose = true
31
+ end
data/UNLICENSE ADDED
@@ -0,0 +1,24 @@
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <http://unlicense.org/>
@@ -0,0 +1,72 @@
1
+ require 'ohm'
2
+ require 'ohm/contrib'
3
+
4
+ module Ohm
5
+ module Tallyable
6
+ module Macros
7
+ def tally(attribute, options={})
8
+ tallies[attribute] = options
9
+ end
10
+
11
+ def tallies
12
+ @tallies ||= {}
13
+ end
14
+
15
+ def leaderboard(attribute, by=nil)
16
+ tally = tallies[attribute]
17
+ if tally.nil? || (tally[:by] && (by.nil? || !by.include?(tally[:by])))
18
+ raise ArgumentError
19
+ end
20
+ key = self.key[:tallies][attribute]
21
+ if by
22
+ key = key[by.keys.first][by.values.first]
23
+ end
24
+
25
+ _load_zset(key)
26
+ .map { |k, v| [k, v.to_i] }
27
+ .sort_by { |k, v| [-v, k] }
28
+ end
29
+
30
+ if Redis::VERSION.to_i == 2
31
+ def _load_zset(key)
32
+ key.zrevrange(0, -1, with_scores: true).each_slice(2)
33
+ end
34
+ else
35
+ def _load_zset(key)
36
+ key.zrevrange(0, -1, with_scores: true)
37
+ end
38
+ end
39
+ end
40
+
41
+ def self.included(model)
42
+ model.before(:delete, :_decrement_tallies)
43
+ model.before(:save, :_decrement_tallies)
44
+ model.after(:save, :_increment_tallies)
45
+
46
+ model.extend(Macros)
47
+ end
48
+
49
+ def _decrement_tallies
50
+ _update_tallies(-1) { |attribute| read_remote(attribute) }
51
+ end
52
+
53
+ def _increment_tallies
54
+ _update_tallies(1) { |attribute| send(attribute) }
55
+ end
56
+
57
+ def _update_tallies(amount, &block)
58
+ self.class.tallies.each do |attribute, options|
59
+ value = yield(attribute)
60
+ key = self.class.key[:tallies][attribute]
61
+ if options[:by]
62
+ value_by = yield(options[:by])
63
+ key = key[options[:by]][value_by]
64
+ end
65
+ if value
66
+ key.zincrby(amount, value)
67
+ key.zrem(value) if key.zscore(value) == 0.0
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,12 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'ohm-tallyable'
3
+ s.version = '0.1.0'
4
+ s.summary = "Ohm Tally Plugin"
5
+ s.description = "A tally plugin for Ohm that keeps counts of records for every value of an attribute"
6
+ s.author = "Federico Bond"
7
+ s.email = 'federico@educabilia.com'
8
+ s.files = Dir["UNLICENSE", "README.md", "Rakefile", "lib/**/*.rb", "*.gemspec", "test/*.*"]
9
+ s.homepage = 'https://github.com/educabilia/ohm-tallyable'
10
+
11
+ s.add_dependency "ohm", ">= 0.1.3"
12
+ end
@@ -0,0 +1,100 @@
1
+ require 'test/unit'
2
+ require 'ohm/tallyable'
3
+
4
+ class Event < Ohm::Model
5
+ include Ohm::Callbacks
6
+ include Ohm::Tallyable
7
+
8
+ attribute :location
9
+ tally :location
10
+ end
11
+
12
+ class TallyableTest < Test::Unit::TestCase
13
+ def setup
14
+ Ohm.flush
15
+ Event.create(location: "Buenos Aires")
16
+ end
17
+
18
+ def test_leaderboard
19
+ Event.create(location: "Buenos Aires")
20
+ Event.create(location: "Rosario")
21
+ l = Event.leaderboard(:location)
22
+ assert_equal [["Buenos Aires", 2], ["Rosario", 1]], l
23
+ end
24
+
25
+ def test_update
26
+ Event[1].update(location: "Corrientes")
27
+ l = Event.leaderboard(:location)
28
+ assert_equal [["Corrientes", 1]], l
29
+ end
30
+
31
+ def test_nil
32
+ Event[1].update(location: nil)
33
+ l = Event.leaderboard(:location)
34
+ assert_equal [], l
35
+ end
36
+
37
+ def test_create_nil
38
+ Event.create(location: nil)
39
+ l = Event.leaderboard(:location)
40
+ assert_equal [["Buenos Aires", 1]], l
41
+ end
42
+
43
+ def test_delete
44
+ Event[1].delete
45
+ l = Event.leaderboard(:location)
46
+ assert_equal [], l
47
+ end
48
+ end
49
+
50
+ class Post < Ohm::Model
51
+ include Ohm::Callbacks
52
+ include Ohm::Tallyable
53
+
54
+ attribute :category
55
+ attribute :site
56
+ tally :category, by: :site
57
+ end
58
+
59
+ class TallyableByTest < Test::Unit::TestCase
60
+ def setup
61
+ Ohm.flush
62
+ Post.create(category: "Personal", site: "ar")
63
+ Post.create(category: "Personal", site: "ar")
64
+ Post.create(category: "Work", site: "ar")
65
+ end
66
+
67
+ def test_leaderboard
68
+ Post.create(category: "Personal", site: "uy")
69
+ uy = Post.leaderboard(:category, site: "uy")
70
+ ar = Post.leaderboard(:category, site: "ar")
71
+ assert_equal [["Personal", 1]], uy
72
+ assert_equal [["Personal", 2], ["Work", 1]], ar
73
+ end
74
+
75
+ def test_update
76
+ Post[1].update(category: "Work", site: "uy")
77
+ uy = Post.leaderboard(:category, site: "uy")
78
+ ar = Post.leaderboard(:category, site: "ar")
79
+ assert_equal [["Work", 1]], uy
80
+ assert_equal [["Personal", 1], ["Work", 1]], ar
81
+ end
82
+
83
+ def test_delete
84
+ Post[1].delete
85
+ l = Post.leaderboard(:category, site: "ar")
86
+ assert_equal [["Personal", 1], ["Work", 1]], l
87
+ end
88
+
89
+ def test_leaderboard_invalid
90
+ assert_raise(ArgumentError) do
91
+ Post.leaderboard(:foo, site: "ar")
92
+ end
93
+ assert_raise(ArgumentError) do
94
+ Post.leaderboard(:category)
95
+ end
96
+ assert_raise(ArgumentError) do
97
+ Post.leaderboard(:category, foo: "bar")
98
+ end
99
+ end
100
+ end
data/test/test.conf ADDED
@@ -0,0 +1,8 @@
1
+ dir ./test/db
2
+ pidfile ./redis.pid
3
+ port 6379
4
+ timeout 300
5
+ loglevel debug
6
+ logfile stdout
7
+ databases 16
8
+ daemonize yes
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ohm-tallyable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Federico Bond
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-08-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ohm
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.1.3
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.1.3
27
+ description: A tally plugin for Ohm that keeps counts of records for every value of
28
+ an attribute
29
+ email: federico@educabilia.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - UNLICENSE
35
+ - README.md
36
+ - Rakefile
37
+ - lib/ohm/tallyable.rb
38
+ - ohm-tallyable.gemspec
39
+ - test/test-tallyable.rb
40
+ - test/test.conf
41
+ homepage: https://github.com/educabilia/ohm-tallyable
42
+ licenses: []
43
+ metadata: {}
44
+ post_install_message:
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ! '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ! '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubyforge_project:
60
+ rubygems_version: 2.0.6
61
+ signing_key:
62
+ specification_version: 4
63
+ summary: Ohm Tally Plugin
64
+ test_files: []