Is it un-RESTful/un-Ruby/bad practice to destroy objects in ruby on rails by doing a ".destroy" on an active record outside it's controller? For instance, when a user is destroyed I call destroy on all their posts from the User controller.
I would say yes, it is bad practice to do this. But mostly because there is a better way that rails has implemented for you. Like MrYoshiji pointed out in his comment, you should create an association between the user and a post.
class Post < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :posts, dependent: :destroy
end
Then when you delete a user, rails will automagically handle the destroys of the posts.
ActiveRecord
In reference to jkeuhlen's answer, I would not say it's outright bad practice to destroy an ActiveRecord object from a different controller, in principle.
In your case, it would not be very efficient to do it your way, but it wouldn't be against convention in my opinion:
As I understand, the MVC programming pattern basically means that if you call a controller, its primary function is to build a set of data depending on the users' input. This may, or may not, involve the controller's corresponding model
Although against the Resourceful principle directly, I don't see why it would be a problem to destroy data depending on various controller actions - if it doesn't involve the model, surely you'd be able to destroy all the relevant data from that controller?
--
Rails
As pointed out by jkeuhlen, Rails comes with a dependent: :destroy method to remove associative data upon record deletion.
The only thing to add to jkheuhlen's answer is as follows (taken from the answer):
class Post < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :posts, dependent: :destroy
end
This would mean that if you called the following, you need to make sure you're able to call the following:
#app/controllers/users_controller.rb
Class UsersController < ApplicationController
def destroy
#user = User.find params[:id]
#user.destroy -> will destroy all associated `posts`
end
end
Related
I'm still newbie in Rails, but got confused with the initialization of a HABTM association. Reading its documentation, it says
When initializing a new has_one or belongs_to association you must use the build_ prefix to build the association, rather than the association.build method that would be used for has_many or has_and_belongs_to_many associations.
So, basically, let's suppose we have two models:
class User < ApplicationRecord
has_and_belongs_to_many :organizations
end
class Organization < ApplicationRecord
has_and_belongs_to_many :users
end
Inside organization_controller, since I'm using Devise, my create method should have something like this:
#organization = current_user.organizations.build(organization_params)
#organization.save
However, it is not working. With byebug, I checked that for the current_user.organizations, the new organization was there, but, if I call #organization.users, there's an empty array. Looks like it's required to run current_user.save as well, is it correct? I was able to associate both models with this code:
#organization = Organization.new(organization_params)
#organization.users << current_user
#organization.save
You should highly consider using has_many, :through as that's the preferred way to do these kinds of relationships now in Rails.
having said that if you want to use has_and_belongs_to_many associations yes its get stored in join table on main object save.
I have these 2 models as follow
class Application < ActiveRecord::Base
has_many :commitments, class_name: "Commitment", \
:source => :application, dependent: :destroy
accepts_nested_attributes_for :commitments
after_create: update_case_code
end
class Commitment < ActiveRecord::Base
belongs_to :application
after_create: send_notification
def send_notification
ap self.application.case_code
end
end
class ApplicationsController < ApplicationController
def create
#application = Application.new(params)
#application.save
end
end
In my application_controller whenever i create a new Application record,a new record is also created in the Commitment and it tries to get the case_code from the application record but the after_create method of the application model hasnt been executed yet.
Is there any way to optimize this code so that it works properly?
Probably there is. Probably you can also use another callback on the application model which happens before, there are plenty of them. See Active Record Callbacks
However this is exactly the case, which other people call rails callback hell
The best practice here would be just creating a form object, which creates the data in the order you need and remove the callbacks
class ApplicationCommitmentForm
include ActiveModel::Model
attr_accessor ...
def submit
a = Application.create ..
a.update_case_code
a.commitments.create ...
end
end
See ActiveModel Form Objects
Btw you could also wrap the submit code into a transactions ensuring that either all records are created or in case of any errors nothing at all.
I’ve setup a has_many :through association between a User and Organisation model, using a Membership model as the join.
class Organisation < ActiveRecord::Base
has_many :memberships
has_many :users, :through => :memberships
end
class User < ActiveRecord::Base
. . .
has_many :memberships
has_many :organisations, :through => memberships
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :organisation
end
When a User creates an organisation, I want a membership to automatically be created linking the user to that organisation.
Where is the best place to attack this?
Options I’ve been investigating:
Use an after_create callback on the organisation
Move this process into a separate Ruby class.
In the organisations Controller, create action.
?
How would you recommend I go about it?
Is there somewhere in the Rails Guides where it outlines best practices for this kind of thing?
Rails 4.2.5.
#config/routes.rb
resources :organizations #-> url.com/organizations/new
#app/controllers/organizations_controller.rb
class OrganizationsController < ApplicationController
before_action :authenticate_user!
def new
#organization = current_user.organizations.new
end
def create
#organization = current_user.organizations.new organization_params
#organization.save
end
private
def organization_params
params.require(:organization).permit(:x, :y, :z) #-> membership automatically created
end
end
The above will automatically create the associated membership; assuming you're using Devise & have access to the current_user method.
--
The best practice is the most succinct; there is no way you're "meant" to do it.
One of the biggest fallacies I see in Rails is people trying to find the most acceptable way to do something (as if there's a rulebook). The best thing you can do is get it working then refactor the code.
As you progress through your app, you'll find that certain patterns can be changed, some removed and many combined. The more "DRY" you make your code, the better it is (as a rule).
My idea is way 3. Normaly, when set up many - many association in models, we should do creating temp table record auto through controller.
For example in controller you can write:
#organisation = current_user.organisations.build organisation_params
if #organisation.save
....
So that if #organisation is save then after that memberships record auto generate.
You can see this tutorial to see that:
http://blog.teamtreehouse.com/what-is-a-has_many-through-association-in-ruby-on-rails-treehouse-quick-tip
I think you should just be able to do something like:
org = Organisation.new
org.otherstuff = "populate other stuff"
org.users = [user_who_created]
org.save
After that the two should be related...? If you wanted to encapsulate this behavior you could do something like have a class method on Organization like create_org_for_user(name, user) and then do this logic in there, or you could do it in the controller action that handles the creation.
If there isn't any additional logic other than creating an organization and membership, I would just do #3. However, if you are planning on adding more logic for creating a new organization in the future, I would create a new service (#2).
Where is the best place to attack this?
I would like to say you should write this in OrganisationsController's create action to make a DRY on update action as well(use strong parameter) .Because form attribute that you are getting is from outer worlds and it is best to use permit method on required params using Strong Parametre concept.
def create
#organisation = Organisation.new(organisation_params)
...
end
def organisation_params
# here you could write all the params which you want to permit from outer worlds
end
Nore more info about Strong Parameters
This is a follow up question on Rails 4 x paper_trail: filter versions by item_id with nested resources.
—————
CONTEXT FROM PREVIOUS QUESTION
In my Rails 4 app, I have the following models:
class User < ActiveRecord::Base
has_many :administrations, dependent: :destroy
has_many :calendars, through: :administrations
end
class Administration < ActiveRecord::Base
belongs_to :user
belongs_to :calendar
end
class Calendar < ActiveRecord::Base
has_many :administrations, dependent: :destroy
has_many :users, through: :administrations
end
class Post < ActiveRecord::Base
belongs_to :calendar
end
I installed the paper_trail gem to track changes on my post model as explained in the documentation and it works like a charm.
Versions are displayed in the Calendars#Index view, which serves as a dashboard to the user.
—————
Based on the answer to my previous question, I now have the following code in my CalendarsController:
def index
#user = current_user
#calendars = #user.calendars.all
#comments = #user.calendar_comments.order("created_at DESC").limit(20)
#versions = PaperTrail::Version.where(item_id: Post.where(calendar_id: #calendars.ids)).order('id DESC').limit(10)
end
Problem is now: when a user destroys a post, all the versions associated with this post disappear.
What I would like instead, is to keep track of a post even after it is destroyed, including the version mentioning that it was destroyed.
How can I achieve that?
The simplest way to achieve this is to use Paper Trail's metadata feature to store the appropriate users' ids on each post's version record. In other words, denormalise your data just a little to make querying easier.
class Post < ActiveRecord::Base
belongs_to :calendar
has_paper_trail meta: {user_id: :user_ids} # remember to add a user_id column to the versions table
def user_ids
calendar.user_ids # or whatever you need
end
end
Then you can do this in your CalendarsController:
#versions = PaperTrail::Version.where(user_id: current_user.id)
.order('id DESC')
.limit(10)
Instead of actually destroying a post, you could just mark it as destroyed and remove it from the scope. This will retain it's version history and allow you to restore a 'deleted' post if a mistake was made.
rails g migration AddDeletedToPosts deleted:boolean
If there's a point where you no longer need a post and it's version history, say after a period of time, you can create a garbage collector for permanent deletion or migration to a separate archive.
Clarification
A version only belongs to the object it is tracking - in this case, a post. As such, when you destroy a post, you are effectively orphaning all of it’s versions. Yes, they do exist in the database and are present in a query of all versions, but you can no longer scope them because they aren’t related to anything. This is why I suggested virtually destroying posts until you no longer care about it’s version history. Does that make any sense?
I have a has_many :through model that works perfectly.
has_many :varietals
has_many :grapes, :through => :varietals, :dependent => :destroy
I would like to call another action instead of :destroy. In fact, I don't want to nullify the item OR destroy it, I want to update the record status field from 1 to 0 instead of destroy the record.
How to call a custom method instead of destroy ? I suppose I can do that in the model itself... Thanks.
Where to put this method ? In the master model or in the model where the record will be destroyed ?
EDIT:
I'm sorry but I think I didn't enough explain my problem. My problem is not only to so something after the master model is destroyed. I want to custom the destroy action in the Varietal model itself even if the master record is not destroyed.
Something like:
class Varietal < ActiveRecord::Base
private
def destroy
self.update_attributes(:status => 0)
end
end
Actually this action is not called...
You can use before_destroy to put your custom logic there. E.g.,
before_destroy :reset_status
def reset_status
...
end
Check here for more details.
You just need add a callback on before_destroy or after_destroy and manipulate your associations. By example
after_destroy :do_on_grapes
def do_on_grapes
grapes.map(&:to_do)
end
has_many :dependent is limited to only a few options. According to the documentation:
:dependent If set to :destroy all the associated objects are destroyed
alongside this object by calling their destroy method. If set to
:delete_all all associated objects are deleted without calling their
destroy method. If set to :nullify all associated objects’ foreign
keys are set to NULL without calling their save callbacks. If set to
:restrict this object raises an ActiveRecord::DeleteRestrictionError
exception and cannot be deleted if it has any associated objects.
If using with the :through option, the association on the join model
must be a belongs_to, and the records which get deleted are the join
records, rather than the associated records.
It looks like you would need to alter the destroy method to update the status field.
I believe that good approach to solve your problem is to provide a custom destroy method. There are several responses to questions like these, but you should keep in mind that ActiveRecord and Relationships like:
class Image < ActiveRecord::Base
has_many :comments, dependent: :destroy
use callback mechanisms that trigger destroy chaining to your relations, too. Usually you should preserve this mechanism and add it to your custom implementation. E.g.
def destroy
self.update deleted_at: Time.now
run_callbacks :destroy
end
You can read this post, too:
Triggering dependent: :destroy with overridden destroy-method