Hi there I have a Mongoid model, Spec, with mongoid-history gem added to it like this:
class Spec
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::History::Trackable
field :due_at, type: DateTime
track_history on: [:due_at],
:modifier_field => :modifier, # adds "referenced_in :modifier" to track who made the change, default is :modifier
:version_field => :version, # adds "field :version, :type => Integer" to track current version, default is :version
:track_create => false, # track document creation, default is false
:track_update => true, # track document updates, default is true
:track_destroy => false # track document destruction, default is false
end
spec = Spec.first
hist = spec.history_tracks.first
#=> { id: 123, modified: {due_at: 2017-06-12}, ... }
hist.modified.class
#=> BSON::Document
My question is, how can we query the modified field by the existence of its due_at field? A failed attempt looked like this:
spec.history_tracks.where(:'modified.due_at.exists' => true)
thanks in advance!!
This should work:
spec.history_tracks.not.where('modified.due_at' => nil)
Related
I'm new to MongoID. I have following models:
class Vehicle
include Mongoid::Document
include Mongoid::Timestamps
##
# Relationship
embeds_one :specification
end
class Specification
include Mongoid::Document
##
# Columns
field :body, type: String
##
# Relationship
embedded_in :vehicle
end
In my controller I'm trying to use $in operator in body field of specification /embed document/. How to use $in operator in embed document? I tried below code but it's not work.
result += Vehicle.where(:specification.matches => { :body.in => param(:body) }) if params[:body].present?
Thank you for help :)
as #mu-is-too-short mentioned in his comment, to search in embedded docs you use
if params[:body].present?
result += Vehicle.where('specification.body' => param(:body)).to_a
end
please note that Vehicle.where(...) returns a Criteria which is not the objects only the query to be evaluated.
also note that if you want to use specific operators like in, <, <= you have to use the corresponding mongodb operator for example
Vehicle.where('specification.body' => {'$in' => param(:body)}) # matches .in
Vehicle.where('specification.body' => {'$lt' => param(:body)}) # matches <
Vehicle.where('specification.body' => {'$lte' => param(:body)}) # matches <=
I'm using elastic search to enhance search capabilities in my app. Search is working perfectly, however sorting is not for fields with multiple words.
When I try to sort the search by log 'message', I was getting the error:
"Can't sort on string types with more than one value per doc, or more than one token per field"
I googled the error and find out that I can use multi-fields mapping on the :message field (one analyzed and the other one not) to sort them. So I did this:
class Log < ActiveRecord::Base
include Tire::Model::Search
include Tire::Model::Callbacks
tire.mapping do
indexes :id, index: :not_analyzed
indexes :source, type: 'string'
indexes :level, type: 'string'
indexes :created_at, :type => 'date', :include_in_all => false
indexes :updated_at, :type => 'date', :include_in_all => false
indexes :message, type: 'multi_field', fields: {
analyzed: {type: 'string', index: 'analyzed'},
message: {type: 'string', index: :not_analyzed}
}
indexes :domain, type: 'keyword'
end
end
But, for some reason is not passing this mapping to ES.
rails console
Log.index.delete #=> true
Log.index.create #=> 200 : {"ok":true,"acknowledged":true}
Log.index.import Log.all #=> 200 : {"took":243,"items":[{"index":{"_index":"logs","_type":"log","_id":"5 ... ...
# Index mapping for :message is not the multi-field
# as I created in the Log model... why?
Log.index.mapping
=> {"log"=>
{"properties"=>
{"created_at"=>{"type"=>"date", "format"=>"dateOptionalTime"},
"id"=>{"type"=>"long"},
"level"=>{"type"=>"string"},
"message"=>{"type"=>"string"},
"source"=>{"type"=>"string"},
"updated_at"=>{"type"=>"date", "format"=>"dateOptionalTime"}}}}
# However if I do a Log.mapping I can see the multi-field
# how I can fix that and pass the mapping correctly to ES?
Log.mapping
=> {:id=>{:index=>:not_analyzed, :type=>"string"},
:source=>{:type=>"string"},
:level=>{:type=>"string"},
:created_at=>{:type=>"date", :include_in_all=>false},
:updated_at=>{:type=>"date", :include_in_all=>false},
:message=>
{:type=>"multi_field",
:fields=>
{:message=>{:type=>"string", :index=>"analyzed"},
:untouched=>{:type=>"string", :index=>:not_analyzed}}},
:domain=>{:type=>"keyword"}}
So, Log.index.mapping is the current mapping in ES which doesn't contain the multi-field that I created. Am I missing something? and why the multi-field is shown in Log.mapping but not in Log.index.mapping?
I have changed the workflow from:
Log.index.delete; Log.index.create; Log.import
to
Log.index.delete; Log.create_elasticsearch_index; Log.import
The MyModel.create_elasticsearch_index creates the index with proper mapping from model definition. See Tire's issue #613.
There is a Object and embedded SubObject
class Object
include Mongoid::Document
embeds_many :sub_objects
end
class SubObject
include Mongoid::Document
field :str1,:type => String
field :ind1,:type => Integer
embedded_in :object
end
Console output
irb(main):060:0> obj = Object.first
=> #<Object _id: 4fd5ed971d41c8252c001f49, ..............>
irb(main):061:0> obj.sub_objects.size
=> 24000
irb(main):062:0> obj.save
=> true
Here is interesting thing happens.
Mongoid does not actually save the object because the object was not changed.
I.e. there is no call to mongodb.
But the save operation itself takes 18 (!!!) seconds.
Can anyone explain me what is happening and how can I avoid it?
Thanks.
this should do it:
obj.save if !obj.persisted? || obj.changed? # only save if new record, or something changed
I use Rails 3.0.6 with mongoID 2.0.2. Recently I encountered an issue with save! method when overriding setter (I am trying to create my own nested attributes).
So here is the model:
class FeedItem
include Mongoid::Document
has_many :audio_refs
def audio_refs=(attributes_array, binding)
attributes_array.each do |attributes|
if attributes[:audio_track][:id]
self.audio_refs.build(:audio_track => AudioTrack.find(attributes[:audio_track][:id]))
elsif attributes[:audio_track][:file]
self.audio_refs.build(:audio_track => AudioTrack.new(:user_id => attributes[:audio_track][:user_id], :file => attributes[:audio_track][:file]))
end
end
if !binding
self.save!
end
end
AudioRef model (which is just buffer between audio_tracks and feed_items) is:
class AudioRef
include Mongoid::Document
belongs_to :feed_item
belongs_to :audio_track
end
And AudioTrack:
class AudioTrack
include Mongoid::Document
has_many :audio_refs
mount_uploader :file, AudioUploader
end
So here is the spec for the FeedItem model which doesn`t work:
it "Should create audio_track and add audio_ref" do
#audio_track = Fabricate(:audio_track, :user_id => #author.id, :file => File.open("#{Rails.root}/spec/stuff/test.mp3"))
#feed_item= FeedItem.new(
:user => #author,
:message => {:body => Faker::Lorem.sentence(4)},
:audio_refs => [
{:audio_track => {:id => #audio_track.id}},
{:audio_track => {:user_id => #author.id, :file => File.open("#{Rails.root}/spec/stuff/test.mp3")}}
]
)
#feed_item.save!
#feed_item.reload
#feed_item.audio_refs.length.should be(2)
end
As you can see, the reason I am overriding audio_refs= method is that FeedItem can be created from existing AudioTracks (when there is params[:audio_track][:id]) or from uploaded file (params[:audio_track][:file]).
The problem is that #feed_item.audio_refs.length == 0 when I run this spec, i.e. audio_refs are not saved. Could you please help me with that?
Some investigation:
1) binding param is "true" by default (this means we are in building mode)
I found a solution to my problem but I didnt understand why save method doesnt work and didn`t make my code work. So first of all let me describe my investigations about the problem. After audio_refs= is called an array of audio_refs is created BUT in any audio_ref is no feed_item_id. Probably it is because the feed_item is not saved by the moment.
So the solution is quite simple - Virtual Attributes. To understand them watch corresponding railscasts
So my solution is to create audio_refs by means of callback "after_save"
I slightly changed my models:
In FeedItem.rb I added
attr_writer :audio_tracks #feed_item operates with audio_tracks array
after_save :assign_audio #method to be called on callback
def assign_audio
if #audio_tracks
#audio_tracks.each do |attributes|
if attributes[:id]
self.audio_refs << AudioRef.new(:audio_track => AudioTrack.find(attributes[:id]))
elsif attributes[:file]
self.audio_refs << AudioRef.new(:audio_track => AudioTrack.new(:user_id => attributes[:user_id], :file => attributes[:file]))
end
end
end
end
And the spec is now:
it "Should create audio_track and add audio_ref" do
#audio_track = Fabricate(:audio_track, :user_id => #author.id, :file => File.open("#{Rails.root}/spec/stuff/test.mp3"))
#feed_item= FeedItem.new(
:user => #author,
:message => {:body => Faker::Lorem.sentence(4)},
:audio_tracks => [
{:id => #audio_track.id},
{:user_id => #author.id, :file => File.open("#{Rails.root}/spec/stuff/test.mp3")}
]
)
#feed_item.save!
#feed_item.reload
#feed_item.audio_refs.length.should be(2)
end
And it works fine!!! Good luck with your coding)
Check that audio_refs=() is actually being called, by adding debug output of some kind. My feeling is that your FeedItem.new() call doesn't use the audio_refs=() setter.
Here's the source code of the ActiveRecord::Base#initialize method, taken from APIdock:
# File activerecord/lib/active_record/base.rb, line 1396
def initialize(attributes = nil)
#attributes = attributes_from_column_definition
#attributes_cache = {}
#new_record = true
#readonly = false
#destroyed = false
#marked_for_destruction = false
#previously_changed = {}
#changed_attributes = {}
ensure_proper_type
populate_with_current_scope_attributes
self.attributes = attributes unless attributes.nil?
result = yield self if block_given?
_run_initialize_callbacks
result
end
I don't currently have an environment to test this, but it looks like it's setting the attributes hash directly without going through each attribute's setter. If that's the case, you'll need to call your setter manually.
Actually, I think the fact you're not getting an exception for the number of arguments (binding not set) proves that your setter isn't being called.
What am I missing here ?
I have a relative simple structure here:
Class Content
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Paranoia
field :title
embeds_many :localized_contents
end
Class LocalizedContent
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Paranoia
include Mongoid::Versioning
field :locale
field :content
embedded_in :content, :inverse_of => :localized_contents
end
if I do:
test = LocalizeContent.new(:locale => 'en', :content => 'blah')
test.save
=> ok, version = 1
test.content = 'blah2'
test.save
=> ok, version = 2, versions.count = 1, etc.
All is ok
Now if I do this through Content, it does not work
test = Content.first.localised_contents.build(:locale => 'en', :content => 'blah')
test.save
=> ok, version = 1
test = Content.first.localized_contents.first
test.content = 'blah2'
test.save
=> KO, version = 1, versions.count = 0, but
Content.first.localized_contents.first.content == 'blah2'
What am I doing wrong here ?!?
Thanks,
Alex
Mongoid::Versioning and Mongoid::Paranoia don't work with embedded documents currently, unfortunately.
I'm using mongo (1.9.1) & mongoid (2.7.1) and there seems to be a way to force embedded docs to be versioned.
This is kindof hackey - but basically we change the nested doc, then update the 'previous_update' field of the parent document.
params = { 'env_name' => 'changeme-qa', 'machine' => {'_id' =>"51f85846f0e1801113000003", 'status' => "described#{version}" }}
env = Environment.find_with_name(params['env_name'])
result = env.machines.where(:_id => params['machine']['_id'])
machine = (result.exists?) ? machine = result.first : nil
if machine.nil?
raise 'failed to find machine'
else
if machine.update_attributes(params['machine'])
env.reload
# here's the magic, since we cause a change in the parent (environment) record,
# the child records get versioned
env['previous_update'] = env['updated_at']
env.save
else
raise 'failed to save'
end
end