Don't delete if has_many > 0

My two models are ProductType and Product. ProductType has_many
Products.

ProductType.destroy should fail and set an error if products.count > 0.

Before I re-invent the wheel, there must be some rails way of
approaching this problem. Any thoughts?

Thanks!

Try something like this in your model:

def before_destroy
raise “Type as associated products” if products.count > 0
end

Steve

photo matt wrote:

My two models are ProductType and Product.
ProductType has_many Products.

ProductType.destroy should fail and set an error
if products.count > 0.

Before I re-invent the wheel, there must be some
rails way of approaching this problem. Any thoughts?

If you’ve (1) included belongs_to ProductType in your Products model,
and
(2) included ProductType_id as a foreign key in your Products table,
then
what you’re looking for is the default behavior.

hth,
Bill

Bill and Steve,

Thank you for your responses.

Steve–your suggestion simply raises an exception, which is not the most
elegant solution. I’d rather use an error that can actually be
returned.

Bill – you may be right, but my application is not working like that.
First, maybe I need a little lesson on naming things…

My ProductType model pulls from a database table called product_types.
My Product model pulls from a table named products, and has the
following association: belongs_to :product_type
The products table also has a column named product_type_id.

Is my problem that I’ve used underscores in my table names? I thought
that was the proper table naming convention for names that have two
words.

Thanks,

Matt

Hi Matt,

photo matt wrote:

First, thank you for signing your post. Maybe I’m just getting old but
I
like to know with whom I’m conversing.

my application is not working like that.

Then it’s broke. But then you already knew that :wink:

I’ll apologize up front for the shortness of my response. I don’t have
much
time at the moment and hope you won’t be offended.

Here’s what I’d do if I had 20 minutes and your code. It’s what I
always
do when I’m going to try something new or having a problem.

Do NOT use any of the Rails magic you don’t absolutely have to except
for
scaffolding. The scaffold code works. Always. Unless you’ve screwed
up
something it depends on. And that’s what you need to find out. If
you’re
using migrations, don’t for this. Because that might be where you’re
problem is. Use a script. Same principle applies across the board.
Focus
on what you’re trying to discover and don’t include ANYTHING that might
obscure that.

  1. Set up a database named ‘sandbox’ (if you don’t already have one)
  2. Create the tables you need to investigate the thing you’re interested
    in.
    In your case you need two tables. Use the same convention you’re
    already
    using. Other than pluralization, Rails doesn’t give a damn whether you
    use
    underscores / CamelCase or not. Use the scaffolding to learn. The
    product_types table should only have two fields: id and a text field
    named
    whatever you’ve got it named in the app you’re having a problem with.
    The
    products table should only have three fields: id, product_type_id, and a
    text field.
  3. Scaffold your sandbox app on both the product and product_type
    models.
  4. Add the relations to your models.
  5. Add a product type
  6. Add a product
  7. Try to delete the product type. You should get an error. If you
    don’t,
    the problem’s in your setup / config. If you do, the problem’s in your
    (other) code.

I’m sorry I can’t offer more right now. I’ll be back in 8-10 hours.
Let us
know how it goes.

Best regards,
Bill

Hi Matt,

photo matt wrote:

I managed to reproduce the same behavior in
a sandbox.

Excellent! Please post your sandbox code. Including your SQL script.

I’ve also searched the documentation and I can’t
seem to find where it says that this is the default
behavior.

Not surprising. It’s an implied understanding about the way databases
work
You may well have discovered and reported a bug!

Good on ya, bud! And even if it’s not a bug, we’ll all learn something.

Best regards,
Bill

Bill,

Thank you for your suggestions. I managed to reproduce the same
behavior in a sandbox. I’ve also searched the documentation and I can’t
seem to find where it says that this is the default behavior.

This definitely can be enforced at the database level, and maybe it
should be done there?

Thanks,

Matt

photo matt wrote:

Unfortunately, Rails migrations don’t provide a
database-independent way to specify these foreign key
constraints, so we had to resort to executing native
DDL statements (in this case, those of MySQL)."

Which is precisely why I said, in my earlier response:

If you’re using migrations, don’t for this. Because that
might be where you’re problem is. Use a script.

" Do I contradict myself? Very well, then I contradict myself, I am
large, I
contain multitudes. "
Walt Whitman

Congrats on your learning, though. Good post. You’ve undoubtedly
helped
someone who will follow your footsteps. I hope you see that as a Good
Thing.

Best regards,
Bill

" Do I contradict myself? Very well, then I contradict myself, I am large,
I
contain multitudes. "
Walt Whitman

The allusion was to Rails.

Well, after a bit more research, I’m pretty convinced that this just
isn’t the way rails works.

According to Agile Web D. with Rails, “Notice that this table
has two foreign keys. Each row in the line_items table is associated
both with an order and with a product. Unfortunately, Rails migrations
don’t provide a database-independent way to specify these foreign key
constraints, so we had to resort to executing native DDL statements (in
this
case, those of MySQL).”

Furthermore, the has_many relationship has a :dependent attribute that
can be set to :destroy_all, :delete_all, or :nullify. It seems to me
that it should have a :delete_restrict option, but it doesn’t.

If you are using InnoDB tables on MySQL, you can use database level
foreign key constraints that will protect the dependent rows from being
orphaned. I’ll do that, and I am reasonably confident it will work.
But it seems to me that Rails should have some method of preventing rows
from being orphaned.

http://dev.mysql.com/doc/refman/5.0/en/innodb-foreign-key-constraints.html

Thanks,

Matt

Bill W. wrote:

Hi Matt,

photo matt wrote:

I managed to reproduce the same behavior in
a sandbox.

Excellent! Please post your sandbox code. Including your SQL script

I’ve also searched the documentation and I can’t
seem to find where it says that this is the default
behavior.

Not surprising. It’s an implied understanding about the way databases
work
You may well have discovered and reported a bug!

Good on ya, bud! And even if it’s not a bug, we’ll all learn something.

Best regards,
Bill

Here is my ruby solution to this problem.

In the product type model, implement the before_destroy method. This
gives us a chance to cancel the destroy by returning false.
def before_destroy
if products.count() > 0
return false
end
end

In the product types controller, check the return value of destroy, and
if it is false, output an error message.

def destroy
if !ProductType.find(params[:id]).destroy
flash[:notice] = “Cannot delete this product type until there are no
products with this product type.”
end
redirect_to :action => ‘list’
end

It seems to me that at least the part in the model should be an option
on the :has_many declaration. The part I’m not so sold on, is shouldn’t
that error message that I’m setting in the controller be set somewhere
in the model? But then, how would the View ever get that error message?