model user's message in rails 3 - ruby-on-rails

I have built the following model to handle user's message exchange:
create_table "messages", :force => true do |t|
t.integer "source_id"
t.integer "destination_id"
t.string "object"
t.string "body"
t.datetime "created_at"
t.datetime "updated_at"
end
These are its associations:
class Message < ActiveRecord::Base
belongs_to :sender, :class_name=>'User', :foreign_key=>'source_id'
belongs_to :reciever, :class_name=>'User', :foreign_key=>'destination_id'
end
And these other are the associations on the other side (the user model):
has_many :sent_messages, :class_name=> 'Message', :foreign_key=>'source_id', :dependent=>:destroy
has_many :recieved_messages, :class_name=> 'Message', :foreign_key=>'destination_id', :dependent=>:destroy
The model is correct and work properly, in fact from the message I can retrieve who is the sender and who is the receiver and from the user, I can get all the sent and received messages. Unfortunately, It does not handle any situation: What if the receiver or the sender delete the message ? The message is unique so it disappear in both sides (bad thing). How to know if one of the side had already read the message ?
Any suggestion ? Do you think I have to replan the model ?
Tnx

this is a nice problem! I would model that to compare as closely as possible to the e-mail model. So a message always belongs to a single user, and it was either sent or received.
In short:
create_table "messages", :force => true do |t|
t.integer :user_id
t.string :subject
t.string :body
t.boolean :sent
end
And the model would like:
class Message < ActiveRecord::Base
belongs_to :user
scope :sent, where(:sent => true)
scope :received, where(:sent => false)
end
And in the user:
class User
has_many :messages
end
You would then simply be able to query all sent messages by
user.messages.sent
and the received messages
user.messages.received
Sending a message does become a bit more complicated then:
class Message
def send_message(from, recipients)
recipients.each do |recipient|
msg = self.clone
msg.sent = false
msg.user_id = recipient
msg.save
end
self.update_attributes :user_id => from.id, :sent => true
end
end
or something along those lines: you copy the message and attach it to all recipients, and lastly make the original message the sent message.
This way each user has total control over the message.
Possible improvements:
also keep an explicit reference to the sender and receiver(s) in the message, to be able to allow replies and stuff
instead of working with a single boolean, maybe allow working with folders?
Hope this helps.

You can add two booleans to mark the message as deleted for both sender and receiver. Then after setting either of them check if the message can be deleted permanently.
Example:
create_table "messages", :force => true do |t|
t.boolean :sender_deleted
t.boolean :receiver_deleted
end
And in model:
class Message
def self.delete_message(id)
m = Message.find(id)
m.destroy if m.sender_deleted && m.receiver_deleted
end
end

You can nullify on a deleted record with :dependent=>:nullify
has_many :sent_messages, :class_name=> 'Message', :foreign_key=>'source_id', :dependent=>:nullify
has_many :recieved_messages, :class_name=> 'Message', :foreign_key=>'destination_id', :dependent=>:nullify
You'll need to handle when displaying the message that the sender/receiver of the message has been deleted, since the sender_id or destination_id will be null, but the message will stay intact.

Related

In a Rails Model, will belongs_to cause a rollback if not defined?

Model:
class UserPosition < ApplicationRecord
belongs_to :user
belongs_to :job_title
end
UserPosition's schema:
t.integer :user_id
t.integer :company_id
t.integer :industry_id
t.integer :department_id
t.integer :job_title_id
t.string :job_title_custom
user_positions_controller.rb
def create
#user_position = UserPosition.find_or_create_by(user_id: current_user.id)
#user_position.update_attributes({
:industry_id => params[:industry_id],
:department_id => params[:department_id],
:job_title_id => params[:job_title_id],
:job_title_custom => params[:job_title_custom]
})
I need UserPosition to either create a record with:
user_id
job_title_custom
OR
t.integer :user_id
t.integer :company_id
t.integer :industry_id
t.integer :department_id
t.integer :job_title_id
Currently, if I try to create a UserPosition with just user_id & job_title_custom
It doesn't work, the logs show ROLLBACK the error message is:
#messages={:job_title=>["must exist"]}
What am I doing wrong here? I think it could be because job_title has a relationship defined in the model but the Rails Guide says that they are optional, so I'm not sure.
Turns out this is a new Rails 5 behavior.
"In Rails 5, whenever we define a belongs_to association, it is required to have the associated record present by default after this change.
It triggers validation error if associated record is not present."
"In Rails 4.x world To add validation on belongs_to association, we need to add option required: true ."
"Opting out of this default behavior in Rails 5. We can pass optional: true to the belongs_to association which would remove this validation check."
Full Answer: http://blog.bigbinary.com/2016/02/15/rails-5-makes-belong-to-association-required-by-default.html

I have a Post and Attachment relation. How do I create the Attachment BEFORE the Post is created

I have two models named Post and Attachment. I am directly uploading the attachment to amazon s3 using Fine-Uploader. Upon successful upload to s3 I send the media url of the uploaded file back to my client in a JSON object.
After the client receives the s3 URL I send an AJAX post request to my rails endpoint where ideally id like to create the attachment BEFORE the user actually submits the #post for creation.
Once the #attachment is created I then render the #attachment IDs back to the client in JSON format so that if/when the user submits the post I can then take the #post.id and update the #attachment post id field with the id of the #post.
This is so that if a user uploads attachments, but never submits the post, I can have a job that goes and deletes all the attachments that never got updated with a post id.
The problem that im experiencing is that whenever I try to create an attachment ( Attachment.create() ) the record creation gets rolled back.
I know that it is possible to implement this configuration because my mentor and the person who assigned me this issue is a code guru and has directed me to do it this way. The problem is that he is traveling overseas so I cannot contact him for help on this. I say that to express that I really appreciate any help you can offer but if your advice consists of departing from this method, I simply cannot. It is a must that I implement this way.
Any help getting this working is greatly appreciated.
[1] pry(main)> Attachment.create(description: 'ho ho ho', media_url: 'test.com')
(0.6ms) BEGIN
(0.8ms) ROLLBACK
=> #<Attachment:0x007f96b8c547b0
id: nil,
post_id: nil,
description: "ho ho ho",
media_url: "test.com",
created_at: nil,
updated_at: nil>
here are all my models and migrations
class Post < ApplicationRecord
has_many :attachments, :dependent => :destroy
end
and
class Attachment < ApplicationRecord
belongs_to :post
end
The migrations look like this
class CreatePosts < ActiveRecord::Migration[5.0]
def change
create_table :posts do |t|
t.references :categories
t.string :title, unique: true
t.text :body
t.text :tags, array: true, default: []
t.string :slug, unique: true
t.string :description
t.string :status
t.boolean :private_post, default: true
t.timestamps
end
add_index :posts, :slug
add_index :posts, :title
end
end
and
class CreateAttachments < ActiveRecord::Migration[5.0]
def change
create_table :attachments do |t|
t.references :post
t.string :description
t.string :media_url
t.timestamps
end
end
end
In Rails 5, whenever we define a belongs_to association, it is required to have the associated record present by default. You need to set optional: true argument to skip the validation presence.
class Attachment < ApplicationRecord
belongs_to :post, optional: true
end

how to join an associated table with a has_one association

In my Rails app, I only require users to enter email and name upon signup, but then give them the option to provide fuller contact details for their profile. Therefore, I have a User.rb model that has an association with Contact.rb, namely,
User.rb
has_one :contact
Contact.rb
belongs_to :user
Contact.rb has the predictable fields you might expect such as address, postal code etc, but it also stores the province_id for a relation with the Province.rb model, so
Contact.rb
attr_accessible :address, :city, :mobile, :postalcode, :province_id, :user_id
belongs_to :user
belongs_to :province
Province.rb
has_many :contacts
I did it that way (rather than storing the name of the province as a "string" on contact.rb) so that I could more easily (so I thought) categorize users by province.
In the show action of one of the artists_controller, I do the following to check whether the user is trying to sort by province and then call an artists_by_province method that does a search
if params[:province_id]
province = params[:province_id]
province = province.to_i #convert string to integer
#artistsbyprovince = User.artists_by_province(province)
else
#artists = User.where(:sculptor => true)
end
This is the method on the User.rb model that it calls if a province id is passed in
scope :artists_by_province, lambda {|province|
joins(:contact).
where( contact: {province_id: province},
users: {sculptor: true})
}
However it gives me this error:
Could not find table 'contact'
If I make contacts plural
scope :artists_by_province, lambda {|province|
joins(:contacts).
where( contacts: {province_id: province},
users: {sculptor: true})
}
This error
Association named 'contacts' was not found; perhaps you misspelled it?
Can anyone tell me what I'm doing wrong when I'm making this query?
Update: I changed some of the details after posting because my copy and paste had some problems with it
P.S. ignore the fact that I'm searching for a 'sculptor.' I changed the names of the user types for the question.
from schema.rb
create_table "contacts", :force => true do |t|
t.string "firm"
t.string "address"
t.string "city"
t.string "postalcode"
t.string "mobile"
t.string "office"
t.integer "user_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.integer "province_id"
end
The problem was fixed by using contact (singular) in the join and contacts (plural) in the where clause. I'm guessing 'contact' (singular) reflects the has_one association between User.rb and Contact.rb, whereas 'contacts' is used in the where clause to represent the name of the table, which is always plural.
User.rb
has_one :contact
scope :artists_by_province, lambda {|province|
joins(:contact).
where( contacts: {province_id: province},
users: {sculptor: true})
}
Can you try the following?
scope :artists_by_province, lambda {|province|
joins(contact: {province: { id: province}}).
where(sculptor: true)
}

Nested Attributes Update with a different Primary Key

I have 2 Models, I face error when i am updating them. I have used nested attributes.
class Channel < ActiveRecord::Base
self.primary_key = 'id'
has_many :channel_mappings , primary_key: 'channel_name', foreign_key: 'channel_name'
attr_accessible :channel_name, :channel_mappings_attributes
validates_presence_of :channel_name
accepts_nested_attributes_for :channel_mappings, :allow_destroy => true
end
2nd Model
class ChannelMapping < ActiveRecord::Base
self.primary_key = 'src_channel'
belongs_to :channel, primary_key: 'channel_name', foreign_key: 'channel_name'
attr_accessible :src_channel, :channel_name , :src_file_type
end
Update Method
def update
#channel = Channel.find(params[:id])
if #channel.update_attributes(params[:channel])
redirect_to #channel, notice: 'Channel was successfully updated.'
else
render action: 'edit'
end
end
Error
Type: ActiveRecord::RecordNotFound
Message: Couldn't find ChannelMapping with ID=ANY NAME for Channel with ID=2
I know it' something to do with Primary key overwritten. Any help will be useful
db/schema.rb
create_table "channels", :force => true do |t|
t.text "channel_name", :null => false
t.string "internal_flag", :limit => nil
t.string "exception_flag", :limit => nil
end
create_table "channel_mappings", :id => false, :force => true do |t|
t.text "src_channel", :null => false
t.text "channel_name", :null => false
end
You could try - #channel.attributes = params[:channel] instead of #channel.update_attributes(params[:channel])
This will also set all the attributes but without save.
Then you can call -
#channel.save
This will save your attributes.
The error seems to be record to be not found rather then update revert like.
Check the error log first and if needed then post it here if nothing works.
It would be better to use if else conditions as:
if #channel.save
#record saved
else
#error in save
end
Then you can know where it's going.
Well, in the first line of Channel.rb you're setting the primary key to be 'id'. So why are you specifying primary_key='channel_name' in your associations? That seems wrong.
Also, it will be helpful to see your definition of the channels table in db/schema.rb.
Update after additional information
In your gist, I see that your parameters contain an id key in channel_mappings_attributes. However , your schema.rb shows that channel_mappings doesn't have an id. That's the first thing you need to fix.

Inbox messages in rails like facebook

I am trying to create a personal inbox message system but couple question are on my mind. First let me explain my system.
Here is my table model
create_table "inboxmessages", :force => true do |t|
t.integer "receiver_id"
t.integer "sender_id"
t.integer "message_id"
t.boolean "isread"
t.boolean "isstarred"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "messages", :force => true do |t|
t.string "subject"
t.text "body"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
The relationship would be has follow
inboxmessages
belongs_to :message
belongs_to :user, :class_name => "User", :foreign_key => "sender_id"
belongs_to :user, :class_name => "User", :foreign_key => "receiver_id"
messages
has_many :inboxmessages
user
has_many :inboxmessages
The problem that i am having his i am uncertain on how to create a message which allows me multiple users. here the schema of the form i am trying to have
Message.subject
Inboxmessage.receiver # but have multiple different user
------------------------------------------------------------------------
Message.body
Inboxmessage.sender = current_user # hidden field
Here are the question that I have regarding building this model/controller/app
1 - Should my new form be in inboxmessages or messages?
2 - Should I use accept_nested_for or should I use nested resources
3 - Is my model/database is okay or not the best?
4 - Are my foreign_key relationship well define?
Thanks in advance!!
I would do something like this:
class User
has_many :mailboxes
has_many :messages, :through => :tags
has_many :tags
end
class Message
has_many :users, :through => :tags
has_many :mailboxes, :through => :tags
has_many :tags
end
class Mailbox
has_many :tags
has_many :messages, :through => :tags
has_many :users, :through => tags
end
class Tag
belongs_to :user
belongs_to :message
belongs_to :mailbox
# this class has "read?", "starred", etc.
end
This enables a message to appear in multiple mailboxes, for multiple users, and each user can have his/her own "read?", "starred", etc. You can limit the logic if you want to ensure that a user has only one copy of a message, i.e. the message is not in two or more mailboxes for the same user.
To improve your schema, read the Rails Guides, especially about associations like these:
belongs_to
has_one
has_many
has_many :through
inverse_of
Also look at the Rails gem acts-as-taggable-on
http://rubygems.org/gems/acts-as-taggable-on
One way to think of a message schema is "a message belongs to a mailbox, and a mailbox has many messages" (this is how Yahoo does it). Another way to think of a message is "a message has many tags, and a mailbox is simply a search by tag" (this is how Gmail does it).
By the way, the Ruby mail gem is excellent. You can use it for creating messages, and look at how it converts headers like from, to, cc, etc.
You asked good questions:
1 - Should my new form be in inboxmessages or messages?
My opinion is that you will get the most benefit if you have a model for the message that is like an email, have a model for a mailbox (or tag). To create a new message, the new form would typically be in ./messages/new.html.erb
2 - Should I use accept_nested_for or should I use nested resources?
Nested resources are fine; see the Rails guide here:
http://guides.rubyonrails.org/routing.html#nested-resources
Nested attributes are also fine; see the API here:
A good rule of thumb is to only nest one level down, because after that it gets more complicated than its worth.
3 - Is my model/database is okay or not the best?
Not the best. A real-world message will typically have a sender and receiver(s). But you're modeling the sender and receiver in the inboxmessages table. Better to model the message with has_many receivers, and use a separate model for the user's interaction with the message for example a table called "marks" with fields "starred", "isread", etc. A mark belongs to a message and belongs to a user. A message has_many marks, and a user has_many marks.
4 - Are my foreign_key relationship well define?
In general, yes. Just be aware that email is surprisingly hard to model. A good rule of thumb is to index your foreign keys. Also look at Rails associations "inverse_of" and use it; this will help with speed and memory use.
I would have my classes set up something like this.
class Inbox
has_many :messages
belongs_to :user
end
class User
has_many :messages
has_one :inbox
end
class Message
belongs_to :user
belongs_to :inbox
has_many recipients, class_name: "User", foreign_key: "recipient_id"
end

Resources