card-mod-counts 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 +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: []
|