I'm having a problem trying to use Mongoid (v 3.1.4) to persist a (really simple) entity to MongoDB (v 2.4.4). I'm using MRI and Ruby 2.0.0-p195 on OS X.
Here's my class (Person.rb):
require 'mongoid'
class Person
include Mongoid::Document
include Mongoid::Timestamps # currently can be ommitted
field :name, type: String
def initialize
# is empty
end
def name
#name
end
def name=(value)
#name = value
end
end
Mongoid.load!('config/mongoid.yml', :development)
user = Person.new
user.name = "John Doe"
user.create
That last sentence greets me with a
[...]mongoid/attributes.rb:320:in 'method_missing': undefined method `has_key?' for nil:NilClass (NoMethodError)
Here's my 'mongoid.yml':
development:
sessions:
default:
database: rbtest
hosts:
- localhost:27017
test:
sessions:
default:
database: test
hosts:
- localhost:27017
options:
consistency: :strong
max_retries: 1
retry_interval: 0
Connection to the DB instance seems ok as the DB is created ('rbtest') however, Collections and Documents fail. I've already tried with 'create!' and 'safely.save!' to no avail.
I tried implementing the has_key? method, for which I couldn't find any documentation, so I'm at a bit of a loss here.
As always, any help is much appreciated.
Regards,
UPDATE -- SOLUTION:
#Frederik Cheung's answer was spot on. Here's the working code (updated with #mu-is-too-short's suggestion)
require 'mongoid'
class Person
include Mongoid::Document
field :name, type: String
end
Mongoid.load!('config/mongoid.yml', :development)
person = Person.new(:name => 'John Doe')
person.save!
The problem is your initialize method: you are overriding the one provided by mongoid, so some of mongoid's internals aren't being setup.
You need to either remove your initialize method or call the mongoid's implementation via super
Related
Thanks for your time!
The code is simple(mongoid was used without rails):
require 'mongoid' # version 6.0.2
Mongoid.load!('mongoid.yml', :development)
class Office
include Mongoid::Document
embeds_one :owner
embeds_many :addresses
end
class Owner
include Mongoid::Document
end
class Address
include Mongoid::Document
end
I could successfully call office.addresses.build as following.
office = Office.new
office.addresses.build
office.save
But when I call office.owner.build, error pop up saying
embed_one.rb:23:in `<main>': undefined method `build' for nil:NilClass (NoMethodError)
It's supposed to work in this way, right? Where is wrong.
puts office.owner.class # NilClass
After refresh myself from a sleep ...
I use puts office.methods to list all the methods office could invoke.
# Here's all the methods has *owner* in it
owner=
owner?
has_owner?
build_owner
create_owner
owner
office.build_owner is what i'm looking for!
I'm sending serialized data to a class which need to access a Mongoid document which may or may not be embedded.
In case of embedded document, I'm accepting a variable number of arguments which I reduce to get the embedded document.
The code is pretty simple:
def perform(object, *arguments)
#opts = arguments.extract_options!
#object = arguments.reduce(object){|object, args| object.public_send(*args)}
# [...]
I used public_send because AFAIK I only need to call public methods.
However, when I try to access an embedded document I have some really strange result where #object is an enumerator.
After some debugging, this is what I found that for any root document object and an embedded collection items, I have:
object.items.public_send(:find)
# => #<Enumerator: ...>
object.items.send(:find) # or __send__
# => nil
The method called is not the same at all when I call public_send or send!
How is it even possible?
Is it normal? Is that a bug?
public_send seems to invoke the find method of Array (Enumerable) but send (or __send__) invokes the find method of Mongoid
Edit: simple reproductible case:
require 'mongoid'
class User
include Mongoid::Document
field :name, type: String
embeds_many :groups
end
class Group
include Mongoid::Document
field :name, type: String
embedded_in :user
end
Mongoid.load_configuration({
sessions: {
default: {
database: 'send_find',
hosts: [
'localhost:27017'
]
}
}
})
user = User.create(name: 'john')
user.groups.create(name: 'g1')
user.groups.create(name: 'g2')
puts "public_send :find"
puts user.groups.public_send(:find).inspect
# => #<Enumerator: [#<Group _id: 5530dea57735334b69010000, name: "g1">, #<Group _id: 5530dea57735334b69020000, name: "g2">]:find>
puts "send :find"
puts user.groups.send(:find).inspect
# => nil
puts "__send__ :find"
puts user.groups.__send__(:find).inspect
# => nil
Okay, after a few hours of debugging, I found that it is actually a bug in Mongoid.
The relation is not an array but a proxy around the array, which delegates most methods to the array.
As public_send was also delegated but not send and __send__, the behavior was not the same.
For more information, see my pull request and the associated commit.
I am using Rails 3.2.13 and postgress.
I am getting below error only in production server
NoMethodError (undefined method `unserialized_value' for "--- []\n":String):
app/controllers/blogs_controller.rb:159:in `content_generators'
I am serializing Array to store it in db. Below is code.
Controller
class BlogsController < ApplicationController
def content_generators
#blog = Blog.find(params[:id])
#users = #blog.content_generators.map do |id|
User.find(id)
end
end
end
Model
class Blog < ActiveRecord::Base
serialize :post_access, Array
serialize :content_generators, Array
attr_accessible :post_access, :content_generators
end
Migration
class AddContentgeneratorsToBlog < ActiveRecord::Migration
def change
add_column :blogs, :content_generators, :string, :default => [].to_yaml
end
end
I have already used serialization. You can see post_access is serialized. And that works perfect.
But now when I added another column content_generators it starts breaking.
Thanks for your help in advance.
Since you are using postgresql I strongly recommend using the built in array functionality:
# Gemfile
gem 'postgres_ext'
class MyMigration
def change
add_column :my_table, :that_array_column, :text, array: true, default: []
end
end
Then remove the serialize calls in your model and that's it. PG serialized array's behave exactly the same as YAML serialized ones on the model, except the db supports some query methods on them.
I'm making a Ruby Sinatra application that uses mongomapper and most of my responses will be in the JSON form.
Confusion
Now I've come across a number of different things that have to do with JSON.
The Std-lib 1.9.3 JSON class: http://www.ruby-doc.org/stdlib-1.9.3/libdoc/json/rdoc/JSON.html
The JSON Gem: http://flori.github.io/json/
ActiveSupport JSON http://api.rubyonrails.org/classes/ActiveSupport/JSON.html because I'm using MongoMapper which uses ActiveSupport.
What works
I'm using a single method to handle responses:
def handleResponse(data, haml_path, haml_locals)
case true
when request.accept.include?("application/json") #JSON requested
return data.to_json
when request.accept.include?("text/html") #HTML requested
return haml(haml_path.to_sym, :locals => haml_locals, :layout => !request.xhr?)
else # Unknown/unsupported type requested
return 406 # Not acceptable
end
end
the line:
return data.to_json
works when data is an instance of one of my MongoMapper model classes:
class DeviceType
include MongoMapper::Document
plugin MongoMapper::Plugins::IdentityMap
connection Mongo::Connection.new($_DB_SERVER_CNC)
set_database_name $_DB_NAME
key :name, String, :required => true, :unique => true
timestamps!
end
I suspect in this case the to_json method comes somewhere from ActiveSupport and is further implemented in the mongomapper framework.
What doesn't work
I'm using the same method to handle errors too. The error class I'm using is one of my own:
# Superclass for all CRUD errors on a specific entity.
class EntityCrudError < StandardError
attr_reader :action # :create, :update, :read or :delete
attr_reader :model # Model class
attr_reader :entity # Entity on which the error occured, or an ID for which no entity was found.
def initialize(action, model, entity = nil)
#action = action
#model = model
#entity = entity
end
end
Of course, when calling to_json on an instance of this class, it doesn't work. Not in a way that makes perfect sense: apparantly this method is actually defined. I've no clue where it would come from. From the stack trace, apparently it is activesupport:
Unexpected error while processing request: object references itself
object references itself
/home/id833541/.rvm/gems/ruby-1.9.3-p392/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:75:in `check_for_circular_references'
/home/id833541/.rvm/gems/ruby-1.9.3-p392/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:46:in `encode'
/home/id833541/.rvm/gems/ruby-1.9.3-p392/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:246:in `block in encode_json'
/home/id833541/.rvm/gems/ruby-1.9.3-p392/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:246:in `each'
/home/id833541/.rvm/gems/ruby-1.9.3-p392/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:246:in `map'
/home/id833541/.rvm/gems/ruby-1.9.3-p392/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:246:in `encode_json'
But where is this method actually defined in my class?
The question
I will need to override the method in my class like this:
# Superclass for all CRUD errors on a specific entity.
class EntityCrudError < StandardError
def to_json
#fields to json
end
end
But I don't know how to proceed. Given the 3 ways mentioned at the top, what's the best option for me?
As it turned out, I didn't need to do anything special.
I had not suspected this soon enough, but the problem is this:
class EntityCrudError < StandardError
..
attr_reader :model # Model class
..
end
This field contains the effective model class:
class DeviceType
..
..
end
And this let to circular references. I now replaced this with just the class name, which will do for my purposes. Now to_json doesn't complain anymore and I'm happy too :)
I'm still wondering what's the difference between all these JSON implementations though.
I'm trying to include a module only when a condition is met.
module PremiumServer
def is_premium
true
end
end
class Server
include Mongoid::Document
include PremiumServer if self.premium
field :premium, :type => Boolean, :default => false
end
This isn't working, and I can't figure out why. Can someone please tell me how I'm supposed to include modules based upon a condition being met, like above?
Thanks!
EDIT:
I found the answer to my problem here: Mongoid and carrierwave
However, I'm awarding the question to the top answer as it is probably the more useful way.
includes happen on the class level. Your premium attribute is at instance level.
There are ways to do the include on per instance level, but I would not recommend them.
Here you are better of using inheritance
class Server; .. ; end
class PremiumServer < Server; ..; end
Or, in your case, if the only method is is_premium add it to the Server class and have it return the premium variable
def is_premium
self.premium
end
oh, and you should use "question" method in ruby... Although Mongoid provides these for boolean values.
def premium?
self.premium
end
Use class inheritance and the scope mechanism of Mongoid:
class Server
include Mongoid::Document
field :premium, type: Boolean, default: false
# ... basic server methods
end
class PremiumServer < Server
default_scope :premium_servers, where(premium: true)
# ... additional premium server methods
end
p_server = PremiumServer.first
p_server.<access to PremiumServer methods>
The default_scope will be used every time you do a query on PremiumServer, you do not need to call .premium_servers manually.
That is "conditional based" in another way - in a mongoid way.
Further information:
Scopes: http://mongoid.org/docs/querying/scopes.html
Inheritance: http://mongoid.org/docs/documents/inheritance.html