card-mod-counts 0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []