Using before_create in rails3 - ruby-on-rails

I'm creating a tag system right now where Post has_many :tags, :through => :tag_joins
Right now when a new tag is created, a join is automatically created, connecting the tag and post the tag was created on. The problem is I'm trying to use before_create to check if a tag with the same name and user ID has already been created. If it has already been created, I'd like the join to use the original tag ID, instead of letting it create a new tag ID.
Any tips for how I can accomplish this?

Why don't you use find_or_create_by_user_id_and_tag_id() or find_or_initialize_by_.
Update
So if you want to avoid creating duplicate tags, you can just use:
#post.tags.find_or_create_by_name('tag_name')
or if you want to apply some changes before saving the new object then use
#post.tags.find_or_initialize_by_name('tag_name')
In both cases the name attribute will be set to 'tag_name' by default.
So this method will return you the tag if exists, otherwise creates it, so you can use this when you set up your join model.
Update 2
This is actually not gonna work with has_many :through, you can see a similar problem and a workaround here:
Error while using `find_or_create_by` on a `has_many` `through` association

Can't you run a private method in your model using :before_save?
If you put code like:
:before_save :method_to_check_something
...you will be able to run any manner of validation in the model without getting the controller involved (and thereby adhering to the skinny controller, fat model methodology).

This should take care of duplicate records between the Post and the Tag but not sure how your associations are set up with the User.
class Post < ActiveRecord::Base
has_many :tags, :through => :tag_joins, :uniq => true
end

Related

rails: has_many association: best practices to validate access to array

I'm pretty new to ruby and ruby-on-rails, so I need to learn best practices.
I have Tag model, each tag can have many sub-tags and many super-tags:
has_many :super_tags, :through => :tag_hier_rels, :source => :super_tag
has_many :sub_tags, :through => :reverse_tag_hier_rels, :source => :sub_tag
has_many :tag_hier_rels, :foreign_key => "sub_tag_id"
has_many :reverse_tag_hier_rels, :foreign_key => "super_tag_id", :class_name => "TagHierRel"
I need to prevent user to create circular references. But with auto-generated methods :super_tags and :sub_tags I can't do this: everyone can do something like:
tag.super_tags.push another_tag, and I have no control on this.
Rails validation mechanism isn't useful here: this mechanism prevents user to save invalid objects to database, but I need to prevent him even to modify object incorrectly: if I have circular reference, and I need to get all the sub-tags or super-tags recursively, I will run into stack overflow.
So I have done the following:
Declared these associations as private ones:
private :sub_tags=, :sub_tags
private :super_tags=, :super_tags
Added methods with _copy postfix:
def sub_tags_copy
return sub_tags.clone
end
def super_tags_copy
return super_tags.clone
end
And added methods that actualy modify arrays:
def sub_tags_push(tag)
sub_tags.push tag if !self.all_sub_tags.include? tag and !self.all_super_tags.include? tag
end
def super_tags_push(tag)
super_tags.push tag if !self.all_sub_tags.include? tag and !self.all_super_tags.include? tag
end
# TODO: more methods (at least we need to remove tags)
(methods all_sub_tags and all_super_tags generate arrays recursively)
It works, but I don't really like this solution: at least, it's not obvious for user that he should use ..._copy methods.
Probably I am doing this wrong?
UPD:
Or, is it bad practice in general to disallow user to change an object in wrong way? Probably I should allow user to change an object in wrong way, but only validate it before saving?
At least, I already figured out that it's hard to supply error messages if user does something wrong: currently, model just silently does not modify an object, and no error message is generated. I have to implement my own error messages engine, and this fact is an evidence that approach is really bad... I seem to struggle against the framework instead of using it.
You are always referring to user where you mean programmers?
If that is the case, you should try to assert this kind of stuff through
Education (developer to developer)
Validation (via tests or rails validations)
Constraints (on the database level through db constraints or triggers)
I would try to do it top down.

How to save related Models in one transaction?

I have two models:
class Customer < ActiveRecord::Base
has_many :contacts
end
class Contact < ActiveRecord::Base
belongs_to :customer
validates :customer, presence: true
end
Then, in my controller, I would expect to be able to create both in
"one" sweep:
#customer = Customer.new
#customer.contacts.build
#customer.save
This, fails (unfortunately translations are on, It translates to
something like: Contact: customer cannot be blank.)
#customer.errors.messages #=> :contacts=>["translation missing: en.activerecord.errors.models.customer.attributes.contacts.invalid"]}
When inspecting the models, indeed, #customer.contacts.first.customer
is nil. Which, somehow, makes sense, since the #customer has not
been saved, and thus has no id.
How can I build such associated models, then save/create them, so that:
No models are persisted if one is invalid,
the errors can be read out in one list, rather then combining the
error-messages from all the models,
and keep my code concise?
From rails api doc
If you are going to modify the association (rather than just read from it), then it is a good idea to set the :inverse_of option on the source association on the join model. This allows associated records to be built which will automatically create the appropriate join model records when they are saved. (See the ‘Association Join Models’ section above.)
So simply add :inverse_of to relationship declaration (has_many, belongs_to etc) will make active_record save models in the right order.
The first thing that came to my mind - just get rid of that validation.
Second thing that came to mind - save the customer first and them build the contact.
Third thing: use :inverse_of when you declare the relationship. Might help as well.
You can save newly created related models in a single database transaction but not with a single call to save method. Some ORMs (e.g. LINQToSQL and Entity Framework) can do it but ActiveRecord can't. Just use ActiveRecord::Base.transaction method to make sure that either both models are saved or none of them. More about ActiveRecord and transactions here http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html

Why Rails update_attributes is calling validation before saving

I am having a very strange problem in Rails. I am using update_attributes on a Parent Object to update all the children objects. The children objects have (custom) validation and indeed this works ok, meaning that if I give wrong values the validation trigger and I get an error back.
Now I am in a strange situation where one of the model is invalid in the database (let's not question why, let's just say I can go in the DB and run some SQL to make the model invalid). If I go in my app I can see the invalid values and this is fine. I fix the values and save again and I can see, stepping in the ruby code that the validation is called also BEFORE saving the new values, meaning that I will get an error and Rails will never execute the SQL to actually update the values to the correct ones.
I hope the above makes sense. Do you have any idea or do you think there is something I am overlooking?
SOLUTION:
What was happening was that a many-to-many relationship was validating the existing DB data before being replaced by the new data. Basically the structure was like this:
class User
has_many :user_permissions
has_many :permissions, :through => :model_permissions
class Permission
has_many :user_permissions
has_many :users, :through => :user_permissions
class UserPermission
belongs_to :user
belongs_to :permission
validates_associated :user # THIS was causing the problem
validates_associated :permission # and THIS as well
I simply removed the validates_associated directive, since I am validating the linked records independently anyway.
Well, Rails does run your validation prior to writing the data to the database (please refer to Active Record callback sequence), so having validation errors means that some piece of the model you are trying to save is not valid. It might an associated model containing errors with the validation turned on or just some missing part – in any case, just have a look at what are the errors you are getting.
In case (let's no question why either :) you want to skip validation - you are open to choose from #update_attribute (to update just one attribute), calling #save(false), using +udpate_all method of the model class or even go down to ActiveRecord::Base.connection.execute – none of these will ever bother you with validation errors :)

How to intercept accepts_nested_attributes_for?

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 ;-)

How do I implement this code without needing to access current_user.id from a model in RoR?

I have a note model, with the following association
note.rb
has_many :note_categories, :dependent => :destroy
has_many :categories, :through => :note_categories
The NoteCategory model was created to function as a join table between notes and categories.
I need to implement the following:
A user removes a category from a note. This can be done by either removing one category from a note (deletes one entry in the note_categories table), or by deleting the note entirely (deletes all entries in the note_categories table relating to the note)
Before the row/s in note_categories is/are deleted, I need to determine if the user who is deleting the category from a note is the same user who initially created the category (creator field in the category model)
If it is the same user, the category entry itself is to be deleted
Obviously to do this, I need to access the id of the user, to check against the creator field of the Category. I am already using a before_destroy method in the NoteCategory model to do some other things, but I can't access current_user.id in there because it's a model, and current_user is a method in the Application Controller. From the questions I've read here on SO, it seems that accessing the id of the current user from a model is bad form.
I don't think I can use the controller in this circumstance because when a note is deleted, the :dependent => :destroy line means that the associated rows in note_categories are deleted as well. I need to do the creator check in this situation as well, but the note_categories rows are removed via the destroy method in the model, not the controller, which is the behavior specified by :dependent => :destroy.
So how should I go about doing it? Thanks for reading!
One way of doing this could be to add an attr_accessor to the Note model like so:
# in Note.rb
attr_accessor :destroyed_by
and set it before destroying the record:
# NotesController#destroy
#note.destroyed_by = current_user.id
#note.destroy
Then in your Note.rb before_destroy call, you can check the Category's creator id against the destroyed_by id.
You say
Before the row/s in note_categories is/are deleted, I need to determine if the user who is deleting the category from a note is the same user who initially created the category (creator field in the category model)
If it is the same user, the category entry itself is to be deleted
Are you sure you wan't to do such a thing? What if a user has used the same category for many notes, and he wants to delete it form one of them? You sure would want to delete an entry from note_categories, but should you also delete the category itself?
A common implementation for such a scenario is to check while deleting a note_category (through perhaps a before_destroy) whether this one is the last note_categories for the category, and delete it if it is. This also means that if a note is deleted, only the related note_categories should be deleted, and not the categories themselves.
I think that you want to maintain a log of who created the post and who is deleting it. The before_destroy method does the part of deleting associations and I think it is working fine for you. as of maintaining record of whether the user who created the note is deleting the note or not comes under logging part. I hope this helps you around this problem
http://rohitsharma9889.wordpress.com/2010/08/19/logging-in-ruby-on-rails/
EDIT:
You can also try reading this article. I would recommend you to prefer this one on the above one
Environment variables in Model

Resources