Build a pizza and choose your toppings
Saving customized pizzas this way, looks OK but it also depends on how the rest of application is structured. I think your biggest concern here should be the historical data integrity you mentioned in your second question.

When it comes to data, the context they live in, helps you figure out how you should treat them. For example, when running a business like a pizza store with online ordering, toppings once available to customers can't just be "deleted". They can become unavailable, discontinued or something in these terms, but not deleted. You can't respond to customers that ask you to add pepperoni to their pizzas: "Oh, we deleted pepperoni!".

Once data hit production they will always be part of your application, despite how their status changes, so you have to treat them as if they will always be around.

This is somewhat a common issue and there are a few ways to solve it. I can mentioned two of them which I've used in the past.

1st Approach: Soft Data Deletion

The first approach, would be to go with some sort of soft delete policy for your data. You can find many posts across the internet about this technique and you can find many gems for ActiveRecord which could be used right out of the box. Practically, what you achieve with soft deletes, is that, when you're deleting data, you're not really deleting them, but you mark them as deleted so you can exclude them from your queries.

The most common ways to do this is either by adding a new column to your table that indicates a record has been deleted (e.g. deleted_at:datetime, deleted:boolean), or by adding a separate, mirror table in which "deleted" data will be moved (e.g. deleted_toppings). Both options have their pros and cons so it really depends on how the application is designed.

Keep in mind that soft delete may add complexity to your app. For example what will happen when you require Topping#name to be unique and you try to add a new topping with an identical name that already belongs to a soft-deleted topping?

2nd Approach: Separate model for customized ordered pizzas

The second approach would be to treat OrderedPizzaToppings in a completely different way. The idea here is to decouple OrderedPizzaTopping from PizzaTopping.

It could look like this:

class OrderedPizza &lt; ActiveRecord::Base
  belongs_to :order

  has_many :ordered_pizza_toppings
end

class OrderedPizzaTopping &lt; ActiveRecord::Base
  belongs_to :ordered_pizza
end


Every time an ordered pizza is created you will dump each Topping's data to a OrderedPizzaTopping instance and you'll save it. For example you could have something like this:

class OrderedPizzaTopping &lt; ActiveRecord::Base
  belongs_to :ordered_pizza

  def self.from(topping = Topping.new)
    create!(data: topping.to_h)
  end
end

# ....

def create_order(pizza:, chosen_toppings:)
  Order.transaction do
    order = Order.create
    ordered_pizza = order.ordered_pizzas.create(name: pizza.name)
    ordered_pizza.toppings &lt;&lt; chosen_toppings.map { |t| OrderPizzaTopping.from(t) }
    order
  end
end


If you noticed the only thing that OrderedPizzaTopping does is to store a hash version of PizzaTopping. Just a simple data structure containing name, description, price, image url, etc. Now, even if the PizzaTopping gets hard-deleted at some point, you'll always have the raw data which will allow you to represent the Order in any way you want. Optionally, you could store a reference to the original PizzaTopping in case it exists in your database.

Historical data integrity is not about deleted data only. What will happen if someone changes the price for a Topping that has been around for a year? All orders that include pizzas containing this topping will be affected.  I think the keyword here is auditing. Auditing a model, means to log data changes throughout time.