Set field on multiple records on save/change of 1 record
I'm working on a maintenance planning/cost application. With the help of gorails, onemonth, codeschool and all other great resources I am almost ready to launch the first release. However I can really use some help for the last part.
I have a items model where I store the item information. An item can be a building, factory, swimming pool or even a single machine or room. A item has many ItemDetails and a ItemDetail has many DetailActions.
For Example:
We have a Item :
Building A
Item “Building A” has a ItemDetail:
electric water heater 80 liter:
ItemDetail “electric water heater 80 liter” has the following DetailActions:
electric water heater (replace)
electric water heater (maintenance)
heat distributor (replace)
heat distributor (maintenance)
The DetailActions model looks like this:
id :integer not null, primary key
name :text
unit :string(255)
starting_year :integer
hierarchy :string(255)
norm :decimal(14, 2)
condition_one :integer
condition_two :integer
condition_three :integer
condition_four :integer
condition_five :integer
item_detail_id :integer
created_at :datetime
updated_at :datetime
amount :decimal(16, 2)
sum :decimal(16, 2)
cycle :integer
sequence :string(255) default([]), is an Array
when an item is created the model calculates the sum and it sets the sequence and that's where I need some help.
Currently the sequence is set like this in the detailaction model:
self.sequence = self.starting_year.step(self.starting_year + 500, step = self.cycle ).to_a
this gives something like the following:
["2026", "2036", "2046", "2056", "2066", "2076", "2086", "2096", "2106", "2116", "2126", "2136", "2146", "2156", "2166", "2176", "2186", "2196", "2206", "2216", "2226", "2236", "2246", "2256", "2266", "2276", "2286", "2296", "2306", "2316", "2326", "2336", "2346", "2356", "2366", "2376", "2386", "2396", "2406", "2416", "2426", "2436", "2446", "2456", "2466", "2476", "2486", "2496", "2506", "2516", "2526"]
as you can imagine you wont need to do any maintaince to the heat distributor when we replace the heat distributor that year. So thats where the hierarchy comes in. in the example the hierarchy would be like this:
electric water heater (replace) 101
electric water heater (maintenance) 102
heat distributor (replace) 201
heat distributor (maintenance) 202
so when the electric water heater gets replaced in for example 2036 we would not like to do any maintenance.
I would like to achieve this to set the sequence based on the hierarchy.
In the console I can do (for example) take the id's the heat distributor
replace = DetailAction.find(5598)
maintenance = DetailAction.find(5599)
replace.sequence
=> ["2036", "2061", "2086", "2111", "2136", "2161", "2186", "2211", "2236", "2261", "2286", "2311", "2336", "2361", "2386", "2411", "2436", "2461", "2486", "2511", "2536"]
maintenance.sequence
=> ["2013", "2014", "2015", "2016", "2017", "2018", "2019", "2020", "2021", "2022", "2023", "2024", "2025", "2026", "2027", "2028", "2029", "2030", "2031", "2032", "2033", "2034", "2035", "2036", "2037", "2038", "2039", "2040", "2041", "2042", "2043", "2044", "2045", "2046", "2047", "2048", "2049", "2050", "2051", "2052", "2053", "2054", "2055", "2056", "2057", "2058", "2059", "2060", "2061", "2062", "2063", …...............................]
and simple do new_maintenance_sequence
= maintenance.sequence
- replace.sequence
new_maintenance_sequence = maintenance.sequence - replace.sequence
=> ["2013", "2014", "2015", "2016", "2017", "2018", "2019", "2020", "2021", "2022", "2023", "2024", "2025", "2026", "2027", "2028", "2029", "2030", "2031", "2032", "2033", "2034", "2035", "2037", "2038", "2039", "2040", "2041", "2042", "2043", "2044", "2045", "2046", "2047", "2048", "2049", "2050", "2051", "2052", "2053", "2054", "2055", "2056", "2057", "2058", "2059", "2060", "2062", "2063", "….............................]
I am stuck how to implement this in the model. I would like to loop trough all the DetailActions that belong to the ItemDetail to set the sequence
so when the hierarchy is:
a = 101
b = 102
c = 103
d = 201
e = 202
the sequence get set like:
c = c – b + a
b = b = b- a
a = a
e = e – d
d = d
Do you guys can get me on the right track or is the path i chose wrong?
My current detail action model looks like this:
class DetailAction < ActiveRecord::Base
belongs_to :item_detail
has_many :detail_action_amounts ,dependent: :destroy
has_many :budget_items ,dependent: :destroy
accepts_nested_attributes_for :detail_action_amounts, :reject_if => :all_blank, :allow_destroy => true
before_create :set_default
before_save :set_sum
def set_default
item_detail = ItemDetail.where(:id => self.item_detail_id)
item = Item.where(:id => item_detail[0].item_id)
self.starting_year = item[0].construction_date + self.condition_three
self.amount = '1' unless self.amount
self.cycle = self.condition_three
self.sum = self.amount * self.norm
self.sequence = self.starting_year.step(self.starting_year + 500, step = self.cycle ).to_a
end
def set_sum
if self.cycle?
self.sum = self.amount * self.norm
self.cycle = self.condition_three unless self.cycle
self.sequence = self.starting_year.step(self.starting_year + 500, step = self.cycle ).to_a
else
self.sum = self.amount * self.norm
end
end
end
I think I'm following you. The before_create
seems like the right place to do this for now. If you're doing this before the record is created, you'll need to reference the in-memory objects through the association.
You might have a simpler time by moving this into the ItemDetail rather than the DetailAction. You'll need some way to match the two DetailActions together based upon the part
(like "electric water heater") and then you'll need to separate those two items by the action ("replace" or "maintenance")
Once you have those two DetailActions together, you can first start with the replacement sequence, generate it, and then generate the maintenance one minus the replacement schedule.
If you've got the fields for the part
and the action
, then you can group these based upon the part
field with Ruby's group method. Then you'll just go through each group, find the replacement DetailAction, calculate the replacement sequence first, save it, and then do the same thing for the maintenance.
Does that help at all?
After a airpair session i got it to work.
We used a lot of methods because you can call them in the console when you use the byebug gem.
but we did it the way you suggested!
class DetailAction < ActiveRecord::Base
belongs_to :item_detail
has_many :detail_action_amounts ,dependent: :destroy
has_many :budget_items ,dependent: :destroy
accepts_nested_attributes_for :detail_action_amounts, :reject_if => :all_blank, :allow_destroy => true
before_create :set_default, on: :create
before_save :set_sum
after_save :recalculate_overridden_actions
def set_default
item_detail = ItemDetail.where(:id => self.item_detail_id)
item = Item.where(:id => item_detail[0].item_id)
self.starting_year = item[0].construction_date + self.condition_three
self.amount = '1' unless self.amount
self.cycle = self.condition_three
end
def set_sum
if self.cycle?
self.sum = self.amount * self.norm
self.sequence = calculate_sequence
else
self.sum = self.amount * self.norm
end
end
def recalculate_overridden_actions
overridden_actions.each(&:set_sum).each(&:save)
end
def calculate_sequence
basic_sequence - overriding_sequences.flatten
end
def basic_sequence
starting_year.step(starting_year + 500, step = cycle ).to_a
end
def overriding_sequences
overriding_actions.map(&:basic_sequence)
end
def overriding_actions
item_detail.detail_actions.to_a.select do |action|
self.is_overridden_by?(action)
end
end
def overridden_actions
item_detail.detail_actions.to_a.select do |action|
action.is_overridden_by?(self)
end
end
def is_overridden_by?(other_action)
other_action.hierarchy < hierarchy &&
other_action.hierarchy.first == hierarchy.first
end
end