I have a MongoDB database that uses mongoid referencing according to the documentation. Here are my models:
class Mydoc
include Mongoid::Document
# ...
has_and_belongs_to_many :editors, class_name: 'User', inverse_of: nil
# ...
end
and
class User
# actually, it's based on devise with some changes
# ...
# it does not reference back to Mydoc, see inverse_of there!
end
Now if I make a reference to a user (to grant her/him editor role), mongoid creates an array field called editor_ids that contains object ids of the documents in the users table. Works nice.
I'd like to have a query that shows all Mydoc documents where a certain user is editor. Can't figure out how to do that in mongoid. It's very simple in the mongo console:
db.mydocs.find({ editor_ids: { $in: [ ObjectId("52c97e58b878bcf156000001") ] } })
It works like charm. But I need this in rails, not in the console. Based on mongoid docs, I've tried this (and a hell lot of variations of it) but it does not work:
#docs_where_editor = Mydoc.where(:editors.in => [#user._id])
the result is an empty dataset. I would appreciate any hint.
I suppose it's not the where method, since in mongoid, find only accepts object ids you want to find.
If you have an :editor_ids array and you're only looking for a single value then you should be able to use multi-keys thusly:
Mydoc.where(:editor_ids => #user.id)
and if you have several editor IDs to search for then:
Mydoc.where(:editors_ids.in => array_of_user_ids)
would be the transliteration of your MongoDB console query to Mongoid.
Related
I am using the acts_as_taggable_on gem in a Rails 4.1 app. I am using a scope to search based on a tag or multiple tags. When multiple tags are given as the param I want the search to return only tagged items that contain ALL tags. Currently the scope I has returns tagged items that contain at least one of the tags (tag_a OR tag_b OR tag_c). I want a scope that instead is an AND condition (tag_a AND tag_b AND tag_c).
Example
document_a_tags = ['programming', 'ruby']
document_b_tags = ['programming', 'python']
class User < ActiveRecord::Base
has_many :documents, dependent: :destroy
scope :with_fields, proc { |fields|
if fields.reject!(&:empty?).present?
joins(documents: [{ taggings: :tag }]).where(tags: { slug: fields })
end
}
end
class Document < ActiveRecord::Base
acts_as_taggable
belongs_to :user
end
Currently, if the search params include programming and ruby both document_a and document_b will be returned. What I want is a scope where if the search params include programming and ruby only document_a will be returned.
Given the way mysql works I would think this is hard to accomplish in a single active record query.
The join adds one row for each tag that is connected to your entry and conditions are only applied to one row at the time. That is, a row can't be excluded by a condition saying that certain other rows in your result.
I think it might be possible to write some complex recursive query adding an undetermined number of columns to the result and applying the conditions on them. But I think it will be much easier and more performant to simply use your current scope and then in ruby code discard entries without all tags.
edit:
This might be done in the gem you are using. Look at:
User.tagged_with(["awesome", "cool"], :match_all => true)
I have the following models defined in Rails, with Mongoid:
Class Character
include Mongoid::Document
field :n, as: name, type: String
field :p, as: :positions, type: Array
field :o, as: :roles, type: Array
field :r, as: :resource, type: String
embeds_one :base_stat
end
class BaseStat
include Mongoid::Document
embedded_in :character
end
I'm trying to seed my database with documents that have these relationships 1) because I'd have to eventually and 2) so I can test I'm using Mongoid correctly. I've tried a couple of different ways to seed it, but every single time, I can create Character documents, then create BaseStat documents based off a Character document, but calling character.base_stat returns nil.
Here are the things I've tried in db/seeds.rb that didn't throw errors:
ch = Character.create!([etc])
ch.build_base_stat([etc])
Character.create!(name: value, name: value, base_stat: BaseStat.new(name: value, name:value))
ch = Character.create!([etc])
ch.create_base_stat([etc])
I've also tried using ch.base_stat.create! (which threw an error when I called rake db:setup).
I know that both the Character and BaseStat documents are created because I can search in the Rails console for the Character documents that were seeded (a = Character.where(name: value)[0] and b = BaseStat.where(name:value)[0]). But it looks like the relationship isn't being stored.
Calling a.metadata also throws a NoMethodError.
I don't have any controllers set up, just the models and the entries in db/seeds.rb. I feel like I must be missing something fundamental because, well, I've been hunting through StackOverflow and haven't seen anything that fixed this.
Versions:
Mongoid 4.0.0.alpha2
Rails 4.0.1
Have you tried a very basic test? Can you open the rails console create a Character, save it, then add a BaseStat to it and save that?
c = Character.new
b = BaseStat.new
b.name = "test"
c.base_stat = b
c.save
c
Does that print out your new record with the BaseStat embedded? If it does then there must be something wrong with the syntax or method in the seeds.
Quoting Mongoid docs, this could be why:
One core difference between Mongoid and Active Record from a behavior standpoint is that Mongoid does not automatically save child relations for relational associations. This is for performance reasons.
Try adding autosave: true to your base_stat relation:
embeds_one :base_stat, autosave: true
I have a class TwitterUser that has many TwitterLists
class TwitterUser < ActiveRecord::Base
has_many :twitter_lists, :dependent => :destroy
end
When I do:
user = TwitterUser.includes(:twitter_lists).find(12615489)
then:
lists = user.twitter_lists
it eagerly loads the twitter lists for that user in the first "find", so it doesn't run a query when I do user.twitter_lists (this is expected).
However when I try to convert the user to JSON like:
user.to_json
I don't see the nested association "twitter_lists" anywhere in the JSON. Even though I used eager loading. Why is this? And how can I make it appear in the JSON?
To include the association in the output of to_json you need to pass :include => :twitter_lists to to_json
There is no connection between the associations that are eager loaded and the associations that are included in the output of to_json - the two are completely independant.
Check this answer
May be this is what you need
Rails find method - select columns from ":include" table parameter
includes is just for eager loading. Which means it is cached somewhere but not actually returned
I'm trying to search for an embedded document by its id, and return it. This is possible, but only, as far as I see, by using mongo to find the document which embeds it, and then searching that document in ruby for the embedded document I'm after. Like this:
# commenter.rb
def post
# todo: find syntax do avoid double query
if user = User.any_of({'posts.commenter_ids' => self.id}).last
user.posts.where('commenter_ids' => self.id).last
end
end
Seems simple, but I haven't found anything I like obviously on google/SO search.
Thoughts?
I override find method on my embedded documents using this gist: https://gist.github.com/cblavier/7889042
It's especially convenient when I want to use DelayedJob to delay embedded document methods (because DJ worker will use find(id) to deserialize the job)
class Order
embeds_many Products
end
class Product
embedded_in Order
end
prod_id = "1234" # the embedded doc's _id you request
o = Order.find(product_ids: prod_id)
p = o.products.find(prod_id)
See also Does querying Mongoid embedded documents hit the database server after loading a parent document
Right now I am including the following functionality into my embedded documents. It requires that you set the inverse_of option on the nested relationship.
# Returns the parent "embedded_in" relationship for this document
# #return [Mongoid::Relations::Metadata]
def self.parent_relationship
#parent_relationship ||= relations.values.find do |relation|
relation.macro == :embedded_in
end
end
# finds the document off of a root document. This method currently only works correctly if
# you explicitly set the inverse_of value on the embedded_in relationship
# #param [string | Moped::BSON::ObjectId] id
def self.find_using_parent(id)
id = id.is_a?(Moped::BSON::ObjectId) ? id : Moped::BSON::ObjectId(id)
parent = parent_relationship.class_name.to_const.where("#{parent_relationship.inverse_of}._id" => id).first
if parent
parent.__send__(parent_relationship.inverse_of).find(id)
end
end
You can't find a resource without the document it embeds. If you just want a relationship between the two instead of embedding it, you should use has_many instead of embeds_many http://mongoid.org/en/mongoid/docs/relations.html#has_many.
You can then find the document without it's related document.
New to Rails and Ruby and trying to do things correctly.
Here are my models. Everything works fine, but I want to do things the "right" way so to speak.
I have an import process that takes a CSV and tries to either create a new record or update an existing one.
So the process is 1.) parse csv row 2.) find or create record 3.) save record
I have this working perfectly, but the code seems like it could be improved. If ParcelType wasn't involved it would be fine, since I'm creating/retrieving a parcel FROM the Manufacturer, that foreign key is pre-populated for me. But the ParcelType isn't. Anyway to have both Type and Manufacturer pre-populated since I'm using them both in the search?
CSV row can have multiple manufacturers per row (results in 2 almost identical rows, just with diff mfr_id) so that's what the .each is about
manufacturer_id.split(";").each do |mfr_string|
mfr = Manufacturer.find_by_name(mfr_string)
# If it's a mfr we don't care about, don't put it in the db
next if mfr.nil?
# Unique parcel is defined by it's manufacturer, it's type, it's model number, and it's reference_number
parcel = mfr.parcels.of_type('FR').find_or_initialize_by_model_number_and_reference_number(attributes[:model_number], attributes[:reference_number])
parcel.assign_attributes(attributes)
# this line in particular is a bummer. if it finds a parcel and I'm updating, this line is superfulous, only necessary when it's a new parcel
parcel.parcel_type = ParcelType.find_by_code('FR')
parcel.save!
end
class Parcel < ActiveRecord::Base
belongs_to :parcel_type
belongs_to :manufacturer
def self.of_type(type)
joins(:parcel_type).where(:parcel_types => {:code => type.upcase}).readonly(false) unless type.nil?
end
end
class Manufacturer < ActiveRecord::Base
has_many :parcels
end
class ParcelType < ActiveRecord::Base
has_many :parcels
end
It sounds like the new_record? method is what you're looking for.
new_record?() public
Returns true if this object hasn’t been saved yet — that is, a record
for the object doesn’t exist yet; otherwise, returns false.
The following will only execute if the parcel object is indeed a new record:
parcel.parcel_type = ParcelType.find_by_code('FR') if parcel.new_record?
What about 'find_or_create'?
I have wanted to use this from a long time, check these links.
Usage:
http://rubyquicktips.com/post/344181578/find-or-create-an-object-in-one-command
Several attributes:
Rails find_or_create by more than one attribute?
Extra:
How can I pass multiple attributes to find_or_create_by in Rails 3?