Rails model associations and destroy method - ruby-on-rails

I need some help to solve my problem.
I'm working on a project where I develop the manager's functions of the application. My problem is related with two Destroy functions and how they related with themselves:
Simplifying, I have 3 models (Test, Application and JoinTestJob) described below in code sample.
I want the manager be able to destroy a JoinTestJob's object only if this is related with any Application's objects. I need consistency in Application's objects. That is the reason I created a before_destroy method in JoinTestJob model. Until here is working fine.
I also want the manager be able to destroy a Test's object, and with that, the associated objects should be destroy too, as declared in Test model. So here start the problem:
When I delete a Test's object I can see in log that all Application's objects associated are deleted first. After that, should be JoinTestJob's objects to be deleted, but I got a rollback. I know the reason of the rollback is the before_destroy method in JoinTestJob (I can see it in log). Apparently, this method still can find Application's objects, even though I saw in log that they were deleted. In this case, it seems they really would be deleted after the transaction is done.
So, how can I accomplish to having those two features working?
Model:
class Test < ApplicationRecord
has_many :applications, :dependent => :destroy
has_many :persons, through: :applications
has_many :join_test_jobs, :dependent => :destroy
has_many :jobs, through: :join_test_jobs
class Application < ApplicationRecord
belongs_to :join_test_job
belongs_to :person
class JoinTestJob < ApplicationRecord
belongs_to :test
belongs_to :job
before_destroy :check_relation_between_application_and_join_test_job
def check_relation_between_application_and_join_test_job
if Application.find_by(join_test_job_id: "#{self.id}")
self.errors.add(:base, "It's not possible to delete this item, because there are some applications related with. Please, delete them first.")
throw(:abort)
end
end
Edit 1:
About the log requested in comments, it's really basic. I will ask to you guys ignore some details in the image. I had to translate the problem to English, so there are some Portuguese words in the image. What is important to observe in the log:
All Application's objects related to Test are deleted first. Then, when rails started to look up for JoinTestJob's objects you can see the rollback. Before the rollback, there is a last request from Application trigged by the before_destroy method, as you can see in Log's image. The throw(:abort) method is called in this last request.
Word's Translation:
Application/applications => Inscricao/inscricaos
JoinTestJob/join_test_jobs => JoinConcursoCargo/join_concurso_cargos
To clarify things, DadosPessoalCandidato and DadosPortadorDeficiencia are models associated with Application and they hold personal informations about who is applying for, like address and phone. They can be ignored.
Edit 2:
Adding more information about what represent the models, so you guys can understand the problem better.
Test model: It's like a Civil service exam, where people would apply for getting a job. Who gets the best score in the exam would be selected for the job. This model hold information as: when this exam will happen, documents related with the exam's rules, when the result of the exam will be release and etc.
JoinTestJob model: It joins the concept of a Job and the Test that are offering these jobs. Here, we could find information as: specificity of the job, like the period of work, how much hours of work per day, salary and etc.
Application model: This model hold information related with the person who is applying for the job and the Test that are offering the job. So, we could find here information as: what job the person apply for, how much he/she payed for applying, the date of the Test (exam) will be happening, personal informations as age, name, address. A person could apply for more than one job for the same Test.
I hope I can help you guys with these new informations.

I think you should change your models:
class Test < ApplicationRecord
has_many :applications, :dependent => :destroy
class Application < ApplicationRecord
has_one :join_test_job, dependent: :destroy
class JoinTestJob < ApplicationRecord
belongs_to :application
validates :application, presence: true
so, if a Test is destroyed, then applications relates with destroyed test will be destroyed, and then JoinTestJob relates with destroyed applications are also destroyed.
I assume JoinTestJob model is stored in join_test_jobs model and have application_id.
You can make field store application_id is NOT NULL at Database level, and add validates :application, presence: true code make sure that a JoinTestJob always has one related application.

Related

Is it really necessary to override the `*_type=` method for ActiveRecord Polymorphic Association with Single Table Inheritance?

I'm refactoring some piece of code that deals with polymorphic belongs_to association that can receive a model that implements Single Table Inheritance. The current code follows an advice from ActiveRecord documentation here, namely that it is best to override attachable_type= so that the base class name of the STI model gets stored in the attachable_type field:
class Asset < ActiveRecord::Base
belongs_to :attachable, polymorphic: true
def attachable_type=(class_name)
super(class_name.constantize.base_class.to_s)
end
end
class Post < ActiveRecord::Base
# because we store "Post" in attachable_type now dependent: :destroy will work
has_many :assets, as: :attachable, dependent: :destroy
end
class GuestPost < Post
end
class MemberPost < Post
end
I understand that we want to store that base class so that ActiveRecord works as expected. However, when I tested a few scenarios including the scenario in the documentation, it seems that overriding attachable_type= is unnecessary. ActiveRecord seems to already handle this case and always stores the base model Post rather than GuestPost or MemberPost.
I created a repo that implements the models in the documentation and allows you to play with some data and see for yourself. I cannot reproduce the issue that the documentation attempts to circumvent all the way up to ActiveRecord version 3.2.22.5 (I haven't tried beyond that).
Does anyone know if there is truly a need to override attachable_type=? Is the documentation out of date? Or is it just solving the use case of directly running Asset.new attachable_type: 'MemberPost', attachable_id: member_post.id instead of Asset.new attachable: member_post?
The reason why this matters to me is that we have 5 models that could be affected by this. Our current code implements overriding 3 of those, and I'm refactoring to DRY it up and potentially introduce it to the additional 2 other models in which we forgot to follow this advice.
OK, I believe I better understand the reasoning behind it. This is an issue when the model needs to be created or updated from a form.
In such scenario, attachable_type and attachable_id are sent to the controller, which typically passes-through this data to the model. Without overriding attachable_type= the model will end up having attachable_type be one of the child classes GuestPost or MemberPost.
This in turn causes a range of issues. For example, assets won't be destroyed when the owner of the asset is destroyed even when dependent: :destroy is specified on the has_many association.
Therefore, if your model is expected to receive data for the polymorphic association field via a form, you should override this method.
I updated the repo to demonstrate this issue.

Persist array from before_save callback for ActiveModel::Dirty like change tracking on has_many through relationship

Firstly, apologies for the snappy question title! It does however sum up what I am trying to do.
I've been using ActiveModel::Dirty successfully to create a kind of audit trail on various model attributes within my app (like on Product below).
I've now have a fairly pressing request to be able to track the changes (additions & deletions in this case) on an associated has_many through relationship.
The models in question are:
class Product < ActiveRecord::Base
has_many :products_territories, :dependent => :destroy
has_many :territories, :through => :products_territories
end
class Territory < ActiveRecord::Base
has_many :products_territories
has_many :products, :through => :products_territories
end
class ProductsTerritory < ActiveRecord::Base
belongs_to :territory
belongs_to :product
end
I've failed with using ActiveModel::Dirty, it doesn't seem possible, so am trying my own thing which on the surface is quite simple; grab an array of a product's products_territories before_save and then again after_save and then perform comparisons on the two arrays to identify the additions and deletions. What I can't get my head around is the best way to persist the array of products_territories from the before save so it's then available to my after_save callback. I'm pretty certain ## class variables aren't the way to go and i'm also not so sure about session variables. I'm wondering whether something like Redis or Memchached is what I should be looking at?
Can anyone that's had to do something similar to this give me any pointers or direct me to some further reading please?
Thanks in advance.

Rails: How to design a data structure that helps users do reporting?

My Rails app has a User model and an Idea model. Each user can have many ideas, and each idea belongs to one user (or none). Users take ownership of an idea using the Idea controller's claim action, and release ideas using the release action.
So far, really simple. But I've just realized I want this to do a bit more, and now I'm not sure how to proceed: Basically, I want users to be able to "report" ideas for being bad/invalid ideas, along with a note specifying the reason for the report.
I would also like to keep track of metrics regarding to claiming ideas -- how long an idea was claimed for before being marked as submitted (it's for managing blog posts), and so on.
In an unrelated question, someone suggested I need a Claim model in my app for this sort of complex tracking. This makes sense to me, but I'm really not sure how that model would be structured, and how it would work. Would it be like a log? If so, how would I figure out what's the current idea status for each idea?
Any insights on this would be most welcome.
I think, since you need a log of all user actions its better if you keep Reports and Claims independent.
So, it might look along the lines of
User has_many Ideas
Idea has_many Claims
Idea has_many Reports
Code:
class User < ActiveRecord::Base
attr_accessible :name
has_many :idea
end
class Idea < ActiveRecord::Base
attr_accessible :desc, :title
belongs_to :user
has_many :claim
has_many :report
end
class Claim < ActiveRecord::Base
attr_accessible :status
belongs_to :idea
end
class Report < ActiveRecord::Base
attr_accessible :reason
belongs_to :idea
end
And since, rails migration would add columns for time-stamps. you would be able to calculate the time differences between change of status etc.
I dont know ruby but as for the design,
Does reporting an idea as invalid disqualify it?
If not, you can have a Report model in which you include the reported ideas with the Notes. While representing you can have a look at it before rendering the post.

Rails checkboxes for a has_many :through association

A person can compete in various events, but they must enter a partner's name for that event. This association is stored in an entry, which contains a field for the partner's name.
class Person < ActiveRecord::Base
has_many :entries
has_many :events, :through => :entries
validates_presence_of :name
end
class Event < ActiveRecord::Base
end
class Entry < ActiveRecord::Base
belongs_to :person
belongs_to :event
validates_presence_of :partner_name
end
The question is: How do you create a single page form that allows a person to enter themselves in multiple events and input their partners' names? I've tried to implement an all_entries method in the person model that will return an array of entry objects for all the available events, and an all_entries_attributes method that will update, create, and delete entry objects, but I can't seem to find a good, clean way to do this. I know this is a rather open ended question, but this must be a pattern that someone else in the rails community has encountered before, so I'm hoping there is a good solution to it.
If you are still looking for the answer you might want to check out my question and answer that I found.
So you want a page in which you can create events for a user, and add people to those events
I won't give you a plain solution because there's some work to an implementation for this, but I will recommend checking out these railscasts about nested model forms
part1 and part2

ActiveRecord relationships between HAVE and IS

So I have the following models in my Ruby on Rails setup: users and courses
The courses need to have content_managers and those content_managers are made up of several individuals in the users model.
I'm a newbie, so bear with me. I was thinking of creating a new model called content_managers that has a user_id and a course_id that links the two tables. It makes sense to me that courses HAVE content_managers. However from the users model, it doesn't make sense that users HAVE content_managers. Some of them ARE content_managers.
From that point of view I believe I'm thinking about it incorrectly and need to set up my ActiveRecord in a different manner from what I'm envisioning. Any help is appreciated.
Thanks!
There's no "have" or "are" in ActiveRecord, only "has_many", "has_one" and "belongs_to". With those tools you should be able to do what you want.
An example:
class Course < ActiveRecord::Base
has_many :content_managers
end
class ContentManager < ActiveRecord::Base
has_many :content_manager_members
has_many :users,
:through => :content_manager_members,
:source => :user
end
class ContentManagerMember < ActiveRecord::Base
belongs_to :course_manager
belongs_to :user
end
class User < ActiveRecord::Base
has_many :content_manager_members
has_many :content_managers,
:through => :content_manager_members
end
Be sure to index these correctly and you should be fine, though navigating from User to Course will be slow. You may need to cache some of this in order to find the level of performance you want, but that's a separate issue that will be uncovered during testing.
Whenever implementing something like this, be sure to load it up with a sufficient amount of test data that will represent about 10x the anticipated usage level to know where the ceiling is. Some structures perform very well only at trivial dataset sizes, but melt down when exposed to real-world conditions.

Resources