Problem
You have a Tag model with separate columns: prefix, loop (a number), and optional suffix. You want a derived attribute full_tag (e.g., “PRE-0042.SFX”) for display and forms, without persisting it. Your attempt used attr_reader, an after_find callback, and a private setter that built a local variable, which resulted in nil in the console and views.
1) attr_reader only exposes an instance variable (@full_tag). You never set @full_tag; inside set_full_tag you assign to a local variable full_tag, which is discarded.
2) You don’t need after_find at all for a computed value—just compute on demand.
3) Using loop as a column name is legal, but Kernel also defines loop. Always reference the attribute as self.loop (and self.loop= in writers) to avoid ambiguity.
4) suffix can be nil. Calling suffix.empty? will raise on nil. Use present?/blank? or to_s.
Define a plain Ruby method. It is nil‑safe and renders correctly in views and the console.
# app/models/tag.rb
class Tag < ApplicationRecord
belongs_to :project
belongs_to :discipline, foreign_key: :discipline, primary_key: :code
# Virtual, read-only attribute
def full_tag
parts = []
# prefix
parts << prefix if prefix.present?
# loop, left-padded to 4 (e.g., 42 => "0042")
parts << self.loop.to_i.to_s.rjust(4, '0') if self.loop.present?
base = parts.join('-')
return base if suffix.blank?
"#{base}.#{suffix}"
end
end
Usage in views and console:
<%= @tag.full_tag %> # Rails console Tag.first.full_tag
If you want to accept a single input and split it into prefix, loop, and suffix, add a writer. Then permit :full_tag in strong parameters.
# app/models/tag.rb
class Tag < ApplicationRecord
belongs_to :project
belongs_to :discipline, foreign_key: :discipline, primary_key: :code
def full_tag
parts = []
parts << prefix if prefix.present?
parts << self.loop.to_i.to_s.rjust(4, '0') if self.loop.present?
base = parts.join('-')
suffix.present? ? "#{base}.#{suffix}" : base
end
# Virtual attribute writer, parses "PRE-0042.SFX" or "PRE-0042"
def full_tag=(value)
return if value.blank?
head, suf = value.split('.', 2)
pre, num = head.to_s.split('-', 2)
self.prefix = pre
# strip non-digits, store as integer (or string if your column is string)
digits = num.to_s.gsub(/\D/, '')
self.loop = digits.presence
self.suffix = suf.presence
end
end
# app/controllers/tags_controller.rb def tag_params params.require(:tag).permit(:prefix, :loop, :suffix, :full_tag) end
<%= simple_form_for @tag do |f| %> <%= f.input :full_tag, label: "Tag" %> <%= f.button :submit %> <% end %>
<%= simple_form_for @tag do |f| %>
<%= f.button :submit %> <% end %>
• Local vs. instance variable: use @full_tag if you truly want to cache, but caching is unnecessary—prefer computing on demand.
• Avoid after_find: it’s extra work and can miss cases (e.g., new records, unsaved changes).
• Nil checks: use present?/blank? or to_s to guard against nil suffix/prefix.
• Column named loop: prefer self.loop and self.loop= in Ruby code; consider renaming to loop_number for clarity.
• Sorting & searching: keep sorting on the atomic columns (prefix, loop, suffix). If you need to search by the combined form, build an expression in SQL (e.g., using CONCAT/LTRIM/LPAD in your DB) or search on parts.
Work with our skilled Ruby on Rails developers to accelerate your project and boost its performance.
Hire Ruby on Rails Developer