Suppose we need to write a range query in Mongoid. Let the field to be queried be range_field, then we do something like this
where(:range_field.lte => some-date-time, :range_field.gte => some-date-time)
But if i want to run a query to choose any of multiple ranges, i'd have to do
.or({:range_field.lte => some-date-time1, :range_field.gte => some-date-time2},{:range_field.lte => some-date-time3, :range_field.gte => some-date-time4})
This apparently doesn't work.
How can I run such queries with Mongoid?
When you say:
:range_field.lte => some_date_time
you're calling a method, lte, that Mongoid monkey patches in Symbol. That method returns an Origin::Key instance that is wrapped around the underlying $lte operator. Somewhere inside Mongoid that Origin::Key will be converted to something that MongoDB will understand:
{ range_field: { $lte: some_date_time } }
If you look at what
where(:range_field.lte => t1, :range_field.gte => t2)
becomes by calling selector on the result, you'll see something like this:
{
"created_at" => {
:$gte => t2,
:$lte => t1
}
}
and everything will work fine.
However, if we use #or and call selector to see the underlying query, we see that Mongoid is expanding the Origin::Keys one by one and merging the results:
or({:range_field.lte => t1, :range_field.gte => t2})
# is expanded as though as you said
or({ :range_field => { :$lte => t1 }, :range_field => { :$gte => t2 } })
# which is the same as
or({ :range_field => { :$gte => t2 } })
Essentially, Mongoid is being inconsistent as to how it expands the Origin::Keys. You'll even get the same confusing result if you use :$or instead of #or:
where(:$or => [ {:range_field.lte => t1, :range_field.gte => t2} ]).selector
will say:
{ "$or" => [ { "range_field" => { "$gte" => t2 } } ] }
The solution is to not use the Symbol monkey patched methods and do that part by hand:
or(
{ :range_field => { :$lte => t1, :$gte => t2 } },
{ :range_field => { :$lte => t3, :$gte => t4 } },
...
)
Related
I'm trying to parse out JSON data and create my own dictionary to show a subset of the data. The thing is, I'm noticing that my input data changes based on what is scanned (with nmap). Some elements might be an array value, whereas some might not. The combinations seem to be pretty broad.
For instance, here is the simplest input where only an IP address was found:
{
'host' => {
'address' => {
'addr' => '192.168.0.1'
},
'status' => {...}
}
}
But then, the IP and MAC address might be found:
{
'host' => {
'address' => [{
'addrtype' => 'ipv4',
'addr' => '192.168.0.1',
},{
'addrtype' => 'mac',
'mac' => '00:AA:BB:CC:DD:EE',
},
'status' => {...}
}]
}
Those are just a couple examples. Other variations I've seen:
`host.class` = Array
`address.class` = Hash
`host['status'].class` = Array
etc...
As I go through to parse the output, I am first checking if the element is an Array, if it is, I access the key/values one way, whereas if it's not an array, I essentially have to duplicate my code with a few tweaks to it, which doesn't seem very eloquent:
hash = {}
if hosts.class == Array
hosts.each do |host|
ip = if host['address'].class == Array
host['address'][0]['addr']
else
host['address']['addr']
end
hash[ip] = {}
end
else
ip = if hosts['address'].class == Array
hosts['address'][0]['addr']
else
hosts['address']['addr']
end
hash[ip] = {}
end
puts hash
end
In the end, I'm just trying to find a better/eloquent way to produce a hash like below, while accounts for the possibility that an element may/may not be an Array:
{
'192.168.0.1' => {
'mac' => '00:aa:bb:cc:dd:ee',
'vendor' => 'Apple',
'ports' => {
'80' => {
'status' => 'open',
'service' => 'httpd'
}
'443' => {
'status' => 'filtered',
'service' => 'httpd'
}
}
},
192.168.0.2 => {
...
}
}
If there a ruby method that I haven't run across yet that will make this more fluid?
Not really... but you can make it always an array eg by doing something like:
hosts = [hosts] unless hosts.is_a?(Array)
or similar... then just pass that to your now-non-duplicated code. :)
The 20 lines of code in your question can be reduced to a single line using Array#wrap instead of conditionals, and using Enumerable#map instead of Enumerable#each:
Array.wrap(hosts).map { |host| [Array.wrap(host['address']).first['addr'], {}] }.to_h
Now that's magic!
I have the following tables/models: A, B, C, BC, D, BCD
(A:B 1:N) Connecting table D to BCD would be no problem. However I would like to filter key attributes as dropdown from A, B, C and D to find results in BCD (because in the end I need BCDid). In BCD next to BCid and Did I can store of course Aid, Bid and Cid, and it would seem to me quite an easy workaround, however I know it's totally against db normalisation. Is there another, better way (with eager loading of course)?
I've now this in BCD:
public function getB() {
return $this->hasOne(\app\models\B::className(), ['id' => 'Bid'])
->via('BC');
}
and it seems to work, but it's not eager loading.
And how do I get to model A? can I define it like this in BCD?:
public function getA() {
return $this->hasOne(\app\models\A::className(), ['id' => 'Aid'])
->via('BC')
->via(B);
}
It doesn't really work yet.
This way it works (BCSearch):
public function search($params) {
$query = BC::find()->joinWith('A', true)->joinWith('C', true);
Relation A in BC defined with "via". Dropdown filter also works.
But I still don't know how to achieve one more level deep into db structure.
This way it seems to work fully:
models/BCDSearch.php
public function search($params) {
$query = BCD::find()
->select([
'BCD.id',
'BCD.amount',
'A.id AS A_Id',
'A.name AS A_name',
'B.name AS B_name',
'B.name2 AS B_name2',
'C.name AS C_name',
'D.name AS D_name',
])
->leftJoin('BC', 'BC.id = BC_Id')
->leftJoin('B', 'B.id = B_Id')
->leftJoin('A', 'A.id = A_Id')
->leftJoin('C', 'C.id = C_Id')
->leftJoin('D', 'D.id = D_Id');
$query->andFilterWhere([
...
'A.id' => $this->A_Id,
'B.name' => $this->B_name,
'B.name2' => $this->B_name2,
'C.name' => $this->C_name,
'D.name' => $this->D_name,
]);
public function rules() {
return [
...
[[... 'A_Id', 'B_name', 'B_name2', 'C_name', 'D_name'], 'safe'],
];
}
models/base/BCD.php:
class BCD extends Whatever {
public $A_Id;
public $A_name;
public $B_name;
public $B_name2;
public $C_name;
public $D_name;
views/BCD/index.php:
GridView::widget([
'layout' => '{summary}{pager}{items}{pager}',
'dataProvider' => $dataProvider,
'pager' => [
'class' => yii\widgets\LinkPager::className(),
'firstPageLabel' => Yii::t('app', 'First'),
'lastPageLabel' => Yii::t('app', 'Last')],
'filterModel' => $searchModel,
'columns' => [
[
'attribute' => 'A_Id',
'value' => 'A_name',
'filter' => yii\helpers\ArrayHelper::map(app\models\A::find()->all(), 'id', 'name')
],
[
'attribute' => 'B_name',
'value' => 'B_name',
'filter' => yii\helpers\ArrayHelper::map([['id' => 'name1', 'name' => 'name1'], ['id' => 'name2', 'name' => 'name2']], 'id', 'name')
],
[
'attribute' => 'B_name2',
'value' => 'B_name2',
'filter' => yii\helpers\ArrayHelper::map([['id' => 'name2_1', 'name' => 'name2_1'], ['id' => 'name2_2', 'name' => 'name2_2']], 'id', 'name')
],
[
'attribute' => 'C_name',
'value' => 'C_name',
'filter' => yii\helpers\ArrayHelper::map(app\models\C::find()->all(), 'C_name', 'C_name')
],
[
'attribute' => 'D_name',
'value' => 'D_name',
'filter' => yii\helpers\ArrayHelper::map(app\models\D::find()->all(), 'D_name', 'D_name')
],
'amount',
...
hope this helps others. It was not easy to figure out (at least for me) this basically easy solution. I don't know why but so far I couldn't find any relevant info on the web like this.
I have a table "Transfer" in my database. In this table transfer I have a column "archive" in which I store a Json object.
So I have something like that:
archive:{
"AuthorId"=>"6621381"
}
My goal is to find all the transfers where "AuthorId"=>"6621381". Is it possible to do that with rails ?
Something that looks like:
Transfer.where(archive: {"AuthorId" => "6621381"})
Use the ->> operator to access the object field as text :
Transfer.where("archive ->> 'AuthorId' = ?", "123")
=> [0] #<Transfer:0x00000002903a60> {
:archive => {
"AuthorId" => "123"
}
}
]
It also works with other operators, such as LIKE/ILIKE :
Transfer.where("archive ->> 'AuthorId' ILIKE ?", "12%")
=> [
[0] #<Transfer:0x00000002893058> {
:archive => {
"AuthorId" => "123"
}
},
[1] #<Transfer:0x00000002892c98> {
:archive => {
"AuthorId" => "124"
}
}
]
I've been pouring over the google adwords api docs and I can't figure out how to format the selector to retrieve CPC information. I am using the google-adwords-api gem. Below is the method I'm working on inside my Adwords api module.
def self.traffic_estimator_service keyword
if !#adwords #If not already authenticated, do it first
Adwords.authenticate()
end
traffic_estimator_service = #adwords.service(:TrafficEstimatorService, API_VERSION)
selector = {
:xsi_type => 'KeywordEstimateRequest',
:match_type => 'EXACT',
:keyword => keyword
}
data = traffic_estimator_service.get(selector)
puts '---------------------------------'
puts data.inspect
puts '---------------------------------'
end
Of course I never get to the data2.inspect line because of the api errors. ie:
AdsCommon::Errors::UnexpectedParametersError (AdsCommon::Errors::UnexpectedParametersError: [:match_type]):
I've moved things around and tried multiple things inside the selector hash. Can someone give me an example of what this selector hash should look like?
selector = {
:campaign_estimate_requests => [
{
:xsi_type => 'CampaignEstimateRequest',
:ad_group_estimate_requests => {
:xsi_type => 'AdGroupEstimateRequest',
:keyword_estimate_requests => [
{
:max_cpc => {
:xsi_type => 'Money',
:micro_amount => 1_000_000
},
:xsi_type => 'KeywordEstimateRequest',
:keyword => {
:xsi_type => 'Keyword',
:text => keyword,
:match_type => 'EXACT'
}
}
]
}
}
]
}
I have an array like this:
['one','three','two','four']
I have a array of hash like this:
[{'three' => {..some data here..} }, {'two' => {..some data here..} }, {:total => some_total }] # etc...
I want to sort the array of hashes by the first array. I know I can do:
array_of_hashes.sort_by{|k,v| k.to_s} to sort them and it will sort by the key
( and the .to_s to convert :total to a string )
How can I make this happen?
Edit:
I was incorrect about how this is setup, it is actually like this:
{'one' => {:total => 1, :some_other_value => 5}, 'two' => {:total => 2, :some_other_value => 3} }
If I need to put this in a new question, just let me know and I will do that.
Thank you
similar to ctcherry answer, but using sort_by.
sort_arr = ['one','three','two','four']
hash_arr = [{'three' => {..some data here..} }, {'two' => {..some data here..} }]
hash_arr.sort_by { |h| sort_arr.index(h.keys.first) }
The index method of Array is your friend in this case:
sort_list = ['one','three','two','four']
data_list = [{'three' => { :test => 3 } }, {'two' => { :test => 2 } }, {'one' => { :test => 1 } }, {'four' => { :test => 4 } }]
puts data_list.sort { |a,b|
sort_list.index(a.keys.first) <=> sort_list.index(b.keys.first)
}.inspect
Resulting in, the same order as the source array:
[{"one"=>{:test=>1}}, {"three"=>{:test=>3}}, {"two"=>{:test=>2}}, {"four"=>{:test=>4}}]