Using geoNear with Rails / Mongoid - ruby-on-rails

I'm having problems querying Geospatial indexes with MongoDB / Rails.
I'm using this gem - https://github.com/kristianmandrup/mongoid_geospatial
Here's my fairly basic model:
class Company
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Geospatial
field :name, type: String
field :location, type: Array, spatial: true
spatial_index :location
validates :location, location: true
end
Then, in my controller, I have this
##vendors = Vendor.where(:location.near => {:point => [-2.1294761000000335,57.0507625], :max => 5})
However, this isn't returning expected results (ie- it's returning things from all over the place, not just near that particular lon / lat)
Also, how would I go about doing a geoNear with this?
So that I can get back the distances from central point for each result?
Note
After writing this question, I've seen the gem has been updated, but I'm not sure if there's a better alternative..?

You don't need the mongoid_geospatial gem to do a geoNear query: mongoid already supports it (in version 3 at least).
Change your model to:
class Company
include Mongoid::Document
include Mongoid::Timestamps
field :name, type: String
field :location, type: Array
index({location: "2d"})
validates :location, location: true
end
And run your query as:
#vendors = Vendor.geo_near([-2.1294761000000335,57.0507625]).max_distance(5)

Related

Rails Mongoid : Query embedded document and access to Mongoid criteria

I have an application where users can create many travels, and they can invite their facebook friends. In the travel document, there is a field "participants" that is an embedded document, Participant model embedded in Travel model.
Here are my models :
class Travel
include Mongoid::Document
include Mongoid::Timestamps
# relations
belongs_to :user
# fields
field :title, type: String
field :description, type: String
field :begin_date, type: Date
field :end_date, type: Date
field :budget, type: Integer
field :go_back, type: Boolean
field :title_namespace, type: String
# friends
embeds_many :participants
accepts_nested_attributes_for :participants
end
class Participant
include Mongoid::Document
include Mongoid::Timestamps
field :name, type: String
field :user_id, type: String
# relations
embedded_in :travel, :inverse_of => :participants
end
When I try to display travel where users have been invited, with this request :
#travel_participations = Travel.where('participants.user_id' => #user.id)
I don't have any result, even if I have this line in byebug :
#<Mongoid::Criteria
selector: {"participants.user_id"=>BSON::ObjectId('592c8da58511989ec850921e')}
options: {}
class: Travel
embedded: false>
So when I put this on my view :
<% unless #participations.nil? %>
<% #travel_participations.each do |travel_participation| %>
<p> <%= travel_participation.title %> </p>
<% end %>
<% end %>
I tried with .all, .first, .to_a, .as_json, no results ... Some one know where is the problem ?
You have this in your embedded model:
field :user_id, type: String
but your query is using a BSON::ObjectId:
Travel.where('participants.user_id' => #user.id)
as shown in the raw query:
selector: {"participants.user_id"=>BSON::ObjectId('592c8da58511989ec850921e')}
Your embedded document probably has a string field like:
"user_id": "592c8da58511989ec850921e"
rather than the ObjectId you're looking for:
"user_id": ObjectId("592c8da58511989ec850921e")
so you won't find what you're looking for due to the type mismatch.
Either fix the embedded field's type:
field :user_id, type: BSON::ObjectId
or query it as the string it is:
Travel.where('participants.user_id' => #user.id.to_s)
Changing the type will involve fix up whatever data you already have, changing the query is ugly in a different way.
Sometimes Mongoid will convert between strings and ObjectIds for you, sometimes it won't. When I used Mongoid I patched to_bson_id methods into BSON::ObjectId, String, Mongoid::Document, ... so that I could say things like:
Model.where(:some_id => some_id.to_bson_id)
and not have to constantly worry about what type some_id was. I also made sure that all ID fields were always specified as BSON::ObjectId.

Mongoid Polymorphic Association Rails

Work env: Rails 4.2 mongoid 5.1
Below are my models:
class Tag
include Mongoid::Document
include Mongoid::Timestamps
field :name, type: String
belongs_to :entity_tags, :polymorphic => true
end
class EntityTag
include Mongoid::Document
include Mongoid::Timestamps
field :tag_id, type: String
field :entity_id, type: String // Entity could be Look or Article
field :entity_type, type: String // Entity could be Look or Article
field :score, type: Float
end
class Look
include Mongoid::Document
include Mongoid::Timestamps
has_many :tags, :as => :entity_tags
end
class Article
include Mongoid::Document
include Mongoid::Timestamps
has_many :tags, :as => :entity_tags
end
We are trying to implement polymorphic functionality between Looks and Articles to Tags.
i.e. Let's say we have a Tag named "politics", and we would like to add the tag to an Article with the score '0.9' and to a Look with the score '0.6'. The Score should be saved at the EntityTags Model.
The problem:
The first assign of the tag works, but then when I try to assign the same tag to another entity, it removes it and reassigns it from the first one to the latter.
The assignment looks like the following:
entity.tags << tag
Does anybody know the proper way to save associations and create the EntityTag Object with the correct polymorphism and assignment properly?
Thanks!
I've managed to implement a non-elegant working solution based on the following answer in this link

Searching Multiple Terms with ElasticSearch + Tire

Using Tire with Mongoid, I'm having trouble figuring out how to structure a query for finding events with ElasticSearch. In particular, I'm trying to find events that users are watching in addition to events with performers the user follows:
# app/models/event.rb
class Event
include Mongoid::Document
include Tire::Model::Search
include Tire::Model::Callbacks
field :name, type: String
has_and_belongs_to_many :performers
has_many :watchers, class_name: 'User'
mapping do
indexes :name
indexes :watcher_ids, type: 'string', index: :not_analyzed
indexes :performer_ids, type: 'string', index: :not_analyzed
end
end
The following query works only for either watchers or performers.
Tire.search 'events' do
query do
string params[:query]
# Only one of these will work at a time:
terms :performer_ids, current_user.followed_performers.collect(&:id).map(&:to_s)
terms :watcher_ids, [current_user.id.to_s]
end
end
small edit because I typed my example wrong.
Here's a solution that seems to be "working"... but feels wrong
Tire.search('events') do
query do
boolean do
should { string params[:query] }
should { string "performer_ids:#{current_user.followed_performers.collect(&:id).map(&:to_s).join(',')}" }
should { string "watcher_ids:#{current_user.id.to_s}" }
end
end
end
You're on a right path, but as advised by Russ Smith, you need to use a filter DSL.
Now, if you just repeatedly call filter, you'll perform a union: AND. If you want to return either events user is watching or by performers the user follows, you have to use a or filter.
Also, for best performance, use the filtered query, as opposed to the top level filter -- in the former case, filters run first, slicing your corpus and perform queries only on this subset.
The code should look like this:
Tire.search 'events' do
query do
filtered do
query do
string params[:query]
end
filter :or, terms: { organization_ids: current_user.followed_performers.collect(&:id).map(&:to_s) },
terms: { watcher_ids: [current_user.id.to_s] }
end
end
end
See the integration tests for more examples:
https://github.com/karmi/tire/blob/master/test/integration/filters_test.rb
https://github.com/karmi/tire/blob/master/test/integration/filtered_queries_test.rb
I think what you are looking for is a filter. This is not fully tested code, but it might lead you in the right direction.
class Event
include Mongoid::Document
include Tire::Model::Search
include Tire::Model::Callbacks
field :name, type: String
has_and_belongs_to_many :performers
has_many :watchers, class_name: 'User'
mapping do
indexes :name
indexes :watcher_ids, type: 'integer', index: :not_analyzed
indexes :performer_ids, type: 'integer', index: :not_analyzed
end
end
Tire.search('events') do
query do
string 'my event'
end
filter :in, :organization_ids, [1,2,3]
filter :in, :watcher_ids, [1]
end

Avoiding Mongoid N+1

I'd like to pull all companies that have at least one position title of "CEO".
I could hack it together with a query for each table and an intersect (I know... no joins http://mongoid.org/en/mongoid/docs/tips.html#relational_associations, and N+1 problem in mongoid, and I could just embed positions in company), but any way to do something like:
Company.includes(:positions).where("positions.title" => "CEO")?
Thanks:
class Position
include Mongoid::Document
field :title, type: String
field :profile_id, type: String
field :tenure, type: BigDecimal
belongs_to :company, index: true
class Company
include Mongoid::Document
field :name, type: String
field :linkedin_id, type: String
field :positions_count, type: Integer #Mongo Index
belongs_to :industry, index: true
has_many :positions
index({ positions_count: 1}, {background: true})
To avoid the the N+1 problem enable Mongoid identity_map feature
This will allow you to do the following query:
companies_with_ceo = Position.where(title: 'CEO').includes(:company).map(&:company)
Which should execute only 2 queries to the database.

Rails 3: Mongoid validation issue

Using Mongoid, I am trying to validate the :code input on the submission form to make sure they are using a proper code that is already stored in the database. There are about 2000+ codes so a helper method array collection wouldn't be feasable.
What is the best way to do this?
I was thinking of doing an inclusion validation, like this:
class Request
include Mongoid::Document
field :code, type: String
validates :code, :presence => true,
:inclusion => { :in => proc { Listing.all_codes } }
end
Then the model that has all the stored codes, like this:
class Listing
include Mongoid::Document
field :code, type: String
def self.all_codes
where(:code => exists?) # <--- this is broken
end
end
But I can't seem to get this to function the way I would like. Any feedback would be much appreciated.
Your Request model looks fine. But the Listing.all_codes needs to return an array of only codes. so:
class Listing
include Mongoid::Document
field :code, type: String
def self.all_codes
only(:code).map(&:code)
end
end

Resources