Searchkick `all` for multiple arrays with OR - ruby-on-rails

I need to build a boolean query with searchkick which will check multiple arrays and condition must be true if all elements of an array exist.
I wants records which contains ["2019-11-05", "2019-11-06", "2019-11-07"] all dates from one array OR from second array ["2019-11-08", "2019-11-09", "2019-11-10"]
it works perfectly for one array like this.
available_on: { all: ["2019-11-05", "2019-11-06", "2019-11-07"] }
I need something like this
available_on: { or: { all: ["2019-11-05", "2019-11-06", "2019-11-07"] , all: ["2019-11-08", "2019-11-09", "2019-11-10"]} }
how can we create a query available_on = A OR B
A, B are arrays and we need to match all elements of arrays
available_on is target term (also an array in index)

You should use where with or filter like this:
where {or: [[{available_on: {all: ["2019-11-05", "2019-11-06", "2019-11-07"]}},
{available_on: {all: ["2019-11-08", "2019-11-09", "2019-11-10"]}}]]}
or use _or filter:
where {_or: [{available_on: {all: ["2019-11-05", "2019-11-06", "2019-11-07"]}},
available_on: {all: ["2019-11-08", "2019-11-09", "2019-11-10"]}]}
There's no difference of results between _or and or, just a little different in syntax(or use one more pair square brackets)

Have you tried using this inside 'where' clause ?
_or: [{available_on: {all: ["2019-11-05", "2019-11-06", "2019-11-07"]}}, {available_on: {all: ["2019-11-05", "2019-11-06", "2019-11-07"]}} ]}

Related

Why does ruby sort Alphanumeric strings with 1's and 0's as binary?

I have an array ["Q10", "Q100", "Q1000", "Q1000a", "Q1001", "Q98"]. After sorting it, I get the following result:
['Q100', 'Q1000', 'Q1000a','Q98', 'Q10', 'Q1001'].sort
["Q10", "Q100", "Q1000", "Q1000a", "Q1001", "Q98"]
Because of this behaviour, I cannot sort my ActiveRecord objects correctly. I have a model of Question which has a field label. I need to sort it based on label. So Question with label Q1 would be first and the question with label Q1a would follow and so on. I get in a similar order with ActiveRecord described to the above example of array. I am using postgresql as my database.
Now I have 3 questions.
Why alphanumeric string sorting behave that way?
How can I achieve my required sorting without using the sort block?
How can I achieve that sorting in ActiveRecord?
If your array were
arr = ["Q10", "Q100", "Q1000", "Q8", "Q1001", "Q98"]
you could write
arr.sort_by { |s| s[/\d+/].to_i }
#=> ["Q8", "Q10", "Q98", "Q100", "Q1000", "Q1001"]
If
s = "Q1000"
then
s[/\d+/].to_i
#=> 1000
See Enumerable#sort_by and String#[].
The regular expression /\d+/ matches a substring of s that contains one or more digits.
If the array were
arr = ["Q10b", "Q100", "Q1000", "Q10a", "Q1001", "Q98", "Q10c"]
you could write
arr.sort_by { |s| [s[/\d+/].to_i, s[/\D+\z/]] }
#=> ["Q10a", "Q10b", "Q10c", "Q98", "Q100", "Q1000", "Q1001"]
If
s = "Q10b"
then
[s[/\d+/].to_i, s[/\D+\z/]]
#=> [10, "b"]
The regular expression /\D+\z/ matches a substring of s that contains one or more non-digits at the end (\z) of the string.
See Array#<=>, specifically the third paragraph, for an explanation of how arrays are ordered when sorting.
If the array were
arr = ["Q10b", "P100", "Q1000", "PQ10a", "Q1001", "Q98", "Q10c"]
you could write
arr.sort_by { |s| [s[/\A\D+/], s[/\d+/].to_i, s[/\D+\z/]] }
#=> ["P100", "PQ10a", "Q10b", "Q10c", "Q98", "Q1000", "Q1001"]
If
s = "PQ10a"
then
[s[/\A\D+/], s[/\d+/].to_i, s[/\D+\z/]]
#=> ["PQ", 10, "a"]
The regular expression /\A\D+/ matches a substring of s that contains one or more non-digits at the beginning (\A) of the string.
This should do the trick for you, casting them to numbers before sorting.
['100', '1000', '98', '10', '1001'].map(&:to_i).sort
This strange map(&:to_i) is shorthand for map { |x| x.to_i }
Edit:
You could do this with AR. This will throw an error if the column doesn't contain a number disguised as a string.
Model.order("some_column::integer")
Edit II:
Try this if it contains strings as well.
Model.order("cast(some_column as integer))"

How to replace entries in a string array with cypher

I have a string array property like so:
["name1", "name2", "name3", "name2", "name4"]
I would like to replace in this array e.g. "name2" with "name5":
["name1", "name5", "name3", "name5", "name4"]
So far i came up with a query like this:
MATCH (parent)-[rel]->(child)
WHERE 'name2' IN rel.names
SET rel.names = [x IN (rel.names+['name5']) WHERE x<>"name2"]
Which results in nearly what i want:
["name1", "name3", "name4", "name5"]
The problem of this query is obvious - it just add's only one times "name5" statically without checking how often "name2" is in the array. For example if I have "name2" n-times the query only add's one "name5" instead of n-times.
Without the "where clause" the query add's a "name5" to arrays that doesn't even have a "name2" included. The right approach should be that the query should instead find 0 times "name2" and add 0 times "name5". So the where part shouldn't be required. How would you solve the problem and is my solution approach the right way to go?
This should work:
MATCH (parent)-[rel]->(child)
WHERE 'name2' IN rel.names
SET rel.names = [x IN rel.names | CASE WHEN "name2" = x THEN "name5" ELSE x END]

How do I query all documents in a Firestore collection for all strings in an array? [duplicate]

From the docs:
You can also chain multiple where() methods to create more specific queries (logical AND).
How can I perform an OR query?
Example:
Give me all documents where the field status is open OR upcoming
Give me all documents where the field status == open OR createdAt <= <somedatetime>
OR isn't supported as it's hard for the server to scale it (requires keeping state to dedup). The work around is to issue 2 queries, one for each condition, and dedup on the client.
Edit (Nov 2019):
Cloud Firestore now supports IN queries which are a limited type of OR query.
For the example above you could do:
// Get all documents in 'foo' where status is open or upcmoming
db.collection('foo').where('status','in',['open','upcoming']).get()
However it's still not possible to do a general OR condition involving multiple fields.
With the recent addition of IN queries, Firestore supports "up to 10 equality clauses on the same field with a logical OR"
A possible solution to (1) would be:
documents.where('status', 'in', ['open', 'upcoming']);
See Firebase Guides: Query Operators | in and array-contains-any
suggest to give value for status as well.
ex.
{ name: "a", statusValue = 10, status = 'open' }
{ name: "b", statusValue = 20, status = 'upcoming'}
{ name: "c", statusValue = 30, status = 'close'}
you can query by ref.where('statusValue', '<=', 20) then both 'a' and 'b' will found.
this can save your query cost and performance.
btw, it is not fix all case.
I would have no "status" field, but status related fields, updating them to true or false based on request, like
{ name: "a", status_open: true, status_upcoming: false, status_closed: false}
However, check Firebase Cloud Functions. You could have a function listening status changes, updating status related properties like
{ name: "a", status: "open", status_open: true, status_upcoming: false, status_closed: false}
one or the other, your query could be just
...where('status_open','==',true)...
Hope it helps.
This doesn't solve all cases, but for "enum" fields, you can emulate an "OR" query by making a separate boolean field for each enum-value, then adding a where("enum_<value>", "==", false) for every value that isn't part of the "OR" clause you want.
For example, consider your first desired query:
Give me all documents where the field status is open OR upcoming
You can accomplish this by splitting the status: string field into multiple boolean fields, one for each enum-value:
status_open: bool
status_upcoming: bool
status_suspended: bool
status_closed: bool
To perform your "where status is open or upcoming" query, you then do this:
where("status_suspended", "==", false).where("status_closed", "==", false)
How does this work? Well, because it's an enum, you know one of the values must have true assigned. So if you can determine that all of the other values don't match for a given entry, then by deduction it must match one of the values you originally were looking for.
See also
in/not-in/array-contains-in: https://firebase.google.com/docs/firestore/query-data/queries#in_and_array-contains-any
!=: https://firebase.googleblog.com/2020/09/cloud-firestore-not-equal-queries.html
I don't like everyone saying it's not possible.
it is if you create another "hacky" field in the model to build a composite...
for instance, create an array for each document that has all logical or elements
then query for .where("field", arrayContains: [...]
you can bind two Observables using the rxjs merge operator.
Here you have an example.
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/merge';
...
getCombinatedStatus(): Observable<any> {
return Observable.merge(this.db.collection('foo', ref => ref.where('status','==','open')).valueChanges(),
this.db.collection('foo', ref => ref.where('status','==','upcoming')).valueChanges());
}
Then you can subscribe to the new Observable updates using the above method:
getCombinatedStatus.subscribe(results => console.log(results);
I hope this can help you, greetings from Chile!!
We have the same problem just now, luckily the only possible values for ours are A,B,C,D (4) so we have to query for things like A||B, A||C, A||B||C, D, etc
As of like a few months ago firebase supports a new query array-contains so what we do is make an array and we pre-process the OR values to the array
if (a) {
array addObject:#"a"
}
if (b) {
array addObject:#"b"
}
if (a||b) {
array addObject:#"a||b"
}
etc
And we do this for all 4! values or however many combos there are.
THEN we can simply check the query [document arrayContains:#"a||c"] or whatever type of condition we need.
So if something only qualified for conditional A of our 4 conditionals (A,B,C,D) then its array would contain the following literal strings: #["A", "A||B", "A||C", "A||D", "A||B||C", "A||B||D", "A||C||D", "A||B||C||D"]
Then for any of those OR combinations we can just search array-contains on whatever we may want (e.g. "A||C")
Note: This is only a reasonable approach if you have a few number of possible values to compare OR with.
More info on Array-contains here, since it's newish to firebase docs
If you have a limited number of fields, definitely create new fields with true and false like in the example above. However, if you don't know what the fields are until runtime, you have to just combine queries.
Here is a tags OR example...
// the ids of students in class
const students = [studentID1, studentID2,...];
// get all docs where student.studentID1 = true
const results = this.afs.collection('classes',
ref => ref.where(`students.${students[0]}`, '==', true)
).valueChanges({ idField: 'id' }).pipe(
switchMap((r: any) => {
// get all docs where student.studentID2...studentIDX = true
const docs = students.slice(1).map(
(student: any) => this.afs.collection('classes',
ref => ref.where(`students.${student}`, '==', true)
).valueChanges({ idField: 'id' })
);
return combineLatest(docs).pipe(
// combine results by reducing array
map((a: any[]) => {
const g: [] = a.reduce(
(acc: any[], cur: any) => acc.concat(cur)
).concat(r);
// filter out duplicates by 'id' field
return g.filter(
(b: any, n: number, a: any[]) => a.findIndex(
(v: any) => v.id === b.id) === n
);
}),
);
})
);
Unfortunately there is no other way to combine more than 10 items (use array-contains-any if < 10 items).
There is also no other way to avoid duplicate reads, as you don't know the ID fields that will be matched by the search. Luckily, Firebase has good caching.
For those of you that like promises...
const p = await results.pipe(take(1)).toPromise();
For more info on this, see this article I wrote.
J
OR isn't supported
But if you need that you can do It in your code
Ex : if i want query products where (Size Equal Xl OR XXL : AND Gender is Male)
productsCollectionRef
//1* first get query where can firestore handle it
.whereEqualTo("gender", "Male")
.addSnapshotListener((queryDocumentSnapshots, e) -> {
if (queryDocumentSnapshots == null)
return;
List<Product> productList = new ArrayList<>();
for (DocumentSnapshot snapshot : queryDocumentSnapshots.getDocuments()) {
Product product = snapshot.toObject(Product.class);
//2* then check your query OR Condition because firestore just support AND Condition
if (product.getSize().equals("XL") || product.getSize().equals("XXL"))
productList.add(product);
}
liveData.setValue(productList);
});
For Flutter dart language use this:
db.collection("projects").where("status", whereIn: ["public", "unlisted", "secret"]);
actually I found #Dan McGrath answer working here is a rewriting of his answer:
private void query() {
FirebaseFirestore db = FirebaseFirestore.getInstance();
db.collection("STATUS")
.whereIn("status", Arrays.asList("open", "upcoming")) // you can add up to 10 different values like : Arrays.asList("open", "upcoming", "Pending", "In Progress", ...)
.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot queryDocumentSnapshots, #Nullable FirebaseFirestoreException e) {
for (DocumentSnapshot documentSnapshot : queryDocumentSnapshots) {
// I assume you have a model class called MyStatus
MyStatus status= documentSnapshot.toObject(MyStatus.class);
if (status!= null) {
//do somthing...!
}
}
}
});
}

Is there a way to apply multiple method by one liner in ruby?

There is an array like this:
a = [1,2,3,4]
I want to get the return values of size and sum like this.
size = a.size
sum = a.sum
Is there a way to get both values by a one-liner like this?
size, sum = a.some_method(&:size, &:sum)
In Ruby, you can do multiple assignments in one line:
size, sum = a.size, a.sum
It doesn't make it more readable, though.
You could do this:
a = [1,2,3,4]
methods = [:size, :max, :min, :first, :last]
methods.map { |m| a.send m }
#=> [4, 4, 1, 1, 4]
Another possible solution:
size, sum = a.size, a.reduce { |a,b| a = a + b }
Previous answers are correct, but if OP was actually concerned about walking the array multiple times, then array.size does not walk the array, it merely returns the length, thus there is no saving from a oneliner in that regard.
On the other hand, if size was just an example and the question is more about making multiple operations on an array in one go, then try something like this:
arr = [1,2,3,4,5,6]
product,sum = arr.inject([1,0]){|sums,el| [sums[0]*el, sums[1]+el]}
# => [720, 21]
That is, inject the array with multiple initial values for the results and then calculate new value for every element.

Find values in array of hashes to create new array of hashes

I have some data returned from an api which I've parsed down to this:
[{:a=>value1, :b=>value2, :c=>value3, :d=>value4}, {:a=>value5, :b=>value6, :c=>value7, :d=>value8},{:a=>value9, :b=>value10, :c=>value11, :d=>value12}, ...]
How can I create a new array of hashes with the keys AND values of b and c, given key = b and key = c? I want to pass the key and return the value and maintain the key. So I want to end up with:
[{:b=>value2, :c=>value3}, {:b=>value6, :c=>value7}, {:b=>value10, :c=>value11}, ...]
Pure ruby
array = [{:a=>'value1', :b=>'value2', :c=>'value3', :d=>'value4'}, {:a=>'value1', :b=>'value2', :c=>'value3', :d=>'value4'}]
b_and_c_array = array.map{|a| a.select{|k, _| [:b, :c].include?(k)} }
We take each hash using the map method that will return a result array. For each hash, we select only [:b, :c] keys. You can add more inside it.
Rails
If using Rails, let's use Hash#slice, prettier :
b_and_c_array = array.map{|a| a.slice(:b, :c) }

Resources