Skip to main content
Ask A Question
Notifications
You’re not receiving notifications from this thread.
Subscribe

Set field on multiple records on save/change of 1 record

Rails • Asked by Wouter van den Beld

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

That turned out really clean! I'm glad you guys got it working. Airpair is pretty great.


Login or Create An Account to join the conversation.

Subscribe to the newsletter

Join 30,005+ developers who get early access to new screencasts, articles, guides, updates, and more.

    By clicking this button, you agree to the GoRails Terms of Service and Privacy Policy.

    More of a social being? We're also on Twitter and YouTube.