I'm getting the error "undefined method 'timestamps!' when using Rails 3 with MongoMapper and was wondering if anyone can help resolve this problem.
I'm using Rails 3.0.1, mongo_mapper 0.8.6, and mongo 1.1
My model:
class User
include MongoMapper::EmbeddedDocument
key :_id, String
key :name, String, :required => true, :limit => 100
key :email, String, :required => false, :limit => 200
key :bio, String, :required => false, :limit => 300
timestamps!
end
First off, I'll note that if you're using Rails 3, you might want to look at Mongoid. It uses ActiveModel so you get all the Rails 3 polish with it. I prefer MongoMapper for 2.3.x projects, but Mongoid has seemed much more stable for me in Rails 3 projects.
That said, the timestamps! method is provided by the Timestamps plugin, which should be loaded as a part of the MongoMapper::Document inclusion. However, you could try including it manually:
class User
include MongoMapper::Document
plugin MongoMapper::Plugins::Timestamps
timestamps!
end
If the timestamps module isn't being loaded for any reason, that should manually include it in your model, and should make it available for use.
Related
I am looking at the Virtus gem used in a few tutorials about Service object in Ruby. In the github page, https://github.com/solnic/virtus, it gives the following example.
Using Virtus with Classes
You can create classes extended with Virtus and define attributes:
class User include Virtus.model
attribute :name, String
attribute :age, Integer
attribute :birthday, DateTime
end
user = User.new(:name => 'Piotr', :age => 31) user.attributes # => { :name => "Piotr", :age => 31, :birthday => nil }
user.name # => "Piotr"
user.age = '31' # => 31 user.age.class # => Fixnum
user.birthday = 'November 18th, 1983' # => #<DateTime: 1983-11-18T00:00:00+00:00 (4891313/2,0/1,2299161)>
# mass-assignment user.attributes = { :name => 'Jane', :age => 21 } user.name # => "Jane" user.age # => 21
I can see how the example works, but would like to understand how is this different than just defining attr_accessors in Ruby? If I have to explain to someone, the benefit of including the Virtus gem and what it does in a couple of lines, what would it be?
The goals of Virtus can be summarized as trying to make attributes a little more "Rails-y". They provide support for parsing form/JSON, encapsulation while retaining type information, and a few other things it's not impossible to get regular attributes to do, but not easy either.
The real benefits, however, come when you combine Virtus with ActiveModel::Validations per this post. Since your basic values have become more responsive to the expectations of Rails form helpers, you have a very powerful alternative to nested forms.
I am able to use the basic map display capabilities in gmaps4rails with mongoid, which is great, but Im falling short with more advanced features. I think I am missing some basics and looking for guidance.
I am able to get the basic geocoding to work with fields named :latitude :longitude and :gmaps but when I try to use an array, as suggested by the readme, I am getting nowhere. I've read the Mongoid tips in the wiki to no avail.
Can anyone point me in the right direction?
Update;
Since Ive gotten no responses, here is some code examples,
The Model
working;
class Account
include Mongoid::Document
include Gmaps4rails::ActsAsGmappable
acts_as_gmappable
field :account_no
field :owner_name
field :address
field :latitude, :type => Float
field :longitude, :type => Float
field :gmaps, :type => Boolean
def gmaps4rails_address
"#{self.address}"
end
end
Not working
class Account
include Mongoid::Document
include Gmaps4rails::ActsAsGmappable
acts_as_gmappable :position => :location
field :account_no
field :owner_name
field :address
field :location, :type => Array
def gmaps4rails_address
"#{self.address}"
end
end
Based on the code snippets in the gmaps4rails readme this should work to geocode the address into the location array but I get this error
NoMethodError: undefined method `gmaps' for #<Account:0x007fc47d051ba0>
As explained here, gmaps is a boolean which purpose is to prevent multiple geocoding of the same address.
So the field is necessary, unless you tell it explicitly (see same doc)
I'm just diving into Mongodb and MongoID with Rails and I find it awesome. One thing the NoSQL helps is when I can add extra fields to my model without any extra effort whenever I want:
class Page
include Mongoid::Document
include Mongoid::MultiParameterAttributes
field :title, :type => String
field :body, :type => String
field :excerpt, :type => String #Added later
field :location, :type => String #Added later
field :published_at, :type => Time
validates :title, :presence => true
validates :body, :presence => true
validates :excerpt, :presence => true
end
And this works perfectly as it should. But my question is, (sorry if this is trivial) the existing entries are blank and have no defined value for the newly added field. For example, in a sample blog application, after I've published two posts, I decide to add an excerpt and a location field to my database (refer code above). Any blog post that is published after the addition of these new fields can be made sure to have a value filled in for the excerpt field. But the posts published prior to the addition of these two new fields have null values (which is understandable why) which I cannot validate. Is there an elegant solution for this?
Thank you.
There are three basic options:
Update everything inside MongoDB to include the excerpt.
Use an after_initialize hook to add a default excerpt to existing objects when you pull them out of MongoDB.
Kludge your validation logic to only check for the existence of excerpt on new objects.
(1) requires a (possible large) time hit when you make the change but it is just a one time thing and you don't have to worry about it after that. You'd pull every Page out of MongoDB, do page.excerpt = 'some default excerpt', and then save it back to MongoDB. If you have a lot of Pages you'll want to process them in chunks of, say, 100 at a time. If you do this, you'll be able to search on the excerpt without worrying about what you should do with nulls. You can also do this inside MongoDB by sending a JavaScript fragment into MongoDB:
connection.eval(%q{
db.pages.find({}, { _id: true }).forEach(function(p) {
db.pages.update(
{ _id: p._id },
{ $set: { excerpt: 'some default excerpt' } }
);
});
})
(2) would go something like this:
after_initialize :add_default_excerpt, :unless => :new_record?
#...
private
def add_default_excerpt
self.excerpt = 'some default excerpt' unless self.excerpt.present?
end
You could move the unless self.excerpt up to the :unless if you didn't mind using a lambda:
after_initialize :add_default_excerpt, :unless => ->{ |o| o.new_record? || o.excerpt.present? }
#...
private
def add_default_excerpt
self.excerpt = 'some default excerpt'
end
This should be pretty quick and easy to set up but there are downsides. First of all, you'd have a bunch of nulls in your MongoDB that you might have to treat specially during searches. Also, you'd be carrying around a bunch of code and logic to deal with old data but this baggage will be used less and less over time. Furthermore, the after_initialize calls do not come for free.
(3) requires you to skip validating the presence of the excerpt for non-new Pages (:unless => :new_record?) or you'd have to find some way to differentiate new objects from old ones while also properly handling edits of both new and old Pages. You could also force people to supply an excerpt when they change a Page and leave your validation as-is; including a :default => '' on your field :excerpt would take care of any nil issues in views and such.
I'd go with (1) if possible. If the update would take too long and you wanted the site up and running while you were fixing up MongoDB, you could add a :default => '' while updating and then remove the :default option, restart, and manually patch up any strays that got through.
# ==Schema Information
# Schema version:
# Table name: terms
# id :integer not null, primary key
...
# user_id :integer
# term_type :string(255)`
term.rb:
class Term < ActiveRecord::Base
validates :term_type, :inclusion => { :in => %w(Term Segment Entity Abbreviation) }`
Rails 3.0.3, ruby 1.8.7, Windows via Cygwin
The validation does not seem to work, i.e. it accepts any value for term_type.
When using inclusion (or validates_inclusion_of which produces the same fail condition) do I need DB validations matching those in the model?
Any issues with Strings I should be wary of?
Term_type is just a string in the DB and not a separate table.
Any pointers much appreciated.
Vince
Have you tried using
validates :field, :inclusion => %{abc ade aeee}
I've changed my model from
class Place
include DataMapper::Resource
has n, :trails
property :id, Serial
property :name, String, :length => 140
property :tag, String, :required => true
timestamps :at
end
to
class Place
include DataMapper::Resource
has n, :trails
property :id, Serial
property :name, String, :length => 140
property :tag, String, :required => true
property :trail_count, Integer, :default => 0
timestamps :at
end
I just added "property :trail_count, Integer, :default => 0"
and i want to migrate the existing appengine table to have the extra field "trail_count"
i've read that DataMapper.auto_upgrade! should do it.
but i get an error "undefined method `auto_upgrade!' for DataMapper:Module"
can you please help How do i migrate the DM models?
After restarting the server for the third time the field was miraculously added.
It's still a weird and not so good way to do migrations.
how do you manipulate data without migrations? like splitting a field "full name" to first and last name fields? you gotta have a migration for that..
I've been looking up the same issue Roy and it appears migrations don't work on app engine using datamapper (or any other interface). It's a function of the datastore, and to update existing database entries you'll have to query the database and update a few at a time to avoid hitting rate limits.
source
Try requiring the dm-migrations gem. This was how I resolved the issue with Sinatra 1.4.7 and do_sqlite3 0.10.17.
require 'dm-migrations'
require 'rubygems'
require 'sinatra'
require 'dm-core'
require 'dm-timestamps'
require 'dm-sqlite-adapter'
require 'dm-migrations'
DataMapper::setup(:default, "sqlite3://#{Dir.pwd}/adserver.db")
class Ad
include DataMapper::Resource
property :id, Serial
property :title, String
property :content, Text
property :width, Integer
property :height, Integer
property :filename, String
property :url, String
property :is_active, Boolean
property :created_at, DateTime
property :updated_at, DateTime
property :size, Integer
property :content_type, String
end
# Create or upgrade all table at once, like magic
DataMapper.auto_upgrade!
answer found here