I have two models:
class Product
belongs_to :storage_box
end
class StorageBox
validates :key
presence: true,
uniqueness: { scope: %i[kind] }
validates :kind
presence: true
def code
"#{key}#{kind.to_s.rjust(4, '0')}"
end
end
I would like to be able to set the storage_box
relation based on the code
method:
Product.new(storage_box: '150001')
How should I proceed to achieve this? I'm not sure if the Product
should handle this (seems not "rails" to do it there) or to instruct the StorageBox
how to find base on the code
.
I have two models:
class Product
belongs_to :storage_box
end
class StorageBox
validates :key
presence: true,
uniqueness: { scope: %i[kind] }
validates :kind
presence: true
def code
"#{key}#{kind.to_s.rjust(4, '0')}"
end
end
I would like to be able to set the storage_box
relation based on the code
method:
Product.new(storage_box: '150001')
How should I proceed to achieve this? I'm not sure if the Product
should handle this (seems not "rails" to do it there) or to instruct the StorageBox
how to find base on the code
.
- I really wouldn't recommend this. If you want to use a primary key that's not an auto-incrementing integer use a UUID or something else that's less likely to cause a collision. – max Commented 4 hours ago
1 Answer
Reset to default 1There are many ways you could code Product.new(storage_box: '150001')
. For example you could probably overwrite the storage_box=
method to detect a string input and generate a new StorageBox
instance to replace the string. But I would strongly recommend you do not!
The problem with that approach is that you are making the standard ActiveRecord methods work in ways that they weren't designed to. In my experience that always causes unexpected consequencies later on. Also it will confuse the next developer who touches your code.
Instead I recommend you write a dedicated class method:
def self.new_in_storage_box(code)
key = code[0..-5]
kind = code[-4..-1]
storage_box = StorageBox.find_or_create_by(key:, kind:)
new(storage_box:)
end
Your call then becomes Product.new_in_storage_box('15001')
Even better, I'd suggest you move the breakdown of the storage box code to the storage box class. So:
class StorageBox
def self.find_or_create_by_code(code)
key = code[0..-5]
kind = code[-4..-1]
find_or_create_by(key:, kind:)
end
...
end
class Product
def self.new_in_storage_box(code)
storage_box = StorageBox.find_or_create_by_code(code)
new(storage_box:)
end
...
end
That way your intension is clear and the new code will not interfere with the normal operation of the Product.new
method.