Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[RailsConf 2025] Derailing Our Application: How...

[RailsConf 2025] Derailing Our Application: How and Why We Are Decoupling Ourselves from Rails

Cisco is probably the largest Rails shop you’ve never heard of. Our 4 million line Ruby codebase has been under continuous development since 2006. As with any large codebase, ours is not without challenges. In “Derailing Our Application,” we share how our architecture has evolved to decouple 800,000 lines of Ruby code from Rails constructs in order to enable us to scale our team to 1,000 engineers and beyond without losing the benefits of Rails. Join us on the track less traveled! All aboard!

Avatar for Alan Ridlehoover

Alan Ridlehoover

July 08, 2025
Tweet

More Decks by Alan Ridlehoover

Other Decks in Programming

Transcript

  1. Fito von Zastrow & Alan Ridlehoover / Cisco Meraki /

    RailsConf 2025 Derailing Our Application How and Why We Are Decoupling Ourselves from Rails
  2. Fito von Zastrow & Alan Ridlehoover / Cisco Meraki /

    RailsConf 2025 Derailing Our Application How and Why We Are Decoupling Ourselves from Rails
  3. Fito von Zastrow & Alan Ridlehoover / Cisco Meraki /

    RailsConf 2025 Derailing Our Application How and Why We Are Decoupling Ourselves from Rails
  4. Fito von Zastrow & Alan Ridlehoover / Cisco Meraki /

    RailsConf 2025 Derailing Our Application How and Why We Are Decoupling Ourselves from Rails
  5. 3X. ~ Kent Beck, author of Extreme Programming Explained and

    Test Driven Development 3X with Kent Beck — https://youtu.be/YX2XR73LnRY
  6. 1.0 2.0 Restul Routes Chainable Active 
 Record queries Bundler

    • • 
 • 1.9 2007 2009 2008 2006 2010 2011 2012 2013
  7. 1.0 2.0 Restul Routes Chainable Active 
 Record queries Bundler

    • • 
 • 1.9 2007 2009 2008 2006 2010 2011 2012 2013
  8. 2007 2009 2008 2006 2010 2011 2012 2013 1.0 2.0

    Restul Routes Chainable Active 
 Record queries Bundler • • 
 • 1.9
  9. $1 Billion! 2009 - Twitter 2010 - Groupon 2011 -

    Airbnb 2012 - Yammer 2012 - Meraki 2013 - GitHub 2014 - Twitch 2015 - Shopify 2015 - Gusto Source: A log of Googling
  10. 2010 2020 2015 2005 2025 2030 2006 2012 2025 The

    Meraki Dashboard’s Lifecycle
  11. 140 280 420 560 700 1M 2M 3M 4M 5M

    Cisco Acquisition (2012) Alan & Fito Join (2019) Today (2025) Unique Contributors Lines of Ruby Code
  12. 140 280 420 560 700 1M 2M 3M 4M 5M

    Cisco Acquisition (2012) Alan & Fito Join (2019) Today (2025) Unique Contributors Lines of Ruby Code
  13. 140 280 420 560 700 1M 2M 3M 4M 5M

    Cisco Acquisition (2012) Alan & Fito Join (2019) Today (2025) Unique Contributors Lines of Ruby Code
  14. 140 280 420 560 700 1M 2M 3M 4M 5M

    Cisco Acquisition (2012) Alan & Fito Join (2019) Today (2025) Unique Contributors Lines of Ruby Code
  15. 2010 2020 2015 2005 2025 2030 2006 2012 2022 2025

    The Meraki Dashboard’s Lifecycle
  16. 140 280 420 560 700 1M 2M 3M 4M 5M

    Cisco Acquisition (2012) Alan & Fito Join (2022) Today (2025) Unique Contributors Lines of Ruby Code
  17. 2010 2020 2015 2005 2025 2030 2006 2012 2022 2025

    The Meraki Dashboard’s Lifecycle
  18. 2010 2020 2015 2005 2025 2030 2006 2012 2022 2025

    The Meraki Dashboard’s Lifecycle
  19. 2010 2020 2015 2005 2025 2030 2006 2012 2022 2025

    The Meraki Dashboard’s Lifecycle
  20. 2010 2020 2015 2005 2025 2030 2006 2012 2022 2025

    The Meraki Dashboard’s Lifecycle
  21. 2010 2020 2015 2005 2025 2030 2006 2012 2022 2025

    The Meraki Dashboard’s Lifecycle
  22. class CoffeeMachineSettingsController < ApplicationController before_filter :set_cafe def update @errors =

    [] parameter_changes = params.dig(:changes, :parameter_changes) || [] return if parameter_changes.blank? coffee_maker_ids = @cafe.coffee_makers.pluck(:id) settings_changes = {} Cafe.transaction do parameter_changes.each do |(coffee_maker_id, changes)| if coffee_maker_ids.exclude?(coffee_maker_id.to_i) @errors << "Trying to modify settings of a coffee maker that doesn't belong to this cafe" break end coffee_maker = CoffeeMaker.find(coffee_maker_id) settings = coffee_maker.settings old_temperature = settings.temperature.nil? ? "null" : "#{settings.temperature} °C" new_temperature = changes[:temperature].nil? ? "null" : "#{changes[:temperature]} °C" if new_temperature.to_i < CoffeeMaker::MIN_TEMPERATURE @errors << "Brew temperature must be at least #{CoffeeMaker::MIN_TEMPERATURE} °C" break end old_temperature_text = "Temperature: #{old_temperature}. " new_temperature_text = "Temperature: #{new_temperature}. " water_tank_capacity = settings.water_tank_capacity old_capacity = water_tank_capacity.nil? ? "null" : "#{water_tank_capacity} liters" new_capacity = changes[:water_tank_capacity].nil? ? "null" : "#{changes[:water_tank_capacity]} liters" old_capacity_text = "Water tank capacity: #{old_capacity}." new_capacity_text = "Water tank capacity: #{new_capacity}." @errors << "Water tank capacity must be between 0.5 and 10 liters" unless changes[:water_tank_capacity].to_f.between?(0.5, 10) if old_temperature_text != new_temperature_text || old_capacity_text != new_capacity_text settings_changes["#{coffee_maker_id}_settings"] = { "label" => "Coffee Maker: #{coffee_maker.get_name} Settings", "old_text" => "#{changes.key?(:temperature) ? old_temperature_text : ""}#{changes.key?(:water_tank_capacity) ? old_capacity_text : ""}", "new_text" => "#{changes.key?(:temperature) ? new_temperature_text : ""}#{changes.key?(:water_tank_capacity) ? new_capacity_text : ""}", } end break if [email protected]? settings.temperature = changes[:temperature] settings.water_tank_capacity = changes[:water_tank_capacity] if !settings.save @errors.concat(settings.errors.full_messages) end end if @errors.empty? CafeEvent.create({ cafe_id: @cafe.id, notify_at: 10.minutes.from_now.utc, user_id: @current_user.id, changes: settings_changes, }) else raise ActiveRecord::Rollback end end rescue ActiveRecord::RecordInvalid => e @errors << e.message ensure @cafe.reload if @errors.empty? render json: { new_settings: @cafe.coffee_machine_data }, status: 200 else @errors.unshift("One or more errors occurred. No changes have been saved.") render json: { update_messages: { errors: @errors } }, status: 400 end end end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 coffee_machine_settings_controller.rb
  23. class CoffeeMachineSettingsController < ApplicationController before_filter :set_cafe def update @errors =

    [] parameter_changes = params.dig(:changes, :parameter_changes) || [] return if parameter_changes.blank? coffee_maker_ids = @cafe.coffee_makers.pluck(:id) settings_changes = {} Cafe.transaction do parameter_changes.each do |(coffee_maker_id, changes)| if coffee_maker_ids.exclude?(coffee_maker_id.to_i) @errors << "Trying to modify settings of a coffee maker that doesn't belong to this cafe" break end coffee_maker = CoffeeMaker.find(coffee_maker_id) settings = coffee_maker.settings old_temperature = settings.temperature.nil? ? "null" : "#{settings.temperature} °C" new_temperature = changes[:temperature].nil? ? "null" : "#{changes[:temperature]} °C" if new_temperature.to_i < CoffeeMaker::MIN_TEMPERATURE @errors << "Brew temperature must be at least #{CoffeeMaker::MIN_TEMPERATURE} °C" break end old_temperature_text = "Temperature: #{old_temperature}. " new_temperature_text = "Temperature: #{new_temperature}. " water_tank_capacity = settings.water_tank_capacity old_capacity = water_tank_capacity.nil? ? "null" : "#{water_tank_capacity} liters" new_capacity = changes[:water_tank_capacity].nil? ? "null" : "#{changes[:water_tank_capacity]} liters" old_capacity_text = "Water tank capacity: #{old_capacity}." new_capacity_text = "Water tank capacity: #{new_capacity}." @errors << "Water tank capacity must be between 0.5 and 10 liters" unless changes[:water_tank_capacity].to_f.between?(0.5, 10) if old_temperature_text != new_temperature_text || old_capacity_text != new_capacity_text settings_changes["#{coffee_maker_id}_settings"] = { "label" => "Coffee Maker: #{coffee_maker.get_name} Settings", "old_text" => "#{changes.key?(:temperature) ? old_temperature_text : ""}#{changes.key?(:water_tank_capacity) ? old_capacity_text : ""}", "new_text" => "#{changes.key?(:temperature) ? new_temperature_text : ""}#{changes.key?(:water_tank_capacity) ? new_capacity_text : ""}", } end break if [email protected]? settings.temperature = changes[:temperature] settings.water_tank_capacity = changes[:water_tank_capacity] if !settings.save @errors.concat(settings.errors.full_messages) end end if @errors.empty? CafeEvent.create({ cafe_id: @cafe.id, notify_at: 10.minutes.from_now.utc, user_id: @current_user.id, changes: settings_changes, }) else raise ActiveRecord::Rollback end end rescue ActiveRecord::RecordInvalid => e @errors << e.message ensure @cafe.reload if @errors.empty? render json: { new_settings: @cafe.coffee_machine_data }, status: 200 else @errors.unshift("One or more errors occurred. No changes have been saved.") render json: { update_messages: { errors: @errors } }, status: 400 end end end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 coffee_machine_settings_controller.rb Cafe.transaction do parameter_changes.each do |(coffee_maker_id, changes)| # ... end end
  24. class CoffeeMachineSettingsController < ApplicationController before_filter :set_cafe def update @errors =

    [] parameter_changes = params.dig(:changes, :parameter_changes) || [] return if parameter_changes.blank? coffee_maker_ids = @cafe.coffee_makers.pluck(:id) settings_changes = {} Cafe.transaction do parameter_changes.each do |(coffee_maker_id, changes)| if coffee_maker_ids.exclude?(coffee_maker_id.to_i) @errors << "Trying to modify settings of a coffee maker that doesn't belong to this cafe" break end coffee_maker = CoffeeMaker.find(coffee_maker_id) settings = coffee_maker.settings old_temperature = settings.temperature.nil? ? "null" : "#{settings.temperature} °C" new_temperature = changes[:temperature].nil? ? "null" : "#{changes[:temperature]} °C" if new_temperature.to_i < CoffeeMaker::MIN_TEMPERATURE @errors << "Brew temperature must be at least #{CoffeeMaker::MIN_TEMPERATURE} °C" break end old_temperature_text = "Temperature: #{old_temperature}. " new_temperature_text = "Temperature: #{new_temperature}. " water_tank_capacity = settings.water_tank_capacity old_capacity = water_tank_capacity.nil? ? "null" : "#{water_tank_capacity} liters" new_capacity = changes[:water_tank_capacity].nil? ? "null" : "#{changes[:water_tank_capacity]} liters" old_capacity_text = "Water tank capacity: #{old_capacity}." new_capacity_text = "Water tank capacity: #{new_capacity}." @errors << "Water tank capacity must be between 0.5 and 10 liters" unless changes[:water_tank_capacity].to_f.between?(0.5, 10) if old_temperature_text != new_temperature_text || old_capacity_text != new_capacity_text settings_changes["#{coffee_maker_id}_settings"] = { "label" => "Coffee Maker: #{coffee_maker.get_name} Settings", "old_text" => "#{changes.key?(:temperature) ? old_temperature_text : ""}#{changes.key?(:water_tank_capacity) ? old_capacity_text : ""}", "new_text" => "#{changes.key?(:temperature) ? new_temperature_text : ""}#{changes.key?(:water_tank_capacity) ? new_capacity_text : ""}", } end break if [email protected]? settings.temperature = changes[:temperature] settings.water_tank_capacity = changes[:water_tank_capacity] if !settings.save @errors.concat(settings.errors.full_messages) end end if @errors.empty? CafeEvent.create({ cafe_id: @cafe.id, notify_at: 10.minutes.from_now.utc, user_id: @current_user.id, changes: settings_changes, }) else raise ActiveRecord::Rollback end end rescue ActiveRecord::RecordInvalid => e @errors << e.message ensure @cafe.reload if @errors.empty? render json: { new_settings: @cafe.coffee_machine_data }, status: 200 else @errors.unshift("One or more errors occurred. No changes have been saved.") render json: { update_messages: { errors: @errors } }, status: 400 end end end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 coffee_machine_settings_controller.rb if coffee_maker_ids.exclude?(coffee_maker_id.to_i) @errors << "Trying to modify settings of a coffee maker that d break end coffee_maker = CoffeeMaker.find(coffee_maker_id) settings = coffee_maker.settings old_temperature = settings.temperature.nil? ? "null" : "#{settin new_temperature = changes[:temperature].nil? ? "null" : "#{chang if new_temperature.to_i < CoffeeMaker::MIN_TEMPERATURE errors << "Brew temperature must be at least #{CoffeeMaker::MI break end old_temperature_text = "Temperature: #{old_temperature}. " new_temperature_text = "Temperature: #{new_temperature}. " water_tank_capacity = settings.water_tank_capacity old_capacity = water_tank_capacity.nil? ? "null" : "#{water_tank new_capacity = changes[:water_tank_capacity].nil? ? "null" : "#{ old_capacity_text = "Water tank capacity: #{old_capacity}." new_capacity_text = "Water tank capacity: #{new_capacity}." errors << "Water tank capacity must be between 0.5 and 10 liters if old_temperature_text != new_temperature_text || old_capacity_ settings_changes["#{coffee_maker_id}_settings"] = {
  25. class CoffeeMachineSettingsController < ApplicationController before_filter :set_cafe def update @errors =

    [] parameter_changes = params.dig(:changes, :parameter_changes) || [] return if parameter_changes.blank? coffee_maker_ids = @cafe.coffee_makers.pluck(:id) settings_changes = {} Cafe.transaction do parameter_changes.each do |(coffee_maker_id, changes)| if coffee_maker_ids.exclude?(coffee_maker_id.to_i) @errors << "Trying to modify settings of a coffee maker that doesn't belong to this cafe" break end coffee_maker = CoffeeMaker.find(coffee_maker_id) settings = coffee_maker.settings old_temperature = settings.temperature.nil? ? "null" : "#{settings.temperature} °C" new_temperature = changes[:temperature].nil? ? "null" : "#{changes[:temperature]} °C" if new_temperature.to_i < CoffeeMaker::MIN_TEMPERATURE @errors << "Brew temperature must be at least #{CoffeeMaker::MIN_TEMPERATURE} °C" break end old_temperature_text = "Temperature: #{old_temperature}. " new_temperature_text = "Temperature: #{new_temperature}. " water_tank_capacity = settings.water_tank_capacity old_capacity = water_tank_capacity.nil? ? "null" : "#{water_tank_capacity} liters" new_capacity = changes[:water_tank_capacity].nil? ? "null" : "#{changes[:water_tank_capacity]} liters" old_capacity_text = "Water tank capacity: #{old_capacity}." new_capacity_text = "Water tank capacity: #{new_capacity}." @errors << "Water tank capacity must be between 0.5 and 10 liters" unless changes[:water_tank_capacity].to_f.between?(0.5, 10) if old_temperature_text != new_temperature_text || old_capacity_text != new_capacity_text settings_changes["#{coffee_maker_id}_settings"] = { "label" => "Coffee Maker: #{coffee_maker.get_name} Settings", "old_text" => "#{changes.key?(:temperature) ? old_temperature_text : ""}#{changes.key?(:water_tank_capacity) ? old_capacity_text : ""}", "new_text" => "#{changes.key?(:temperature) ? new_temperature_text : ""}#{changes.key?(:water_tank_capacity) ? new_capacity_text : ""}", } end break if [email protected]? settings.temperature = changes[:temperature] settings.water_tank_capacity = changes[:water_tank_capacity] if !settings.save @errors.concat(settings.errors.full_messages) end end if @errors.empty? CafeEvent.create({ cafe_id: @cafe.id, notify_at: 10.minutes.from_now.utc, user_id: @current_user.id, changes: settings_changes, }) else raise ActiveRecord::Rollback end end rescue ActiveRecord::RecordInvalid => e @errors << e.message ensure @cafe.reload if @errors.empty? render json: { new_settings: @cafe.coffee_machine_data }, status: 200 else @errors.unshift("One or more errors occurred. No changes have been saved.") render json: { update_messages: { errors: @errors } }, status: 400 end end end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 coffee_machine_settings_controller.rb if @errors.empty? CafeEvent.create({ cafe_id: @cafe.id, notify_at: 10.minutes.from_now.utc, user_id: @current_user.id, changes: settings_changes, }) else raise ActiveRecord::Rollback end
  26. class CoffeeMachineSettingsController < ApplicationController before_filter :set_cafe def update @errors =

    [] parameter_changes = params.dig(:changes, :parameter_changes) || [] return if parameter_changes.blank? coffee_maker_ids = @cafe.coffee_makers.pluck(:id) settings_changes = {} Cafe.transaction do parameter_changes.each do |(coffee_maker_id, changes)| if coffee_maker_ids.exclude?(coffee_maker_id.to_i) @errors << "Trying to modify settings of a coffee maker that doesn't belong to this cafe" break end coffee_maker = CoffeeMaker.find(coffee_maker_id) settings = coffee_maker.settings old_temperature = settings.temperature.nil? ? "null" : "#{settings.temperature} °C" new_temperature = changes[:temperature].nil? ? "null" : "#{changes[:temperature]} °C" if new_temperature.to_i < CoffeeMaker::MIN_TEMPERATURE @errors << "Brew temperature must be at least #{CoffeeMaker::MIN_TEMPERATURE} °C" break end old_temperature_text = "Temperature: #{old_temperature}. " new_temperature_text = "Temperature: #{new_temperature}. " water_tank_capacity = settings.water_tank_capacity old_capacity = water_tank_capacity.nil? ? "null" : "#{water_tank_capacity} liters" new_capacity = changes[:water_tank_capacity].nil? ? "null" : "#{changes[:water_tank_capacity]} liters" old_capacity_text = "Water tank capacity: #{old_capacity}." new_capacity_text = "Water tank capacity: #{new_capacity}." @errors << "Water tank capacity must be between 0.5 and 10 liters" unless changes[:water_tank_capacity].to_f.between?(0.5, 10) if old_temperature_text != new_temperature_text || old_capacity_text != new_capacity_text settings_changes["#{coffee_maker_id}_settings"] = { "label" => "Coffee Maker: #{coffee_maker.get_name} Settings", "old_text" => "#{changes.key?(:temperature) ? old_temperature_text : ""}#{changes.key?(:water_tank_capacity) ? old_capacity_text : ""}", "new_text" => "#{changes.key?(:temperature) ? new_temperature_text : ""}#{changes.key?(:water_tank_capacity) ? new_capacity_text : ""}", } end break if [email protected]? settings.temperature = changes[:temperature] settings.water_tank_capacity = changes[:water_tank_capacity] if !settings.save @errors.concat(settings.errors.full_messages) end end if @errors.empty? CafeEvent.create({ cafe_id: @cafe.id, notify_at: 10.minutes.from_now.utc, user_id: @current_user.id, changes: settings_changes, }) else raise ActiveRecord::Rollback end end rescue ActiveRecord::RecordInvalid => e @errors << e.message ensure @cafe.reload if @errors.empty? render json: { new_settings: @cafe.coffee_machine_data }, status: 200 else @errors.unshift("One or more errors occurred. No changes have been saved.") render json: { update_messages: { errors: @errors } }, status: 400 end end end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 coffee_machine_settings_controller.rb rescue ActiveRecord::RecordInvalid => e @errors << e.message ensure @cafe.reload if @errors.empty? render json: { new_settings: @cafe.coffee_machine_data } else @errors.unshift("One or more errors occurred. No changes render json: { update_messages: { errors: @errors } }, s end end
  27. class CoffeeMachineSettingsController < ApplicationController before_filter :set_cafe def update @errors =

    [] parameter_changes = params.dig(:changes, :parameter_changes) || [] return if parameter_changes.blank? coffee_maker_ids = @cafe.coffee_makers.pluck(:id) settings_changes = {} Cafe.transaction do parameter_changes.each do |(coffee_maker_id, changes)| if coffee_maker_ids.exclude?(coffee_maker_id.to_i) @errors << "Trying to modify settings of a coffee maker that doesn't belong to this cafe" break end coffee_maker = CoffeeMaker.find(coffee_maker_id) settings = coffee_maker.settings old_temperature = settings.temperature.nil? ? "null" : "#{settings.temperature} °C" new_temperature = changes[:temperature].nil? ? "null" : "#{changes[:temperature]} °C" if new_temperature.to_i < CoffeeMaker::MIN_TEMPERATURE @errors << "Brew temperature must be at least #{CoffeeMaker::MIN_TEMPERATURE} °C" break end old_temperature_text = "Temperature: #{old_temperature}. " new_temperature_text = "Temperature: #{new_temperature}. " water_tank_capacity = settings.water_tank_capacity old_capacity = water_tank_capacity.nil? ? "null" : "#{water_tank_capacity} liters" new_capacity = changes[:water_tank_capacity].nil? ? "null" : "#{changes[:water_tank_capacity]} liters" old_capacity_text = "Water tank capacity: #{old_capacity}." new_capacity_text = "Water tank capacity: #{new_capacity}." @errors << "Water tank capacity must be between 0.5 and 10 liters" unless changes[:water_tank_capacity].to_f.between?(0.5, 10) if old_temperature_text != new_temperature_text || old_capacity_text != new_capacity_text settings_changes["#{coffee_maker_id}_settings"] = { "label" => "Coffee Maker: #{coffee_maker.get_name} Settings", "old_text" => "#{changes.key?(:temperature) ? old_temperature_text : ""}#{changes.key?(:water_tank_capacity) ? old_capacity_text : ""}", "new_text" => "#{changes.key?(:temperature) ? new_temperature_text : ""}#{changes.key?(:water_tank_capacity) ? new_capacity_text : ""}", } end break if [email protected]? settings.temperature = changes[:temperature] settings.water_tank_capacity = changes[:water_tank_capacity] if !settings.save @errors.concat(settings.errors.full_messages) end end if @errors.empty? CafeEvent.create({ cafe_id: @cafe.id, notify_at: 10.minutes.from_now.utc, user_id: @current_user.id, changes: settings_changes, }) else raise ActiveRecord::Rollback end end rescue ActiveRecord::RecordInvalid => e @errors << e.message ensure @cafe.reload if @errors.empty? render json: { new_settings: @cafe.coffee_machine_data }, status: 200 else @errors.unshift("One or more errors occurred. No changes have been saved.") render json: { update_messages: { errors: @errors } }, status: 400 end end end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 coffee_machine_settings_controller.rb rescue ActiveRecord::RecordInvalid => e @errors << e.message ensure @cafe.reload if @errors.empty? render json: { new_settings: @cafe.coffee_machine_data } else @errors.unshift("One or more errors occurred. No changes render json: { update_messages: { errors: @errors } }, s end end
  28. class CoffeeMachineSettingsController < ApplicationController before_filter :set_cafe def update @errors =

    [] parameter_changes = params.dig(:changes, :parameter_changes) || [] return if parameter_changes.blank? coffee_maker_ids = @cafe.coffee_makers.pluck(:id) settings_changes = {} Cafe.transaction do parameter_changes.each do |(coffee_maker_id, changes)| if coffee_maker_ids.exclude?(coffee_maker_id.to_i) @errors << "Trying to modify settings of a coffee maker that doesn't belong to this cafe" break end service = UpdateWaterTankSettings.new(coffee_maker_id, changes) service.perform @errors.concat(service.errors) break if @errors.any? end if @errors.empty? CafeEvent.create({ cafe_id: @cafe.id, notify_at: 10.minutes.from_now.utc, user_id: @current_user.id, changes: settings_changes, }) else raise ActiveRecord::Rollback end end rescue ActiveRecord::RecordInvalid => e @errors << e.message ensure @cafe.reload if @errors.empty? render json: { new_settings: @cafe.coffee_machine_data }, status: 200 else @errors.unshift("One or more errors occurred. No changes have been saved.") render json: { update_messages: { errors: @errors } }, status: 400 end end end class UpdateWaterTankSettings attr_reader :coffee_maker_id, :changes, :errors def initialize(coffee_maker_id, changes) @coffee_maker_id = coffee_maker_id @changes = changes @errors = [] end def perform coffee_maker = CoffeeMaker.find(coffee_maker_id) settings = coffee_maker.settings old_temperature = settings.temperature.nil? ? "null" : "#{settings.temperature} °C" new_temperature = changes[:temperature].nil? ? "null" : "#{changes[:temperature]} °C" if new_temperature.to_i < CoffeeMaker::MIN_TEMPERATURE errors << "Brew temperature must be at least #{CoffeeMaker::MIN_TEMPERATURE} °C" return end old_temperature_text = "Temperature: #{old_temperature}. " new_temperature_text = "Temperature: #{new_temperature}. " water_tank_capacity = settings.water_tank_capacity old_capacity = water_tank_capacity.nil? ? "null" : "#{water_tank_capacity} liters" new_capacity = changes[:water_tank_capacity].nil? ? "null" : "#{changes[:water_tank_capacity]} li old_capacity_text = "Water tank capacity: #{old_capacity}." new_capacity_text = "Water tank capacity: #{new_capacity}." errors << "Water tank capacity must be between 0.5 and 10 liters" unless changes[:water_tank_capa if old_temperature_text != new_temperature_text || old_capacity_text != new_capacity_text settings_changes["#{coffee_maker_id}_settings"] = { "label" => "Coffee Maker: #{coffee_maker.get_name} Settings", "old_text" => "#{changes.key?(:temperature) ? old_temperature_text : ""}#{changes.key?(:water "new_text" => "#{changes.key?(:temperature) ? new_temperature_text : ""}#{changes.key?(:water } end return if !errors.empty? settings.temperature = changes[:temperature] settings.water_tank_capacity = changes[:water_tank_capacity] if !settings.save errors.concat(settings.errors.full_messages) end end end update_water_tank_settings.rb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 coffee_machine_settings_controller.rb
  29. class CoffeeMachineSettingsController < ApplicationController before_filter :set_cafe def update @errors =

    [] parameter_changes = params.dig(:changes, :parameter_changes) || [] return if parameter_changes.blank? coffee_maker_ids = @cafe.coffee_makers.pluck(:id) settings_changes = {} Cafe.transaction do parameter_changes.each do |(coffee_maker_id, changes)| if coffee_maker_ids.exclude?(coffee_maker_id.to_i) @errors << "Trying to modify settings of a coffee maker that doesn't belong to this cafe" break end service = UpdateWaterTankSettings.new(coffee_maker_id, changes) service.perform @errors.concat(service.errors) break if @errors.any? end if @errors.empty? CafeEvent.create({ cafe_id: @cafe.id, notify_at: 10.minutes.from_now.utc, user_id: @current_user.id, changes: settings_changes, }) else raise ActiveRecord::Rollback end end rescue ActiveRecord::RecordInvalid => e @errors << e.message ensure @cafe.reload if @errors.empty? render json: { new_settings: @cafe.coffee_machine_data }, status: 200 else @errors.unshift("One or more errors occurred. No changes have been saved.") render json: { update_messages: { errors: @errors } }, status: 400 end end end class UpdateWaterTankSettings attr_reader :coffee_maker_id, :changes, :errors def initialize(coffee_maker_id, changes) @coffee_maker_id = coffee_maker_id @changes = changes @errors = [] end def perform coffee_maker = CoffeeMaker.find(coffee_maker_id) settings = coffee_maker.settings old_temperature = settings.temperature.nil? ? "null" : "#{settings.temperature} °C" new_temperature = changes[:temperature].nil? ? "null" : "#{changes[:temperature]} °C" if new_temperature.to_i < CoffeeMaker::MIN_TEMPERATURE errors << "Brew temperature must be at least #{CoffeeMaker::MIN_TEMPERATURE} °C" return end old_temperature_text = "Temperature: #{old_temperature}. " new_temperature_text = "Temperature: #{new_temperature}. " water_tank_capacity = settings.water_tank_capacity old_capacity = water_tank_capacity.nil? ? "null" : "#{water_tank_capacity} liters" new_capacity = changes[:water_tank_capacity].nil? ? "null" : "#{changes[:water_tank_capacity]} li old_capacity_text = "Water tank capacity: #{old_capacity}." new_capacity_text = "Water tank capacity: #{new_capacity}." errors << "Water tank capacity must be between 0.5 and 10 liters" unless changes[:water_tank_capa if old_temperature_text != new_temperature_text || old_capacity_text != new_capacity_text settings_changes["#{coffee_maker_id}_settings"] = { "label" => "Coffee Maker: #{coffee_maker.get_name} Settings", "old_text" => "#{changes.key?(:temperature) ? old_temperature_text : ""}#{changes.key?(:water "new_text" => "#{changes.key?(:temperature) ? new_temperature_text : ""}#{changes.key?(:water } end return if !errors.empty? settings.temperature = changes[:temperature] settings.water_tank_capacity = changes[:water_tank_capacity] if !settings.save errors.concat(settings.errors.full_messages) end end end update_water_tank_settings.rb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 coffee_machine_settings_controller.rb
  30. class CoffeeMachineSettingsController < ApplicationController before_filter :set_cafe def update service =

    UpdateCoffeeMachinesSettings.new(@cafe, @current_user, params) new_settings = service.perform errors = service.errors if errors.empty? render json: { new_settings: new_settings }, status: 200 else render json: { update_messages: { errors: errors } }, status: 400 end end end class UpdateCoffeeMachinesSettings attr_reader :cafe, :user, :params, :errors def initialize(cafe, user, params) @cafe = cafe @user = user @params = params @errors = [] end def perform parameter_changes = params.dig(:changes, :parameter_changes) || [] return if parameter_changes.blank? coffee_maker_ids = cafe.coffee_makers.pluck(:id) settings_changes = {} Cafe.transaction do parameter_changes.each do |(coffee_maker_id, changes)| if coffee_maker_ids.exclude?(coffee_maker_id.to_i) errors << "Trying to modify settings of a coffee maker that doesn't belong to this cafe" break end service = UpdateWaterTankSettings.new(coffee_maker_id, changes) service.perform errors.concat(service.errors) break if errors.any? end if errors.empty? CafeEvent.create({ cafe_id: cafe.id, notify_at: 10.minutes.from_now.utc, user_id: user.id, changes: settings_changes, }) else raise ActiveRecord::Rollback end end rescue ActiveRecord::RecordInvalid => e errors << e.message ensure if !errors.empty? errors.unshift("One or more errors occurred. No changes have been saved.") end cafe.reload cafe.coffee_machine_data end end update_coffee_machines_settings.rb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 coffee_machine_settings_controller.rb
  31. class CoffeeMachineSettingsController < ApplicationController before_filter :set_cafe def update service =

    UpdateCoffeeMachinesSettings.new(@cafe, @current_user, params) new_settings = service.perform errors = service.errors if errors.empty? render json: { new_settings: new_settings }, status: 200 else render json: { update_messages: { errors: errors } }, status: 400 end end end class UpdateCoffeeMachinesSettings attr_reader :cafe, :user, :params, :errors def initialize(cafe, user, params) @cafe = cafe @user = user @params = params @errors = [] end def perform parameter_changes = params.dig(:changes, :parameter_changes) || [] return if parameter_changes.blank? coffee_maker_ids = cafe.coffee_makers.pluck(:id) settings_changes = {} Cafe.transaction do parameter_changes.each do |(coffee_maker_id, changes)| if coffee_maker_ids.exclude?(coffee_maker_id.to_i) errors << "Trying to modify settings of a coffee maker that doesn't belong to this cafe" break end service = UpdateWaterTankSettings.new(coffee_maker_id, changes) service.perform errors.concat(service.errors) break if errors.any? end if errors.empty? CafeEvent.create({ cafe_id: cafe.id, notify_at: 10.minutes.from_now.utc, user_id: user.id, changes: settings_changes, }) else raise ActiveRecord::Rollback end end rescue ActiveRecord::RecordInvalid => e errors << e.message ensure if !errors.empty? errors.unshift("One or more errors occurred. No changes have been saved.") end cafe.reload cafe.coffee_machine_data end end update_coffee_machines_settings.rb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 coffee_machine_settings_controller.rb
  32. app │ ├── controllers │ │ │ └── coffee_machine_settings_controller.rb │

    └── domain │ └── coffee_machine │ ├── update_coffee_machine_settings.rb │ └── update_water_tank_settings.rb Adding boundaries.
  33. app │ ├── controllers │ │ │ └── coffee_machine_settings_controller.rb │

    └── domain │ └── coffee_machine │ ├── update_coffee_machine_settings.rb │ └── update_water_tank_settings.rb Adding boundaries.
  34. app │ ├── controllers │ │ │ └── coffee_machine_settings_controller.rb │

    └── domain │ └── coffee_machine │ ├── update_settings.rb │ └── update_water_tank_settings.rb Adding boundaries.
  35. class CoffeeMachinesController < ApplicationController def configuration @coffee_machine = CoffeeMachine.find(params[:id]) @ssid

    = @coffee_machine.network && @coffee_machine.network.ssids.first @allowed_users = Users.allowed_to_use_coffee_machine(@coffee_machine) render plain: render_to_string(template: "coffee_machines/configuration") rescue ActiveRecord::RecordNotFound render plain: "Coffee machine not found", status: :not_found end end <% if !@coffee_machine.flavor_list.blank? -%> flavors_available: <%= @coffee_machine.flavor_list.join(", ") %> <% end -%> name <%= @coffee_machine.name %> system_version: <%= @coffee_machine.system_version %> <% if @ssid %> ssid_name: <%= @ssid.name %> ssid_passphrase: <%= @ssid.passphrase %> <% end -%> <% if @coffee_machine.limited_usage_mode_enabled %> limited_usage_mode: true usage_limit: <%= @coffee_machine.usage_limit %> cups <% else -%> limited_usage_mode: false <% end -%> <% if @coffee_machine.user_restrictions_enabled %> user_restrictions_enabled: true allowed_users: <%= @allowed_users.map(&:name).join(", ") %> <% else -%> user_restrictions_enabled: false <% end -%> con fi guration.erb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 1 2 3 4 5 6 7 8 9 10 11 12 13 coffee_machines_controller.rb
  36. flavors_available: blonde,medium_roast,dark_roast name: office_mermaid system_version: 1.42.0 ssid_name: office_wifi ssid_passphrase: pass123

    limited_usage_mode: true usage_limit: 5 cups user_restrictions_enabled: true allowed_users: alan,fito 1 2 3 4 5 6 7 8 9 10 con fi guration.txt
  37. class CoffeeMachinesController < ApplicationController def configuration @coffee_machine = CoffeeMachine.find(params[:id]) @ssid

    = @coffee_machine.network && @coffee_machine.network.ssids.first @allowed_users = Users.allowed_to_use_coffee_machine(@coffee_machine) render plain: render_to_string(template: "coffee_machines/configuration") rescue ActiveRecord::RecordNotFound render plain: "Coffee machine not found", status: :not_found end end <% if !@coffee_machine.flavor_list.blank? -%> flavors_available: <%= @coffee_machine.flavor_list.join(", ") %> <% end -%> name <%= @coffee_machine.name %> system_version: <%= @coffee_machine.system_version %> <% if @ssid %> ssid_name: <%= @ssid.name %> ssid_passphrase: <%= @ssid.passphrase %> <% end -%> <% if @coffee_machine.limited_usage_mode_enabled %> limited_usage_mode: true usage_limit: <%= @coffee_machine.usage_limit %> cups <% else -%> limited_usage_mode: false <% end -%> <% if @coffee_machine.user_restrictions_enabled %> user_restrictions_enabled: true allowed_users: <%= @allowed_users.map(&:name).join(", ") %> <% else -%> user_restrictions_enabled: false <% end -%> con fi guration.erb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 1 2 3 4 5 6 7 8 9 10 11 12 13 coffee_machines_controller.rb
  38. class CoffeeMachinesController < ApplicationController def configuration coffee_machine = CoffeeMachine.find_by(id: params[:id])

    if coffee_machine.present? render plain: CoffeeMachineConfiguration.new(coffee_machine).to_s, status: :ok else render plain: "Coffee machine not found", status: :not_found end end end class CoffeeMachineConfiguration attr_reader :coffee_machine def initialize(coffee_machine) @coffee_machine = coffee_machine end def to_s if !coffee_machine.flavor_list.blank? add_option("flavors_available", coffee_machine.flavor_list.join(", ")) end add_option("name", coffee_machine.name) add_option("system_version", coffee_machine.system_version) if ssid.present? add_option("ssid_name", ssid.name) add_option("ssid_passphrase", ssid.passphrase) end if coffee_machine.limited_usage_mode_enabled add_option("limited_usage_mode", true) add_option("usage_limit", coffee_machine.usage_limit) else add_option("limited_usage_mode", false) end if coffee_machine.user_restrictions_enabled add_option("user_restrictions_enabled", true) add_option("allowed_users", allowed_users.map(&:name).join(", ")) else add_option("user_restrictions_enabled", false) end end private def ssid @ssid ||= coffee_machine.network && coffee_machine.network.ssids.first end def allowed_users Users.allowed_to_use_coffee_machine(coffee_machine) end def output @output ||= "" end def add_option(key, value) output << "#{key} #{value}\n" end end coffee_machine_con fi guration.rb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 1 2 3 4 5 6 7 8 9 10 11 12 coffee_machines_controller.rb
  39. class CoffeeMachinesController < ApplicationController def configuration coffee_machine = CoffeeMachine.find_by(id: params[:id])

    if coffee_machine.present? render plain: CoffeeMachineConfiguration.new(coffee_machine).to_s, status: :ok else render plain: "Coffee machine not found", status: :not_found end end end class CoffeeMachineConfiguration attr_reader :coffee_machine def initialize(coffee_machine) @coffee_machine = coffee_machine end def to_s if !coffee_machine.flavor_list.blank? add_option("flavors_available", coffee_machine.flavor_list.join(", ")) end add_option("name", coffee_machine.name) add_option("system_version", coffee_machine.system_version) if ssid.present? add_option("ssid_name", ssid.name) add_option("ssid_passphrase", ssid.passphrase) end if coffee_machine.limited_usage_mode_enabled add_option("limited_usage_mode", true) add_option("usage_limit", coffee_machine.usage_limit) else add_option("limited_usage_mode", false) end if coffee_machine.user_restrictions_enabled add_option("user_restrictions_enabled", true) add_option("allowed_users", allowed_users.map(&:name).join(", ")) else add_option("user_restrictions_enabled", false) end end private def ssid @ssid ||= coffee_machine.network && coffee_machine.network.ssids.first end def allowed_users Users.allowed_to_use_coffee_machine(coffee_machine) end def output @output ||= "" end def add_option(key, value) output << "#{key} #{value}\n" end end coffee_machine_con fi guration.rb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 1 2 3 4 5 6 7 8 9 10 11 12 coffee_machines_controller.rb
  40. class CoffeeMachinesController < ApplicationController def configuration coffee_machine = CoffeeMachine.find_by(id: params[:id])

    if coffee_machine.present? render plain: CoffeeMachineConfiguration.new(coffee_machine).to_s, status: :ok else render plain: "Coffee machine not found", status: :not_found end end end class CoffeeMachineConfiguration attr_reader :coffee_machine def initialize(coffee_machine) @coffee_machine = coffee_machine end def to_s if !coffee_machine.flavor_list.blank? add_option("flavors_available", coffee_machine.flavor_list.join(", ")) end add_option("name", coffee_machine.name) add_option("system_version", coffee_machine.system_version) if ssid.present? add_option("ssid_name", ssid.name) add_option("ssid_passphrase", ssid.passphrase) end if coffee_machine.limited_usage_mode_enabled add_option("limited_usage_mode", true) add_option("usage_limit", coffee_machine.usage_limit) else add_option("limited_usage_mode", false) end if coffee_machine.user_restrictions_enabled add_option("user_restrictions_enabled", true) add_option("allowed_users", allowed_users.map(&:name).join(", ")) else add_option("user_restrictions_enabled", false) end end private def ssid @ssid ||= coffee_machine.network && coffee_machine.network.ssids.first end def allowed_users Users.allowed_to_use_coffee_machine(coffee_machine) end def output @output ||= "" end def add_option(key, value) output << "#{key} #{value}\n" end end coffee_machine_con fi guration.rb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 1 2 3 4 5 6 7 8 9 10 11 12 coffee_machines_controller.rb
  41. class CoffeeMachinesController < ApplicationController def configuration coffee_machine = CoffeeMachine.find_by(id: params[:id])

    if coffee_machine.present? render plain: CoffeeMachineConfiguration.new(coffee_machine).to_s, status: :ok else render plain: "Coffee machine not found", status: :not_found end end end class CoffeeMachineConfiguration attr_reader :coffee_machine def initialize(coffee_machine) @coffee_machine = coffee_machine end def to_s if !coffee_machine.flavor_list.blank? add_option("flavors_available", coffee_machine.flavor_list.join(", ")) end add_option("name", coffee_machine.name) add_option("system_version", coffee_machine.system_version) if ssid.present? add_option("ssid_name", ssid.name) add_option("ssid_passphrase", ssid.passphrase) end if coffee_machine.limited_usage_mode_enabled add_option("limited_usage_mode", true) add_option("usage_limit", coffee_machine.usage_limit) else add_option("limited_usage_mode", false) end if coffee_machine.user_restrictions_enabled add_option("user_restrictions_enabled", true) add_option("allowed_users", allowed_users.map(&:name).join(", ")) else add_option("user_restrictions_enabled", false) end end private def ssid @ssid ||= coffee_machine.network && coffee_machine.network.ssids.first end def allowed_users Users.allowed_to_use_coffee_machine(coffee_machine) end def output @output ||= "" end def add_option(key, value) output << "#{key} #{value}\n" end end coffee_machine_con fi guration.rb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 1 2 3 4 5 6 7 8 9 10 11 12 coffee_machines_controller.rb
  42. app │ ├── controllers │ │ │ └── coffee_machine_controller.rb │

    └── views │ └── coffee_machines │ └── configuration.erb Adding boundaries.
  43. app │ ├── controllers │ │ │ └── coffee_machine_controller.rb │

    └── domain │ └── coffee_machine │ └── configuration.rb Adding boundaries.
  44. class CoffeeMachinesController < ApplicationController def broadcast_details machine = CoffeeMachine.find_by(id: params[:id])

    if machine.present? render json: machine.broadcast_details, status: 200 else render status: :not_found end end end class CoffeeMachine < ApplicationRecord # ... def broadcast_details broadcasting = true supply_status = nil recent_activity = nil matching_tag = nil unless available_to_all matching_tag = tagged_in(availability_tags) if matching_tag recent_activity = "just brewed a beverage" else recent_activity = "idle" broadcasting = false end end if supply_monitoring_enabled if supplies_low? supply_status = "low" broadcasting = false else supply_status = "ok" end end reason = broadcasting ? "active" : "inactive" if supply_status reason += ", supply status: #{supply_status}" end unless recent_activity.nil? reason += recent_activity == "just brewed a beverage" ? ", recently active" : ", i end "#{name}: #{reason}" end # ... end coffee_machine.rb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 1 2 3 4 5 6 7 8 9 10 11 12 coffee_machines_controller.rb
  45. class CoffeeMachinesController < ApplicationController def broadcast_details machine = CoffeeMachine.find_by(id: params[:id])

    if machine.present? render json: machine.broadcast_details, status: 200 else render status: :not_found end end end class CoffeeMachine < ApplicationRecord # ... def broadcast_details broadcasting = true supply_status = nil recent_activity = nil matching_tag = nil unless available_to_all matching_tag = tagged_in(availability_tags) if matching_tag recent_activity = "just brewed a beverage" else recent_activity = "idle" broadcasting = false end end if supply_monitoring_enabled if supplies_low? supply_status = "low" broadcasting = false else supply_status = "ok" end end reason = broadcasting ? "active" : "inactive" if supply_status reason += ", supply status: #{supply_status}" end unless recent_activity.nil? reason += recent_activity == "just brewed a beverage" ? ", recently active" : ", i end "#{name}: #{reason}" end # ... end coffee_machine.rb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 1 2 3 4 5 6 7 8 9 10 11 12 coffee_machines_controller.rb
  46. class CoffeeMachinesController < ApplicationController def broadcast_details machine = CoffeeMachine.find_by(id: params[:id])

    if machine.present? render json: CoffeeMachineBroadcastDetails.new(machine).generate, status: 200 else render status: :not_found end end end class CoffeeMachineBroadcastDetails attr_reader :machine def initialize(machine:) @machine = machine end def generate broadcasting = true supply_status = nil recent_activity = nil unless machine.available_to_all matching_tag = machine.tagged_in(machine.availability_tags) if matching_tag recent_activity = "just brewed a beverage" else recent_activity = "idle" broadcasting = false end end if machine.supply_monitoring_enabled if machine.supplies_low? supply_status = "low" broadcasting = false else supply_status = "ok" end end reason = broadcasting ? "active" : "inactive" if supply_status reason += ", supply status: #{supply_status}" end unless recent_activity.nil? reason += recent_activity == "just brewed a beverage" ? ", recently active" : ", i end "#{name}: #{reason}" end end coffee_machine_broadcast_details.rb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 1 2 3 4 5 6 7 8 9 10 11 12 coffee_machine.rb
  47. class CoffeeMachinesController < ApplicationController def broadcast_details machine = CoffeeMachine.find_by(id: params[:id])

    if machine.present? render json: CoffeeMachineBroadcastDetails.new(machine).generate, status: 200 else render status: :not_found end end end class CoffeeMachineBroadcastDetails attr_reader :machine def initialize(machine:) @machine = machine end def generate broadcasting = true supply_status = nil recent_activity = nil unless machine.available_to_all matching_tag = machine.tagged_in(machine.availability_tags) if matching_tag recent_activity = "just brewed a beverage" else recent_activity = "idle" broadcasting = false end end if machine.supply_monitoring_enabled if machine.supplies_low? supply_status = "low" broadcasting = false else supply_status = "ok" end end reason = broadcasting ? "active" : "inactive" if supply_status reason += ", supply status: #{supply_status}" end unless recent_activity.nil? reason += recent_activity == "just brewed a beverage" ? ", recently active" : ", i end "#{name}: #{reason}" end end coffee_machine_broadcast_details.rb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 1 2 3 4 5 6 7 8 9 10 11 12 coffee_machine.rb
  48. app │ ├── controllers │ │ │ └── coffee_machine_controller.rb │

    └── models │ └── coffee_machine.rb Adding boundaries.
  49. app │ ├── controllers │ │ │ └── coffee_machine_controller.rb │

    ├── models │ │ │ └── coffee_machine.rb │ └── domain │ └── coffee_machine │ └── broadcast_details.rb Adding boundaries.
  50. app │ └── domain │ └── coffee_machine │ ├── broadcast_details.rb

    │ ├── configuration.rb │ ├── update_settings.rb │ └── update_water_tank_settings.rb Adding boundaries.
  51. app │ └── domain │ └── coffee_machine │ ├── broadcast_details.rb

    │ ├── configuration.rb │ ├── update_settings.rb │ └── update_water_tank_settings.rb Adding boundaries.
  52. app │ └── domain │ └── coffee_machine │ ├── broadcast_details.rb

    │ ├── configuration.rb │ ├── update_settings.rb │ └── update_water_tank_settings.rb Adding boundaries.
  53. fi [email protected] @Fito @ fi to.codegardener.com [email protected] @aridlehoover @alan.codegardener.com the.codegardener.com

    E-MAIL GITHUB BLUESKY BLOG PODCAST OSS https://podcast.the.codegardener.com fi rsttry.software