Associated record is not deleted in Rails - ruby-on-rails

I am trying to associate a set of uploaded pictures with a post. In my controller I am using the following code:
image_uploads = params[:image_uploads]
iu = ImageUpload.find(image_uploads)
#post.image_uploads = iu
While this does make the uploaded images accessible from #post.image_uploads, I think it doesn't associate these images with the post because when the post is deleted, the image uploads are not deleted -- even though I have used :dependent=>:destroy for their relationships.
> Post.first.delete
=>[]
> ImageUpload.all
=> [#<ImageUpload id: 3 ...>]
And this is the model:
class Post < ActiveRecord::Base
has_many :image_uploads, :dependent => :destroy
end
class ImageUploads < ActiveRecord::Base
belongs_to :post
end
What can I do to make sure that cascading deletes work?

Try instead:
Post.first.destroy
destroy invokes the callbacks defined, whereas delete deletes the record directly using SQL without creating an AR object.

What you do seems correct to me. You have a post with image uploads dependent on the post and you should get them deleted when a post is deleted. I could be wrong but i do not see anything.
However, i see something that could cause heavy problems and it could also be the source of your problem(it's a source of many).
You have a model that ends with an S. Not a good idea. Do yourself a favor and change to ImageUpload please :)

Related

Best way to update (create & delete) multiple associations (has_many :through)

I have what i feel could be a simple question, and i have this working, but my solution doesn't feel like the "Rails" way of doing this. I'm hoping for some insight on if there is a more acceptable way to achieve these results, rather than the way i would currently approach this, which feels kind of ugly.
So, lets say i have a simple has_many :through setup.
# Catalog.rb
class Catalog < ActiveRecord::Base
has_many :catalog_products
has_many :products, through: :catalog_products
end
# Products.rb
class Product < ActiveRecord::Base
has_many :catalog_products
has_many :catalogs, through: :catalog_products
end
# CatalogProduct.rb
class CatalogProduct < ActiveRecord::Base
belongs_to :catalog
belongs_to :product
end
The data of Catalog and the data of Product should be considered independent of each other except for the fact that they are being associated to each other.
Now, let's say that for Catalog, i have a form with a list of all Products, in say a multi-check form on the front end, and i need to be able to check/uncheck which products are associated with a particular catalog. On the form field end, i would return a param that is an array of all of the checked products.
The question is: what is the most accepted way to now create/delete the catalog_product records so that unchecked products get deleted, newly checked products get created, and unchanged products get left alone?
My current solution would be something like this:
#Catalog.rb
...
def update_linked_products(updated_product_ids)
current_product_ids = catalog_products.collect{|p| p.product_id}
removed_products = (current_product_ids - updated_product_ids)
added_products = (updated_product_ids - current_product_ids)
catalog_products.where(catalog_id: self.id, product_id: removed_products).destroy_all
added_products.each do |prod|
catalog_products.create(product_id: prod)
end
end
...
This, of course, does a comparison between the current associations, figures out which records need to be deleted, and which need to be created, and then performs the deletions and creations.
It works fine, but if i need to do something similar for a different set of models/associations, i feel like this gets even uglier and less DRY every time it's implemented.
Now, i hope this is not the best way to do this (ignoring the quality of the code in my example, but simply what it is trying to achieve), and i feel that there must be a better "Rails" way of achieving this same result.
Take a look at this https://guides.rubyonrails.org/association_basics.html#methods-added-by-has-many-collection-objects
You don't have to remove and create manually each object.
If you have already the product_ids array, I think this should work:
#Catalog.rb
...
def update_linked_products(updated_product_ids)
selected_products = Product.where(id: updated_product_ids)
products = selected_products
end
...
First,
has_many :products, through: :catalog_products
generate some methods for you like product_ids, check this under auto-generated methods to know more about the other generated methods.
so we don't need this line:
current_product_ids = catalog_products.collect{|p| p.product_id}
# exist in both arrays are going to be removed
will_be_removed_ids = updated_product_ids & product_ids
# what's in updated an not in original, will be appended
will_be_added_ids = updated_product_ids - product_ids
Then, using <<, and destroy methods which are also generated from the association (it gives you the ability to deal with Relations as if they are arrays), we are going to destroy the will_be_removed_ids, and append the will_be_added_ids, and the unchanged will not be affected.
Final version:
def update_linked_products(updated_product_ids)
products.destroy(updated_product_ids & product_ids)
products << updated_product_ids - product_ids
end

where constraint on a related record

I'm not getting a concept (nothing new there) on how to scope a Active Record query. I want to only receive the records where there is a certain condition in a related record. The example I have happens to be polymorphic just in case that is a factor. I'm sure there is somewhere where this is explained but I have not found it for whatever reason.
My Models:
class User < ActiveRecord::Base
belongs_to :owner, polymorphic: true
end
class Member < ActiveRecord::Base
has_one :user, as: :owner
end
I want to basically run a where on the Member class for related records that have a certain owner_id/owner_type.
Lets say we have 5 Members with ids 1-5 and we have one user with the owner_id set to 3 and the owner_type set to 'Member'. I want to only receive back the one Member object with id 3. I'm trying to run this in Pundit and thus why I'm not just going at it form the User side.
Thanks for any help as always!!!
Based on your comment that you said was close I'd say you should be able to do:
Member.joins(:user).where('users.id = ?', current_user.id)
However based on how I'm reading your question I would say you want to do:
Member.joins(:user).where('users.owner_id = ?', current_user.id)
Assuming current_user.id is 3.
There may be a cleaner way to do this, but that's the syntax I usually use. If these aren't right, try being a little more clear in your question and we can go from there! :)

Limit number of images - Paperclip

I have User model with the following association:
class User < ActiveRecord
has_many :pictures, :as => :imageable
accepts_nested_attributes :pictures
end
My Picture model looks like this:
class Picture < ActiveRecord
belongs_to :imageable, :polymorphic => true
has_attached_file :image
end
Now, I want to user to be able to upload maximum 5 images. And he will select 1 image as his avatar. Now, user can upload images but I don't know how to limit the maximum number of pictures. One more thing, user needs to be able to change his avatar image. How can I achieve this?
In my view, I use input file with name user[picture_attributes][0][image] in order to allow user to change the first picture but it keeps inserting new pictures into database instead of replacing the first picture.
Please help me on this.
Thanks in advance
For the first part of the problem you have, i would suggest you use rails built-in counter_cache method.
Your picture model would thus become:
class Picture < ActiveRecord::Base
belongs_to :imageable, :polymorphic => true, counter_cache: true
has_attached_file :image
end
Also you would need to add a column called pictures_count to the User model.
This way in your controller you could check if the count is upto 5 records and therefore inform them that they have uploaded the maximum allowed.
if #user.pictures.size == 5 #sorry no more uploads
For the second part of the problem. Is the form action pointed to the new/create action or to your update action. If pointed to the new action a new record would be created but if pointed to the update action then it should change the first image record as you expect.
#charinten For the second part of the problem, some suggestions:
You could try making the id of the pictures accessible in the user model. This way when you try to point to the image to use as an avatar, rails uses that id to update the record. If you try to update the record without pointing rails to that id, it would assume you are trying to create a new record.
Also rather than using user[picture_attributes][0][image] you could in your user profile. Find a specific image and point to that image on the edit action.
Hope this helps

Using before_create in rails3

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

Rails has_many_polymorphs in reverse?

So I have some models set up that can each have a comment. I have it set up using has_many_polymorphs, but I'm starting to run into some issues where it's not working how I think it should.
For example:
class Project < ActiveRecord::Base
end
class Message < ActiveRecord::Base
has_many_polymorphs :consumers,
:from => [:projects, :messages],
:through => :message_consumers,
:as => :comment # Self-referential associations have to rename the non-polymorphic key
end
class MessageConsumer < ActiveRecord::Base
# Self-referential associations have to rename the non-polymorphic key
belongs_to :comment, :foreign_key => 'comment_id', :class_name => 'Message'
belongs_to :consumer, :polymorphic => true
end
In this case, the Message wouldn't get deleted when the Project is removed, because the Message is really the parent in the relationship.
I simplified it a little for the example, but there are other models that have have a Message, and there are also Attachments that work similarly.
What would be the correct way to set this up so that the children get removed when the parent is deleted? I'm hoping to not have a million tables, but I can't quite figure out another way to do this.
When you say "so that the children get removed when the parent is deleted?", can you give an example? I.e. when a project is deleted I want all its messages to be deleted too? What happens when you delete a message, do you want anything else (e.g. all corresponding message_consumer entries) to be deleted as well?
UPDATE
OK, so has_many_polymorphs will automatically delete "orphaned" message_consumers. Your problem is that a message may have more than one consumer, so deleting a project may not be sufficient grounds for deleting all its associated messages (as other consumers may depend on those messages.)
In this particular case you can set up an after_destroy callback in MessageConsumer, to check whether there still exist other MessageConsumer mappings (other than self) that reference the Message and, if none exist, also delete the message, e.g.:
class MessageConsumer < ActiveRecord::Base
...
after_destroy :delete_orphaned_messages
def delete_orphaned_messages
if MessageConsumer.find(:first, :conditions => [ 'comment_id = ?', self.comment_id] ).empty?
self.comment.delete
end
end
end
All this is happening inside a transaction, so either all deletes succeed or none succeed.
You should only be aware of potential race conditions whereby one session would arrive at the conclusion that a Message is no longer used, whereas another one may be in the process of creating a new MessageConsumer for that exact same Message. This can be enforced by referential integrity at the DB level (add foreign key constraints, which will make on of the two sessions fail, and will keep your database in a consistent state), or locking (ugh!)
You could simplify this a lot by using acts_as_commentable.

Resources