almicube 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: 94b199c38dcd6cb5b6599020ca0f935f7081810c
4
+ data.tar.gz: fe414a3b3946b9cfee359866f94780e36c75dc6e
5
+ SHA512:
6
+ metadata.gz: 8200ccaadf03f53d208f55f8e0659089e38075fd5c40ef0d0bca1e2bf306dd86a30020c74861c88c4065ad731cadc8f74fc690943be20beb17aef9bdad999dcb
7
+ data.tar.gz: ecae74f249d13c1668cc564b9420ae2c33fa1f4a9c9453e6a8ece13a4f852d435647cffd70c0d32b69b6198f7e446afdaad66a9865e6fbedf3f71861ff88e5a4
data/.gitignore ADDED
@@ -0,0 +1,34 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /test/tmp/
9
+ /test/version_tmp/
10
+ /tmp/
11
+
12
+ ## Specific to RubyMotion:
13
+ .dat*
14
+ .repl_history
15
+ build/
16
+
17
+ ## Documentation cache and generated files:
18
+ /.yardoc/
19
+ /_yardoc/
20
+ /doc/
21
+ /rdoc/
22
+
23
+ ## Environment normalisation:
24
+ /.bundle/
25
+ /lib/bundler/man/
26
+
27
+ # for a library or gem, you might want to ignore these files since the code is
28
+ # intended to run in multiple environments; otherwise, check them in:
29
+ # Gemfile.lock
30
+ # .ruby-version
31
+ # .ruby-gemset
32
+
33
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
34
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in chromesphere.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Nia
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,37 @@
1
+ almicube
2
+ ========
3
+
4
+ Extend Your Model
5
+ --------
6
+
7
+ in `app/models/item.rb`
8
+
9
+ ```ruby
10
+ class Item < ActiveRecord::Base
11
+ ranking_with :access, as: Integer
12
+ end
13
+ ```
14
+
15
+ in `app/controllers/items\_controller.rb`
16
+
17
+ ```ruby
18
+ class ItemsController
19
+ def index
20
+ @items = Item.access_ranking.page params[:page]
21
+ end
22
+ end
23
+
24
+ in `app/views/items/index.html.erb`
25
+
26
+ ```erb
27
+ <ul>
28
+ <% @items.each do |item| %>
29
+ <li>[<%= item.access_ranking.rank %>] <%= item.name %><li>
30
+ <% end %>
31
+ </ul>
32
+ ```
33
+
34
+ Note
35
+ --------
36
+
37
+ - Almicube uses Redis for DataStore.
data/almicube.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'almicube/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "almicube"
7
+ spec.version = Almicube::VERSION
8
+ spec.authors = ["niaeashes"]
9
+ spec.email = ["nia.eashes@gmail.com"]
10
+ spec.summary = "Ranking extension for your model"
11
+ spec.description = "Provide raking system for your ActiveRecord Model on Rails 4 with SortedSet of Redis DataBase."
12
+ spec.homepage = "https://github.com/niaeashes/almicube"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency "redis", "~> 3.0"
21
+ spec.add_dependency "activesupport", "~> 4.0"
22
+ end
@@ -0,0 +1,46 @@
1
+ require 'active_support/all'
2
+
3
+ module Almicube
4
+ module Model
5
+ def self.included(base)
6
+ base.class_eval do
7
+ class << self
8
+
9
+ def ranking_with(ranker, options={})
10
+ ranker = ranker.to_s
11
+ options.symbolize_keys!.merge(Ranking.default_options)
12
+
13
+ class_name = ( options.fetch :class_name, Almicube::Ranking ).to_s
14
+ as = options.fetch :as, Float
15
+ per_page = options.fetch :per_page, 10
16
+
17
+ class_eval <<-EVAL
18
+ def self.#{ranker}_ranking(date = Date.today)
19
+ @rankings ||= {}
20
+ @rankings[:"#{ranker}:\#{date.to_s}"] ||= #{class_name}.build attribute_name: :#{ranker}, class_name: self, as: #{as}, date: date, per_page: #{per_page}
21
+ end
22
+
23
+ def #{ranker}_ranking(date = Date.today)
24
+ Almicube::AssociationProxy.new(self.class.#{ranker}_ranking(date), self)
25
+ end
26
+
27
+ def #{ranker}_rank(date = Date.today)
28
+ self.#{ranker}_ranking(date).rank
29
+ end
30
+
31
+ def #{ranker}_score(date = Date.today)
32
+ self.#{ranker}_ranking(date).score
33
+ end
34
+
35
+ def #{ranker}_incr(date = Date.today, score = 1)
36
+ self.#{ranker}_ranking(date).incr(score)
37
+ end
38
+ EVAL
39
+ end
40
+
41
+ end
42
+
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,25 @@
1
+ module Almicube
2
+ class AssociationProxy
3
+ def initialize(ranking, item)
4
+ raise TypeError, "without the Almicube::Ranking as #1 argument" unless ranking.kind_of? Ranking
5
+ @item = item
6
+ @ranking = ranking
7
+ end
8
+
9
+ def score
10
+ @ranking.score(@item)
11
+ end
12
+
13
+ def rank
14
+ @ranking.rank(@item)
15
+ end
16
+
17
+ def incr(score = 1)
18
+ @ranking.incr(@item, score)
19
+ end
20
+
21
+ def to_s
22
+ "#{@item.to_s}: #{@ranking.key} [#{rank} - #{score}]"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,44 @@
1
+ module Almicube
2
+ class RangedRanking < Almicube::Ranking
3
+ class << self
4
+ def overwrite_params
5
+ { key: '%{prefix}:%{class_name}:%{range}:%{suffix}', type: :range }
6
+ end
7
+ end
8
+
9
+ def aggregate(options={})
10
+ keys = range_days(range).times.inject([]) do |keys, i|
11
+ key = key_format(@options[:data_key], @options.merge( date: date.to_date - i.days, type: :data ))
12
+ keys << key if connection.exists key
13
+ keys
14
+ end
15
+ connection.zunionstore key, keys, aggregate: :sum
16
+ end
17
+
18
+ def range
19
+ @options.fetch(:range, default_range)
20
+ end
21
+
22
+ def default_range
23
+ :weekly
24
+ end
25
+
26
+ protected
27
+
28
+ def range_days(range, default = nil)
29
+ case range.to_sym
30
+ when :yesterday
31
+ 1
32
+ when :weekly
33
+ 7
34
+ when :twice_weekly
35
+ 14
36
+ when :monthly
37
+ 30
38
+ else
39
+ return default unless default.nil?
40
+ range_days(default_range, 7)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,133 @@
1
+ require "redis"
2
+ require "active_support/all"
3
+
4
+ module Almicube
5
+ class Ranking
6
+ class << self
7
+ def default_options
8
+ { attribute_name: :score,
9
+ type: :data,
10
+ prefix: 'ranking:%{type}',
11
+ suffix: '%{attribute_name}',
12
+ key: '%{prefix}:%{class_name}:%{date}:%{suffix}',
13
+ date_format: '%Y%m%d',
14
+ date: Date.today,
15
+ per_page: 10,
16
+ as: Float,
17
+ default_score: 0 }
18
+ end
19
+
20
+ def connection
21
+ @connection ||= Redis.new
22
+ end
23
+
24
+ def build(options={})
25
+ self.new options
26
+ end
27
+ end
28
+
29
+ def initialize(options={})
30
+ @options = self.class.default_options.merge options.symbolize_keys
31
+
32
+ raise TypeError, ":as option is only allowed Integer or Float" unless [Integer, Float].include? @options[:as]
33
+ end
34
+
35
+ def ranged(range)
36
+ Almicube::RangedRanking.new @options.merge( range: range, data_key: @options[:key] ).merge(Almicube::RangedRanking.overwrite_params)
37
+ end
38
+
39
+ KEY_PATTERN = /%{([a-z_]+)}/ # Example) %{sample_key}
40
+
41
+ def key
42
+ key_format(@options[:key], @options)
43
+ end
44
+
45
+ def all
46
+ target_class.all
47
+ end
48
+
49
+ def page(page=1)
50
+ page = [1, page.to_i].max
51
+ revrange = connection.zrevrange(key, (page - 1) * per_page, page * per_page - 1)
52
+ records = target_class.find(revrange)
53
+ revrange.inject([]) { |l, v| l << records.detect { |r| r.to_param == v } }
54
+ end
55
+
56
+ def attribute_name
57
+ @options.fetch(:attribute_name, :score).to_s.to_sym
58
+ end
59
+
60
+ def date
61
+ @options.fetch(:date, Date.today)
62
+ end
63
+
64
+ def per_page
65
+ @options.fetch(:per_page, 0)
66
+ end
67
+
68
+ def per_page=(value)
69
+ @options[:per_page] = value
70
+ end
71
+
72
+ def aggregate!(options = {})
73
+ overwrite = !! ( options.try(:overwrite) || false )
74
+ raise "ranking is already exists. if you want to force aggregating: set { overwrite: true }" if exists? && ! overwrite
75
+
76
+ all.each do |record|
77
+ score = @options.fetch(:default_score, 0)
78
+ score = record.send attribute_name if record.respond_to? attribute_name
79
+ connection.zadd key, score.to_f, record.to_param
80
+ end
81
+ end
82
+
83
+ def aggregate(options = {})
84
+ aggregate! options
85
+ true
86
+ rescue Exception => e
87
+ false
88
+ end
89
+
90
+ def exists?
91
+ connection.exists key
92
+ end
93
+
94
+ def score(item)
95
+ actual_score = ( connection.zscore key, item.to_param ) || @options.fetch(:default_score, 0)
96
+ case @options[:as].to_s
97
+ when "Integer"
98
+ actual_score.to_i
99
+ when "Float"
100
+ actual_score.to_f
101
+ else
102
+ actual_score
103
+ end
104
+ end
105
+
106
+ def rank(item)
107
+ connection.zrevrank(key, item.to_param) + 1
108
+ end
109
+
110
+ def incr(item, score = 1)
111
+ connection.zincrby key, score, item.to_param
112
+ end
113
+
114
+ protected
115
+
116
+ def connection
117
+ self.class.connection
118
+ end
119
+
120
+ def target_class
121
+ @options[:class_name].to_s.constantize
122
+ end
123
+
124
+ def key_format(key_base, options)
125
+ key = key_base.clone
126
+ tmp_options = options.merge date: options.fetch(:date, Date.today).strftime(@options[:date_format])
127
+ key.match( self.class::KEY_PATTERN ) do |m|
128
+ key.gsub! m[0], tmp_options.fetch(m[1].to_sym, '').to_s
129
+ end while key =~ self.class::KEY_PATTERN
130
+ key
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,3 @@
1
+ module Almicube
2
+ VERSION = '0.0.1'
3
+ end
data/lib/almicube.rb ADDED
@@ -0,0 +1,10 @@
1
+ module Almicube
2
+ autoload :Ranking, 'almicube/ranking'
3
+ autoload :RangedRanking, 'almicube/ranged_ranking'
4
+ autoload :Model, 'almicube/model'
5
+ autoload :AssociationProxy, 'almicube/proxy/association_proxy'
6
+
7
+ if defined? ActiveRecord
8
+ ActiveRecord::Base.send :include, Almicube::Model
9
+ end
10
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: almicube
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - niaeashes
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-04-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: redis
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '4.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '4.0'
41
+ description: Provide raking system for your ActiveRecord Model on Rails 4 with SortedSet
42
+ of Redis DataBase.
43
+ email:
44
+ - nia.eashes@gmail.com
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - .gitignore
50
+ - Gemfile
51
+ - LICENSE
52
+ - README.md
53
+ - almicube.gemspec
54
+ - lib/almicube.rb
55
+ - lib/almicube/model.rb
56
+ - lib/almicube/proxy/association_proxy.rb
57
+ - lib/almicube/ranged_ranking.rb
58
+ - lib/almicube/ranking.rb
59
+ - lib/almicube/version.rb
60
+ homepage: https://github.com/niaeashes/almicube
61
+ licenses:
62
+ - MIT
63
+ metadata: {}
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - '>='
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubyforge_project:
80
+ rubygems_version: 2.2.2
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: Ranking extension for your model
84
+ test_files: []