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 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,14 @@
1
+ include_set Abstract::CqlSearch
2
+ include_set Abstract::SearchViews
3
+
4
+ def self.included host_class
5
+ host_class.include_set Abstract::CachedCount
6
+ end
7
+
8
+ def virtual?
9
+ new?
10
+ end
11
+
12
+ def type_id
13
+ SearchTypeID
14
+ end
@@ -0,0 +1,7 @@
1
+ def self.included host_class
2
+ host_class.class_eval do
3
+ include_set Abstract::CachedCount
4
+ recount_trigger(host_class) { |changed_card| changed_card }
5
+ self
6
+ end
7
+ 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: []