I've a Rails model set up with Mongoid::Versioning.
class Post
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Versioning
field :name, type: String
end
I want to delete a specific older version. For eg.
post = Post.create name: 'A'
post.update_attributes name: 'B'
post.update_attributes name: 'C'
post.version #=> 3
post.versions.count #=> 2
first_version = post.versions.first #=> #<Post _id: , created_at: _, updated_at: _, version: 2, name: "B">
I want to delete first_version, but when I try to delete it..
first_version.delete
post.versions.count #=> 1
post.reload
post.versions.count #=> 0
..all versions get deleted.
I've tried using destroy instead, tried running the code inside a block passed to post.versionless, to no avail. What should I do?
UPDATE:
I've gotten it to work with Mongoid::Paranoia. But it'd be nice to have the flexibility of not using Mongoid::Paranoia.
The concept of deleting intermediate versions is incorrect and corrupts the case for versioning. If you delete intermediate versions, then you are never sure if it's intentional or some corruption.
In spite of this, if you really want to do this, you should change to code to the following.
deleted = post.versions.first
post.versions.delete(deleted)
I am not sure why you want to do this though. If you want to ensure that you don't have too many versions and want to clean up, use max_versions class method.
Post.max_versions(5)
If you want to avoid versioning in some cases, use versionless
post.versionless(&:save)
Related
Can't find the configuration or setting for this (already googled several times). When inspecting some model objects in Rails console, why is it sometimes the model fields are displayed alphabetically and most times in other deployments, it does not?
Example:
### FIELDS NOT SORTED
#rails console
(main)> l = Loan.find '123'
=> #<Loan:0x000000066722e8
id: "8f196106c00e",
user_id: "f90084a53972",
borrower_id: "043bb77b3aac",
parent_id: nil,
score_id: "f00c39e74570",
number: 11231321,
scoring_tag: nil,
.....
but in other deployments, when I go in rails console
# FIELDS SORTED ALPHABETICALLY
(main)> l = Loan.find '123'
=> #<Loan:0x007fca8b45a468
active_servicer_id: nil,
amortization: nil,
amount: 150000.0 (BigDecimal),
application_fee: nil,
borrower_id: "asdasdajdjasd",
borrower_requested_closing_attorney: nil,
channel: nil,
closed_date: nil,
commitment_end_at: nil,
How can I make rails console display output of models sorted? This is sometimes necessary when comparing two records.
If you just want to print out the attributes, you can do that more explicitly:
Loan.find(123).attributes.sort_by(&:first).to_h
If you really want the console print to do that, you may want to override the inspect method. At least for irb and some other consoles, this is the method that determines what gets printed. If you have some other plugin or gem that the console is using, you may have to look at the docs to find the appropriate method.
Something like (modify as you wish, e.g. to better mimic the default inspect, display object_id, etc)
class Loan ...
def inspect
(self.class.name + ": " + attributes.sort_by(&:first).to_h).to_s
end
end
In Ruby's Hash, the order of its elements is arbitrary. When it is inspect-ed, (as in the output of rails console), the method inspect just uses each_pair or something similar, and so the order can be arbitrary. In practice, the order for Hash#each_pair usually seems to follow the order each Hash element is created; however there is no guarantee! Also, how Rails (or ActiveRecord) creates those instances is another issue (I guess the alphabetical order you see originates how Rails created them; it may have something to do with how they are stored in the DB). Anyway, the bottom line is you can not control how they are stored, as long as a Hash is used. But you can tweak how they are inspect-ed.
The following strategy is to redefine ApplicationRecord#inspect for any of its child classes so the inspect method of any Models are modified but of none of any other classes.
Put the following code in whatever file in your app/ directory (e.g., an obvious filename is /app/models/application_record.rb):
class ApplicationRecord
alias_method :inspect_orig, :inspect if ! self.method_defined?(:inspect_orig)
def inspect
klass = self.class
if klass == ApplicationRecord
return inspect_orig
end
attr_list = attribute_names.sort.map { |name| "#{name}: "+attributes[name].inspect }.join ', '
sprintf('<%s:0x%s %s>', klass.name, object_id.to_s(16), attr_list)
end
end
Or, instead you can run it every time you open rails console if you want to activate it only temporarily during the session.
Note the name of the parent class of ApplicationRecord may be added as follows (in the case of Rails 5.2; but I don't think inclusion of the parent class is mandatory, for the class ApplicationRecord should be defined before the file is read).
class ApplicationRecord < ActiveRecord::Base
I notice your rails console outputs look slightly different from my Rails 5.2.1, maybe due to the difference in the versions(?). In the code snippet above, I have tried to mimic the outputs you seem to get. Anyway adjust it to your liking; now you have a total control on it!
EDIT1:
The description above applies to Rails 5. In Rails 4, the class name should be
class ActiveRecord::Base
EDIT2:
In the code snippet above, it shows #object_id (n.b., it is not displayed in Rails 5.2). However, what #inspect shows for Object instances is different from #object_id, as explained in detail in this answer. If you prefer to display it, a way is this (the left side value is the format for sprintf):
'0x%016x' % (object_id << 1)
I have a Rails project that has models versioned using the paper_trail gem.
I'm looking to have that when I restore a version, I only want to change the fields that were changed in that particular version to be reverted, and not undo the changes of different fields of versions that came after.
For example, say I have a Person model with the fields: name, favorite_color and age.
# Let's say this doesn't create any version
Person.create(name: 'John', age: 20, favorite_color: 'Green')
# This creates a version (V1) with the changeset: 20 => 21
Person.update_attributes(age: 21)
# This creates another version (V2) with the changeset: 'Green' => 'Blue'
Person.update_attributes(favorite_color: 'Blue')
# This creates another version (V3) with the changeset: 'John' => 'James'
# This is also the latest version now
Person.update_attributes(name: 'James')
The functionality I'm looking for is if I revert back to V1 so the Person's age goes back to 20, I don't want to undo the changes that came after (i.e. the favorite color change and the name change). So I guess a better way of saying it is I only want to undo that specific field change. (In this case a Person with age: 20, favorite_color: 'Blue' and name: 'James').
I feel like this would be something that PaperTrail already supports. I scanned the docs and the code for something along these lines, searched Google, and looked at past issues, but haven't found anything.
So my real question is: Did I miss it, or is this just something that PaperTrail doesn't support?
Until I find out if this is something PaperTrail actually supports, I've extended PaperTrail::Version to support this with a function I've called revert.
Here's the code if else is interested:
# app/models/paper_trail/version.rb
module PaperTrail
class Version < ActiveRecord::Base
include PaperTrail::VersionConcern
def revert
item = self.item
item_attributes = {}
self.changeset.keys.each do |k|
item_attributes[k] = self.changeset[k][0]
end
item.assign_attributes(item_attributes)
item
end
end
end
Say I have a class User that inherits from ActiveRecord::Base, and it has a :name attribute. Then in the console I create a new user by:
> user1 = User.create(name: 'Bob')
=> #<User id: 1, name: 'Bob'>
Then I update the user's name to 'Bob'. Note I'm not doing user1.update_attribute.
> User.find_by(name: 'Bob').update_attribute(:name, 'Bill')
Now typing in:
> User.find_by(name: 'Bob')
=> nil
> User.find_by(name: 'Bill')
=> #<User id: 1, name: 'Bill'>
Which is what I expected. However, when I check my user1 reference I get:
> user1
=> #<User id: 1, name: 'Bob'>
Somehow this still has the old user name. Is user1 not a reference to the ActiveRecord? Is it a snapshot of some sort?
That is what it still has in the memory. user1.reload will do a new select and return refreshed record.
ActiveRecord fetches data from the database and converts it to objects. So yes, it's effectively a snapshot. There's just no good way to determine if any of its values changed in the meantime. While some database engines offer some sort of listen/notify, doing that for every single object is major (and useless most of the time) performance overhead.
There are alternative ORMs that adhere to "the same row is the same object" principle (such as DataMapper, see "Identity map"), but under certain circumstances that's also unreliable: the object in the meantime may have been modified by a different process your ORM has no idea about.
So ActiveRecord offers a compromise: it caches the object, but leaves you with reload method, that refreshes the object with data from the database in-place, returning itself, so you can fetch it and act on it without extra assignments.
Your user1 is not updated automatically. You need to call user1.reload first.
In the Rails 3 docs, the build method for associations is described as being the same as the new method, but with the automatic assignment of the foreign key. Straight from the docs:
Firm#clients.build (similar to Client.new("firm_id" => id))
I've read similar elsewhere.
However, when I use new (e.g. some_firm.clients.new without any parameters), the new client's firm_id association is automatically created. I'm staring at the results right now in the console!
Am I missing something? Are the docs a bit out of date (unlikely)? What's the difference between build and new?
You're misreading the docs slightly. some_firm.client.new is creating a new Client object from the clients collection, and so it can automatically set the firm_id to some_firm.id, whereas the docs are calling Client.new which has no knowledge of any Firm's id at all, so it needs the firm_id passed to it.
The only difference between some_firm.clients.new and some_firm.clients.build seems to be that build also adds the newly-created client to the clients collection:
(some_firm = Firm.new).save # Create and save a new Firm
#=> true
some_firm.clients # No clients yet
#=> []
some_firm.clients.new # Create a new client
#=> #<Client id: nil, firm_id: 1, created_at: nil, updated_at: nil>
some_firm.clients # Still no clients
#=> []
some_firm.clients.build # Create a new client with build
#=> #<Client id: nil, firm_id: 1, created_at: nil, updated_at: nil>
some_firm.clients # New client is added to clients
#=> [#<Client id: nil, firm_id: 1, created_at: nil, updated_at: nil>]
some_firm.save
#=> true
some_firm.clients # Saving firm also saves the attached client
#=> [#<Client id: 1, firm_id: 1, created_at: "2011-02-11 00:18:47", updated_at: "2011-02-11 00:18:47">]
If you're creating an object through an association, build should be preferred over new as build keeps your in-memory object, some_firm (in this case) in a consistent state even before any objects have been saved to the database.
build is just an alias for new:
alias build new
Full code can be found: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/relation.rb#L74
You are correct, the build and new functions have the same effect of setting the foreign key, when they are called through an association. I believe the reason the documentation is written like this is to clarify that a new Client object is being instantiated, as opposed to a new active record relationship. This is the same effect that calling .new on a class would have in Ruby. That is to say that the documentation is clarifying that calling build on an association is the same is creating a new object (calling .new) and passing the foreign keys to that object. These commands are all equivalent:
Firm.first.clients.build
Firm.first.clients.new
Client.new(:firm_id => Firm.first.id)
I believe the reason .build exists is that Firm.first.clients.new might be interpreted to mean that you are creating a new has_many relationship object, rather than an actual client, so calling .build is a way of clarifying this.
build vs new:
mostly new and build are same but build stores object in memory,
eg:
for new:
Client.new(:firm_id=>Firm.first.id)
For build:
Firm.first.clients.build
Here clients are stored in memory, when save firm , associated records are also saved.
Model.new
Tag.new post_id: 1 will instantiate a Tag with its post_id set.
#model.models.new
#post.tags.build does the same AND the instantiated Tag will be in #post.tags even before it's saved.
This means #post.save will save both the #post and the newly built tag (assuming :inverse_of is set). This is great because Rails will validate both objects before saving, and neither will be saved if either one of them fails validation.
models.new vs models.build
#post.tags.build and #post.tags.new are equivalent (at least since Rails 3.2).
I'm using ar_fixtures to seed data in a rails project (the procedure was written before seed arrived in 2.3.4)
It is all working fine, except I find that one column in particular is not getting populated (user_id). user_id is set in the yaml file, for example:
- !ruby/object:Service
attributes:
name: name
updated_at: 2009-10-14 11:50:36
provider_id: "1"
id: "1"
description: ""
user_id: "1"
created_at: 2009-10-14 11:47:01
attributes_cache: {}
But even though the related user object exists when running the import (Service.load_from_file), user_id is nil after import. Other foreign keys (like provider_id in the example above) are loaded correctly.
I am suspecting this is because user_id is protected in the model and this getting blocked during mass assignment.
Does anyone know if this is the case, and if so, how to get around the mass assignment protection? Of course, I want to leave mass assignment protection in place for the application at runtime.
Fixed! Answering my own question..
Simply requires a tweak of the attr_protected / attr_accessible setting prior to loading. e.g.
Service.attr_protected.delete "user_id"
Service.load_from_file
Or if the restriction is based on attr_accessible:
Service.attr_accessible :user_id
Service.load_from_file