card-mod-counts 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +51 -0
- data/db/migrate/20170330102819_counts_table.rb +18 -0
- data/lib/card/query/cached_count_join.rb +21 -0
- data/lib/card/query/cached_count_sorting.rb +14 -0
- data/lib/count.rb +84 -0
- data/set/abstract/01_cached_count.rb +67 -0
- data/set/abstract/02_search_cached_count.rb +14 -0
- data/set/abstract/list_cached_count.rb +7 -0
- data/set/abstract/list_ref_cached_count.rb +52 -0
- metadata +82 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 07bf8f2a2e551a3ba76bb0a9be5acc9f60d06eafe89acdf74756e3fb69a49280
|
4
|
+
data.tar.gz: 8a033c80c7e6b778bd34f9c4be1402b73d7fb69daf5a9f16287fac5c88ca1b34
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 39ed100f2208371f60233693c7b04d6d0e42f8519bbf58259ac83f54e3944e331c487a0313178a3fe3422714f5174bf87bb08f2348351292ba63a6b6d2fb11c0
|
7
|
+
data.tar.gz: '08f255b8eb96a16f2a4ee5e023fab2d54bb3562dc7f39f8095f4c08ad55a62b271ee274594f420249ecdcfab2c7d5bcda4831df53e5847d93ae9207cb560cdf1'
|
data/README.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
<!--
|
2
|
+
# @title README - mod: counts
|
3
|
+
-->
|
4
|
+
|
5
|
+
# Counts mod
|
6
|
+
|
7
|
+
Counting things can be slow. This mod helps you store and cache counts so you can retrieve
|
8
|
+
them quickly.
|
9
|
+
|
10
|
+
This is a mod for monkeys; it's specifically aimed at optimizing code. The only support
|
11
|
+
for sharks to date is an override of the `:count` view and a `CQL` extension (see below.)
|
12
|
+
|
13
|
+
|
14
|
+
## Database
|
15
|
+
|
16
|
+
Installing the mod will add a `counts` table. It mimics the cards table structure by
|
17
|
+
storing `value` for a unique `left_id` and `right_id`. No card with those two ids is
|
18
|
+
required; counts are often stored for virtual cards.
|
19
|
+
|
20
|
+
|
21
|
+
## Abstract Sets
|
22
|
+
|
23
|
+
Each of the following sets works on the same underlying principle: you include it
|
24
|
+
with `include_set` to make the set ready to handle counts. Note that many sets will
|
25
|
+
require that you configure recounting.
|
26
|
+
|
27
|
+
### Abstract::CachedCount
|
28
|
+
|
29
|
+
The base class. The following use it, and it can also be used directly for nonstandard
|
30
|
+
cases.
|
31
|
+
|
32
|
+
### Abstract::ListCachedCount
|
33
|
+
|
34
|
+
Count the number of items in an explicit list.
|
35
|
+
|
36
|
+
### Abstract::SearchCachedCount
|
37
|
+
|
38
|
+
Count the number of items returned by a standard CQL search. `recount_trigger` calls are
|
39
|
+
required.
|
40
|
+
|
41
|
+
### Abstract::ListRefCachedCount
|
42
|
+
|
43
|
+
Count a common search pattern: items referred to by a explicit field list. When including
|
44
|
+
this set, you will need to specify two arguments: `type_to_count` and `list_field`.
|
45
|
+
|
46
|
+
## CQL
|
47
|
+
|
48
|
+
You can sort by the counts generated with the following CQL
|
49
|
+
```
|
50
|
+
sort: { right: RIGHT_NAME, item: "cached_count", return: "count" }
|
51
|
+
```
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
class CountsTable < ActiveRecord::Migration[4.2]
|
4
|
+
def up
|
5
|
+
drop_table :counts if table_exists? :counts
|
6
|
+
create_table :counts do |t|
|
7
|
+
t.integer :left_id
|
8
|
+
t.integer :right_id
|
9
|
+
t.integer :value
|
10
|
+
end
|
11
|
+
|
12
|
+
add_index :counts, %i[left_id right_id], name: "left_id_right_id_index"
|
13
|
+
end
|
14
|
+
|
15
|
+
def down
|
16
|
+
drop_table :counts
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Card
|
2
|
+
module Query
|
3
|
+
# joins card query with cached count table
|
4
|
+
class CachedCountJoin < Join
|
5
|
+
def initialize cardquery, right
|
6
|
+
validate_right right
|
7
|
+
super(side: :left, from: cardquery, from_field: "id",
|
8
|
+
to: %w[counts counts_table left_id])
|
9
|
+
@conditions << "counts_table.right_id = #{@right_id}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def validate_right right
|
13
|
+
raise Card::Error::BadQuery, "sort by cached_count: no right given" unless right
|
14
|
+
unless (@right_id = right.card_id)
|
15
|
+
raise Card::Error::BadQuery,
|
16
|
+
"cached count for +#{right}: #{right} does not exist"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class Card
|
2
|
+
module Query
|
3
|
+
attributes[:item] = :ignore # item is in Card::Query; this shouldn't have to be here.
|
4
|
+
|
5
|
+
module CachedCountSorting
|
6
|
+
def sort_by_count_cached_count val
|
7
|
+
count_join = CachedCountJoin.new self, val[:right]
|
8
|
+
joins << count_join
|
9
|
+
@mods[:sort_as] = "integer"
|
10
|
+
@mods[:sort] = "#{count_join.to_alias}.value"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/count.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
class Count < ActiveRecord::Base
|
2
|
+
def step
|
3
|
+
update value + 1
|
4
|
+
end
|
5
|
+
|
6
|
+
def update new_value
|
7
|
+
update! value: new_value
|
8
|
+
new_value
|
9
|
+
end
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def create card
|
13
|
+
validate_count_card card
|
14
|
+
count = Count.new left_id: left_id(card),
|
15
|
+
right_id: right_id(card),
|
16
|
+
value: card.recount
|
17
|
+
count.save!
|
18
|
+
count
|
19
|
+
end
|
20
|
+
|
21
|
+
def fetch_value card
|
22
|
+
find_value_by_card(card) || create(card).value
|
23
|
+
end
|
24
|
+
|
25
|
+
def fetch card
|
26
|
+
find_by_card(card) || create(card)
|
27
|
+
end
|
28
|
+
|
29
|
+
def step card
|
30
|
+
count = find_by_card(card)
|
31
|
+
return create(card).value unless count
|
32
|
+
count.step
|
33
|
+
end
|
34
|
+
|
35
|
+
def refresh card
|
36
|
+
count = find_by_card(card)
|
37
|
+
return create(card).value unless count
|
38
|
+
count.update card.recount
|
39
|
+
end
|
40
|
+
|
41
|
+
def find_value_by_card card
|
42
|
+
where_card(card).pluck(:value).first
|
43
|
+
end
|
44
|
+
|
45
|
+
def find_by_card card
|
46
|
+
where_card(card).take
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def where_card card
|
52
|
+
Count.where(left_id: left_id(card), right_id: right_id(card))
|
53
|
+
end
|
54
|
+
|
55
|
+
def left_id card
|
56
|
+
if card.compound?
|
57
|
+
card.left_id || ((l = card.left) && l.id)
|
58
|
+
else
|
59
|
+
card.id
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def right_id card
|
64
|
+
if card.compound?
|
65
|
+
card.right_id || ((r = card.right) && r.id)
|
66
|
+
else
|
67
|
+
-1
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def validate_count_card card
|
72
|
+
reason = "has to respond to 'recount'" unless card.respond_to? :recount
|
73
|
+
reason ||=
|
74
|
+
if card.compound?
|
75
|
+
"needs left_id" unless left_id(card)
|
76
|
+
"needs right_id" unless right_id(card)
|
77
|
+
elsif !card.id
|
78
|
+
"needs id"
|
79
|
+
end
|
80
|
+
return unless reason
|
81
|
+
raise Card::Error, card.name, "count not cacheable: card #{card.name} #{reason}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
# Cards in this set cache a count in the counts table
|
4
|
+
|
5
|
+
def self.included host_class
|
6
|
+
host_class.extend ClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
def cached_count
|
10
|
+
@cached_count || hard_cached_count(::Count.fetch_value(self))
|
11
|
+
end
|
12
|
+
|
13
|
+
def update_cached_count _changed_card=nil
|
14
|
+
hard_cached_count ::Count.refresh(self)
|
15
|
+
end
|
16
|
+
|
17
|
+
def hard_cached_count value
|
18
|
+
Card.cache.hard&.write_attribute key, :cached_count, value
|
19
|
+
@cached_count = value
|
20
|
+
end
|
21
|
+
|
22
|
+
# called to refresh the cached count
|
23
|
+
# the default way is that the card is a search card and we just
|
24
|
+
# count the search result
|
25
|
+
# for special calculations override this method in your set
|
26
|
+
def recount
|
27
|
+
count
|
28
|
+
end
|
29
|
+
|
30
|
+
module ClassMethods
|
31
|
+
# @param parts [Array] set parts of changed card
|
32
|
+
def recount_trigger *set_parts, &block
|
33
|
+
event_args = set_parts.last.is_a?(Hash) ? set_parts.pop : {}
|
34
|
+
set = ensure_set { set_parts }
|
35
|
+
event_name = recount_event_name set, event_args[:on]
|
36
|
+
define_recount_event set, event_name, event_args, &block
|
37
|
+
end
|
38
|
+
|
39
|
+
def field_recount field_card
|
40
|
+
yield unless field_card.left&.action&.in? %i[create delete]
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def define_recount_event set, event_name, event_args
|
46
|
+
set.class_eval do
|
47
|
+
event event_name, :after_integrate, event_args do
|
48
|
+
Array.wrap(yield(self)).compact.each do |count_card|
|
49
|
+
count_card.update_cached_count self if count_card.respond_to? :recount
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def recount_event_name set, on
|
56
|
+
changed_set = set.to_s.tr(":", "_").underscore
|
57
|
+
count_set = to_s.tr(":", "_").underscore
|
58
|
+
on_actions = on.present? ? "_on_#{Array.wrap(on).join '_'}" : nil
|
59
|
+
:"update_cached_count_for_#{count_set}_triggered_by_#{changed_set}#{on_actions}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
format do
|
64
|
+
def count
|
65
|
+
card.cached_count
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# Include with options :type_to_count and :tag
|
2
|
+
# counts all cards of a type "type_to_count" that are tagged with self via a
|
3
|
+
# +<list_field> pointer.
|
4
|
+
# If <type_to_count>+<list_field> card is updated, all
|
5
|
+
# <changed item name>+<type_to_count> cached counts are updated
|
6
|
+
#
|
7
|
+
# @example count for topic cards the metrics that are tagged with the topic
|
8
|
+
# via a +topic card
|
9
|
+
# include_set Abstract::ListRefCachedCount, type_to_count: :metric,
|
10
|
+
# list_field: :wikirate_topic
|
11
|
+
|
12
|
+
include_set Abstract::SearchCachedCount
|
13
|
+
|
14
|
+
def self.included host_class
|
15
|
+
host_class.class_eval do
|
16
|
+
include_set Abstract::CachedCount
|
17
|
+
recount_trigger :type_plus_right,
|
18
|
+
host_class.type_to_count,
|
19
|
+
host_class.list_field do |changed_card|
|
20
|
+
trait_name = host_class.try(:count_trait) || host_class.type_to_count
|
21
|
+
changed_card.changed_item_names.map do |item_name|
|
22
|
+
Card.fetch item_name.to_name.trait(trait_name)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
define_method :type_id_to_count do
|
27
|
+
Card::Codename.id host_class.type_to_count
|
28
|
+
end
|
29
|
+
|
30
|
+
define_method :list_field_id do
|
31
|
+
Card::Codename.id host_class.list_field
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def cql_content
|
37
|
+
{ type_id: type_id_to_count, right_plus: right_plus_val }
|
38
|
+
end
|
39
|
+
|
40
|
+
def right_plus_val
|
41
|
+
[list_field_id, { refer_to: left.id }]
|
42
|
+
end
|
43
|
+
|
44
|
+
format do
|
45
|
+
# TODO: make less hacky.
|
46
|
+
# Without this filter cql can overwrite right_plus
|
47
|
+
def filter_cql
|
48
|
+
cql = super
|
49
|
+
cql[:right_plus] = [cql[:right_plus], card.right_plus_val].compact
|
50
|
+
cql
|
51
|
+
end
|
52
|
+
end
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: card-mod-counts
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Philipp Kühl
|
8
|
+
- Ethan McCutchen
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2021-10-26 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: card
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: card-mod-search
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
description: ''
|
43
|
+
email:
|
44
|
+
- info@decko.org
|
45
|
+
executables: []
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- README.md
|
50
|
+
- db/migrate/20170330102819_counts_table.rb
|
51
|
+
- lib/card/query/cached_count_join.rb
|
52
|
+
- lib/card/query/cached_count_sorting.rb
|
53
|
+
- lib/count.rb
|
54
|
+
- set/abstract/01_cached_count.rb
|
55
|
+
- set/abstract/02_search_cached_count.rb
|
56
|
+
- set/abstract/list_cached_count.rb
|
57
|
+
- set/abstract/list_ref_cached_count.rb
|
58
|
+
homepage: http://decko.org
|
59
|
+
licenses:
|
60
|
+
- GPL-3.0
|
61
|
+
metadata:
|
62
|
+
card-mod: counts
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options: []
|
65
|
+
require_paths:
|
66
|
+
- lib
|
67
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '2.5'
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
requirements: []
|
78
|
+
rubygems_version: 3.2.28
|
79
|
+
signing_key:
|
80
|
+
specification_version: 4
|
81
|
+
summary: caching of counts
|
82
|
+
test_files: []
|