I'm using Mongoid::Versioning which works great except that I would like to prevent several fields from being versioned.
There's not a lot of info written about it in the docs so I'm not sure how to do this.
http://mongoid.org/docs/extras.html
class Person
include Mongoid::Document
include Mongoid::Versioning
# keep at most 5 versions of a record
max_versions 5
end
They show how to skip a version altogether but not how to restrict certain fields from being versioned.
Any ideas?
UPDATE
I found something like this digging through the code, but I'm not sure how to use it.
https://github.com/mongoid/mongoid/blob/master/lib/mongoid/versioning.rb#L90
All Field has option :versioned true by default If you don't want this versionned you can pass false. By exemple I want name versioned but no login
class User
include Mongoid::Document
include Mongoid::Versioning
field :name, :type => String
field :login, :type => String, :versioned => false
end
You can pass the :versioned option in embed association too.
You can override this option by iterating over the .fields on your Document.
So in your code you can add avoid versionned on some field by creating a little methode :
class User
include Mongoid::Document
include Mongoid::Versioning
include Mongoid::Voteable
field :name, :type => String
field :login, :type => String
def self.avoid_versioned(*unversioned_fields)
unversioned_fields.each do |f|
fe = self.fields[f.to_s]
fe.options[:versioned] = false if fe
re = self.relations[f.to_s]
re[:versioned] = false if re
end
end
avoid_versioned( :login, :votes )
end
You can probably find a way to do this, but I would suggest checking out this gem instead.
https://github.com/aq1018/mongoid-history
track_history :on => [:title, :body], # I want to track title and body fields only. Default is :all
:modifier_field => :modifier, # Adds "referened_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, # Do you want to track document creation? Default is false
:track_update => true, # Do you want to track document updates? Default is true
:track_destroy => false, # Do you want to track document destruction? Default is false
You may skip versioning at any point in time by wrapping the persistence call in a versionless block.
class Person
include Mongoid::Document
include Mongoid::Versioning
end
person.versionless do |doc|
doc.update_attributes(name: "Theodore")
end
Related
Let's say that I have an input field with a value, and I want to validate it (on the server side) to make sure, for instance, that the field has at least 5 characters.
The problem is that it is not something that I want to save in the database, or build a model. I just want to check that the value validates.
In PHP with Laravel, validation is quite easy:
$validator = Validator::make($data, [
'email' => ['required', 'email'],
'message' => ['required']]);
if ($validator->fails()) { // Handle it... }
Is there anything similar in Rails, without need of ActiveRecord, or ActiveModel? Not every data sent from a form makes sense as a Model.
You can use ActiveModel::Validations like this
class MyClass
include ActiveModel::Validations
validates :email, presence: true
validates :message, presence: true
end
It will act as a normal model and you will be able to do my_object.valid? and my_object.errors.
Rails validations live in ActiveModel so doing it without ActiveModel seems kind of counter-productive. Now, if you can loosen that requirement a bit, it is definitely possible.
What I read you asking for, and as I read the PHP code doing, is a validator-object that can be configured on the fly.
We can for example build a validator class dynamically and use instance of that class to run our validations. I have opted for an API that looks similar to the PHP one here:
class DataValidator
def self.make(data, validations)
Class.new do
include ActiveModel::Validations
attr_reader(*validations.keys)
validations.each do |attribute, attribute_validations|
validates attribute, attribute_validations
end
def self.model_name
ActiveModel::Name.new(self, nil, "DataValidator::Validator")
end
def initialize(data)
data.each do |key, value|
self.instance_variable_set("##{key.to_sym}", value)
end
end
end.new(data)
end
end
Using DataValidator.make we can now build instances of classes with the specific validations that we need. For example in a controller:
validator = DataValidator.make(
params,
{
:email => {:presence => true},
:name => {:presence => true}
}
)
if validator.valid?
# Success
else
# Error
end
Let's say I have a Model Item which uses Mongoid
class Item
include Mongoid::Document
field :title, type: String
...
...
end
I want to add some dynamic fields to Item right in Model before passing data to a Controller - because Item is being used by several Controllers.
For example I want to add thumb field which I will generate by adding /path/to + filename.
I tried some solutions with attr_accessor:
class Item
include Mongoid::Document
field :title, type: String
...
...
attr_accessor :thumb
def prepare_data
#thumb = "/path/to/thumb"
end
end
...And later in some Controller:
#items_all = Item.all
#thumbs = []
#items_all.each do |i]
i.prepare_data
#thumbs.push(i[:thumb])
end
# #thumbs >>> (empty)
So it seems that I'm missing some point here because it doesn't work.
Also can I avoid calling prepare_data each time manually? May be with help of after_initialize? (which didn't work for me also).
I found my mistake. First I forgot to add after_initialize :do_something and then I found that I can user #attributes.merge!
after_initialize :do_after_initialize
def do_after_initialize
#attributes.merge!({
:title => self.get_title,
:type => self.get_type,
:thumb => ImageUploader::thumb(self[:pictures][0]["filename"])
:price_f => ActionController::Base.helpers.number_to_currency(self[:price], {:precision=>0})
})
end
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)
Assume, that a user wants to create a set consisting of items created by other users. Mongoid Document for item has versioning, and the user who creates the set might not enjoy changes the item authors do with the items of the set. Therefore I would like the set document to refer to specific versions of items, allowing the set author to update item references if wanted. I am planning adding an array of item version numbers to the set document, and some methods for getting set items of certain version and for updating the item versions. Do you find this approach reasonable? How would you solve this problem?
class Item
include Mongoid::Document
include Mongoid::Paranoia
include Mongoid::Versioning
field :title, type: String
has_and_belongs_to_many :item_sets
end
class ItemSet
include Mongoid::Document
field :name, type: String
field :item_versions, type: Array
has_and_belongs_to_many :items
end
I solved problems like this by creating a model in the "middle" like "ItemReference"
MongoDB is a document store and not a relational database, so it is legitimate to store duplicate information when necessary. MongoDB has the ability to store embedded documents, so we're gonna use this great feature.
The ItemReference holds all crucial information about the Item which is needed for creating a view. This reduces the queries on the view side but in increases the queries on the insert/update side.
The thing is that you need a "composite primary key" that consists of the item_id and the version number.
Let's talk code:
The Item Model:
class Item
include Mongoid::Document
include Mongoid::Paranoia
include Mongoid::Versioning
field :title, :type => String
# create a reference
def to_reference
# create new reference, containing all crucial attributes for displaying
ItemReference.new(
:item_id => self._parent.nil? ? self.id : self._parent.id,
:version => self.version,
:title => self.title
)
end
# get a certain version of this item
def get_version(version_number)
return self if version_number == self.version
self.versions.where(:version => version_number).first
end
end
The ItemSet Model
class ItemSet
include Mongoid::Document
field :name, :type => String
embeds_many :item_references
end
The ItemReference Model
class ItemReference
include Mongoid::Document
embedded_in :item_sets
field :title, :type => String
# this points to the newest version
belongs_to :item
# get the original version
def original
self.item.get_version(self.version)
end
# update this reference to a certain version
def update_to!(new_version)
new_version = self.item.get_version(new_version)
if new_version.present?
# copy attribute, except id
self.attributes = new_version.to_reference.attributes.reject{|(k,v)| k == "_id"}
self.save
else
# version not found
false
end
end
# update to the newest version
def update_to_head!
self.update_to!(self.item.version)
end
end
This combination allows you to create sets holding Items with different versions, and you can update certain ItemReferences in the set to a specific version.
Here's an example:
first = Item.create(:title => 'Item 1')
first.title = 'Item 1.1'
first.save
myset = ItemSet.create(:title => 'My Set')
myset.item_references << first.to_reference
myset.save
first.title = 'Item 1.2'
first.save
p myset.item_references.first.title # prints Item 1.1
p myset.item_references.first.update_to_head!
p myset.item_references.first.title # prints Item 1.2
p myset.item_references.first.update_to!(1)
p myset.item_references.first.title # prints Item 1
I need to create a named scope in Mongoid that compares two Time fields within the same document. Such as
scope :foo, :where => {:updated_at.gt => :checked_at}
This obviously won't work as it treats :checked_at as a symbol, not the actual field. Any suggestions on how this can be done?
Update 1
Here is my model where I have this scope declared, with a lot of extra code stripped out.
class User
include Mongoid::Document
include Mongoid::Paranoia
include Mongoid::Timestamps
field :checked_at, :type => Time
scope :unresolved, :where => { :updated_at.gt => self.checked_at }
end
This gives me the following error:
'<class:User>': undefined method 'checked_at' for User:Class (NoMethodError)
As far as I know, mongodb doesn't support queries against dynamic values.
But you could use a javascript function:
scope :unresolved, :where => 'this.updated_at >= this.checked_at'
To speed this up you could add an attribute like "is_unresolved" which will be set to true on update when this condition is matched ( and index that ).
scope :foo, :where => {:updated_at.gt => self.checked_at}
For example, this will work:
scope :foo, where(:start_date.lte=>Date.today.midnight)
Not sure if you'll like this method, it's not the best, but it should work.
class User
include Mongoid::Document
include Mongoid::Paranoia
include Mongoid::Timestamps
field :checked_at, :type => Time
scope :unresolved, lambda{ |user| where(:updated_at.gt => user.checked_at) }
end
You call it with User.unresolved(my_user_object)
It seems now after rereading your post that this probably won't do what you want. If this is true, then you will probably need to use MapReduce or possibly Baju's method (have not tested it)