How to use regex to match for number and string? [duplicate] - ruby

I have some IDs 214001, 214002, 215001, etc...
From a searchbar, I want autocompletion with the ID
"214" should trigger autocompletion for IDs 214001, 214002
Apparently, I can't just do a
scope :by_number, ->(number){
where(:number => /#{number.to_i}/i)
}
with mongoid. Anyone know a working way of matching a mongoid Integer field with a regex ?
This question had some clue, but how can I do this inside Rails ?
EDIT : The context is to be able to find a project by its integer ID or its short description :
scope :by_intitule, ->(regex){
where(:intitule => /#{Regexp.escape(regex)}/i)
}
# TODO : Not working !!!!
scope :by_number, ->(numero){
where(:number => /#{number.to_i}/i)
}
scope :by_name, ->(regex){
any_of([by_number(regex).selector, by_intitule(regex).selector])
}

The MongoDB solution from the linked question would be:
db.models.find({ $where: '/^124/.test(this.number)' })
Things that you hand to find map pretty much one-to-one to Mongoid:
where(:$where => "/^#{numero.to_i}/.test(this.number)")
The to_i call should make string interpolation okay for this limited case.
Keep in mind that this is a pretty horrific thing to do to your database: it can't use indexes, it will scan every single document in the collection, ...
You might be better off using a string field so that you can do normal regex matching. I'm pretty sure MongoDB will be able to use an index if you anchor your regex at the beginning too. If you really need it to be a number inside the database then you could always store it as both an Integer and a String field:
field :number, :type => Integer
field :number_s, :type => String
and then have some hooks to keep :number_s up to date as :number changes. If you did this, your pattern matching scope would look at :number_s. Precomputing and duplicating data like this is pretty common with MongoDB so you shouldn't feel bad about it.

The way to do a $where in mongoid is using Criteria#for_js
Something like this
Model.for_js("new RegExp(number).test(this.int_field)", number: 763)

Related

Searching a Hash

I'm trying to complete this Codewars Challenge and I'm confused as to where I'm going wrong. Could someone please give me a hand?
The question provides a "database" of translations for Welcome, and the instructions say:
Think of a way to store the languages as a database (eg an object). The languages are listed below so you can copy and paste!
Write a 'welcome' function that takes a parameter 'language' (always a string), and returns a greeting - if you have it in your database. It should default to English if the language is not in the database, or in the event of an invalid input.
My attempt:
def greet(language)
greeting = { 'english'=>'Welcome',
'czech'=>'Vitejte',
'danish'=>'Velkomst',
'dutch'=>'Welkom',
'estonian'=>'Tere tulemast',
'finnish'=>'Tervetuloa',
'flemish'=>'Welgekomen',
'french'=>'Bienvenue',
'german'=>'Willkommen',
'irish'=>'Failte',
'italian'=>'Benvenuto',
'latvian'=>'Gaidits',
'lithuanian'=>'Laukiamas',
'polish'=>'Witamy',
'spanish'=>'Bienvenido',
'swedish'=>'Valkommen',
'welsh'=>'Croeso'
}
greeting.key?(language) ? greeting.each { |k, v| return v if language == k } : 'IP_ADDRESS_INVALID'
end
To my eyes when I run my code through the IDE it seems to be working as per request but I guess I must be wrong somehow.
It's telling me it :
Expected: "Laukiamas", instead got: "Welcome"
But when I type:
p greet("lithuanian")
I get Laukiamas.
You can provide you greeting hash with a default value. It is as simple as
greeting.default = "Welcome"
This enhanced hash does all the work for you. Just look up the key; when it is not there you'll get "Welcome".
Preface
First of all, please don't post links to exercises or homework questions. Quote them in your original question to avoid link rot or additional create work for people trying to help you out.
Understanding the Problem Defined by the Linked Question
Secondly, you're misunderstanding the core question. The requirement is basically to return the Hash value for a given language key if the key exists in the Hash. If it doesn't, then return the value of the 'english' key instead. Implicit in the exercise is to understand the various types of improper inputs that would fail to find a matching key; the solution below addresses most of them, and will work even if your Ruby has frozen strings enabled.
A Working Solution
There are lots of ways to do this, but here's a simple example that will handle invalid keys, nil as a language argument, and abstract away capitalization as a potential issue.
DEFAULT_LANG = 'english'
TRANSLATIONS = {
'english' => 'Welcome',
'czech' => 'Vitejte',
'danish' => 'Velkomst',
'dutch' => 'Welkom',
'estonian' => 'Tere tulemast',
'finnish' => 'Tervetuloa',
'flemish' => 'Welgekomen',
'french' => 'Bienvenue',
'german' => 'Willkommen',
'irish' => 'Failte',
'italian' => 'Benvenuto',
'latvian' => 'Gaidits',
'lithuanian' => 'Laukiamas',
'polish' => 'Witamy',
'spanish' => 'Bienvenido',
'swedish' => 'Valkommen',
'welsh' => 'Croeso'
}
# Return a translation of "Welcome" into the language
# passed as an argument.
#
# #param language [String, #to_s] any object that can
# be coerced into a String, and therefore to
# String#downcase
# #return [String] a translation of "Welcome" or the
# string-literal +Welcome+ if no translation found
def greet language
language = language.to_s.downcase
TRANSLATIONS.fetch language, TRANSLATIONS[DEFAULT_LANG]
end
# Everything in the following Array of examples except
# +Spanish+ should return the Hash value for +english+.
['Spanish', 'EspaƱol', 123, nil].map { greet(_1) }
This will correctly return:
#=> ["Bienvenido", "Welcome", "Welcome", "Welcome"]
because only Spanish (when lower-cased) will match any of the keys currently defined in the TRANSLATIONS Hash. All the rest will use the default value defined for the exercise.
Test Results
Since there are some RSpec tests included with the linked question:
describe "Welcome! Translation" do
it "should translate input" do
Test.assert_equals(greet('english'), 'Welcome', "It didn't work out this time, keep trying!");
Test.assert_equals(greet('dutch'), 'Welkom', "It didn't work out this time, keep trying!");
Test.assert_equals(greet('IP_ADDRESS_INVALID'), 'Welcome', "It didn't work out this time, keep trying!")
end
end
The code provided not only passes the provided tests, but it also passes a number of other edge cases not defined in the unit tests. When run against the defined tests, the code above passes cleanly:
If this is homework, then you might want to create additional tests to cover all the various edge cases. You might also choose to refactor to less idiomatic code if you want more explanatory variables, more explicit intermediate conversions, or more explicit key handling. The point of good code is to be readable, so be as explicit in your code and as thorough in your tests as you need to be in order to make debugging easier.

Rails mongoid regex on an Integer field

I have some IDs 214001, 214002, 215001, etc...
From a searchbar, I want autocompletion with the ID
"214" should trigger autocompletion for IDs 214001, 214002
Apparently, I can't just do a
scope :by_number, ->(number){
where(:number => /#{number.to_i}/i)
}
with mongoid. Anyone know a working way of matching a mongoid Integer field with a regex ?
This question had some clue, but how can I do this inside Rails ?
EDIT : The context is to be able to find a project by its integer ID or its short description :
scope :by_intitule, ->(regex){
where(:intitule => /#{Regexp.escape(regex)}/i)
}
# TODO : Not working !!!!
scope :by_number, ->(numero){
where(:number => /#{number.to_i}/i)
}
scope :by_name, ->(regex){
any_of([by_number(regex).selector, by_intitule(regex).selector])
}
The MongoDB solution from the linked question would be:
db.models.find({ $where: '/^124/.test(this.number)' })
Things that you hand to find map pretty much one-to-one to Mongoid:
where(:$where => "/^#{numero.to_i}/.test(this.number)")
The to_i call should make string interpolation okay for this limited case.
Keep in mind that this is a pretty horrific thing to do to your database: it can't use indexes, it will scan every single document in the collection, ...
You might be better off using a string field so that you can do normal regex matching. I'm pretty sure MongoDB will be able to use an index if you anchor your regex at the beginning too. If you really need it to be a number inside the database then you could always store it as both an Integer and a String field:
field :number, :type => Integer
field :number_s, :type => String
and then have some hooks to keep :number_s up to date as :number changes. If you did this, your pattern matching scope would look at :number_s. Precomputing and duplicating data like this is pretty common with MongoDB so you shouldn't feel bad about it.
The way to do a $where in mongoid is using Criteria#for_js
Something like this
Model.for_js("new RegExp(number).test(this.int_field)", number: 763)

How to query a Mongoid Regexp Field?

I want to create a regex field in my Mongoid document so that I can have a behavior something like this:
MagicalDoc.create(myregex: /abc\d+xyz/)
MagicalDoc.where(myregex: 'abc123xyz')
I'm not sure if this is possible and what kind of affect it would have. How can I achieve this sort of functionality?
Update: I've learned from the documentation that Mongoid supports Regexp fields but it does not provide an example of how to query for them.
class MagicalDoc
include Mongoid::Document
field :myregex, type: Regexp
end
I would also accept a pure MongoDB answer. I can find a way to convert it to Mongoid syntax.
Update: Thanks to SuperAce99 for helping find this solution. Pass a string to a Mongoid where function and it will create a javascript function:
search_string = 'abc123xyz'
MagicalDoc.where(%Q{ return this.myregex.test("#{search_string}") })
The %Q is a Ruby method that helps to escape quotes.
regexp is not a valid BSON type, so you'll have to figure out how Mongoid represents it to devise a proper query.
Query String using Regex
If you want to send MongoDB a regular expression and return documents MongoDB provides the $regex query operator, which allows you to return documents where a string matches your regular expression.
Query Regex using String
If you want to sent Mongo a string and return all documents that have a regular expression that matches the provided string, you'll probably need the $where operator. This allows you to run a Javascript command on each document:
db.myCollection.find( { $where: function() { return (this.credits == this.debits) } } )
You can define a function which returns True when the provided string matches the Regex stored in the document. Obviously this can't use an Index because it has to execute code for every document in the collection. These queries will be very slow.

Can I avoid transposing an array in Ruby on Rails?

I have a Rails app that has a COUNTRIES list with full country names and abbreviations created inside the Company model. The array for the COUNTRIES list is used for a select tag on the input form to store abbreviations in the DB. See below. VALID_COUNTRIES is used for validations of abbreviations in the DB. FULL_COUNTRIES is used to display the full country name from the abbreviation.
class Company < ActiveRecord::Base
COUNTRIES = [["Afghanistan","AF"],["Aland Islands","AX"],["Albania","AL"],...]
COUNTRIES_TRANSFORM = COUNTRIES.transpose
VALID_COUNTRIES = COUNTRIES_TRANSPOSE[1]
FULL_COUNTRIES = COUNTRIES_TRANSPOSE[0]
validates :country, inclusion: { in: VALID_COUNTRIES, message: "enter a valid country" }
...
end
On the form:
<%= select_tag(:country, options_for_select(Company::COUNTRIES, 'US')) %>
And to convert back the the full country name:
full_country = FULL_COUNTRIES[VALID_COUNTRIES.index(:country)]
This seems like an excellent application for a hash, except the key/value order is wrong. For the select I need:
COUNTRIES = {"Afghanistan" => "AF", "Aland Islands" => "AX", "Albania" => "AL",...}
While to take the abbreviation from the DB and display the full country name I need:
COUNTRIES = {"AF" => "Afghanistan", "AX" => "Aland Islands", "AL" => "Albania",...}
Which is a shame, because COUNTRIES.keys or COUNTRIES.values would give me the validation list (depending on which hash layout is used).
I'm relatively new to Ruby/Rails and am looking for the more Ruby-like way to solve the problem. Here are the questions:
Does the transpose occur only once, and if so, when is it executed?
Is there a way to specify the FULL_ and VALID_ lists that do not require the transpose?
Is there a better or reasonable alternate way to do this? For instance, VALID_COUNTRIES is COUNTRIES[x][1] and FULL_COUNTRIES is COUNTRIES[x][0], but VALID_ must work with the validation.
Is there a way to make a hash work with just one hash rather then one for the select_tag and one for converting the abbreviations in the DB back to full names for display?
1) Does the transpose occur only once, and if so, when is it executed?
Yes at compile time because you are assigning to constants if you want it to be evaluated every time use a lambda
FULL_COUNTRIES = lambda { COUNTRIES_TRANSPOSE[0] }
2) Is there a way to specify the FULL_ and VALID_ lists that do not require the transpose?
Yes use a map or collect (they are the same thing)
VALID_COUNTRIES = COUNTRIES.map &:first
FULL_COUNTRIES = COUNTRIES.map &:last
3) Is there a better or reasonable alternate way to do this? For instance, VALID_COUNTRIES is COUNTRIES[x][1] and FULL_COUNTRIES is COUNTRIES[x][0], but VALID_ must work with the validation.
See Above
4) Is there a way to make the hash work?
Yes I am not sure why a hash isn't working as the rails docs say options_for_select will use hash.to_a.map &:first for the options text and hash.to_a.map &:last for the options value so the first hash you give should be working if you can clarify why it is not I can help you more.

How do you stop a MongoDB search from being applied recursively to the key-value tree?

Imagine I have this object (written with Ruby literals) stored in a MongoDB:
{"tags" => ["foo", "bar"],
"jobs" => [{"title" => "Chief Donkey Wrangler", "tags" => ["donkeys"]}] }
Now, I want to search for objects based on the tags on the first level of data, not the second. I can write a query like this (using the Ruby MongoDB library):
things.find("tags" => {"$exists" => "foo"})
This will obviously match the first example, but it will also match an example like this:
{"tags" => ["baz", "bar"],
"jobs" => [{"title" => "Trainee Donkey Wrangler", "tags" => ["donkeys", "foo"]}] }
How do I ensure that I am searching only the top-level of keys? I'm interested in knowing the answer in both JavaScript, Ruby and in a language-agnostic way, as I'd like to use MongoDB as a cross-language store.
Obviously, I could pass a map-reduce function to the datastore to pick out the stuff I'm trying to get, but I'm interested to see if it is supported at a higher level (and to reduce the amount of time I spend writing JavaScript map-reduce functions!)
Actually, the query you specify won't match your second example. To match the second example, you'd do:
things.find({"jobs.tags" => "foo"})
There's no recursive application of the query selector.
You're not using $exists properly. $exists does not allow you to search for a match of a field, it just checks for the existence of such a field. I'm guessing that the Ruby MongoDB library is treating your request for 'foo' as equivalent to true, b/c $exists only accepts true/false as an argument
As #kb points out, you want to use the dot notation to reach into the objects.

Resources