For this application, an App
has many Elements
, each of which has many Features
. I'd like to update Elements
attributes each time my AppsController#show
runs. However, I run into a problem since I have to dig through each Element's Features
. I can't seem to call @app.elements.features
from my app controller (or features
at all.)
I have tables embedded on the App's Show View
that list an App's Elements
and Features
, so it's not like I can call it for a ElementsController#show
or something easy like that, because then it'll never get updated. I have to find some way to call this action in the ElementsController
from the AppsController
, but that doesn't sound very Rails-ey to me.
This is what I've done so far, and now I'm stuck as to what I should do next. Whats the best way to get this done?
My Apps Controller (parent):
before_action :set_completion, only: :show
def set_completion
@app = App.find(params[:id])
@app.update_attribute(:completion_status,
(((@app.elements.where(completion: true).count) / (@app.elements.count).to_f) * 100 )
)
end
Elements Controller (child):
def update_feature_completion
@app = App.find(params[:app_id])
@element.update_attribute(:element_feature_completion,
(@element.features.where(completion: true).count)
)
end
EDIT: What I have so far (not simple by any means):
def set_completion
@parent = Parent.find(params[:id])
@parent.childs.each do |child|
if (child.childs_childs.where(completion: true).count) == (child.childs_childs.count) #if all childs_childs of the child are completed...
then child.update_attribute(:completion, true) #...mark the child as completed.
else child.update_attribute(:completion, false) #if all childs_childs of the child are not completed, then mark the child as not completed
end
#changes child completion status to % of childs_childs completed
child.update_attribute(:child_completion_status,
child.childs_childs.where(completion: true).count / child.childs_childs.count.to_f
)
@parent.update_attribute(:parent_completion_status,
@parent.childs.child_completion_status.sum
)
end
end
Parent Model
class Parent < ActiveRecord::Base
belongs_to :user
has_many :children, dependent: :destroy
has_many :childs_children, dependent: :destroy
accepts_nested_attributes_for :children
accepts_nested_attributes_for :childs_childs
end
Child Model
class Child < ActiveRecord::Base
belongs_to :parent
has_many :childs_children, dependent: :destroy
accepts_nested_attributes_for :childs_children
end
Child's Child Model
class childs_child < ActiveRecord::Base
belongs_to :child
belongs_to :parent
end
This could end up biting you in the rear later down the road. The idea of data changing in the database every time a GET request hits the show page could cause unnecessary strain on your server. Think about all those web crawlers that hit your page. They would inadvertently trigger these database transactions to occur.
If you are going to make changes to any persisted data it should be done via the POST method. Action Callbacks are not typically meant for this type of work and should generally be used only for things like authentication and caching. So may I offer an alternative solution?
So we start with a deeply nested relationship which is fine. But instead of constantly doing these calculations when we a GET request to apps#show occurs, we have to look at what causes these changes in the first place. It appears that your data will only really change as a feature is updated as complete. That is where you should force these changes to occur. So what if you did something like this
Let's try to take a RESTful approach to this problem. As features are marked as complete, their respective Element and App should also be updated. Let's also assume that an Element can't be marked as complete without each of the features being marked as complete.
On your apps#show page I assume you are displaying all the App's Elements and the Elements subordinate features. I would imagine you would want your users to click a button on a feature that marks that feature as complete. So let's make a RESTful route that allows us to do that.
config/routes.rb
So now we have a RESTful route that will look for the complete action inside your FeaturesController.
features_controller.rb
So now you have an action that works with that feature. Now all you have to do is create a link in your apps#show page as you iterate through each element's features
app/views/apps/show.html.haml
As users complete each feature they can click the link that will send a post method to features#complete action
This way the appropriate data is updated only when a POST request is made to mark a feature as complete. All you should be doing in the apps#show action is provide the appropriate object to be displayed.