Regenerate keys in Mongoid? - ruby-on-rails

Just trying out Mongoid at the moment, I've run into an issue that's probably pretty simple but has me at a loss:
I have a really simple Article model:
class Article
include Mongoid::Document
field :title, :type => String
field :content, :type => String
key :title
referenced_in :subject
validates_presence_of :title
end
I added key :title after I had already created one test record. Newly created records work as expected, but the first Article (which originally had the normal mongoid object id) behaves strangely:
In rails views this first article still returns its object id instead of the new key. ie using link_to article.name, article returns:
Show
... when all the rest return the parameterized keys, like:
Show
If I click that link I get "Record not found". I tried loading and resaving this record in the console, and after that calling article.id on that record did return the parameterized key, but it still shows up the old way in the view and doesn't work.
So, a couple questions:
What's going on here?
How do you fix it?
This situation indicates to me that if you set a field on a mongoid model to be the key, you need to be really sure that it will never change. How do you handle something like using the title of an article as a slug, then, when those may occasionally need change?
Thanks!

Well, since _id is immutable, your only option is to reinsert this document with your new 'sluggish' id and delete the old one.
And yes, _id format and shard key (if you use sharding) are the two things you better have right from the beginning :-)
Everything else can be fixed relatively easily.

Related

ActiveAdmin Changing Links to Name Instead of ID

For one of my models in ActiveAdmin, it is changing the URLs to use the name instead of ID.
For example: http://localhost:3000/admin/product/PH instead of http://localhost:3000/admin/product/1
I don't understand why it's doing that since all of the other models are working correctly (using ID).
This model has no models/product.rb file.
# app/admin/product.rb
ActiveAdmin.register Product do
permit_params :name,
:amount,
:description
end
I checked the documentation and didn't see anything that looks like it would make this happen.
Also, all of the other SO posts I've seen related to name and URL seem to be trying to do the opposite - changing the default route to use name (instead of ID).
Late reply, but I just met the same problem.
It was due to slug. I just remove slug from my model and it worked.
Hope it could help someone.

How to use `first_or_initialize` step with `accepts_nested_attributes_for` - Mongoid

I'd like to incorporate a step to check for an existing relation object as part of my model creation/form submission process. For example, say I have a Paper model that has_and_belongs_to_many :authors. On my "Create Paper" form, I'd like to have a authors_attributes field for :name, and then, in my create method, I'd like to first look up whether this author exists in the "database"; if so, then add that author to the paper's authors, if not, perform the normal authors_attributes steps of initializing a new author.
Basically, I'd like to do something like:
# override authors_attributes
def authors_attributes(attrs)
attrs.map!{ |attr| Author.where(attr).first_or_initialize.attributes }
super(attrs)
end
But this doesn't work for a number of reasons (it messes up Mongoid's definition of the method, and you can't include an id in the _attributes unless it's already registered with the model).
I know a preferred way of handling these types of situations is to use a "Form Object" (e.g., with Virtus). However, I'm somewhat opposed to this pattern because it requires duplicating field definitions and validations (at least as I understand it).
Is there a simple way to handle this kind of behavior? I feel like it must be a common situation, so I must be missing something...
The way I've approached this problem in the past is to allow existing records to be selected from some sort of pick list (either a search dialog for large reference tables or a select box for smaller ones). Included in the dialog or dropdown is a way to create a new reference instead of picking one of the existing items.
With that approach, you can detect whether the record already exists or needs to be created. It avoids the need for the first_or_initialize since the user's intent should be clear from what is submitted to the controller.
This approach struggles when users don't want to take the time to find what they want in the list though. If a validation error occurs, you can display something friendly for the user like, "Did you mean to pick [already existing record]?" That might help some as well.
If I have a model Paper:
class Paper
include Mongoid::Document
embeds_many :authors
accepts_nested_attributes_for :authors
field :title, type: String
end
And a model Author embedded in Paper:
class Author
include Mongoid::Document
embedded_in :paper, inverse_of: :authors
field :name, type: String
end
I can do this in the console:
> paper = Paper.create(title: "My Paper")
> paper.authors_attributes = [ {name: "Raviolicode"} ]
> paper.authors #=> [#<Author _id: 531cd73331302ea603000000, name: "Raviolicode">]
> paper.authors_attributes = [ {id: paper.authors.first, name: "Lucia"}, {name: "Kardeiz"} ]
> paper.authors #=> [#<Author _id: 531cd73331302ea603000000, name: "Lucia">, #<Author _id: 531cd95931302ea603010000, name: "Kardeiz">]
As you can see, I can update and add authors in the same authors_attributes hash.
For more information see Mongoid nested_attributes article
I followed the suggestion of the accepted answer for this question and implemented a reject_if guard on the accepts_nested_attributes_for statement like:
accepts_nested_attributes_for :authors, reject_if: :check_author
def check_author(attrs)
if existing = Author.where(label: attrs['label']).first
self.authors << existing
true
else
false
end
end
This still seems like a hack, but it works in Mongoid as well...

Check if data is already inserted in database in rails

I'm doing an ajax call to save data.Now the issue is that user might unknowingly submit the same data twice and it will become duplicate. Is there any shortcut or validation for abandoning the user from submitting or saving the same data twice?
Also please note that created_at,updated_at will not be same when the user submits the form for the second time. Also the only thing that can be unique in my table is ID :) .
If I have 10 form fields and until and Unless all the 10 fields of my form are same the validator will not reject the data.
You can use an attribute like 'title' and check if it's uniq for that user :
class SomeModel < AR
validates_uniqueness_of :title, :scope => [:user_id]
end
I do not think there's a reasonable solution of this problem on the server side.
Try a simple magic of javascript disable_with.

Audited audited_changes confusion

Im using collectiveidea's audited solution for auditing in rails.
So, there's a column (audited_changes) that is a TEXT definition in the database. When i retrieve an audit from the database, i get a plain string, and when i call that attribute in the view is non formated string. In the rdocs it says that theres a serialized hash of all changes. How can i get this hash? Also, in the same docs it says that there's access to old_attributes and new_attributes, how is this?
In my view:
<%= #audit.action %> # => update
<%= #audit.audited_changes %> # => --- name: - oldname - newname code: - oldcode - newcode
Or any chance on formatting this?
I think there might currently be a bug in audited. Are you using 3.0.0rc1? That is what I am using and I had something similar happen. First off, it didn't seem to recognize "Audit" as an ActiveRecord object, so I created an empty model in app/models/audit.rb. Once I did that I was seeing the behaviour you are seeing. To fix it, I removed the app/models/audit.rb and added an config/initializers/audited.rb with this in it:
include Audited::Adapters::ActiveRecord
This is an old question but I have an alternative answer that seems to be working well for me using Rails 4.2. Instead of using the initializer in the above answer I suggest keeping the model and adding "serialize :audited_changes" to the top.
class Audit < ActiveRecord::Base
belongs_to :user
serialize :audited_changes
end
You could use the built-in Audited::Audit model to query its data.
For example,
audit = Audited::Audit.last
audit.audited_changes # => {"name"=>["Steve", "Ryan"]}
"Steve" is the old value, "Ryan" is the new value. By default, the hash is stored in yaml format in the database.

Using attr_accessor and attr_accessible on the same field

What happens in the background with the following code?
class User < ActiveRecord::Base
attr_accessor :name
attr_accessible :name
end
Hint: When instantiating the class, will it be persisted to the database? Why or why not?
attr_accessor is ruby code and is used when you do not have a column in your database, but still want to show a field in your forms. The only way to allow this is to attr_accessor :fieldname and you can use this field in your View, or model, if you wanted, but mostly in your View.
attr_accessible allows you to list all the columns you want to allow Mass Assignment, as andy eluded to above. The opposite of this is attr_protected which means this field i do NOT want anyone to be allowed to Mass Assign to. More then likely it is going to be a field in your database that you don't want anyone monkeying around with. Like a status field, or the like.
In most cases, you don't need to use attr_accessor if the field is a column in the users table in your database. ActiveRecord will figure it out for you.
attr_accessible simply allows to field to be assigned via mass assignment (e.g., with update_attributes). This is good for security purposes. More information from the MassAssignmentSecurity API docs.
Thanks everyone for quick answers!
Your answers combined gave me the pieces I needed to understand this puzzle, I think.
(In a related problem, I was getting a lot of nil errors like "Object doesn’t support #inspect", and "undefined method ‘keys’ for nil:NilClass". I managed to solve it now, by removing the att_accessor field altogether.)
By experimenting with this particular case, this is what I've found out:
Actually, the :name field won't be persisted to the database.
user = User.new(:name=>"somename")
Will only set the attribute on the object, but not persist the :name column to the database. Like the following 'rails console' output shows:
> user
=> <User id: nil, created_at: nil, updated_at: nil>
> user.save
=> true
> user
=> <User id:1, created_at: 2011-01-19 12:37:21, updated_at: 2011-01-19 12:37:21>
I assume this is because *the setter made by attr_accessor will override ActiveRecord's setter* (which takes care of the database persistence). You can still retrieve the value from the :name field from the object though, like this:
> user.name
=> "somename"
So, in conclusion, I've learnt that using attr_accessor on fields might lead to them not being persisted to the database. And while I thought attr_accessible describes fields in the database that should be accessible from the outside, it doesn't seem to make a difference in this case.
Since it inherits ActiveRecord, it will be persisted when you call the save method (but not when it is instantiated).
If you don't have any attributes for that model, I assume ActiveRecord will simply save a new row in the database (i.e. your object will only have a persisted id). This makes sense, as you might later add attributes to your User model, and the persisted instances should still be retrievable.

Resources