like_query 0.0.6 → 0.0.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 84fed18b7e4d5122c2d760bdf76c27bcc7094d7e44a140e0b42d8dff1d574827
4
- data.tar.gz: adb485617d123592abd58140a086073da2745eef15d25a4c84a40635ae1e92f7
3
+ metadata.gz: 310d79668485fd3063a1e6c938d0c1bb925bf988e08cd93a7430c256c1f5cc50
4
+ data.tar.gz: 5b6c403c42b64078d728171d9c977ae267d482954e666b0afbf9068cbba8795c
5
5
  SHA512:
6
- metadata.gz: 5e7b363e0409f55048163956d485b2b93668eddb3f1de4b49daca8aaffafd2f9866c4a3dfc6abf6b12fecbfdae5e190fe69e8265c162fac97b7e3aff0bfb858d
7
- data.tar.gz: 4b5a6c0aff2b9a4c1f7c7fa2f08da962e6e4937146a351f8a2c57d8946f2880051a4b1d209a997fbdf4f0eac0e208dcf379603530bc0413b298e8ee8ec6f39e8
6
+ metadata.gz: 43a1cc9aab0e0df7fc3f8bf9fca7d2e4c0e44a72857294d779a8af8c633ff56ad54bb6921c10675661ae9ea533d1ad2f82ac2bd56cadd75e061612b6b4302132
7
+ data.tar.gz: ecf6ce1eaff200351093c36073e6e320ddf9645d2ba926f2098c2fbef815d4bc0a921cd2d4f53b09827d1cccdb82073f432a3c6c21695628c58522cd18e2e483
data/README.md CHANGED
@@ -2,10 +2,14 @@
2
2
 
3
3
  For my customers apps, newly built with turbo, search queries are mostly for two purposes:
4
4
 
5
- Index view callable by a url like `/customers?find=müller screw` or a javascript dropdown on the front, in our case built with svelte, which receives a json and renders a table in a dropdown.
5
+ - Index view callable by a url like `/customers?find=müller screw`
6
+ - javascript component / dropdown on the front, in our case
7
+ built with svelte, which receives a json and renders a table in a dropdown.
6
8
 
7
9
  This query generator is built for this two purposes.
8
10
 
11
+ modules like a above mentioned svelte component having a total response time (from pressing a key until the result is rendered) from aproximately 60 msec while by turbo the same time is mostly around 140-160 msec. The gem itselve, from querying the database until producing a hash has a time from around 3 msec. Theese results are for small data sets (if found 30 records, by example)
12
+
9
13
  ## Installation
10
14
 
11
15
  add
@@ -14,18 +18,18 @@ add
14
18
 
15
19
  to Gemfile
16
20
 
17
- this adds the methods `#like` and `#like_result` to all models.
21
+ this adds the methods `#like` and `#generate_hash` to all models.
18
22
 
19
23
  ## Usage
20
24
 
21
- **.like**
25
+ **#like**
22
26
 
23
27
  ```ruby
24
28
  customer = Customer.create(name: 'Ambühl')
25
29
  art1 = Article.create(name: 'first', number: '01', customer: customer)
26
30
 
27
31
  Article.like('fir', :name, :number)
28
- # => would find art1
32
+ # => <Article:0x00000001067107a8 id: ...>
29
33
  # => searches by: "where name like '%fir%' or number like '%fir%'"
30
34
  # => queries are built with Article.arel_table[:name].matches('%fir%')
31
35
 
@@ -35,75 +39,154 @@ Article.like(['fir', 'ambühl'], :name, :number, customer: :name)
35
39
  # => would also find art1
36
40
  ```
37
41
 
38
- **.like_result**
42
+ **#generate_hash**
39
43
 
44
+ can only be chained behind `#like`.
40
45
  returns a hash that can easily be transformed by `#to_json` for a javascript-frontend, by example
41
46
 
42
- can only be chained behind `#like`.
43
-
44
47
  ```ruby
45
48
  customer = Customer.create(name: 'Ambühl')
46
49
  art1 = Article.create(name: 'first', number: '01', customer: customer)
47
50
 
48
- Article.like('fir', :name).like_result(limit: 10)
51
+ Article.like('fir', :name).generate_hash(limit: 10)
49
52
  # returns:
50
- {
51
- :data=>[
53
+ {
54
+ :data => [
52
55
  {
53
- :attributes=>[[:name, "first"]],
54
- :id=>1}],
55
- :length=>1,
56
- :overflow=>false,
57
- :columns_count=>1,
58
- :sub_records_columns_count=>0
56
+ :attributes => [[:name, "first"]],
57
+ :id => 1 }],
58
+ :length => 1,
59
+ :overflow => false,
60
+ :columns_count => 1,
61
+ :sub_records_columns_count => 0
59
62
  }
60
63
 
61
- Article.like('fir', :name).like_result( :number, limit: 10)
64
+ Article.like('fir', :name).generate_hash(:number, limit: 10)
62
65
  # would query like the above example: Search scope is only :name
63
66
  # but would return article-number instead of article-name inside the data block
64
67
  ```
65
68
 
66
- `#like_result` uses `LikeQuery::Collect`, functionality is the same.
69
+ `#generate_hash` uses `LikeQuery::Collect`, functionality is the same.
67
70
 
68
- **Class LikeQuery::Collect**
71
+ **Class LikeQuery::Collect**
69
72
 
70
73
  ```ruby
71
74
  cust = Customer.create(name: 'Müller')
72
- 20.times {Article.create(name: 'any-article', customer: cust)}
75
+ 20.times { Article.create(name: 'any-article', customer: cust) }
73
76
 
74
77
  c = LikeQuery::Collect.new(4)
75
78
  # => 4 is the limit
76
79
 
77
- c.receive { Article.like('any-art', :name) }
80
+ c.collect { Article.like('any-art', :name) }
78
81
  # => would add 4 articles to the result hash because of limit
79
82
 
80
- c.receive { Customer.like('any-art', :name, image: :image_column, articles: :name) }
83
+ c.collect { Customer.like('any-art', :name, image: :image_column, articles: :name) }
81
84
  # => limit is already exhausted: does nothing
82
85
  # => otherwise it would add Customers to the result hash
83
86
 
84
87
  c.result
85
88
  # => would return anything like (this output is from different code!!):
86
89
  {
87
- :data=>[
88
- {:attributes=>[[:name, "Ambühl"]], :id=>1, :image=>"src:customer-image",
89
- :associations=>{
90
- :articles=>[
91
- {:attributes=>[[:number, "01"]], :id=>1, :image=>"src:article-image"},
92
- {:attributes=>[[:number, "01"]], :id=>2, :image=>"src:article-image"}
93
- ]
94
- }
90
+ :data => [
91
+ { :attributes => [[:name, "Ambühl"]], :id => 1, :image => "src:customer-image",
92
+ :associations => {
93
+ :articles => [
94
+ { :attributes => [[:number, "01"]], :id => 1, :image => "src:article-image" },
95
+ { :attributes => [[:number, "01"]], :id => 2, :image => "src:article-image" }
96
+ ]
97
+ }
95
98
  }
96
- ],
97
- :length=>3,
98
- :overflow=>false,
99
- :columns_count=>1,
100
- :sub_records_columns_count=>1
99
+ ],
100
+ :length => 3,
101
+ :overflow => false,
102
+ :columns_count => 1,
103
+ :sub_records_columns_count => 1
104
+ }
105
+ ```
106
+
107
+ **query schema and result_schema**
108
+
109
+ The resulting hash for one record looks like:
110
+
111
+ ```ruby
112
+ {
113
+ :values => ["abc", "123"],
114
+ :id => 456,
115
+ :model => "article",
116
+ :image => "src:img..."
117
+ }
118
+ ```
119
+
120
+ The resulting hash or json is built from the schema, which can look like:
121
+
122
+ ```ruby
123
+ :number
124
+ ```
125
+
126
+ or
127
+
128
+ ```ruby
129
+ [:number, :name]
130
+ ```
131
+
132
+ or
133
+
134
+ ```ruby
135
+ {
136
+ values: [:number, :name],
137
+ image: :name_of_a_column_or_method
101
138
  }
102
139
  ```
103
140
 
141
+ There is a query schema and a output schema.
142
+ If no output schema is defined, query schema is used for both
143
+
144
+ ```ruby
145
+ Article.like('first', :name).generate_hash
146
+ # => :values => ["first"]
147
+ ```
148
+
149
+ If output schema is given, result can be different to the search scope:
150
+
151
+ ```ruby
152
+ Article.like('first', :name).generate_hash(:number, :name)
153
+ # => :values => ["012", "first"]
154
+ ```
155
+
156
+ The collect class reminds the schema for a model:
157
+
158
+ ```ruby
159
+ c = LikeQuery::Collect.new
160
+ c.collect([:name]) { Article.like('x') }
161
+ c.collect { Article.like('x') } #=> schema [:name] is used
162
+ c.collect { Article.like('x', :number) } # => schema [:number] is used
163
+ ```
164
+
165
+ **#set_schema**
166
+
167
+ If a child delivers its parent, the schema for parent has to be given.
168
+ Otherwise `#generate_hash` would not know which values it should return
169
+
170
+ ```ruby
171
+ c = LikeQuery::Collect.new
172
+ c.set_schema(Customer, :name)
173
+ # => now the customer will be returned with { values: [<name>] }
174
+ c.collect(parent: :customer) { Article.like('screw', :name) }
175
+ # => this will add the customer (unless it exists in the list) and add the Article as a child to the customer
176
+ r = c.generate_hash
177
+ ```
178
+
179
+ **Performance**
180
+
181
+ Values defined by schema are processed by `#send` method, but recursive.
182
+ This, by example means, that for a `Article` with given key `customer.name` in schema would return the name of the associated customer.
183
+
184
+ ATTENTION: This can trigger lots of database queries, depending on your structure or if or which method is behind the called names.
185
+
104
186
  ## Tests
105
187
 
106
- Tests for this gem, by rspec, are included not inside this gem, they can be found in a [test project](https://gitlab.com/sedl/like_query_project)
188
+ Tests for this gem, by rspec, are included not inside this gem, they can be found in
189
+ a [test project](https://gitlab.com/sedl/like_query_project)
107
190
 
108
191
  - [ ] [Set up project integrations](https://gitlab.com/sedl/like_query/-/settings/integrations)
109
192
 
@@ -1,116 +1,90 @@
1
1
  module LikeQuery
2
2
  class Collect
3
- def initialize(limit = 50)
3
+ def initialize(limit = 20)
4
4
  @limit = limit
5
5
  @length = 0
6
- @data = []
6
+ @data = {}
7
7
  @overflow = false
8
8
  @columns_count = 0
9
9
  @sub_records_columns_count = 0
10
10
  @image = false
11
- @sub_records_image = false
11
+ #@sub_records_image = false
12
+ @start_time = Time.now
13
+ @schemes = {}
14
+ @images = {}
12
15
  end
13
16
 
14
- def receive(*result_pattern, limit: nil, image: nil, &block)
17
+ def set_schema(model, schema)
18
+ @schemes[model.to_s] = schema_to_hash(schema)
19
+ end
20
+
21
+ def collect(output_schema = nil, limit: nil, parent: nil, image: nil, &block)
22
+
23
+ Rails.logger.debug(' x x x x x x x x x START COLLECT x x x x x x x x x x x x x x x x x x')
15
24
 
16
- return false if @length >= @limit
25
+ _limit = (limit ? (@limit && @limit < limit ? @limit : limit) : @limit)
26
+ return false if @length >= _limit
17
27
  length = 0
18
- @image = image.present?
19
28
 
20
- recs = yield
29
+ recs = yield.includes(parent).limit(_limit)
21
30
 
22
- _pattern = (result_pattern.present? ? result_pattern : recs.like_query_pattern)
23
- if _pattern.first.is_a?(Array)
24
- raise 'pattern can only be a array of hashes or symbols' if _pattern.length >= 2
25
- pattern = _pattern.first
26
- else
27
- pattern = _pattern
31
+ scm = (output_schema.present? ? output_schema : recs.like_query_schema)
32
+ if scm.present?
33
+ @schemes[recs.klass.to_s] = schema_to_hash(scm)
34
+ end
35
+ model_name = recs.klass.to_s
36
+ schema = @schemes[model_name] || schema_to_hash(nil)
37
+ _img = image || schema[:image]
38
+ if _img.present?
39
+ @images[model_name] = _img
40
+ @image = true
28
41
  end
29
42
 
30
- associations = []
31
- pattern.each do |p|
32
- if p.is_a?(Hash)
33
- associations += p.keys.map { |k| (k.to_sym >= :image ? nil : k.to_sym) }.compact
43
+ if parent
44
+ parent_assoc = recs.klass.reflect_on_association(parent)
45
+ if !parent_assoc
46
+ raise "parent «#{parent}» is not a valid association"
34
47
  end
48
+ # parent_polymorphic = parent_assoc.options[:polymorphic]
49
+ # unless parent_polymorphic
50
+ # parent_class_name = parent_assoc.klass.to_s
51
+ # parent_schema = @schemes[parent_class_name]
52
+ # unless parent_schema
53
+ # Rails.logger.debug("WARNING: NO SCHEMA GIVEN FOR «#{parent_class_name}»")
54
+ # parent_schema = schema_to_hash(nil)
55
+ # end
56
+ # end
35
57
  end
36
58
 
37
- recs.includes(associations).each do |r|
38
- rec_attr = {}
39
- pattern.each do |p|
40
- if p.is_a?(Hash)
41
-
42
- p.each do |assoc, cols|
59
+ recs.each do |rec|
43
60
 
44
- if assoc.to_sym == :image
45
-
46
- # raise 'Missing input: image column for given key :image' unless cols.present?
47
- #
48
- # # IMAGE COLUMN
49
- #
50
- # rec_attr[:image] = get_column_value(r, cols)
51
- # @image = true
52
-
53
- else
54
-
55
- # ASSOCIATIONS
56
-
57
- sub_records = r.send(assoc)
58
- image_column = nil
59
-
60
- _cols = if cols.is_a?(Hash)
61
- image_column = cols[:image]
62
- cols[:values]
63
- elsif cols.is_a?(Enumerable)
64
- cols
65
- else
66
- [cols]
67
- end
68
-
69
- (sub_records.is_a?(Enumerable) ? sub_records : [sub_records]).each do |sub_record|
70
- sub_attr = []
71
- _cols.each do |c|
72
- sub_attr.push(get_column_value(sub_record, c))
73
- end
74
-
75
- if @length >= @limit || (limit && length >= limit)
76
- @overflow = true
77
- break
78
- else
79
- c = sub_attr.length
80
- @sub_records_columns_count = c if c > @sub_records_columns_count
81
- rec_attr[:associations] ||= {}
82
- rec_attr[:associations][assoc] ||= []
83
- sub_hash = { values: sub_attr, id: sub_record.id, model: sub_record.class.to_s.underscore }
84
- if image_column
85
- sub_hash[:image] = get_column_value(sub_record, image_column)
86
- @sub_records_image = true
87
- end
88
- rec_attr[:associations][assoc].push(sub_hash)
89
- @length += 1
90
- length += 1
91
- end
92
- end
93
- end
94
- end
95
- elsif p.is_a?(Symbol) || p.is_a?(String)
96
-
97
- # MAIN RECORD
98
-
99
- rec_attr[:values] ||= []
100
- rec_attr[:values].push(get_column_value(r, p))
101
- rec_attr[:id] = r.id
102
- rec_attr[:model] = r.class.to_s.underscore
103
- rec_attr[:image] = r.send(image) if @image
104
-
105
- end
106
- end
107
- if @length >= @limit || (limit && length >= limit)
61
+ if @length >= _limit
108
62
  @overflow = true
109
63
  break
110
64
  else
111
- c = (image ? 1 : 0) + rec_attr[:values].length
65
+ r = record_to_hash(rec, schema, image)
66
+ c = (@image ? 1 : 0) + r[:values].to_a.length
112
67
  @columns_count = c if c > @columns_count
113
- @data.push(rec_attr)
68
+ if parent
69
+ parent_record = rec.send(parent)
70
+ parent_class_name = parent_record.class
71
+ parent_key = "#{parent_class_name}#{parent_record.id}"
72
+
73
+ unless @data[parent_key]
74
+ pk = parent_record.class.to_s
75
+ parent_schema = @schemes[pk]
76
+ unless parent_schema
77
+ Rails.logger.debug("WARNING: NO SCHEMA GIVEN FOR «#{pk}»")
78
+ parent_schema = schema_to_hash(nil)
79
+ end
80
+ @data[parent_key] = record_to_hash(parent_record, parent_schema, @images[parent_class_name])
81
+ @length += 1
82
+ end
83
+ @data[parent_key][:children] ||= []
84
+ @data[parent_key][:children].push(r)
85
+ else
86
+ @data["#{rec.class}#{rec.id}"] = r
87
+ end
114
88
  @length += 1
115
89
  length += 1
116
90
  end
@@ -119,23 +93,94 @@ module LikeQuery
119
93
  true
120
94
  end
121
95
 
122
- def result
96
+ def generate_hash
97
+ data = @data.map { |_, v| v }
123
98
  {
124
- data: @data,
99
+ data: data,
125
100
  length: @length,
126
101
  overflow: @overflow,
127
102
  columns_count: @columns_count,
128
103
  sub_records_columns_count: @sub_records_columns_count,
129
104
  image: @image,
130
- sub_records_image: @sub_records_image
105
+ # sub_records_image: @sub_records_image,
106
+ time: Time.now - @start_time
131
107
  }
132
108
  end
133
109
 
134
110
  private
135
111
 
112
+ def record_to_hash(record, schema, image)
113
+ r = {}
114
+ schema[:values].each do |v|
115
+ r[:values] ||= []
116
+ r[:values].push(get_column_value(record, v))
117
+ end
118
+ r[:id] = record.id
119
+ r[:model] = record.class.to_s.underscore
120
+ r[:image] = record.send(image) if image
121
+ r
122
+ end
123
+
124
+ def schema_to_hash(schema)
125
+
126
+ if schema.is_a?(Array) && schema.first.is_a?(Array)
127
+ _schema = schema.first
128
+ raise 'invalid schema format' if schema.length >= 2
129
+ else
130
+ _schema = schema
131
+ end
132
+
133
+ if _schema.is_a?(String) || _schema.is_a?(Symbol)
134
+ { values: [_schema.to_sym] }
135
+ elsif _schema.is_a?(Array)
136
+ r = {}
137
+ _schema.each do |s|
138
+ if s.is_a?(String) || s.is_a?(Symbol)
139
+ r[:values] ||= []
140
+ r[:values].push(s.to_sym)
141
+ elsif s.is_a?(Hash)
142
+ s.each do |k, v|
143
+ if k.to_sym == :image
144
+ r[:image] = v
145
+ end
146
+ end
147
+ else
148
+ raise "invalid schema format (#{s}) in schema => «#{schema}»"
149
+ end
150
+ end
151
+ r
152
+ elsif _schema.is_a?(Hash)
153
+ _schema
154
+ elsif !schema.present?
155
+ { values: [] }
156
+ else
157
+ raise "invalid schema format => «#{schema}»"
158
+ end
159
+ end
160
+
136
161
  def get_column_value(record, column)
137
162
  val = nil
138
- column.to_s.split('.').each { |i| val = (val ? val : record).send(i) }
163
+ if column.is_a?(Hash)
164
+ r = []
165
+ column.each do |k, v|
166
+ if v.is_a?(Array)
167
+ v.each do |_v|
168
+ if _v.is_a?(String) || _v.is_a?(Symbol)
169
+ r.push(get_column_value(record, "#{k}.#{_v}"))
170
+ else
171
+ raise "Too deeply nested objects: #{v}"
172
+ end
173
+ end
174
+ elsif v.is_a?(String) || v.is_a?(Symbol)
175
+ r.push(get_column_value(record, "#{k}.#{v}"))
176
+ else
177
+ raise "query column value can only be done by string or symbol, but given: #{v}"
178
+ end
179
+ end
180
+ val = r.join(', ')
181
+ else
182
+ column.to_s.split('.').each { |i| val = (val ? val : record).send(i) }
183
+ end
139
184
  (val ? val : '')
140
185
  end
141
186
 
@@ -1,21 +1,21 @@
1
1
  module LikeQuery
2
2
  module ModelExtensions
3
3
 
4
- def like(search_string, *pattern)
4
+ def like(search_string, *schema)
5
5
 
6
6
  raise 'like can only be called from a model' if self == ApplicationRecord
7
7
 
8
8
  queries = nil
9
9
  associations = []
10
- @like_query_pattern = pattern
10
+ @like_query_schema = schema
11
11
 
12
12
  (search_string.is_a?(String) ? search_string.split(' ') : search_string).each do |s|
13
13
  str = "%#{s}%"
14
14
  q = nil
15
- if pattern.first.is_a?(Array) && pattern.length >= 2
16
- raise "only one array can be given: Either pattern as one array or as multiple args, not as array"
15
+ if schema.first.is_a?(Array) && schema.length >= 2
16
+ raise "only one array can be given: Either schema as one array or as multiple args, not as array"
17
17
  end
18
- (pattern.first.is_a?(Array) ? pattern.first : pattern).each do |p|
18
+ (schema.first.is_a?(Array) ? schema.first : schema).each do |p|
19
19
  if p.is_a?(Symbol) || p.is_a?(String)
20
20
  _q = arel_table[p].matches(str)
21
21
  q = (q ? q.or(_q) : _q)
@@ -42,28 +42,25 @@ module LikeQuery
42
42
  queries = (queries ? queries.and(q) : q)
43
43
  end
44
44
 
45
- @query = if associations.present?
46
- left_outer_joins(associations).where(
47
- queries
48
- )
49
- else
50
- where(
51
- queries
52
- )
53
- end
45
+ if associations.present?
46
+ left_outer_joins(associations).where(
47
+ queries
48
+ )
49
+ else
50
+ where(
51
+ queries
52
+ )
53
+ end
54
54
  end
55
55
 
56
- def like_result(*pattern, limit: 50, image: nil)
57
-
58
-
59
- raise 'has to be called behind #like' unless @like_query_pattern.is_a?(Array)
56
+ def generate_hash(output_schema = nil, limit: 50, image: nil)
60
57
  c = LikeQuery::Collect.new(limit)
61
- c.receive(*pattern, limit: limit, image: image){@query}
62
- c.result
58
+ c.collect(output_schema, limit: limit, image: image) { all }
59
+ c.generate_hash
63
60
  end
64
61
 
65
- def like_query_pattern
66
- @like_query_pattern
62
+ def like_query_schema
63
+ @like_query_schema
67
64
  end
68
65
 
69
66
  end
@@ -1,3 +1,3 @@
1
1
  module LikeQuery
2
- VERSION = "0.0.6"
2
+ VERSION = "0.0.7"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: like_query
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - christian
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-08-28 00:00:00.000000000 Z
11
+ date: 2023-08-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails