I'm trying to build an advanced search option (similar to Twitter's). Users can enter the words included, words excluded, words containing, exact phrase, and such in a search query.
I'm using Searchkick to do this. Particularity Searckick's regexp searches.
Here is what I'm doing to find the companies which have the words "Facebook or less" in their slogans.
Company.search("Be", where: { short_desc: /.*(#{ar}).*/ })
This works well. But, how would I do a negative of this search?
Doing something like, Company.search("Be", where: { short_desc: /.*(?!(#{ar})).*/ }) is not yielding results. Also, can I combine a search that has words to be included AND words to be excluded?
May be this helps you, try this:
Business.search("Be", where: { short_desc: {not: /.*(#{ar}).*/} })
And check the results. You can combine this with AND or OR operators. Anywhere using the where clause. Extracted from the gem readme file:
where: {
expires_at: {gt: Time.now}, # lt, gte, lte also available
orders_count: 1..10, # equivalent to {gte: 1, lte: 10}
aisle_id: [25, 30], # in
store_id: {not: 2}, # not
aisle_id: {not: [25, 30]}, # not in
user_ids: {all: [1, 3]}, # all elements in array
category: /frozen .+/, # regexp
or: [
[{in_stock: true}, {backordered: true}]
]
}
This is how you can combine it with query.
Hope this helps.
Related
I have a problem with a relevant search. Results of following request are very strange:
Candidate.search('martin', fields: [:first_name, :last_name],
match: :word_start, misspellings: false).map(&:name)
["Kautzer Martina",
"Funk Martin",
"Jaskolski Martin",
"Gutmann Martine",
"Wiegand Martina",
"Schueller Martin",
"Dooley Martin",
"Stiedemann Martine",
"Bartell Martina",
"Gerlach Martine",
"Green Martina",
"Lang Martine",
"Legros Martine",
"Ernser Martina",
"Boehm Martina",
"Green Martine",
"Nolan Martin",
"Schmidt Martin",
"Hoppe Martin",
"Macejkovic Martine",
"Emard Martine"]
Why Martina is going earlier than Martin?
Searckick config:
searchkick language: %w(German English), word_start: [:first_name, :last_name]
Searchkick 1.4 fixes this issue. There's even a test case dedicated to this question :)
When using word_start, what searchkick actually does is to tokenize the chosen fields (:first_name and :last_name) using the searchkick_word_start_index analyzer. That analyzer is a custom one which uses the following edgeNGram token filter:
searchkick_edge_ngram: {
type: "edgeNGram",
min_gram: 1,
max_gram: 50
},
So, when Kautzer Martina gets indexed, the following tokens are actually produced and indexed:
:first_name: m, ma, mar, mart, marti, martin, martina
:last_name: k, ka, kau, kaut, kautz, kautze, kautzer
Similarly, for Funk Martin:
:first_name: m, ma, mar, mart, marti, martin
:last_name: f, fu, fun, funk
As you can see, when searching for martin, both will match because both contain the token martin and they will be sorted by descending score (default). If you want to order the results differently, you can use sorting and call your search with
order: [{last_name: :asc},{first_name: :asc}]
Try this
misspellings: {edit_distance: 0}
The problem with match: is you have to match the exact word, and caps.
I hope this works.
I am using a hash constant in my ROR application. I want to show the names from the hash constant to drop down.
helper.rb
PRODUCE_GROWING_METHODS = [
{id: 1, name: 'Conventional'},
{id: 2, name: 'Organic'},
]
def produce_growing_methods
PRODUCE_GROWING_METHODS
end
_produce.haml
= f.simple_fields_for :produce_details do |pd|
= pd.input :produce_growing_method, :collection => produce_growing_methods.collect { |x| [x[0], x[1]] }, :prompt => "Select Growing Method"
I tried as shown above in _produce.haml but i am getting the empty drop down. Names from the constant are not populated in drop down.
Can any one help me how to show the names from the PRODUCE_GROWING_METHODS hash constant to a drop down.
Thanks
You should map the hash by keys. In your case the keys are :id and :name:
produce_growing_methods.map { |x| [x[:id], x[:name]] }
In reality you are always better of using a generic solution rather then manual mapping.
Here is a better way of achieving the same, but it will work as well for array of thousand hashes:
ary = [
{id: 1, name: 'Conventional'},
{id: 2, name: 'Organic'},
]
ary.map(&:values)
#=> [[1, "Conventional"], [2, "Organic"]]
In Ruby on Rails 4, I'm trying to make an API for my website and instead of using an array like so:
[{id: 1, name: "John"}, {id: 2, name: "Foo"}, {id: 3, name: "Bar"}]
I want to render it like this, because it makes it much easier to search through in javascript (and for other reasons):
{"1": {id: 1, name: "John"}, "2": {id: 2, name: "Foo"}, "3": {id: 3, name: "Bar"}}
This works:
# users/index.rabl
#users.each do |user|
node(users.id.to_s) do
partial('api/v1/users/show', object: user)
end
end
But in the partial, I want another collection of elements (which belong to the user) and I can't get that working. Is there a more elegant way to do this?
To choose hash-map rather than array is definitely better option if have control on API backend codebase. From BigO notation hash lookup happens with constant O(1) time, not O(n) as for array.
Primary key lookup from the database is the most performant way to query data. Primary key is usually short and always indexed. In case of big data set use pagination.
Let's assume there is no RABL (you can always instantiate pure Ruby classes in RABL DSL code) but just an array:
array = [{id: 1, name: "John"}, {id: 2, name: "Foo"}, {id: 3, name: "Bar"}]
hash = {}
array.each{ |elem,i| hash[elem[:id].to_s] = elem }
# {"1"=>{:id=>1, :name=>"John"}, "2"=>{:id=>2, :name=>"Foo"}, "3"=>{:id=>3, :name=>"Bar"}}
To pass the Ruby hash to Javascript on client you probably want to encode it appropriately:
# hash.to_json
# {"1":{"id":1,"name":"John"},"2":{"id":2,"name":"Foo"},"3":{"id":3,"name":"Bar"}}
From Javascript you query hash by its key:
hash = {"1":{"id":1,"name":"John"},"2":{"id":2,"name":"Foo"},"3":{"id":3,"name":"Bar"}}
hash[1]
# Object { id=1, name="John"}
I have a model Event that is connected to MongoDB using Mongoid:
class Event
include Mongoid::Document
include Mongoid::Timestamps
field :user_name, type: String
field :action, type: String
field :ip_address, type: String
scope :recent, -> { where(:created_at.gte => 1.month.ago) }
end
Usually when I use ActiveRecord, I can do something like this to group results:
#action_counts = Event.group('action').where(:user_name =>"my_name").recent.count
And I get results with the following format:
{"action_1"=>46, "action_2"=>36, "action_3"=>41, "action_4"=>40, "action_5"=>37}
What is the best way to do the same thing with Mongoid?
Thanks in advance
I think you'll have to use map/reduce to do that. Look at this SO question for more details:
Mongoid Group By or MongoDb group by in rails
Otherwise, you can simply use the group_by method from Enumerable. Less efficient, but it should do the trick unless you have hundreds of thousands documents.
EDIT: Example of using map/reduce in this case
I'm not really familiar with it but by reading the docs and playing around I couldn't reproduce the exact same hash you want but try this:
def self.count_and_group_by_action
map = %Q{
function() {
key = this.action;
value = {count: 1};
emit(key, value);
# emit a new document {"_id" => "action", "value" => {count: 1}}
# for each input document our scope is applied to
}
}
# the idea now is to "flatten" the emitted documents that
# have the same key. Good, but we need to do something with the values
reduce = %Q{
function(key, values) {
var reducedValue = {count: 0};
# we prepare a reducedValue
# we then loop through the values associated to the same key,
# in this case, the 'action' name
values.forEach(function(value) {
reducedValue.count += value.count; # we increment the reducedValue - thx captain obvious
});
# and return the 'reduced' value for that key,
# an 'aggregate' of all the values associated to the same key
return reducedValue;
}
}
self.map_reduce(map, reduce).out(inline: true)
# we apply the map_reduce functions
# inline: true is because we don't need to store the results in a collection
# we just need a hash
end
So when you call:
Event.where(:user_name =>"my_name").recent.count_and_group_by_action
It should return something like:
[{ "_id" => "action1", "value" => { "count" => 20 }}, { "_id" => "action2" , "value" => { "count" => 10 }}]
Disclaimer: I'm no mongodb nor mongoid specialist, I've based my example on what I could find in the referenced SO question and Mongodb/Mongoid documentation online, any suggestion to make this better would be appreciated.
Resources:
http://docs.mongodb.org/manual/core/map-reduce/
http://mongoid.org/en/mongoid/docs/querying.html#map_reduce
Mongoid Group By or MongoDb group by in rails
This is how I went about to query for one specific element.
results << read_db.collection("users").find(:created_at => {:$gt => initial_date}).to_a
Now, I am trying to query by more than one.
db.inventory.find({ $and: [ { price: 1.99 }, { qty: { $lt: 20 } }, { sale: true } ] } )
Now how do I build up my query? Essentially I will have have a bunch of if statements, if true, i want to extend my query. I heard there is a .extend command in another langue, is there something similar in ruby?
Essentially i want to do this:
if price
query = "{ price: 1.99 }"
end
if qty
query = query + "{ qty: { $lt: 20 } }"
end
and than just have
db.inventory.find({ $and: [query]})
This syntax is wrong, what is the best way to go about doing this?
You want to end up with something like this:
db.inventory.find({ :$and => some_array_of_mongodb_queries})
Note that I've switched to the hashrocket syntax, you can't use the JavaScript notation with symbols that aren't labels. The value for :$and should be an array of individual queries, not an array of strings; so you should build an array:
parts = [ ]
parts.push(:price => 1.99) if(price)
query.push(:qty => { :$lt => 20 }) if(qty)
#...
db.inventory.find(:$and => parts)
BTW, you might run into some floating point problems with :price => 1.99, you should probably use an integer for that and work in cents instead of dollars. Some sort of check that parts isn't empty might be a good idea too.