Not Equal (ne) and OR not returning correct results in Mongomapper scopes - ruby

I can't chain these two scopes together in Mongomapper using an OR:
scope :comment_is_nil, where(:comment => nil)
scope :post_not_blank, where(:post.ne => "")
It should return model objects where the comment is not nil, OR the post is not blank.
This doesn't work:
Model.where("$or" => [{:comment_is_nil, :post_not_blank])
Any ideas?

Chaining scopes is an and operation so M.comment_is_nil.post_not_blank won't work as you know. MongoDB's or syntax looks like this:
Model.where(
:$or => [
{ :comment => nil },
{ :post.ne => '' }
]
)
So you need to give it an array of individual conditions by manually expanding the scopes.

Related

Mongodb ruby driver: edit Collection::View instance filter

When I create Collection::View instance with:
client = Mongo::Client.new('mongodb://127.0.0.1:27017/test')
view = client[:users].find( { name: "Sally" } )
=> #<Mongo::Collection::View:0x69824029475340 namespace='test.users' #filter={"name" => "Sally"} #options={}>
How I can change filter hash of this instance later? This does not work:
view.filter.merge!("age" => 30)
=> #FrozenError: can't modify frozen BSON::Document
I don't think you can. .filter is a method which takes arguments. It is not a hash.
See examples
and also search the code
However you might be able to do something like:
view = lambda { |hash| client[:users].find(hash) }
search_params = { name: "Sally" }
view.(search_params)
view.(search_params.merge!({foo: 'bar'}))

How to use a string description to access data from a hash-within-hash structure?

I have the following:
data_spec['data'] = "some.awesome.values"
data_path = ""
data_spec['data'].split('.').each do |level|
data_path = "#{data_path}['#{level}']"
end
data = "site.data#{data_path}"
At this point, data equals a string: "site.data['some']['awesome']['values']"
What I need help with is using the string to get the value of: site.data['some']['awesome']['values']
site.data has the following value:
{
"some" => {
"awesome" => {
"values" => [
{
"things" => "Stuff",
"stuff" => "Things",
},
{
"more_things" => "More Stuff",
"more_stuff" => "More Things",
}
]
}
}
}
Any help is greatly appreciated. Thanks!
You could do as tadman suggested and use site.data.dig('some', 'awesome', values') if you are using ruby 2.3.0 (which is awesome and I didn't even know existed). This is probably your best choice. But if you really want to write the code yourself read below.
You were on the right track, the best way to do this is:
data_spec['data'] = "some.awesome.values"
data = nil
data_spec['data'].split('.').each do |level|
if data.nil?
data = site.data[level]
else
data = data[level]
end
end
To understand why this works first you need to understand that site.data['some']['awesome']['values'] is the same as saying: first get some then inside that get awesome then inside that get values. So our first step is retrieving the some. Since we don't have that first level yet we get it from site.data and save it to a variable data. Once we have that we just get each level after that from data and save it to data, allowing us to get deeper and deeper into the hash.
So using your example data would initally look like this:
{"awesome" => {
"values" => [
{
"things" => "Stuff",
"stuff" => "Things",
},
{
"more_things" => "More Stuff",
"more_stuff" => "More Things",
}
]
}
}
Then this:
{"values" => [
{
"things" => "Stuff",
"stuff" => "Things",
},
{
"more_things" => "More Stuff",
"more_stuff" => "More Things",
}
]
}
and finally output like this:
[
{
"things" => "Stuff",
"stuff" => "Things",
},
{
"more_things" => "More Stuff",
"more_stuff" => "More Things",
}
]
If you're receiving a string like 'x.y.z' and need to navigate a nested hash, Ruby 2.3.0 includes the dig method:
spec = "some.awesome.values"
data = {
"some" => {
"awesome" => {
"values" => [
'a','b','c'
]
}
}
}
data.dig(*spec.split('.'))
# => ["a", "b", "c"]
If you don't have Ruby 2.3.0 and upgrading isn't an option you can just patch it in for now:
class Hash
def dig(*path)
path.inject(self) do |location, key|
location.respond_to?(:keys) ? location[key] : nil
end
end
end
I wrote something that does exactly this. Feel free to take any information of value from it or steal it! :)
https://github.com/keithrbennett/trick_bag/blob/master/lib/trick_bag/collections/collection_access.rb
Check out the unit tests to see how to use it:
https://github.com/keithrbennett/trick_bag/blob/master/spec/trick_bag/collections/collection_access_spec.rb
There's an accessor method that returns a lambda. Since lambdas can be called using the [] operator (method, really), you can get such a lambda and access arbitrary numbers of levels:
accessor['hostname.ip_addresses.0']
or, in your case:
require 'trick_bag'
accessor = TrickBag::CollectionsAccess.accessor(site.data)
do_something_with(accessor['some.awesome.values'])
What you are looking for is something generally looked down upon and for good reasons. But here you go - it's called eval:
binding.eval data

Error constructing Gmail API request in Ruby

I'm having troubles sending a draft in Gmail through their API and the documentation doesn't help very much, especially since I'm working with Ruby.
I can create a draft without any issue, but then when I try to send the newly created draft, I get an error saying:
ArgumentError (wrong number of arguments (0 for 1))
The involved code is as follows:
#gmail = client.discovered_api('gmail', 'v1')
#send_result = client.execute(
:api_method => #gmail.users.drafts.send,
:parameters => { 'userId' => 'me' },
:body_object => { 'id' => '<message_id>' }
)
Taking a look at the debugger, the error seems to appear because of this:
#gmail.users.drafts.send
What am I missing here? I haven't seen anywhere that I should be passing parameters into the api_method? Also where can I find where this is documented and what is the parameter supposed to be?
Thanks!
The question is pretty old at this point, but I just ran into the same problem and figured it's better to answer late than never.
#gmail.users.drafts.send is colliding with Ruby's Object#send. You can work around the collision by converting the Google::APIClient::Resource item to a hash and then reading the value by key:
:api_method => #gmail.users.drafts.to_h["gmail.users.drafts.send"]
Your example, including the workaround:
#gmail = client.discovered_api('gmail', 'v1')
#send_result = client.execute(
:api_method => #gmail.users.drafts.to_h["gmail.users.drafts.send"],
:parameters => { 'userId' => 'me' },
:body_object => { 'id' => '<message_id>' }
)
I hope that helps!
I'm just going off of:
https://developers.google.com/gmail/api/v1/reference/users/drafts/send
But I think you have it right. The userId should be a parameter (e.g. in the URL) and the draft ID should be in the (POST) body. Can you confirm you're actually providing a draft ID and not the message.id?
Are you able to get an HTTP trace of the actual request, that would help immensely (you should likely be able to set this on the client or underlying http library your client uses, etc).

Ruby Loop Array and Create Hash for Each Array Object

I'd like to loop through an array and create a hash for each object in the array, then group all those hashes into an array of hashes.
Here's an example starting array for me:
urls = ["http://stackoverflow.com", "http://example.com", "http://foobar.com"]
Now let's say I'd like to have a hash for each of those URLs into an array like this:
urls =[ {
'url' => "http://stackoverflow.com",
'dns_status' => "200",
'title' => "Stack Overflow"
},
{
'url' => "http://example.com",
'dns_status'=> "200",
'title' => "Example"
}
]
Leaving aside where I get the values for the dns_status and title keys in the example, I guess what I'm missing is how to loop through the original array and create a hash for each object...
I've played around with inject, collect, map and each and read through the docs but can't quite make sense of it or get anything to work.
Any recommendation? Will this be easier to accomplish with a class?
EDIT:
Thanks for your help everyone. Figured this out and got it working. Cheers!
Do something with each element of something enumerable and store the result in an array: that is what map does. Specify what you want in the block, like this:
urls = ["http://stackoverflow.com", "http://example.com", "http://foobar.com"]
p res = urls.map{|url| {"url"=>url, "dns_status"=>200, "title"=>url[7..-5]} }
#=> [{"url"=>"http://stackoverflow.com", "dns_status"=>200, "title"=>"stackoverflow"}, {"url"=>"http://example.com", "dns_status"=>200, "title"=>"example"}, {"url"=>"http://foobar.com", "dns_status"=>200, "title"=>"foobar"}]
"what I'm missing is how to loop through the original array and create a hash for each object..."
urls = [
"http://stackoverflow.com",
"http://example.com",
"http://foobar.com"
]
urls.each {|entry|
puts entry
}
You could use .map! for instance. But I am still not sure what your target result ought to be. How about this?
urls.map! {|entry|
{ 'url' => entry, 'dns_status' => "200", 'title' => "Stack Overflow"}
}
urls # => [{"url"=>"http://stackoverflow.com", "dns_status"=>"200", "title"=>"Stack Overflow"}, {"url"=>"http://example.com", "dns_status"=>"200", "title"=>"Stack Overflow"}, {"url"=>"http://foobar.com", "dns_status"=>"200", "title"=>"Stack Overflow"}]
Yikes, the result is hard to see. It is this:
[
{
"url"=>"http://stackoverflow.com",
"dns_status"=>"200",
"title"=>"Stack Overflow"
},
{
"url"=>"http://example.com",
"dns_status"=>"200",
"title"=>"Stack Overflow"
},
{
"url"=>"http://foobar.com",
"dns_status"=>"200",
"title"=>"Stack Overflow"
}
]
Obviously, you need to still supply the proper content for title,
but you did not give this in your original question so I could not
fill it in.

Mongoid Complex Query Including Embedded Docs

I have a model with several embedded models. I need to query for a record to see if it exists. the issue is that I will have to include reference to multiple embedded documents my query would have to include the following params:
{
"first_name"=>"Steve",
"last_name"=>"Grove",
"email_addresses"=>[
{"type"=>"other", "value"=>"steve#stevegrove.com", "primary"=>"true"}
],
"phone_numbers"=>[
{"type"=>"work_fax", "value"=>"(720) 555-0631"},
{"type"=>"home", "value"=>"(303) 555-1978"}
],
"addresses"=>[
{"type"=>"work", "street_address"=>"6390 N Main Street", "city"=>"Elbert", "state"=>"CO"}
],
}
How can I query for all the embedded docs even though some fields are missing such as _id and associations?
A few things to think about.
Are you sure the query HAS to contain all these parameters? Is there not a subset of this information that uniquely identifies the record? Say (first_name, last_name, and an email_addresses.value). It would be silly to query all the conditions if you could accomplish the same thing in less work.
In Mongoid the where criteria allows you to use straight javascript, so if you know how to write the javascript criteria you could just pass a string of javascript to where.
Else you're left writing a really awkward where criteria statement, thankfully you can use the dot notation.
Something like:
UserProfile.where(first_name: "Steve",
last_name: "Grove",
:email_addresses.matches => {type: "other",
value: "steve#stevegrove.com",
primary: "true"},
..., ...)
in response to the request for embedded js:
query = %{
function () {
var email_match = false;
for(var i = 0; i < this.email_addresses.length && !email_match; i++){
email_match = this.email_addresses[i].value === "steve#stevegrove.com";
}
return this.first_name === "Steve" &&
this.last_name === "Grove" &&
email_match;
}
}
UserProfile.where(query).first
It's not pretty, but it works
With Mongoid 3 you could use elem_match http://mongoid.org/en/origin/docs/selection.html#symbol
UserProfile.where(:email_addresses.elem_match => {value: 'steve#stevegrove.com', primary: true})
This assumes
class UserProfile
include Mongoid::Document
embeds_many :email_addresses
end
Now if you needed to include every one of these fields, I would recommend using the UserProfile.collection.aggregate(query). In this case you could build a giant hash with all the fields.
query = { '$match' => {
'$or' => [
{:email_addresses.elem_match => {value: 'steve#stevegrove.com', primary: true}}
]
} }
it starts to get a little crazy, but hopefully that will give you some insight into what your options might be. https://coderwall.com/p/dtvvha for another example.

Resources