7 Replies - 4500 Views - Last Post: 20 December 2012 - 04:41 PM Rate Topic: -----

#1 NotarySojac  Icon User is offline

  • D.I.C Regular
  • member icon

Reputation: 53
  • View blog
  • Posts: 428
  • Joined: 30-September 10

Delete child records: has_many *, :dependent => :destroy

Posted 19 December 2012 - 06:37 PM

I'm adding records to a database from a chat server process running outside of my rails app. There are chat_rooms and chat_messages which belong to chat rooms. I'm trying to set it up so that when I delete a chat_room (from within rails), all of it's chat_messages are deleted as well, which I thought would be a sinch, but it doesn't seem to be working.

The primary key is effectively 'room_hash' rather than ID, because the process that adds messages to the database only knows the room_hash, not the ID of that room. I have the feeling that's where things must get a little complicated... I tried playing with primary key stuff to no avail.

(chat_room.rb)
class ChatRoom < ActiveRecord::Base
  validates :room_hash, :uniqueness => true
  has_many :chat_messages, :foreign_key => :room_hash, :dependent => :destroy  # damn I don't know how to pull this off outside of rails
  .
  .
  .
end



(chat_room.rb)
class ChatMessage < ActiveRecord::Base
  belongs_to :chat_room, :foreign_key => :room_hash
  
  def decrypted_msg
    self.msg.tr('+/', '-_').unpack('m')[0]
  end
end



So when I try to delete a chat_room, it doesn't decrease the ChatMessages.count amount. What else do I need to do to complete the relationship?

This post has been edited by NotarySojac: 19 December 2012 - 06:55 PM


Is This A Good Question/Topic? 0
  • +

Replies To: Delete child records: has_many *, :dependent => :destroy

#2 NotarySojac  Icon User is offline

  • D.I.C Regular
  • member icon

Reputation: 53
  • View blog
  • Posts: 428
  • Joined: 30-September 10

Re: Delete child records: has_many *, :dependent => :destroy

Posted 19 December 2012 - 09:37 PM

It seems as though primary keys must be integers, so I'm thinking what I should have done was have rails handle putting the messages in the database and built an API for doing that from external processes/servers. I guess you live and you learn =/ Luckily it's not such a big deal because chat messages aren't intended for destruction really.
Was This Post Helpful? 0
  • +
  • -

#3 sepp2k  Icon User is online

  • D.I.C Lover
  • member icon

Reputation: 2017
  • View blog
  • Posts: 3,046
  • Joined: 21-June 11

Re: Delete child records: has_many *, :dependent => :destroy

Posted 20 December 2012 - 08:30 AM

What columns do your tables have? According to your validation, your ChatRoom table has a column named room_hash. According to your associations ChatMessage also has a room_hash column. But the assocation's use ChatRoom's id column, not it's room_hash column (unless I misunderstood how :foreign_key and :primary_key work in Rails associations). So that looks problematic to me.

View PostNotarySojac, on 20 December 2012 - 05:37 AM, said:

It seems as though primary keys must be integers


I'm not sure that's true. And even if it were, you never set room_hash to be the actual primary key of ChatRoom, so that wouldn't be a problem.
Was This Post Helpful? 1
  • +
  • -

#4 NotarySojac  Icon User is offline

  • D.I.C Regular
  • member icon

Reputation: 53
  • View blog
  • Posts: 428
  • Joined: 30-September 10

Re: Delete child records: has_many *, :dependent => :destroy

Posted 20 December 2012 - 11:15 AM

View Postsepp2k, on 20 December 2012 - 08:30 AM, said:

What columns do your tables have? According to your validation, your ChatRoom table has a column named room_hash. According to your associations ChatMessage also has a room_hash column. But the assocation's use ChatRoom's id column, not it's room_hash column (unless I misunderstood how :foreign_key and :primary_key work in Rails associations). So that looks problematic to me.


Here's the gist of what the faye-server.rb script is aware of as far as the data tables go.
class DatabaseStuff
  .
  .
  .
  def create_room(room_hash, admin_name, client_name)
    begin
      the_time = Time.now-Time.zone_offset('CST')
      insert_new_chatroom = @db.prepare "INSERT INTO chat_rooms (room_hash, admin_name, client_name, created_at, updated_at) VALUES (?, ?, ?, ?, ?)"
      insert_new_chatroom.execute room_hash, admin_name, client_name, the_time, the_time
    
      insert_new_chatroom.close
    ensure
      #@db.close
    end
  end
  .
  .
  .
  def record_message(room_hash, is_admin, msg, admin_name, speaker)
    create_chatroom(room_hash, admin_name, 'client_name')
    
    begin
      the_time = Time.now-Time.zone_offset('CST')
      insert_new_msg = @db.prepare "INSERT INTO chat_messages (room_hash, chat_user_id, msg, speaker, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)"
      #binding.pry
      insert_new_msg.execute room_hash, is_admin, msg, speaker, the_time, the_time
    
      insert_new_msg.close
    ensure
      #@db.close
    end
  end
  .
  .
  .
end



Here's what the schema looks like though in rails:

  create_table "chat_messages", :force => true do |t|
    t.string   "room_hash"
    t.text     "msg"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.string   "speaker"
  end

  add_index "chat_messages", ["created_at"], :name => "index_chat_messages_on_created_at"
  add_index "chat_messages", ["room_hash"], :name => "index_chat_messages_on_room_hash"

  create_table "chat_rooms", :force => true do |t|
    t.string   "room_hash"
    t.datetime "last_alive_ping"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.string   "admin_name"
    t.string   "client_name"
  end

  add_index "chat_rooms", ["room_hash"], :name => "index_chat_rooms_on_room_hash", :unique => true




Would it possibly work if I created a colummn in chat_messages called 'chat_room_room_hash'? (as that in other associations where forgien key isn't set explicitly, the columns like, say, user_id are needed as foreign keys? Other than that, I thought I was setting foreign keys as best as possible in the actual model. Am I still missing something there?

View Postsepp2k, on 20 December 2012 - 08:30 AM, said:

View PostNotarySojac, on 20 December 2012 - 05:37 AM, said:

It seems as though primary keys must be integers


I'm not sure that's true. And even if it were, you never set room_hash to be the actual primary key of ChatRoom, so that wouldn't be a problem.


I found myself at http://stackoverflow...-to-be-a-string and did a
class ChatRoom < ActiveRecord::Base  
  self.primary_key = "room_hash"  
  .
  .
  .
end



And everything went wonky after that. It turned out I couldn't set anything to @chat_room.room_hash and have it save, and when it was creating them, somehow the value of room_hash was like FixNum:86 which was discouraging to say the least.

This post has been edited by NotarySojac: 20 December 2012 - 11:26 AM

Was This Post Helpful? 0
  • +
  • -

#5 sepp2k  Icon User is online

  • D.I.C Lover
  • member icon

Reputation: 2017
  • View blog
  • Posts: 3,046
  • Joined: 21-June 11

Re: Delete child records: has_many *, :dependent => :destroy

Posted 20 December 2012 - 12:00 PM

What I think you should be doing (from my understanding of how associations with non-standard keys work in Rails) is to add :primary_key => :room_hash to your calls to belongs_to and has_many (in addition to :foreign_key => :room_hash, which you already have). I believe that this should tell Rails that a chat room's messages are those messages whose room_hash is equal to the room_hash of the chat room, while your current version says that a chat room's messages are those whose room_hash is equal to the room's id.

That is :foreign_key tells Rails what the relevant column in ChatMessage is and :primary_key tells it what the relevant column in ChatRoom is (defaulting to id). So since the relevant column in both of those tables is room_hash, you should set that as the value for both parameters.

PS: It seems dangerous to me that you rely on (and even validate) the uniqueness of a hash value. What happens in case of hash collision?
Was This Post Helpful? 2
  • +
  • -

#6 NotarySojac  Icon User is offline

  • D.I.C Regular
  • member icon

Reputation: 53
  • View blog
  • Posts: 428
  • Joined: 30-September 10

Re: Delete child records: has_many *, :dependent => :destroy

Posted 20 December 2012 - 01:21 PM

View Postsepp2k, on 20 December 2012 - 12:00 PM, said:

What I think you should be doing (from my understanding of how associations with non-standard keys work in Rails) is to add :primary_key => :room_hash to your calls to belongs_to and has_many (in addition to :foreign_key => :room_hash, which you already have). I believe that this should tell Rails that a chat room's messages are those messages whose room_hash is equal to the room_hash of the chat room, while your current version says that a chat room's messages are those whose room_hash is equal to the room's id.

That is :foreign_key tells Rails what the relevant column in ChatMessage is and :primary_key tells it what the relevant column in ChatRoom is (defaulting to id). So since the relevant column in both of those tables is room_hash, you should set that as the value for both parameters.


Oh, of course, that makes so much more sense now, thanks for pointing that out. I'm giving that a shot now and... It WORKED! Thanks! I'm glad I posted this now.


View Postsepp2k, on 20 December 2012 - 12:00 PM, said:

PS: It seems dangerous to me that you rely on (and even validate) the uniqueness of a hash value. What happens in case of hash collision?


You're probably right here. If there is a room_hash collision, it would make reading the chat logs seem as though an old customer returned from a long time ago and changed their alias. I was feeling really flustered when I first started out and I was just thinking "just make it do!" and moved on. I added it to my post-beta todo list so I don't forget about it though.

I think the collisions will be rare for my use cases, and was trying to achieve more of a GUID when I generated them. Here's the code I threw down, what do you think?

Oh, before you start commenting, I haven't had a chance to refactor the gunk out of the controller and into the model so that's why it's all in the controller right now.

(real_time_interaction_controller.rb)
class RealTimeInteractionController < ApplicationController
  def chatroom_admin
    @greeting = ""
    @title = "Admin Chat"
    @function = "admin"
    render :layout => false
  end

  def client_chat_interface
    @chatroom = create_chat_room
    @room_hash = @chatroom.room_hash
    @client_name = @chatroom.client_name
    @greeting = "One of our specialists will be with you momentarily, " + @room_hash + "."
    @title = "Customer Chat"
  end
  .
  .
  .
  # creates a new chatroom, or gets into an old one...
  def create_chat_room
    chatroom = ChatRoom.where(:room_hash => cookies[:room_hash]) unless cookies[:room_hash].nil?
    unless cookies[:room_hash].nil? or !chatroom.exists?
      return chatroom.first
    end
    
    room_hash = generate_room_hash
    cookies[:room_hash] = room_hash
    
    # Save room number in database...
    chatroom = ChatRoom.create(:room_hash => room_hash) if ChatRoom.where(:room_hash => room_hash).empty?
    
    return chatroom
  end

  def generate_room_hash
    return Digest::SHA1.hexdigest(Time.now.to_s + request.remote_ip)   # request.remote_ip
  end
end



So my hash is coming from that very last method. Now that I really look at it, I would have added some code to check that this room_hash doesn't already exist before returning it, otherwise recalculate a new hash. Would that fix collision problems?
Was This Post Helpful? 0
  • +
  • -

#7 sepp2k  Icon User is online

  • D.I.C Lover
  • member icon

Reputation: 2017
  • View blog
  • Posts: 3,046
  • Joined: 21-June 11

Re: Delete child records: has_many *, :dependent => :destroy

Posted 20 December 2012 - 03:36 PM

Why do you make a hash of that rather than just using Time.now.to_s + request.remote_ip? You'd still need to check that the string isn't already in used (because, at least theoretically, the same IP may create two rooms at the same time), but at least you'd guarantee that recalculating will fix the issue. Using the hash there's a (probably very low) chance that you'd have to recalculate a lot of times before you get a fresh value (though that chance's mostly theoretical). Also I just don't see what hashing the string adds.

But yes, recalculating the hash until it's fresh, will solve the problem. At least until all possible hashes are used up (which is probably definitely not a realistic concern).
Was This Post Helpful? 2
  • +
  • -

#8 NotarySojac  Icon User is offline

  • D.I.C Regular
  • member icon

Reputation: 53
  • View blog
  • Posts: 428
  • Joined: 30-September 10

Re: Delete child records: has_many *, :dependent => :destroy

Posted 20 December 2012 - 04:41 PM

You know, you have a good point about not needing to hash. I liked it at first because the hash gets sent to the client, and if it's completely unencrypted, it allows a hacker to guess room names a bit easier. I think I might instead use a hybrid for the GUID/hash, something like:

better_guid = XOR(Time.now.to_s + request.remote_ip)[0..half] + hash(Time.now.to_s + request.remote_ip + Rand)[half..end]



That way, from the security standpoint: the client/attacker doesn't necessarily know much about how the room names are being generated, nor would it matter if he/she did.

And from the Collision standpoint (a little more important in my current setting), colisions are many times less likely.
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1