Class: FloorPlanDisplay

Inherits:
ApplicationRecord show all
Includes:
Models::Auditable, Models::Taggable
Defined in:
app/models/floor_plan_display.rb

Overview

== Schema Information

Table name: floor_plan_displays
Database name: primary

id :bigint not null, primary key
breadcrumbs :string default([]), not null, is an Array
custom_slug :string
description :text
enabled_buttons :jsonb
filter_value :string
name :string
room_types :string default([]), not null, is an Array
seo_description :text
seo_title :string
state :string
created_at :datetime not null
updated_at :datetime not null
creator_id :integer
customer_id :integer
updater_id :integer

Indexes

index_floor_plan_displays_on_creator_id (creator_id)
index_floor_plan_displays_on_custom_slug (custom_slug) UNIQUE
index_floor_plan_displays_on_customer_id (customer_id)
index_floor_plan_displays_on_enabled_buttons (enabled_buttons) USING gin
index_floor_plan_displays_on_updater_id (updater_id)

Foreign Keys

fk_rails_... (customer_id => parties.id)

Constant Summary collapse

AVAILABLE_BUTTONS =

Button management methods

%w[
  button_design_room
  button_floor_heating_quote
  button_customize_floor_plan
  button_snow_melting_quote
  button_floor_heating
  button_snow_melting
  button_roof_gutter_deicing
  button_pipe_freeze_protection
].freeze

Constants included from Models::Auditable

Models::Auditable::ALWAYS_IGNORED

Instance Attribute Summary collapse

Belongs to collapse

Methods included from Models::Auditable

#creator, #updater

Has many collapse

Methods included from Models::Taggable

#tag_records, #taggings

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Models::Taggable

#add_tag, all_tags, #has_tag?, normalize_tag_names, not_tagged_with, #remove_tag, #tag_list, #tag_list=, #taggable_type_for_tagging, tagged_with, #tags, #tags=, tags_cloud, tags_exclude, tags_include, with_all_tags, with_any_tags, without_all_tags, without_any_tags

Methods included from Models::Auditable

#all_skipped_columns, #audit_reference_data, #should_not_save_version, #stamp_record

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, ransortable_attributes, #to_relation

Methods included from Models::EventPublishable

#publish_event

Instance Attribute Details

#custom_slugObject (readonly)



53
# File 'app/models/floor_plan_display.rb', line 53

validates :custom_slug, presence: true, uniqueness: true, format: { with: /\A[a-z0-9-]+\z/, message: 'can only contain lowercase letters, numbers, and hyphens' }

#enabled_buttonsObject (readonly)



54
# File 'app/models/floor_plan_display.rb', line 54

validates :enabled_buttons, presence: true

#nameObject (readonly)

Validations

Validations:



51
# File 'app/models/floor_plan_display.rb', line 51

validates :name, presence: true

#stateObject (readonly)



52
# File 'app/models/floor_plan_display.rb', line 52

validates :state, presence: true

Class Method Details

.archivedActiveRecord::Relation<FloorPlanDisplay>

A relation of FloorPlanDisplays that are archived. Active Record Scope

Returns:

See Also:



82
# File 'app/models/floor_plan_display.rb', line 82

scope :archived, -> { where(state: 'archived') }

.draftActiveRecord::Relation<FloorPlanDisplay>

A relation of FloorPlanDisplays that are draft. Active Record Scope

Returns:

See Also:



81
# File 'app/models/floor_plan_display.rb', line 81

scope :draft, -> { where(state: 'draft') }

.filter_value_optionsObject



232
233
234
235
236
# File 'app/models/floor_plan_display.rb', line 232

def self.filter_value_options
  ['G Shaped - Peninsula', 'L Shaped', 'U Shaped', 'Single Wall or Straight Kitchen',
   'Corridor or Gallery Kitchen', 'Island Kitchen','Small', 'Medium', 'Large', '1-49 sq.ft.', '50-99 sq.ft.',
   '100-149 sq.ft.', '150-199 sq.ft.', '200+ sq.ft.', 'Shower Only', 'Shower + Bench']
end

.flush_edge_cacheObject

Class methods



206
207
208
209
# File 'app/models/floor_plan_display.rb', line 206

def self.flush_edge_cache
  # Implementation for edge cache flushing
  # This would typically call a cache service
end

.publishedActiveRecord::Relation<FloorPlanDisplay>

A relation of FloorPlanDisplays that are published. Active Record Scope

Returns:

See Also:



80
# File 'app/models/floor_plan_display.rb', line 80

scope :published, -> { where(state: 'published') }

.ransackable_scopes(_auth_object = nil) ⇒ Object

Note: all_tags method provided by Models::Taggable concern



240
241
242
# File 'app/models/floor_plan_display.rb', line 240

def self.ransackable_scopes(_auth_object = nil)
  %i[tags_include room_types_include]
end

.room_types_includeActiveRecord::Relation<FloorPlanDisplay>

A relation of FloorPlanDisplays that are room types include. Active Record Scope

Returns:

See Also:



84
85
86
87
# File 'app/models/floor_plan_display.rb', line 84

scope :room_types_include, ->(*room_type_ids) {
  ids = room_type_ids.flatten.map(&:presence).compact.uniq.map(&:to_s)
  where('room_types && ARRAY[?]::varchar[]', ids)
}

Instance Method Details

Build breadcrumb hash like Articles/Posts do



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'app/models/floor_plan_display.rb', line 175

def breadcrumbs_hash
  bc = []
  # Only use the first breadcrumb if multiple exist
  first_breadcrumb = (breadcrumbs || []).first
  return bc unless first_breadcrumb.present?

  begin
    parts = first_breadcrumb.split('@')
    path_url = nil
    path_name = nil
    if parts.size == 2
      path_name, path_url = parts
    else
      path_url = first_breadcrumb
      path_name = first_breadcrumb.split('/').last.to_s.scan(/\w+/).join(' ').humanize
    end
    bc << { url: "/#{I18n.locale}/#{path_url}".squeeze('/'), name: path_name }
  rescue StandardError
    Rails.logger.error 'Error in FloorPlanDisplay#breadcrumbs_hash path formatting'
  end
  # Append "Floor Plans" linking to the public index as the last crumb
  begin
    floor_plans_url = Rails.application.routes.url_helpers.floor_plans_path(locale: I18n.locale)
    bc << { name: 'Floor Plans', url: floor_plans_url }
  rescue StandardError
    # ignore if routes not available
  end
  bc
end

#button_display_name(button_name) ⇒ Object



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'app/models/floor_plan_display.rb', line 151

def button_display_name(button_name)
  case button_name
  when 'button_design_room'
    'Design Room'
  when 'button_floor_heating_quote'
    'Floor Heating Quote'
  when 'button_customize_floor_plan'
    'Customize Floor Plan'
  when 'button_snow_melting_quote'
    'Snow Melting Quote'
  when 'button_floor_heating'
    'Floor Heating'
  when 'button_snow_melting'
    'Snow Melting'
  when 'button_roof_gutter_deicing'
    'Roof Gutter Deicing'
  when 'button_pipe_freeze_protection'
    'Pipe Freeze Protection'
  else
    button_name.humanize
  end
end

#button_enabled?(button_name) ⇒ Boolean

Returns:

  • (Boolean)


137
138
139
# File 'app/models/floor_plan_display.rb', line 137

def button_enabled?(button_name)
  enabled_buttons.include?(button_name)
end

#customerCustomer

Associations

Returns:

See Also:



40
# File 'app/models/floor_plan_display.rb', line 40

belongs_to :customer, optional: true

#digital_assetsActiveRecord::Relation<DigitalAsset>

Returns:

See Also:



44
# File 'app/models/floor_plan_display.rb', line 44

has_many :digital_assets, through: :floor_plan_display_digital_assets

#disable_button(button_name) ⇒ Object



147
148
149
# File 'app/models/floor_plan_display.rb', line 147

def disable_button(button_name)
  self.enabled_buttons = enabled_buttons - [button_name]
end

#enable_button(button_name) ⇒ Object



141
142
143
144
145
# File 'app/models/floor_plan_display.rb', line 141

def enable_button(button_name)
  return false unless AVAILABLE_BUTTONS.include?(button_name)

  self.enabled_buttons = (enabled_buttons + [button_name]).uniq
end

#floor_plan_display_digital_assetsActiveRecord::Relation<FloorPlanDisplayDigitalAsset>

Returns:

See Also:



43
# File 'app/models/floor_plan_display.rb', line 43

has_many :floor_plan_display_digital_assets, dependent: :destroy

#floor_plan_display_room_configurationsActiveRecord::Relation<FloorPlanDisplayRoomConfiguration>

Returns:

  • (ActiveRecord::Relation<FloorPlanDisplayRoomConfiguration>)

See Also:



41
# File 'app/models/floor_plan_display.rb', line 41

has_many :floor_plan_display_room_configurations, dependent: :destroy

#main_imageObject



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'app/models/floor_plan_display.rb', line 104

def main_image
  # prefer using preloaded associations to avoid N+1
  preloaded = floor_plan_display_digital_assets
              .select { |fpda| fpda.room_configuration_id.nil? && fpda.digital_asset&.type == 'Image' }
              .min_by(&:position)
  return preloaded.digital_asset if preloaded

  # fallback query when not preloaded
  floor_plan_display_digital_assets
    .joins(:digital_asset)
    .where(room_configuration_id: nil)
    .where(digital_assets: { type: 'Image' })
    .order(:position)
    .first
    &.digital_asset
end

#purge_edge_cache(include_indirect_associations: false, extra_urls: []) ⇒ Object



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'app/models/floor_plan_display.rb', line 211

def purge_edge_cache(include_indirect_associations: false, extra_urls: [])
  urls = site_maps.map(&:url) + extra_urls
  # Also purge index and relevant room-type listing pages
  begin
    uh = Rails.application.routes.url_helpers
    urls << uh.floor_plans_path(locale: I18n.locale)
    Array(room_types).each do |rt_id|
      rt = RoomType.find_by(id: rt_id.to_i)
      next unless rt

      slug = rt.seo_key.presence || rt.name.parameterize
      urls << uh.floor_plans_by_room_type_path(slug, locale: I18n.locale)
    end
  rescue StandardError
    # Ignore routing issues in purge helper
  end
  urls = urls.compact.uniq
  EdgeCacheWorker.perform_async('urls' => urls) if urls.present?
  urls
end

#room_configurationsActiveRecord::Relation<RoomConfiguration>

Returns:

  • (ActiveRecord::Relation<RoomConfiguration>)

See Also:



42
# File 'app/models/floor_plan_display.rb', line 42

has_many :room_configurations, through: :floor_plan_display_room_configurations

#site_mapsActiveRecord::Relation<SiteMap>

Returns:

  • (ActiveRecord::Relation<SiteMap>)

See Also:



45
# File 'app/models/floor_plan_display.rb', line 45

has_many :site_maps, as: :resource, dependent: :destroy

#to_paramObject



121
122
123
# File 'app/models/floor_plan_display.rb', line 121

def to_param
  custom_slug
end

#total_cost_to_operateObject

Methods



90
91
92
93
94
95
96
# File 'app/models/floor_plan_display.rb', line 90

def total_cost_to_operate
  if association(:floor_plan_display_room_configurations).loaded?
    floor_plan_display_room_configurations.map { |rc| rc.cost_to_operate.to_f }.sum
  else
    floor_plan_display_room_configurations.sum(:cost_to_operate)
  end
end

#total_flooring_area_sqftObject

Sum of installation coverage across linked room configurations



99
100
101
102
# File 'app/models/floor_plan_display.rb', line 99

def total_flooring_area_sqft
  # assumes room_configurations and their nested associations are eager loaded by caller
  room_configurations.map { |rc| rc.respond_to?(:installation_sqft) ? rc.installation_sqft.to_f : 0.0 }.sum.round
end