Mongoid upsert with a different key - ruby-on-rails

Is it possible to upsert into a mongodb collection with a JSON/hash of fields, but instead of using the _id ObjectId field, to use a different indexed field such as external_id?
I am using it to update some items that I receive from a feed on a daily basis, and as such, the feed items don't contain my internal ID.

Yes, it is possible to upsert with a custom id in Mongoid, but only in 3.0.0.rc circa June 27, 2012.
app/models/item.rb
class Item
include Mongoid::Document
field :external_id, type: String
field :_id, type: String, default: ->{ external_id }
field :text, type: String
end
test/unit/item_test.rb
require 'test_helper'
class ItemTest < ActiveSupport::TestCase
def setup
Item.delete_all
end
test "external id" do
Item.new( text: 'Lorem ipsum' ).upsert
Item.new( external_id: 'an external id', text: 'dolor sit amet' ).upsert
puts Item.all.to_a.collect{|item|item.inspect}
end
end
output
Run options: --name=test_external_id
# Running tests:
#<Item _id: 4ff202501f98ce8202c03268, _type: nil, external_id: nil, text: "Lorem ipsum">
#<Item _id: an external id, _type: nil, external_id: "an external id", text: "dolor sit amet">
.
Finished tests in 0.028232s, 35.4208 tests/s, 0.0000 assertions/s.
1 tests, 0 assertions, 0 failures, 0 errors, 0 skips
To do this you will have to install from github, visit and clone from
https://github.com/mongoid/mongoid
bundle install
bundle exec rake install
Here's the link to the commit that makes this possible.
https://github.com/mongoid/mongoid/commit/3062363bad3ab947d7689502d6805652b20e89a0

Related

Getting unknown attribute error on spec - works fine in development environment

I have a spec (Rspec 2.13.1 and rails 3.2.19) and am getting the following error
1) ApiMenusController task0041: test saving new menu will create new menu
Failure/Error: post :create, {is_new: "true", location_id: lucques.id, name: "my newer jtjt menu"}
ActiveRecord::UnknownAttributeError:
unknown attribute: attributes_changes
# ./app/models/save_app_event.rb:37:in `before_save'
My AppEvent looks like this:
class AppEvent < ActiveRecord::Base
attr_accessible :loggable_id, :loggable_type, :user_id, :event, :attributes_changes, :location_id, :menu_id
It is called in a before_save
class SaveAppEvent
def self.before_save(model)
Rails.logger.info("within SaveAppEvent with a " + model.class.to_s)
tmp=model.changes.except(:admin_frag).except(:menu_item_frag)
if model.class.to_s=='Menu'
Rails.logger.info("within SaveAppEvent with menu.location_id #{model.location_id}")
tmp_menu_id=model.id
tmp_menu_id=0 if !tmp_menu_id
tmp_location_id=model.location_id
event="a menu was saved with location_id: #{tmp_location_id} "
#Rails.logger.info("within SaveAppEvent with name location_id: #{location_id} and menu_id: #{menu_id}")
else
menu_id=nil
location_id=nil
end
Rails.logger.info("within SaveAppEvent right before creation location_id: #{tmp_location_id} and menu_id: #{tmp_menu_id} and attributes_changes: #{tmp}")
AppEvent.create event: event, menu_id: tmp_menu_id, attributes_changes: tmp, user_id: model.current_user_id, loggable_type: model.class.name, loggable_id: model.id
end
The info is there:
within SaveAppEvent right before creation location_id: 95 and menu_id: 0 and attributes_changes: {"location_id"=>[nil, 95], "name"=>[nil, "my newer jtjt menu"]}
For some reason, in this context, it doesn't think attributes_changes exists. On development, it works fine. Why would test environment see attributes on an object differently?

Mongomapper querying on 'belongs_to' model

I have two models:
class Post
include MongoMapper::Document
many :comments
key :content, String
end
and
class Comment
include MongoMapper::Document
belongs_to :post
key :post_id, ObjectId
key :content, String
end
in a rails console session I can find all Posts:
Post.all # -> [#<Post _id: BSON::ObjectId('519b0b613…
and all comments associated with a Post:
post = Post.first # -> #<Post _id: BSON::ObjectId('519b0b613e477b…
post.comments # -> [#<Comment _id: BSON::ObjectId('519d14f93e…
however, the following query strangely returns an empty array
Comment.all # -> []
Why? How can I get a list of all comments independently of the posts?
With models exactly as per your post, it works for me as shown in the the following test,
running rails (3.2.13), mongo_mapper (0.12.0), mongo (1.6.4). Next time please post the full minimal script, you probably just have a simple error.
test/unit/post_test.rb
require 'test_helper'
class PostTest < ActiveSupport::TestCase
def setup
Post.delete_all
Comment.delete_all
end
test "post and comment" do
post = Post.create(:content => 'Twas brillig')
comment = Comment.create(:post_id => post.id, :content => 'and the slythy toves')
post.comments << comment
assert_equal 1, Post.count
assert_equal 1, Comment.count
puts
puts "all posts: #{Post.all.inspect}"
puts "first post comments: #{Post.first.comments.inspect}"
puts "all comments: #{Comment.all.inspect}"
end
end
$ rake test
Run options:
# Running tests:
[1/1] PostTest#test_post_and_comment
all posts: [#<Post _id: BSON::ObjectId('51ddb56a7f11ba9bbf000001'), content: "Twas brillig">]
first post comments: [#<Comment _id: BSON::ObjectId('51ddb56a7f11ba9bbf000002'), content: "and the slythy toves", post_id: BSON::ObjectId('51ddb56a7f11ba9bbf000001')>]
all comments: [#<Comment _id: BSON::ObjectId('51ddb56a7f11ba9bbf000002'), content: "and the slythy toves", post_id: BSON::ObjectId('51ddb56a7f11ba9bbf000001')>]
Finished tests in 0.036030s, 27.7546 tests/s, 0.0000 assertions/s.
1 tests, 0 assertions, 0 failures, 0 errors, 0 skips
ruby -v: ruby 2.0.0p195 (2013-05-14 revision 40734) [x86_64-darwin12.3.0]

Default_scope in Rails model when testing with Rspec using FactoryGirl

I'm developing a blog in Rails and I'm stuck when I was trying to test the default scope I added to the Post model in order to have the posts in descending order of their creation date.
Post code:
class Post < ActiveRecord::Base
attr_accessible :content, :name, :title
validates :title, presence: true,uniqueness: true
validates :name, presence: true
validates :content, presence: true
default_scope order: "posts.created_at DESC"
end
Rspec code:
describe "Posts descending order of creation date" do
let(:older_post) do
FactoryGirl.create(:post, created_at: 1.day.ago)
end
let(:newer_post) do
FactoryGirl.create(:post, created_at: 1.hour.ago)
end
it "should have the 2 posts in desc order" do
Post.all.should == [newer_post, older_post]
end
end
FactoryGirl definition
FactoryGirl.define do
factory :post do
sequence(:title) { |n| "A book #{n}" }
name "Johnny"
content "Lorem Ipsum"
end
end
The output
.....F...
Failures:
1) Post Posts descending order of creation date should have the 2 posts in desc order
Failure/Error: Post.all.should == [newer_post, older_post]
expected: [#<Post id: 1, name: "Johnny", title: "A book 1", content: "Lorem Ipsum", created_at: "2013-05-01 14:44:45", updated_at: "2013-05-01 15:44:45">, #<Post id: 2, name: "Johnny", title: "A book 2", content: "Lorem Ipsum", created_at: "2013-04-30 15:44:45", updated_at: "2013-05-01 15:44:45">]
got: [] (using ==)
Diff:
## -1,3 +1,2 ##
-[#<Post id: 1, name: "Johnny", title: "A book 1", content: "Lorem Ipsum", created_at: "2013-05-01 14:44:45", updated_at: "2013-05-01 15:44:45">,
- #<Post id: 2, name: "Johnny", title: "A book 2", content: "Lorem Ipsum", created_at: "2013-04-30 15:44:45", updated_at: "2013-05-01 15:44:45">]
+[]
# ./spec/models/post_spec.rb:54:in `block (3 levels) in <top (required)>'
Finished in 1.03 seconds
9 examples, 1 failure
Failed examples:
rspec ./spec/models/post_spec.rb:53 # Post Posts descending order of creation date should have the 2 posts in desc order
I also want to mention that when I type Post.all in the Rails console, I get the records in descending order ( so as I wanted them).
Can someone give me a suggestion on what the problem might be?
Please know that let is evaluated lazily in RSpec. This often creates problem in such scenarios where ordering is concerned.
Try these two alternatives:
describe "Posts descending order of creation date" do
let!(:older_post) do
FactoryGirl.create(:post, created_at: 1.day.ago)
end
let!(:newer_post) do
FactoryGirl.create(:post, created_at: 1.hour.ago)
end
it "should have the 2 posts in desc order" do
Post.all.should == [newer_post, older_post]
end
end
Note, the use of let! instead of let.
Or, use before as:
describe "Posts descending order of creation date" do
it "should have the 2 posts in desc order" do
#older_post = FactoryGirl.create(:post, created_at: 1.day.ago)
#newer_post = FactoryGirl.create(:post, created_at: 1.hour.ago)
Post.all.should == [#newer_post, #older_post]
end
end
Do let me know if it works or not. :)

Mongoid exclude all records with :state of "this" OR "that" ?

Model.excludes(state: "this).excludes(state: "that")
produces:
"state"=>{"$ne"=>"that"}
but what I really want is to exclude all records that have either "this" or "that"
Here's a working test that uses Model.not_in
http://two.mongoid.org/docs/querying/criteria.html#not_in
app/models/model.rb
class Model
include Mongoid::Document
field :state, type: String
end
test/unit/model_test.rb:
require 'test_helper'
class ModelTest < ActiveSupport::TestCase
def setup
Model.delete_all
end
test "not_in" do
Model.create({ state: 'this' })
Model.create({ state: 'that' })
Model.create({ state: 'not this or that'})
assert_equal(3, Model.count)
p Model.not_in(state: ['this', 'that']).to_a
assert_equal(1, Model.not_in(state: ['this', 'that']).to_a.count)
end
end
rake test:
Run options:
# Running tests:
[#<Model _id: 50e6fdef29daeb27e5000003, _type: nil, state: "not this or that">]
.
Finished tests in 0.021175s, 47.2255 tests/s, 94.4510 assertions/s.
1 tests, 2 assertions, 0 failures, 0 errors, 0 skips

RSpec/Mongoid inheritance of defaults completely different result in test/development

This is one of those ones that makes you think you're going insane...
I have a class Section, and a DraftSection that inherits from it:
(Trimmed for brevity)
class Section
include Mongoid::Document
belongs_to :site
field :name, type: String
end
And
class DraftSection < Section
field :name, type: String, default: "New Section"
end
All simple stuff... console proves (again, trimmed for brevity):
004 > site = Site.first
=> #<Site _id: initech, name: "INITECH">
005 > site.sections.build
=> #<Section _id: 1, site_id: "initech", name: nil>
006 > site.draft_sections.build
=> #<DraftSection _id: 2, site_id: "initech", name: "New Section">
As you can see - the draft section name correctly defaults to "New Section" as it is overridden in the subclass.
Now when I run this spec:
describe "#new" do
it "should return a draft section" do
get 'new', site_id: site.id, format: :json
assigns(:section).should == "Something..."
end
end
Which tests this controller method:
def new
#section = #site.draft_sections.build
respond_with #section
end
Which fails (as expected), but with this:
Failure/Error: assigns(:section).should == "Something..."
expected: "Something..."
got: #<DraftSection _id: 1, site_id: "site-name-4", name: nil> (using ==)
What gives???
Update:
I figured it might be an issue with the different environment settings, so I looked at the mongoid.yml config file and saw this in the options:
# Preload all models in development, needed when models use
# inheritance. (default: false)
preload_models: true
I added it to the test environment settings too, but still no joy :(
Update 2 - the plot thickens...
Thought I'd try loading up the console in the test environment and trying the same as before:
001 > site = Site.first
=> #<Site _id: initech, name: "INITECH">
002 > site.draft_sections.build
=> #<DraftSection _id: 1, site_id: "initech", name: "New Section">
WTF?
Ok, no one's listening, but I'll post the solution here for future reference anyway...
For some reason, some time ago I had a debug session that meant I had left this code in my Spork.each_run block
# Reload all model files when run each spec
# otherwise there might be out-of-date testing
# require 'rspec/rails'
Dir["#{Rails.root}/app/controllers//*.rb"].each do |controller|
load controller
end
Dir["#{Rails.root}/app/models//*.rb"].each do |model|
load model
end
Dir["#{Rails.root}/lib//*.rb"].each do |klass|
load klass
end
This was causing the models to get reloaded on each run of a spec. Not surprisingly, this screwed up the way the classes were set up in memory whilst the specs were running.
Definitely explains why it was such a hard one to debug...
So for future googlers with similar inheritance problems in Rspec only - make sure that there's nothing reloading models anywhere in the test stack.

Resources