card-mod-lookup 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 +118 -0
- data/lib/card/lookup_filter_query/active_record_extension.rb +43 -0
- data/lib/card/lookup_filter_query/filtering.rb +98 -0
- data/lib/card/lookup_filter_query.rb +115 -0
- data/lib/lookup_table/class_methods.rb +104 -0
- data/lib/lookup_table.rb +64 -0
- data/set/abstract/lookup.rb +7 -0
- data/set/abstract/lookup_events.rb +12 -0
- data/set/abstract/lookup_field.rb +23 -0
- data/set/abstract/lookup_search.rb +94 -0
- metadata +69 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b4215382b1478803a91069e8f71e7a980a64d632c2d5b62e1bd69cf5fb0c7cbe
|
4
|
+
data.tar.gz: 746772c19ae1fa95a3d685a8f8befd8d2d2b516033908d99ae82a7ce0175eeed
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 03eef8d51212c24dffb854ea9d1727c8accd0cb1371a4d46f0b3c05ae478b209c3bd3cd86946ee9a4637e18513126fc342b26d9a7395f5656baaa45972df8c07
|
7
|
+
data.tar.gz: 3eab763cc4378322b543659203326ab436a4990b6ec8be550e5b2b97bd2ed4714c74e54db61c310b9eae44cb6e8573d426f4ee45baa0a370c53ebcd7f686ca3f
|
data/README.md
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
<!--
|
2
|
+
# @title README - mod: lookup
|
3
|
+
-->
|
4
|
+
|
5
|
+
# Lookup mod
|
6
|
+
|
7
|
+
This mod manages lookup tables that help decko sites scale as their decks
|
8
|
+
grow increasingly large and their queries increasingly complex.
|
9
|
+
|
10
|
+
## Why
|
11
|
+
|
12
|
+
CQL is a powerful language for navigating card relationships. It is easy, for
|
13
|
+
example, to find all the cards of a given type that have a field card of a given
|
14
|
+
name that link to a third card with given content and on and on and on.
|
15
|
+
|
16
|
+
But to work, these CQL queries must be translated into SQL and executed,
|
17
|
+
and when a database grows large, it can become quite expensive to execute all
|
18
|
+
the implied self joins.
|
19
|
+
|
20
|
+
For example, consider the use case that first inspired this code: _answers_ on
|
21
|
+
WikiRate.org. WikiRate is a site that collects answers to questions about
|
22
|
+
companies. A given answer is a response to a given metric for a given year
|
23
|
+
for a given company. When you consider all the kinds of companies and metrics
|
24
|
+
and metric designers that WikiRate supports, you can imagine the queries
|
25
|
+
becoming quite complex if we have to re-join the cards table to itself every
|
26
|
+
time we want to consider a different variable.
|
27
|
+
|
28
|
+
The solution was to make a "lookup" table for answers that employs much more
|
29
|
+
conventional relational database design.
|
30
|
+
|
31
|
+
_An increasingly common Decko pattern is to design structures organically and
|
32
|
+
fluidly with cards and then to optimize those structures with lookup tables once
|
33
|
+
the structures have matured. This pattern can be a surprisingly pleasant change
|
34
|
+
to those accustomed to having to try to perfect their data structure before
|
35
|
+
they've collected any data._
|
36
|
+
|
37
|
+
## How
|
38
|
+
|
39
|
+
### LookupTable
|
40
|
+
|
41
|
+
To create a lookup table, you will need to create a database table (most
|
42
|
+
often by using Rails migrations) and a ruby class like the following `Country`
|
43
|
+
lookup:
|
44
|
+
|
45
|
+
in lib/country.rb:
|
46
|
+
```
|
47
|
+
# class for lookup table named "countries"
|
48
|
+
class Country < Cardio::Record
|
49
|
+
|
50
|
+
# country_id in countries table corresponds to id of company card
|
51
|
+
@card_column = :country_id
|
52
|
+
|
53
|
+
# query of all cards in lookup
|
54
|
+
@card_query = { type_id: :company, trash: false }
|
55
|
+
|
56
|
+
include LookupTable
|
57
|
+
|
58
|
+
# The following three are equivalent.
|
59
|
+
# Each would populate a `continent_id` column based on a value returned
|
60
|
+
# by the continent_id method on the country card.
|
61
|
+
|
62
|
+
# explicit fetch method
|
63
|
+
def fetch_continent_id
|
64
|
+
card.continent_d
|
65
|
+
end
|
66
|
+
|
67
|
+
# fetcher with hash argument. hash value becomes method
|
68
|
+
fetcher continent_id: :continent_id
|
69
|
+
|
70
|
+
# fetcher with symbol arguments. column name and method must be the same
|
71
|
+
fetcher :continent_id
|
72
|
+
|
73
|
+
```
|
74
|
+
|
75
|
+
### Abstract::Lookup
|
76
|
+
|
77
|
+
in set/type/country.rb:
|
78
|
+
```
|
79
|
+
# methods for accessing lookup entries
|
80
|
+
include_set Abstract::Lookup
|
81
|
+
|
82
|
+
# events for maintaining lookup sync
|
83
|
+
include_set Abstract::LookupEvents
|
84
|
+
|
85
|
+
# record class from above
|
86
|
+
def lookup_class
|
87
|
+
::Country
|
88
|
+
end
|
89
|
+
|
90
|
+
# called by lookup instance; uses cards
|
91
|
+
def continent_id
|
92
|
+
fetch(:continent)&.id
|
93
|
+
end
|
94
|
+
|
95
|
+
# uses lookup table
|
96
|
+
def continent_id_from_lookup
|
97
|
+
lookup.continent_id
|
98
|
+
end
|
99
|
+
|
100
|
+
```
|
101
|
+
|
102
|
+
### Abstract::LookupField
|
103
|
+
|
104
|
+
in set/right/continent.rb
|
105
|
+
```
|
106
|
+
# methods for maintaing companies table when continent changes
|
107
|
+
# (admittedly an infrequent occurence)
|
108
|
+
include_set Abstract::LookupField
|
109
|
+
```
|
110
|
+
|
111
|
+
|
112
|
+
### Abstract::LookupSearch
|
113
|
+
|
114
|
+
Include this set to gain a helpful framework for developing a filter interface
|
115
|
+
for lookup tables.
|
116
|
+
|
117
|
+
|
118
|
+
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class Card
|
2
|
+
class LookupFilterQuery
|
3
|
+
# support methods for sort and page
|
4
|
+
module ActiveRecordExtension
|
5
|
+
# @params hash [Hash] key1: dir1, key2: dir2
|
6
|
+
def sort hash
|
7
|
+
hash.present? ? sort_by_hash(hash) : self
|
8
|
+
end
|
9
|
+
|
10
|
+
def paging args
|
11
|
+
return self unless valid_page_args? args
|
12
|
+
limit(args[:limit]).offset(args[:offset])
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def valid_page_args? args
|
18
|
+
args.present? && args[:limit].to_i.positive?
|
19
|
+
end
|
20
|
+
|
21
|
+
def sort_by_hash hash
|
22
|
+
rel = self
|
23
|
+
hash.each do |fld, dir|
|
24
|
+
rel, fld = interpret_sort_field rel, fld
|
25
|
+
rel = rel.order Arel.sql("#{fld} #{dir}")
|
26
|
+
end
|
27
|
+
rel
|
28
|
+
end
|
29
|
+
|
30
|
+
def interpret_sort_field rel, fld
|
31
|
+
if (match = fld.match(/^(\w+)_bookmarkers$/))
|
32
|
+
sort_by_bookmarkers match[1], rel
|
33
|
+
else
|
34
|
+
[rel, fld]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def sort_by_bookmarkers type, rel
|
39
|
+
[Card::Bookmark.add_sort_join(rel, "#{table.name}.#{type}_id"), "cts.value"]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
class Card
|
2
|
+
class LookupFilterQuery
|
3
|
+
# shared filtering methods for FilterQuery classes built on lookup tables
|
4
|
+
module Filtering
|
5
|
+
def process_filters
|
6
|
+
normalize_filter_args
|
7
|
+
return if @empty_result
|
8
|
+
@filter_args.each { |k, v| process_filter_option k, v if v.present? }
|
9
|
+
@restrict_to_ids.each { |k, v| filter k, v }
|
10
|
+
end
|
11
|
+
|
12
|
+
def normalize_filter_args
|
13
|
+
# override
|
14
|
+
end
|
15
|
+
|
16
|
+
def process_filter_option key, value
|
17
|
+
if (method = filter_method key)
|
18
|
+
send method, key, value
|
19
|
+
else
|
20
|
+
try "#{key}_query", value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def filter_method key
|
25
|
+
case key
|
26
|
+
when *simple_filters
|
27
|
+
:filter_exact_match
|
28
|
+
when *card_id_filters
|
29
|
+
:filter_card_id
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def filter_exact_match key, value
|
34
|
+
filter key, value if value.present?
|
35
|
+
end
|
36
|
+
|
37
|
+
def filter_card_id key, value
|
38
|
+
return unless (card_id = to_card_id value)
|
39
|
+
|
40
|
+
filter card_id_map[key], card_id
|
41
|
+
end
|
42
|
+
|
43
|
+
def not_ids_query value
|
44
|
+
add_condition "#{lookup_class.card_column} not in (?)", value.split(",")
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_card_id value
|
48
|
+
if value.is_a?(Array)
|
49
|
+
value.map { |v| Card.fetch_id(v) }
|
50
|
+
else
|
51
|
+
Card.fetch_id(value)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def restrict_to_ids col, ids
|
56
|
+
ids = Array(ids)
|
57
|
+
@empty_result ||= ids.empty?
|
58
|
+
restrict_lookup_ids col, ids
|
59
|
+
end
|
60
|
+
|
61
|
+
def restrict_lookup_ids col, ids
|
62
|
+
existing = @restrict_to_ids[col]
|
63
|
+
@restrict_to_ids[col] = existing ? (existing & ids) : ids
|
64
|
+
end
|
65
|
+
|
66
|
+
def restrict_by_cql col, cql
|
67
|
+
cql.reverse_merge! return: :id, limit: 0
|
68
|
+
restrict_to_ids col, Card.search(cql)
|
69
|
+
end
|
70
|
+
|
71
|
+
def filter field, value, operator=nil
|
72
|
+
condition = "#{filter_table field}.#{field} #{op_and_val operator, value}"
|
73
|
+
add_condition condition, value
|
74
|
+
end
|
75
|
+
|
76
|
+
def filter_table _field
|
77
|
+
lookup_table
|
78
|
+
end
|
79
|
+
|
80
|
+
def op_and_val op, val
|
81
|
+
"#{db_operator op, val} #{db_value val}"
|
82
|
+
end
|
83
|
+
|
84
|
+
def add_condition condition, value
|
85
|
+
@conditions << condition
|
86
|
+
@values << value
|
87
|
+
end
|
88
|
+
|
89
|
+
def db_operator operator, value
|
90
|
+
operator || (value.is_a?(Array) ? "IN" : "=")
|
91
|
+
end
|
92
|
+
|
93
|
+
def db_value value
|
94
|
+
value.is_a?(Array) ? "(?)" : "?"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
class Card
|
2
|
+
# base class for FilterQuery classes built on lookup tables
|
3
|
+
class LookupFilterQuery
|
4
|
+
include Filtering
|
5
|
+
|
6
|
+
attr_accessor :filter_args, :sort_args, :paging_args
|
7
|
+
class_attribute :card_id_map, :card_id_filters, :simple_filters
|
8
|
+
|
9
|
+
def initialize filter, sorting={}, paging={}
|
10
|
+
@filter_args = filter
|
11
|
+
@sort_args = sorting
|
12
|
+
@paging_args = paging
|
13
|
+
|
14
|
+
@conditions = []
|
15
|
+
@joins = []
|
16
|
+
@values = []
|
17
|
+
@restrict_to_ids = {}
|
18
|
+
|
19
|
+
process_sort
|
20
|
+
process_filters
|
21
|
+
end
|
22
|
+
|
23
|
+
def lookup_query
|
24
|
+
q = lookup_class.where lookup_conditions
|
25
|
+
q = q.joins(@joins.uniq) if @joins.present?
|
26
|
+
q
|
27
|
+
end
|
28
|
+
|
29
|
+
def lookup_table
|
30
|
+
@lookup_table ||= lookup_class.arel_table.name
|
31
|
+
end
|
32
|
+
|
33
|
+
def condition_sql conditions
|
34
|
+
lookup_class.sanitize_sql_for_conditions conditions
|
35
|
+
end
|
36
|
+
|
37
|
+
def lookup_relation
|
38
|
+
sort_and_page { lookup_query }
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return args for AR's where method
|
42
|
+
def lookup_conditions
|
43
|
+
condition_sql([@conditions.join(" AND ")] + @values)
|
44
|
+
end
|
45
|
+
|
46
|
+
# TODO: support optionally returning lookup objects
|
47
|
+
|
48
|
+
# @return array of metric answer card objects
|
49
|
+
# if filtered by missing values then the card objects
|
50
|
+
# are newly instantiated and not in the database
|
51
|
+
def run
|
52
|
+
@empty_result ? [] : main_results
|
53
|
+
end
|
54
|
+
|
55
|
+
# @return [Array]
|
56
|
+
def count
|
57
|
+
@empty_result ? 0 : main_query.count
|
58
|
+
end
|
59
|
+
|
60
|
+
def limit
|
61
|
+
@paging_args[:limit]
|
62
|
+
end
|
63
|
+
|
64
|
+
def main_query
|
65
|
+
@main_query ||= lookup_query
|
66
|
+
end
|
67
|
+
|
68
|
+
def main_results
|
69
|
+
# puts "SQL: #{lookup_relation.to_sql}"
|
70
|
+
lookup_relation.map(&:card)
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def sort_and_page
|
76
|
+
relation = yield
|
77
|
+
@sort_joins.uniq.each { |j| relation = relation.joins(j) }
|
78
|
+
|
79
|
+
relation.sort(@sort_hash).paging(@paging_args)
|
80
|
+
end
|
81
|
+
|
82
|
+
def process_sort
|
83
|
+
@sort_joins = []
|
84
|
+
@sort_hash = @sort_args.each_with_object({}) do |(by, dir), h|
|
85
|
+
h[sort_by(by)] = sort_dir(dir)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def sort_by sort_by
|
90
|
+
if (id_field = sort_by_cardname[sort_by])
|
91
|
+
sort_by_join sort_by, lookup_table, id_field
|
92
|
+
else
|
93
|
+
simple_sort_by sort_by
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def sort_by_cardname
|
98
|
+
{}
|
99
|
+
end
|
100
|
+
|
101
|
+
def sort_dir dir
|
102
|
+
dir
|
103
|
+
end
|
104
|
+
|
105
|
+
def simple_sort_by sort_by
|
106
|
+
sort_by
|
107
|
+
end
|
108
|
+
|
109
|
+
def sort_by_join sort_by, from_table, from_id_field
|
110
|
+
@sort_joins <<
|
111
|
+
"JOIN cards as #{sort_by} ON #{sort_by}.id = #{from_table}.#{from_id_field}"
|
112
|
+
"#{sort_by}.key"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module LookupTable
|
2
|
+
# shared class methods for lookup tables.
|
3
|
+
module ClassMethods
|
4
|
+
attr_reader :card_column,
|
5
|
+
:card_query # cql that finds all items in the cards table
|
6
|
+
|
7
|
+
def for_card cardish
|
8
|
+
card_id = Card.id cardish
|
9
|
+
card_id ? where(card_column => card_id).take : nil
|
10
|
+
end
|
11
|
+
alias_method :find_for_card, :for_card
|
12
|
+
|
13
|
+
def new_for_card cardish
|
14
|
+
new.tap do |lookup|
|
15
|
+
lookup.card_id = Card.id cardish
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# TODO: change to create_for_card for consistency
|
20
|
+
def create cardish
|
21
|
+
new_for_card(cardish).refresh
|
22
|
+
end
|
23
|
+
|
24
|
+
def create! cardish
|
25
|
+
lookup = new_for_card cardish
|
26
|
+
raise ActiveRecord::RecordInvalid, lookup if lookup.invalid?
|
27
|
+
lookup.refresh
|
28
|
+
end
|
29
|
+
|
30
|
+
def create_or_update cardish, *fields
|
31
|
+
lookup = for_card(cardish) || new_for_card(cardish)
|
32
|
+
fields = nil if lookup.new_record? # update all fields if record is new
|
33
|
+
lookup.refresh(*fields)
|
34
|
+
end
|
35
|
+
|
36
|
+
# @param ids [Array<Integer>] ids of answers in the answer table (NOT card ids)
|
37
|
+
def update_by_ids ids, *fields
|
38
|
+
Array(ids).each do |id|
|
39
|
+
next unless (entry = find_by_id(id))
|
40
|
+
entry.refresh(*fields)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def delete_for_card cardish
|
45
|
+
for_card(cardish)&.destroy
|
46
|
+
end
|
47
|
+
|
48
|
+
# @param ids [Integer, Array<Integer>] card ids of metric answer cards
|
49
|
+
def refresh ids=nil, *fields
|
50
|
+
Array(ids).compact.each do |ma_id|
|
51
|
+
refresh_entry fields, ma_id
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def refresh_entry fields, card_id
|
56
|
+
if Card.exists? card_id
|
57
|
+
create_or_update card_id, *fields
|
58
|
+
else
|
59
|
+
delete_for_card card_id
|
60
|
+
end
|
61
|
+
rescue StandardError => e
|
62
|
+
raise e, "failed to refresh #{name} lookup table " \
|
63
|
+
"for card id #{card_id}: #{e.message}"
|
64
|
+
end
|
65
|
+
|
66
|
+
def refresh_all fields=nil
|
67
|
+
count = 0
|
68
|
+
Card.where(card_query).pluck_in_batches(:id) do |batch|
|
69
|
+
count += batch.size
|
70
|
+
puts "#{batch.first} - #{count}"
|
71
|
+
refresh(batch, *fields)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# define standard lookup fetch methods.
|
76
|
+
#
|
77
|
+
# Eg. fetcher(:company_id) defines #fetch_company_id to fetch from card.company_id
|
78
|
+
#
|
79
|
+
# And fetcher(foo: :bar) defines #fetch_foo to fetch from card.bar
|
80
|
+
def fetcher *args
|
81
|
+
fetcher_hash(*args).each { |col, method| define_fetch_method col, method }
|
82
|
+
end
|
83
|
+
|
84
|
+
def define_main_fetcher
|
85
|
+
define_fetch_method @card_column, :id
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def define_fetch_method column, card_method
|
91
|
+
define_method "fetch_#{column}" do
|
92
|
+
card.send card_method
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def fetcher_hash *args
|
97
|
+
if args.first.is_a?(Hash)
|
98
|
+
args.first
|
99
|
+
else
|
100
|
+
args.each_with_object({}) { |item, h| h[item] = item }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
data/lib/lookup_table.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# lookup table to optimize complex card systems
|
2
|
+
#
|
3
|
+
# TODO: make this a class and have lookup classes inherit from it
|
4
|
+
module LookupTable
|
5
|
+
def self.included host_class
|
6
|
+
host_class.extend LookupTable::ClassMethods
|
7
|
+
host_class.define_main_fetcher
|
8
|
+
end
|
9
|
+
|
10
|
+
def card_column
|
11
|
+
self.class.card_column
|
12
|
+
end
|
13
|
+
|
14
|
+
def card
|
15
|
+
@card ||= Card.fetch send(card_column), look_in_trash: true
|
16
|
+
end
|
17
|
+
|
18
|
+
def card_id
|
19
|
+
send card_column
|
20
|
+
end
|
21
|
+
|
22
|
+
def card_id= id
|
23
|
+
send "#{card_column}=", id
|
24
|
+
end
|
25
|
+
|
26
|
+
def delete_on_refresh?
|
27
|
+
!card || card.trash
|
28
|
+
end
|
29
|
+
|
30
|
+
def refresh *fields
|
31
|
+
return delete if delete_on_refresh?
|
32
|
+
|
33
|
+
refresh_fields fields
|
34
|
+
raise Card::Error, "invalid #{self.class} lookup" if invalid?
|
35
|
+
|
36
|
+
save!
|
37
|
+
end
|
38
|
+
|
39
|
+
def refresh_fields fields=nil
|
40
|
+
keys = fields.present? ? fields : attribute_names
|
41
|
+
keys.delete("id")
|
42
|
+
keys.each { |method_name| refresh_value method_name }
|
43
|
+
end
|
44
|
+
|
45
|
+
def refresh_value method_name
|
46
|
+
send "#{method_name}=", send("fetch_#{method_name}")
|
47
|
+
end
|
48
|
+
|
49
|
+
def method_missing method_name, *args, &block
|
50
|
+
if card.respond_to? method_name
|
51
|
+
card.send method_name, *args, &block
|
52
|
+
else
|
53
|
+
super
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def respond_to_missing? *args
|
58
|
+
card.respond_to?(*args) || super
|
59
|
+
end
|
60
|
+
|
61
|
+
def is_a? klass
|
62
|
+
klass == Card || super
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
event :create_lookup, :finalize, on: :create do
|
2
|
+
lookup_class.create self
|
3
|
+
end
|
4
|
+
|
5
|
+
# lookup fields are often based on cards' compound names
|
6
|
+
event :refresh_lookup, :integrate, changed: :name, on: :update do
|
7
|
+
lookup.refresh
|
8
|
+
end
|
9
|
+
|
10
|
+
event :delete_lookup, :finalize, on: :delete do
|
11
|
+
lookup_class.delete_for_card id
|
12
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
def lookup_card
|
2
|
+
left
|
3
|
+
end
|
4
|
+
|
5
|
+
def lookup
|
6
|
+
lookup_card.lookup
|
7
|
+
end
|
8
|
+
|
9
|
+
def lookup_columns
|
10
|
+
Codename[right_id]
|
11
|
+
end
|
12
|
+
|
13
|
+
event :update_lookup_field, :finalize, changed: :content do
|
14
|
+
lookup_field_update do
|
15
|
+
lookup.refresh(*Array.wrap(lookup_columns))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def lookup_field_update
|
22
|
+
yield unless lookup_card.action.in? %i[create delete]
|
23
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
def search args={}
|
2
|
+
return_type = args.delete :return
|
3
|
+
query = args.delete(:query) || query(args)
|
4
|
+
run_query_returning query, return_type
|
5
|
+
end
|
6
|
+
|
7
|
+
def run_query_returning query, return_type
|
8
|
+
case return_type
|
9
|
+
when :name then query.run.map(&:name)
|
10
|
+
when :count then query.count
|
11
|
+
else query.run
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def filter_class
|
16
|
+
raise Error::ServerError, "filter_class required!"
|
17
|
+
end
|
18
|
+
|
19
|
+
def query paging={}
|
20
|
+
filter_class.new query_hash, {}, paging
|
21
|
+
end
|
22
|
+
|
23
|
+
def query_hash
|
24
|
+
{}
|
25
|
+
end
|
26
|
+
|
27
|
+
def count
|
28
|
+
query.count
|
29
|
+
end
|
30
|
+
|
31
|
+
format do
|
32
|
+
delegate :filter_class, to: :card
|
33
|
+
|
34
|
+
def search_with_params
|
35
|
+
@search_with_params ||= card.search query: query
|
36
|
+
end
|
37
|
+
|
38
|
+
def count_with_params
|
39
|
+
@count_with_params ||= card.search query: count_query, return: :count
|
40
|
+
end
|
41
|
+
|
42
|
+
def query
|
43
|
+
filter_class.new query_hash, sort_hash, paging_params
|
44
|
+
end
|
45
|
+
|
46
|
+
def count_query
|
47
|
+
filter_class.new query_hash
|
48
|
+
end
|
49
|
+
|
50
|
+
def query_hash
|
51
|
+
(filter_hash || {}).merge card.query_hash
|
52
|
+
end
|
53
|
+
|
54
|
+
def default_filter_hash
|
55
|
+
card.query_hash
|
56
|
+
end
|
57
|
+
|
58
|
+
def sort_hash
|
59
|
+
{ sort_by.to_sym => sort_dir }
|
60
|
+
end
|
61
|
+
|
62
|
+
def sort_dir
|
63
|
+
return unless sort_by
|
64
|
+
@sort_dir ||= safe_sql_param("sort_dir") || default_sort_dir(sort_by)
|
65
|
+
end
|
66
|
+
|
67
|
+
def default_sort_dir sort_by
|
68
|
+
if default_desc_sort_dir.include? sort_by.to_sym
|
69
|
+
:desc
|
70
|
+
else
|
71
|
+
:asc
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def default_desc_sort_dir
|
76
|
+
[]
|
77
|
+
end
|
78
|
+
|
79
|
+
def sort_by
|
80
|
+
@sort_by ||= sort_by_from_param || default_sort_option
|
81
|
+
end
|
82
|
+
|
83
|
+
def default_sort_option
|
84
|
+
# override
|
85
|
+
end
|
86
|
+
|
87
|
+
def sort_by_from_param
|
88
|
+
safe_sql_param(:sort_by)&.to_sym
|
89
|
+
end
|
90
|
+
|
91
|
+
def default_limit
|
92
|
+
Auth.signed_in? ? 5000 : 500
|
93
|
+
end
|
94
|
+
end
|
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: card-mod-lookup
|
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
|
+
description: ''
|
29
|
+
email:
|
30
|
+
- info@decko.org
|
31
|
+
executables: []
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- README.md
|
36
|
+
- lib/card/lookup_filter_query.rb
|
37
|
+
- lib/card/lookup_filter_query/active_record_extension.rb
|
38
|
+
- lib/card/lookup_filter_query/filtering.rb
|
39
|
+
- lib/lookup_table.rb
|
40
|
+
- lib/lookup_table/class_methods.rb
|
41
|
+
- set/abstract/lookup.rb
|
42
|
+
- set/abstract/lookup_events.rb
|
43
|
+
- set/abstract/lookup_field.rb
|
44
|
+
- set/abstract/lookup_search.rb
|
45
|
+
homepage: http://decko.org
|
46
|
+
licenses:
|
47
|
+
- GPL-3.0
|
48
|
+
metadata:
|
49
|
+
card-mod: lookup
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options: []
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '2.5'
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
requirements: []
|
65
|
+
rubygems_version: 3.2.28
|
66
|
+
signing_key:
|
67
|
+
specification_version: 4
|
68
|
+
summary: lookup
|
69
|
+
test_files: []
|