like_query 0.0.5 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 334fa972fb8d61c1e906bd9dc086e10163b7840dd10fd6a13eed59118841f04c
4
- data.tar.gz: 0d76d9085d8e3f65cca59b3cad4abcd798106cf3792a85bf67634a776bf89ae9
3
+ metadata.gz: 310d79668485fd3063a1e6c938d0c1bb925bf988e08cd93a7430c256c1f5cc50
4
+ data.tar.gz: 5b6c403c42b64078d728171d9c977ae267d482954e666b0afbf9068cbba8795c
5
5
  SHA512:
6
- metadata.gz: cbf4216ef96477617063c1a920b4754706302b6437bde18c303c16a0a2409d6bbb0d340a2674027097f89e47eea66816257f5e63995cc1017782a00b1de2c26e
7
- data.tar.gz: cd639637a1835e3e5e67c6c94f1ceb4b01adc9ed5c7f083edd025cd690b86d2d57d754efbb932f1dea34a2ae2e28fad32f637622882138e37d4523ded05d0771
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(sub_record.send(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,24 +93,95 @@ 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) }
139
- val
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
184
+ (val ? val : '')
140
185
  end
141
186
 
142
187
  end
@@ -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.5"
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.5
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