Assume the following contrived (but plausible) geometric model in which
every Edge has two Vertices, every Vertex has many edges.
create_table “vertices”, :force => true do |t|
t.float “x”
t.float “y”
t.float “z”
end
create_table “edges”, :force => true do |t|
t.integer “vertex_a_id”
t.integer “vertex_b_id”
end
When we destroy a Vertex, we want all of its attached Edges to be
destroyed as well:
class Vertex << ActiveRecord::Base
has_many :edges, :dependent => :destroy
end
class Edge << ActiveRecord::Base
belongs_to :vertex_a, :class_name => ‘Vertex’, :foreign_key =>
‘vertex_a_id’
belongs_to :vertex_b, :class_name => ‘Vertex’, :foreign_key =>
‘vertex_b_id’
end
Not surprisingly, the above associations won’t work when you try to
delete a vertex:
va = Vertex.create
=> #<Vertex id: 1…>
vb = Vertex.create
=> #<Vertex id: 2…>
e = Edge.create(:vertex_a => va, :vertex_b => vb)
=> #<Edge id: 1, vertex_a_id: 1, vertex_b_id: 2…>
vb.destroy
=> ActiveRecord::StatementInvalid: Mysql::Error: Unknown column
‘edges.vertex_id’ in ‘where clause’: SELECT * FROM edges
WHERE
(edges
.vertex_id = 2)
=====
… since Edge does not have a column named ‘vertex_id’.
So: What’s the Rails-approved technique for destroying all Edges when
you destroy their Vertex, regardless of the Vertex’s role (vertex_a or
vertex_b)?
TIA.
So I know it’s bad form to answer your own question, but I realized this
must be a job for callbacks, so I RTFM’d. My new definition for Vertex
is:
class Vertex < ActiveRecord::Base
before_destroy do |record|
Edge.destroy_all “vertex_a_id = #{record.id}”
Edge.destroy_all “vertex_b_id = #{record.id}”
end
end
It appears to do exactly what you’d expect: deleting a Vertex deletes
all connected Edges.
Note that I also deleted the “:has_many edges” association for the same
reason: Edge lacks the vertex_id field that AR depends upon to make the
association, so you can’t ask for “va.edges()”. But it’s simple enough
to mimic its functionality, as in:
class Vertex
def edges
Edge.find_all_by_vertex_a_id(self.id) +
Edge.find_all_by_vertex_b_id(self.id)
end
end
Thanks for listening…
Fearless F. wrote:
So I know it’s bad form to answer your own question, but I realized this
must be a job for callbacks, so I RTFM’d. My new definition for Vertex
is:
class Vertex < ActiveRecord::Base
before_destroy do |record|
Edge.destroy_all “vertex_a_id = #{record.id}”
Edge.destroy_all “vertex_b_id = #{record.id}”
end
end
It appears to do exactly what you’d expect: deleting a Vertex deletes
all connected Edges.
Great! You can probably unify those into one query and get better
performance at the expense of a bit of Rails magic.
Note that I also deleted the “:has_many edges” association for the same
reason: Edge lacks the vertex_id field that AR depends upon to make the
association, so you can’t ask for “va.edges()”. But it’s simple enough
to mimic its functionality, as in:
class Vertex
def edges
Edge.find_all_by_vertex_a_id(self.id) +
Edge.find_all_by_vertex_b_id(self.id)
end
end
…which you can now probably use a variant of in :finder_sql to restore
your has_many :edges. Again, you may be able to refactor it into one
query.
Thanks for listening…
Best,
Marnen Laibow-Koser
http://www.marnen.org
[email protected]
Resurrecting an old thread because it has a useful lesson:
Marnen Laibow-Koser wrote in post #927576:
Fearless F. wrote:
[stuff about a before_destroy method using two destroy_all calls]
Great! You can probably unify those into one query and get better
performance at the expense of a bit of Rails magic.
[stuff about an edges method that concatenates two arrays]
…which you can now probably use a variant of in :finder_sql to restore
your has_many :edges. Again, you may be able to refactor it into one
query.
So as usual, Marnen is right (if not ahead of his time). Using the
scoping constructs in Rails 3.0, this all becomes elegant and simple:
class Edge << ActiveRecord::Base
belongs_to :vertex_a, :class_name => ‘Vertex’, :foreign_key =>
‘vertex_a_id’
belongs_to :vertex_b, :class_name => ‘Vertex’, :foreign_key =>
‘vertex_b_id’
scope :connected_to, lambda (vertex) {
where(“edges.vertex_a_id = ? OR edges.vertex_b_id = ?”, vertex.id,
vertex.id
}
end
class Vertex < ActiveRecord::Base
# caution: we are using delete_all for efficiency
before_destroy { |vertex| Edge.connected_to(vertex).delete_all }
end
This generates exactly the compact SQL that you’d expect, both for
finding:
Edge.connected_to(v1)
=> SELECT “edges.*” FROM “edges” WHERE (edges.vertex_a_id = 2 OR
edges.vertex_b_id = 2)
and for deleting:
Edge.connected_to(v1).delete_all
=> DELETE FROM “edges” WHERE (edges.vertex_a_id = 2 OR
edges.vertex_b_id = 2)
On 12 August 2011 18:35, Fearless F. [email protected] wrote:
Resurrecting an old thread because it has a useful lesson:
Marnen Laibow-Koser wrote in post #927576:
[snip]
O Marnen, Marnen! wherefore art thou Marnen?
Walter D. wrote in post #1016477:
On Aug 12, 2011, at 4:07 PM, Colin L. wrote:
On 12 August 2011 18:35, Fearless F. [email protected] wrote:
Resurrecting an old thread because it has a useful lesson:
Marnen Laibow-Koser wrote in post #927576:
[snip]
O Marnen, Marnen! wherefore art thou Marnen?
Fun fact: “wherefore” means “why”. [1]
Indeed.
I wonder where Marnen went, but I never wondered why he was Marnen. I
did appreciate his unique brand of scorched earth and incredibly
precise answers.
Um, thanks…? I don’t know whether to feel flattered or put down.
Anyway, I’m still around. I just haven’t been posting much. I went on
a self-imposed vacation from posting back in January (because I was
getting too upset to respond as nicely as I wanted to!), and never got
back into the habit. I’ll probably be a more regular contributor again
in the near future.
Walter
- In the famous quote, Juliet is wondering why Romeo is named the
way he is; because of his name, she cannot be allowed to love him. Her
logic, typical for Shakespeare women, is quite sound: “A rose by any
other name would smell as sweet…”
Yup.
Best,
Marnen Laibow-Koser
http://www.marnen.org
[email protected]
On Aug 28, 2011, at 6:23 PM, Marnen Laibow-Koser wrote:
Um, thanks…? I don’t know whether to feel flattered or put down.
I hope you feel flattered, at least that was what I was aiming for. I
did very much enjoy your posts, and learned quite a lot from them. I
hope you can find a middle path between saint-like “suffer fools
gladly” and and “getting too upset” to start answering again. I’m sure
you have quite a lot more to teach me, and others here.
Walter
On Aug 12, 2011, at 4:07 PM, Colin L. wrote:
On 12 August 2011 18:35, Fearless F. [email protected] wrote:
Resurrecting an old thread because it has a useful lesson:
Marnen Laibow-Koser wrote in post #927576:
[snip]
O Marnen, Marnen! wherefore art thou Marnen?
Fun fact: “wherefore” means “why”. [1]
I wonder where Marnen went, but I never wondered why he was Marnen. I
did appreciate his unique brand of scorched earth and incredibly
precise answers.
Walter
- In the famous quote, Juliet is wondering why Romeo is named the
way he is; because of his name, she cannot be allowed to love him. Her
logic, typical for Shakespeare women, is quite sound: “A rose by any
other name would smell as sweet…”