Hello i have a rails app that handles sales, right now, what i need is to be able to delete the sale in order to keep accounting clear, but log somewhere else, the details of that record.
I am thinking i may need to create a logger, but have no idea how, or maybe another object who gets created on the destroy of the sale.
Thanks in advance
Just an idea - you could add a column to your current table that would act as a "deleted" flag (I've always called this a logical delete). Then you could add a default scope to filter out "deleted" records and add some named scopes that would include the "deleted" records when you need them.
acts as paranoid is a plugin that will handle this for you, but if there's anything about it you don't like you can roll your own version like Andy suggested, maybe with a deleted_at timestamp. You might try overriding the destroy action on the model - I haven't tried it myself, but something like this should work:
class Sale < ActiveRecord::Base
def destroy
update_attributes(:deleted_at => Time.now)
end
end
Like you said, you could create another object/model that you create a new instance of each time a Sale is deleted. Name it SaleRecord or SaleHistory... something like that. Then you can retrieve these records and do whatever. Then, a nice use case would be to look up sales records for a particular product to calculate statistics on how popular it was...
i ended up creating a delete_sale object, and then on my sale observer i created and populated with the data of the sale just before it was destroyed.
#delsale = DeletedSale.create
#last_deleted_sale = DeletedSale.find(:last)
ActiveRecord::Base.connection.execute "UPDATE deleted_sales SET name = #{#venta.id} WHERE id = #{#last_deleted_sale.id};"
#delsale.update_attributes :cliente => #venta.cliente.name
#delsale.update_attributes :vendedor => #venta.vendedor.name
#delsale.update_attributes :instalador => #venta.instalador.name
#delsale.update_attributes :precio_de_venta => #venta.precio_de_venta
#delsale.update_attributes :precio_de_instalacion => #venta.precio_de_instalacion
if #venta.producto_id?
#delsale.update_attributes :producto_id => #venta.producto_id
end
if #venta.cart_id?
#delsale.update_attributes :cart_id => #venta.cart_id
end
Related
I have a Ratings model where a user can rate an object... After a rating takes place I want to then automatically create a RatingLog record so the user can see a list of all the ratings.
Models: Rating.rb and RatingLog.rb
I see RatingLog including a list of events other than just Ratings, like Badges etc... VERY similar to StackOverflow.
What is the right way with Rails 5, to populate RatingLog.rb after a Rating is created... Should I use after_create in Rating.rb? Should I create some other type of callback? Would love to hear what is the right way to implement the above in Rails 5.
Thank you
I assume that you have models:
class Rating
has_one :rating_blogs
end
class RatingBlog
belongs_to :rating
end
so, to create a rating blog afer rating is created, you can do:
#rating = Rating.new
#rating.build_rating_blog(some_message: 'Message')
#rating.save
So when #rating is created, a rating blog will be created and associated with #rating.
I don't know if this is considered okay... But I do this right inside of the create action, in your case I'll guess:
RatingController
def create
...
#ratinglog = RatingLog.create
#ratinglog.user = current_user
#ratinglog.rating = #rating.id
#ratinglog.comment_type = "server generated"
#ratinglog.comment = "#{current_user.fname} #{current_user.lname} submitted a rating of #{#rating.content}"
#ratinglog.save!
...
end
Another option is an after_commit callback which will only run when the Rating object is saved.
after_create will work too, but if you have an error when you try to save your RatingLog then the Rating record you created will also not save (checkout this answer).
So depending on the behavior you want, choose one:
after_commit RatingLog errors don't effect Rating saves
after_create RatingLog errors rollback Rating saves
Also:
It's not totally clear from your post, but it sounds like Rating and RatingLog are holding very similar data?? or if RatingLog will hold logs of other events, maybe name it something else.
Check out this answer on naming conventions -- the convention for model file naming is rating.rb and rating_log.rb
Ok, We f&^%$**&ed up.
We lost a bunch of user records. At some point, an integration file ran which re-inserted some of the lost records.
The problem is that the new users have a different ID than the original user, so all the existing related content for the old User id has been orphaned. I now need to go back in and reassociate all the orphaned stuff to the new User id. It won't be enough to simply give the new user the old Id from backup, because there will be new content associated to the new User Id.
We know the reflect_on_all_associations method, but that is hard to use for finding stuff. However, this could be a starting point for a script of some kind.
Any clues on how to have a method return all models related to a particular model based on associations, without having to specify or know those associations?
Here's a way to use reflect_all_associations: You can iterate through the associations, select only the has_many and has_one, and then update those records. Here's a helper class to do the job which you can execute by calling AssociationFixer.new(user_to_destroy, original_user_id).fix_associations:
class AssociationFixer
USER_ASSOCIATIONS = User.reflect_on_all_associations
def initialize(user_to_destroy, original_user_id)
#user_to_destroy = user_to_destroy
#original_user_id = original_user_id
end
def fix_associations
USER_ASSOCIATIONS.each do |association|
next if association.options.has_key? :through
if association.macro == :has_many
fix_has_many(association)
elsif association.macro == :has_one
fix_has_one(association)
end
end
end
def fix_has_many(association)
#user_to_destroy.send(association.name).each do |record|
if association.options.has_key? :foreign_key
record.send(assignment_method(association.foreign_key), #original_user_id)
else
record.user_id = #original_user_id
end
record.save
end
end
def fix_has_one(association)
if association.options.has_key? :foreign_key
#user_to_destroy.send(association.name).send(assignment_method(association.foreign_key), #original_user_id)
else
#user_to_destroy.send(assignment_method(association.name.user_id), #original_user_id)
end
record.save
end
def assigment_method(method_name)
method_name.to_s + '='
end
end
This sounds like a problem for SQL. I would look at importing your backup tables into the database in a separate table, if it's possible to join them on a column, maybe email or user_name or something. Then you can run a select based on the old id and update to the new id. Best of luck!
Suppose I deleted a document or subdocument in mongodb. Can I create document / subdocument with the same _id as the deleted one? In this case, we assume, we cannot do update operation, just delete and create.
For example using Mongoid (Rails gem for mongodb) :
We have Person Model
class Person
include Mongoid::Document
field :a, :type => String
embeds_many :personattributes
end
class Personattribute
include Mongoid::Document
field :myattribute, :type => String
embedded_in :person
end
And in my Rails controller
class MyController < ApplicationController
...
#the_attributes=#person.personattributes.entries
...
#controller will render page, an instance variable #the_attributes will be available as JSON in clientside
end
Then user does some client side data modifications. They can add 1 or more personattributes to that person data. They can do some changes on its attributes. They can delete some also.
All in client side.
Then by AJAX call, user sends the modified data back in JSON format like
[{_id:"5253fd494db79bb271000009",myattribute:"test"},{...},...]
The retriever in controller retrieves the data
Then totally replace the attribute list inside person with the new one. Total deletion and insertion, no update.
class MyController < ApplicationController
...
#person.personattributes.delete_all #delete all attributes a #person has
attributes=params[:attributes]
attributes.map {|attr|
Personattribute.new(:_id => Moped::BSON::ObjectId.from_string(attr["_id"].to_s), :myattribute => attr["myattribute"])
}
#person.personattributes=attributes
#person.save
...
end
Can I do this? It simply means, delete all, and insert all and reuse the _ids.
If not, I will be happy to get some advice on a better approach on this.
I can't do upsert since the deleted documents will need another loop to handle.
Thank you
Yes, you can do it but I would recommend you not to do that. It seems to have lots of security issues if someone modifies the array manually
I could send:
[{_id:"5253fd494db79bb271000009",myattribute:"test_modified"},{...},...]
or even:
[{_id:"my_new_id_1",myattribute:"test_modified"},{...},...]
which would raise an exception
Moped::BSON::ObjectId.from_string "my_new_id_1" #=> raises an Exception
Try something like:
attributes=params[:attributes]
attributes.each do |attr|
#person.personattributes.find(attr["_id"]).myattribute = attr["myattribute"]
#or #person.personattributes.find(attr["_id"]).try(:myattribute=,attr["myattribute"])
end
Probably in a future you want to change the action and send just the modified personattributes in the array instead of all the personattributes. What would you do then if you delete_all and rebuild personattributes with just the sent personattributes?
EDIT
This handles personattributes updates. Create or delete personattributes should go in different actions:
Create action
#person.personattributes.push Personattribute.new(my_attribute: params[:my_attribute])
Delete action
#person.personattributes.delete(params[:personattribute_id])
Yes, you can keep using the same _id. They just need to be unique within a collection -- and that's only true for the document's _id.
Any ObjectId you might use elsewhere in an another field in a document (or in a subdocument) doesn't need to be unique, unless you've created an index where it must be unique.
Before everything i would like to thank you for your help
I have a model like this:
attr_protected nil
belongs_to :product
belongs_to :user
before_create :add_ammount
def carted_product_price(ammount, price)
ammount * price
end
def add_ammount
carted_product = CartedProduct.where(:product_id => self.product_id, :user_id => self.user_id)
if carted_product
carted_product.first.ammount += self.ammount
carted_product.first.update_attributes(:ammount => carted_product.first.ammount)
else
self.save
end
end
it saves buying orders in a table called Carted_Products connected to Users and Products in the belogings
the problem is that when the Before create executes i want it to update the record in the table adding the ammount passed by the controller if the record already exists and if not, create one, as far as iv done, it updates the ammount but STILL CREATES A NEW one with the passed params in the order, i want it only to update, not to do both actions when the record is found
thnx for your patience
EDIT:
Tried returning false after the update attributes, it cancels the filter, and dont create or update attributes
Return false in the before_create filter to prevent the object form being saved. add_amount is not responsible for saving the object, and shouldn't call save by itself.
You cannot do this in before_create filter. You need to fetch existing CartedProduct in controller where you're calling create.
I have a Rails application, with two models: SalesTransactions and PurchaseOrders.
In the PurchaseOrders model, new entries are registered using 'purchase_order_number' as the key field. I use the create method of the model to search if that 'purchase_order_number' has been previously registered, and if so, reuse that record and use its id in the SalesTransaction record. If that name wasn't already registered, I go ahead and perform the create, and then use the new PurchaseOrder record id in the SalesTransaction (the foreign_id linking to the associated PO).
Note that I don't have the existing PurchaseOrder record id until I've done a look-up in the create method (so this is not a question of 'how do I update a record using 'accepts_nested_attributes_for'?', I can do that once I have the id).
In some situations, my application records a new SalesTransaction, and creates a new PurchaseOrder at the same time. It uses accepts_nested_attributes_for to create the PurchaseOrder record.
The problem appears to be that when using 'accepts_nested_attributes_for', create is not called and so my model does not have the opportunity to intercept the create, and look-up if the 'purchase_order_number' has already been registered and handle that case.
I'd appreciate suggestions as to how to intercept 'accepts_nested_attributes_for' creations to allow some pre-processing (i.e. look up if the PurchaseOrder record with that number already exists, and if so, use it).
Not all Sales have a PurchaseOrder, so the PurchaseOrder record is optional within a SalesTransaction.
(I've seen a kludge involving :reject_if, but that does not allow me to add the existing record id as the foreign_id within the parent record.)
Thanks.
You could use validate and save callbacks to do what you need.
Assuming the setup:
class SalesTransaction < ActiveRecord::Base
belongs_to :purchase_order, :foreign_key => "po_purchase_order_no",
:primary_key => "purchase_order_no"
accepts_nested_attributes_for :purchase_order
end
class PurchaseOrder < ActiveRecord::Base
has_many :sales_transactions, :foreign_key => "po_purchase_order_no",
:primary_key => "purchase_order_no"
before_validation :check_for_exisitng_po # maybe only on create?
accepts_nested_attributes_for :sales_transactions
private
def check_for_exisitng_po
existing_po = PurchaseOrder.find_by_purchase_order_no(self.purchase_order_no)
if existing_po
self.id = existing_po.id
self.reload # don't like this, also will overwrite incoming attrs
#new_record = false # tell AR this is not a new record
end
true
end
end
This should give back full use of accepts_nested_attributes_for again.
gist w/tests
Two ideas: Have you taken a look at association callbacks? Perhaps you can "intercept" accepts_nested_attributes_for at this level, using :before_add to check if it is already in the DB before creating a new record.
The other idea is to post-process instead. In an after_save/update you could look up all of the records with the name (that ought to be unique), and if there's more than one then merge them.
I was going to write a before_save function, but you say this:
It uses accepts_nested_attributes_for to create the PurchaseOrder record.
So in the SalesTransaction process flow, why look it up at all? You should just get the next one available... there shouldn't be a reason to search for something that didn't exist until NOW.
OK, I've left this question out there for a while, and offered a bounty, but I've not got the answer I'm looking for (though I certainly appreciate folk trying to help).
I'm concluding that I wasn't missing some trick and, at the time of writing, there isn't a neat solution, only work-arounds.
As such, I'm going to rewrite my App to avoid using accept_nested_attributes_for, and post the SalesTransaction and the PurchaseOrder records separately, so the create code can be applied in both cases.
A shame, as accept_nested... is pretty cool otherwise, but it's not complete enough in this case.
I still love Rails ;-)