denshobato 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,60 @@
1
+ module Denshobato
2
+ module MessageHelper
3
+ def send_message(text, recipient)
4
+ # This method sends message directly to the recipient
5
+ # Takes responsibility to create conversation if it doesn`t exist yet
6
+ # sends message to an existing conversation
7
+
8
+ # Find conversation.
9
+ room = hato_conversation.find_by(sender: self, recipient: recipient)
10
+
11
+ # If conversation doesn`t exist, create one.
12
+ conversation = room.nil? ? create_conversation(self, recipient) : room
13
+
14
+ # Return validation error, if conversation is a String (see create_conversation method)
15
+ return errors.add(:blacklist, conversation) if conversation.is_a?(String)
16
+
17
+ # Create message for this conversation.
18
+ send_message_to(conversation.id, body: text)
19
+ end
20
+
21
+ def send_message_to(id, params)
22
+ # This method sends message directly to conversation
23
+
24
+ return errors.add(:message, 'Conversation not present') unless id
25
+
26
+ # Expect record id
27
+ # If id == active record object, get it`s id
28
+ id = id.id if id.is_a?(ActiveRecord::Base)
29
+
30
+ room = hato_conversation.find(id)
31
+
32
+ # Show validation error if the author of a message is not in conversation
33
+ return message_error(id, self) unless user_in_conversation(room, self)
34
+
35
+ # If everything is ok, build message
36
+ hato_messages.build(params)
37
+ end
38
+
39
+ private
40
+
41
+ def create_conversation(sender, recipient)
42
+ room = sender.make_conversation_with(recipient)
43
+
44
+ # Get validation error
45
+ room.valid? ? room.save && room : room.errors[:blacklist].join('')
46
+ end
47
+
48
+ def message_error(id, author)
49
+ # TODO: Return validation error in the most efficient way
50
+
51
+ author.hato_messages.build(conversation_id: id)
52
+ end
53
+
54
+ def user_in_conversation(room, author)
55
+ # Check if user is in conversation as sender or recipient
56
+
57
+ hato_conversation.where(id: room.id, sender: author).present? || hato_conversation.where(id: room.id, recipient: author).present?
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,21 @@
1
+ module Denshobato
2
+ module HelperUtils
3
+ private
4
+
5
+ def class_name(klass)
6
+ klass.class.name
7
+ end
8
+
9
+ def hato_conversation
10
+ Denshobato::Conversation
11
+ end
12
+
13
+ def hato_message
14
+ Denshobato::Message
15
+ end
16
+
17
+ def hato_blacklist
18
+ Denshobato::Blacklist
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,61 @@
1
+ module Denshobato
2
+ module ViewHelper
3
+ include Denshobato::HelperUtils
4
+ include Denshobato::ViewMessagingHelper
5
+
6
+ def conversation_exists?(sender, recipient)
7
+ # Check if sender and recipient already have conversation together.
8
+
9
+ hato_conversation.find_by(sender: sender, recipient: recipient)
10
+ end
11
+
12
+ def can_create_conversation?(sender, recipient)
13
+ # If current sender is current recipient, return false
14
+
15
+ sender == recipient ? false : true
16
+ end
17
+
18
+ def user_in_black_list?(blocker, blocked)
19
+ hato_blacklist.where(blocker: blocker, blocked: blocked).present?
20
+ end
21
+
22
+ def devise_url_helper(action, user, controller)
23
+ # Polymorphic devise urls
24
+ # E.g, you have two models, seller and customer
25
+ # You can create helper (like current_account)
26
+ # Use this method for url's
27
+
28
+ # devise_url_helper(:edit, current_account, :registration)
29
+ # => :edit_seller_registration, or :edit_customer_registration
30
+
31
+ "#{action}_#{user.class.name.downcase}_#{controller}".to_sym
32
+ end
33
+
34
+ def fill_conversation_form(form, recipient)
35
+ # = form_for @conversation do |form|
36
+ ### = fill_conversation_form(form, @conversation)
37
+ ### = f.submit 'Start Chating', class: 'btn btn-primary'
38
+
39
+ recipient_id = form.hidden_field :recipient_id, value: recipient.id
40
+ recipient_type = form.hidden_field :recipient_type, value: recipient.class.name
41
+
42
+ recipient_id + recipient_type
43
+ end
44
+
45
+ def fill_message_form(form, user, room_id)
46
+ # @message = current_user.build_conversation_message(@conversation)
47
+ # = form_for [@conversation, @message] do |form|
48
+ ### = form.text_field :body
49
+ ### = fill_message_form(form, @message)
50
+ ### = form.submit
51
+
52
+ room_id = room_id.id if room_id.is_a?(ActiveRecord::Base)
53
+
54
+ sender_id = form.hidden_field :sender_id, value: user.id
55
+ sender_class = form.hidden_field :sender_type, value: user.class.name
56
+ conversation_id = form.hidden_field :conversation_id, value: room_id
57
+
58
+ sender_id + sender_class + conversation_id
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,52 @@
1
+ module Denshobato
2
+ module ViewMessagingHelper
3
+ # OPTIMIZE: Metaprogram interlocutors methods.
4
+
5
+ def interlocutor_avatar(user, image_column, conversation, css_class)
6
+ sender = conversation.sender
7
+ recipient = conversation.recipient
8
+
9
+ return show_image(sender, image_column, css_class) if user == sender
10
+ return show_image(recipient, image_column, css_class) if user == recipient
11
+ end
12
+
13
+ def interlocutor_name(user, conversation, *fields)
14
+ sender = conversation.sender
15
+ recipient = conversation.recipient
16
+
17
+ return show_filter(sender, fields) if fields.any? && user == sender
18
+ return show_filter(recipient, fields) if fields.any? && user == recipient
19
+ end
20
+
21
+ def message_from(message, *fields)
22
+ # Show information about message creator
23
+
24
+ return unless message
25
+ show_filter(message.author, fields)
26
+ end
27
+
28
+ def interlocutor_info(klass, *fields)
29
+ show_filter(klass, fields)
30
+ end
31
+
32
+ def interlocutor_image(user, column, css_class)
33
+ show_image(user, column, css_class)
34
+ end
35
+
36
+ private
37
+
38
+ def show_image(user, image, css_class)
39
+ # Show image_tag with user avatar and css class
40
+
41
+ image_tag(user.try(image) || '', class: css_class)
42
+ end
43
+
44
+ def show_filter(klass, fields)
45
+ # Adds fields to View
46
+ # h3 = "Conversation with: #{interlocutor_name(user, conversation, :first_name, :last_name)}"
47
+ # => Conversation with John Doe
48
+
49
+ fields.each_with_object([]) { |field, array| array << klass.send(:try, field) }.join(' ').strip
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,13 @@
1
+ module Denshobato
2
+ class Blacklist < ::ActiveRecord::Base
3
+ self.table_name = 'denshobato_blacklists'
4
+
5
+ # Set up polymorphic association
6
+ belongs_to :blocker, polymorphic: true
7
+ belongs_to :blocked, polymorphic: true
8
+
9
+ # Validation
10
+ validates :blocker_id, :blocker_type, uniqueness: { scope: [:blocked_id, :blocked_type], message: 'User already in your blacklist' }
11
+ validates :blocked_id, :blocked_type, :blocker_id, :blocker_type, presence: true
12
+ end
13
+ end
@@ -0,0 +1,103 @@
1
+ module Denshobato
2
+ class Conversation < ::ActiveRecord::Base
3
+ include Denshobato::HelperUtils
4
+
5
+ self.table_name = 'denshobato_conversations'
6
+
7
+ # Set-up Polymorphic association
8
+ belongs_to :sender, polymorphic: true
9
+ belongs_to :recipient, polymorphic: true
10
+
11
+ # Has Many association
12
+ has_many :denshobato_notifications, class_name: '::Denshobato::Notification', dependent: :destroy
13
+
14
+ # Validate fields
15
+ validates :sender_id, :sender_type, :recipient_id, :recipient_type, presence: true
16
+ validate :conversation_uniqueness, on: :create
17
+ before_validation :check_sender # Sender can't create conversation with himself
18
+ before_validation :blocked_user # Check if blocked user tries to start conversation
19
+
20
+ # Callbacks
21
+ after_create :recipient_conversation # Create conversation for recipient, where he is sender.
22
+ after_destroy :remove_messages, if: :both_conversation_removed? # Remove messages and notifications
23
+
24
+ # Scopes
25
+ scope :my_conversations, lambda { |user, bool|
26
+ bool ? where(trashed: bool, sender: user).order(updated_at: :desc) : includes(:recipient).where(trashed: bool, sender: user).order(updated_at: :desc)
27
+ }
28
+
29
+ # Methods
30
+ def messages
31
+ # Return all messages of conversation
32
+
33
+ ids = notifications.pluck(:message_id)
34
+ hato_message.where(id: ids)
35
+ end
36
+
37
+ def to_trash
38
+ # Move conversation to trash
39
+
40
+ bool = block_given? ? yield : true
41
+ update(trashed: bool)
42
+ end
43
+
44
+ def from_trash
45
+ # Move conversation from trash
46
+
47
+ to_trash { false }
48
+ end
49
+
50
+ # Alias
51
+ alias notifications denshobato_notifications
52
+
53
+ private
54
+
55
+ def recipient_conversation
56
+ if hato_conversation.where(recipient: sender, sender: recipient).present?
57
+ errors.add(:conversation, 'You already have conversation with this user')
58
+ else
59
+ recipient.make_conversation_with(sender).save
60
+ end
61
+ end
62
+
63
+ def check_sender
64
+ errors.add(:conversation, 'You can`t create conversation with yourself') if sender == recipient
65
+ end
66
+
67
+ def conversation_uniqueness
68
+ # Check conversation for uniqueness, when recipient is sender, and vice versa.
69
+
70
+ hash = Hash[*columns.flatten] # => { sender_id: 1, sender_type: 'User' ... }
71
+
72
+ errors.add(:conversation, 'You already have conversation with this user.') if hato_conversation.where(hash).present?
73
+ end
74
+
75
+ def remove_messages
76
+ # When sender and recipient remove their conversation together
77
+ # remove all messages and notifications belonging to this conversation
78
+
79
+ hato_message.where(id: messages.map(&:id)).destroy_all
80
+ notifications.destroy_all
81
+ end
82
+
83
+ def both_conversation_removed?
84
+ # Check when both conversations are removed
85
+
86
+ hato_conversation.where(sender: recipient, recipient: sender).empty?
87
+ end
88
+
89
+ def blocked_user
90
+ if hato_blacklist.where(blocker: recipient, blocked: sender).present?
91
+ errors.add(:blacklist, 'You`re in blacklist')
92
+ end
93
+
94
+ if hato_blacklist.where(blocker: sender, blocked: recipient).present?
95
+ errors.add(:blacklist, 'Remove user from blacklist, to start conversation')
96
+ end
97
+ end
98
+
99
+ def columns
100
+ [['sender_id', sender_id], ['sender_type', sender_type], ['recipient_id', recipient_id], ['recipient_type', recipient_type]]
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,96 @@
1
+ module Denshobato
2
+ class Message < ::ActiveRecord::Base
3
+ attr_accessor :conversation_id
4
+
5
+ self.table_name = 'denshobato_messages'
6
+
7
+ # Associations
8
+ belongs_to :author, polymorphic: true
9
+ has_many :denshobato_notifications, class_name: '::Denshobato::Notification'
10
+
11
+ # Validations
12
+ before_validation :access_to_posting_message
13
+ validates :body, :author_id, :author_type, presence: true
14
+
15
+ # Callbacks
16
+ before_destroy :skip_deleting_messages, if: :message_belongs_to_conversation?
17
+
18
+ # Alias
19
+ alias notifications denshobato_notifications
20
+
21
+ # Methods
22
+ def send_notification(id)
23
+ # Find current conversation
24
+ conversation = hato_conversation.find(id)
25
+
26
+ # Create Notifications
27
+ create_notifications_for(conversation)
28
+ end
29
+
30
+ def message_time
31
+ # Formatted time for chat panel
32
+
33
+ created_at.strftime('%a %b %d | %I:%M %p')
34
+ end
35
+
36
+ private
37
+
38
+ def skip_deleting_messages
39
+ errors.add(:base, 'Can`t delete message, as long as it belongs to the conversation')
40
+
41
+ # The before_destroy callback needs a true/false value to determine whether or not to proceeed
42
+ false
43
+ end
44
+
45
+ def access_to_posting_message
46
+ return unless conversation_id
47
+
48
+ room = hato_conversation.find(conversation_id)
49
+
50
+ # If author of message is not present in conversation, show error
51
+
52
+ errors.add(:message, 'You can`t post to this conversation') unless user_in_conversation(room, author)
53
+ end
54
+
55
+ def create_notifications_for(conversation)
56
+ # Take sender and recipient
57
+ sender = conversation.sender
58
+ recipient = conversation.recipient
59
+
60
+ # Find conversation where sender it's recipient
61
+ conversation_2 = recipient.find_conversation_with(sender)
62
+
63
+ # If recipient deletes conversation, create it for him
64
+ conversation_2 = create_conversation_for_recipient(sender, recipient) if conversation_2.nil?
65
+
66
+ # Send notifications for new messages to sender and recipient
67
+ [conversation.id, conversation_2.id].each { |id| notifications.create(conversation_id: id) }
68
+ end
69
+
70
+ def user_in_conversation(room, author)
71
+ # Check if user is already in conversation
72
+
73
+ hato_conversation.where(id: room.id, sender: author).present? || hato_conversation.where(id: room.id, recipient: author).present?
74
+ end
75
+
76
+ def create_conversation_for_recipient(sender, recipient)
77
+ # Create Conversation for recipient
78
+ # Skip callbacks, because conversation for sender exists already
79
+
80
+ conv = hato_conversation.new(sender: recipient, recipient: sender)
81
+ hato_conversation.skip_callback(:create, :after, :recipient_conversation)
82
+ conv.save
83
+ conv
84
+ end
85
+
86
+ def message_belongs_to_conversation?
87
+ # Check if message has live notifications for any conversation
88
+
89
+ hato_conversation.where(id: notifications.pluck(:conversation_id)).present?
90
+ end
91
+
92
+ def hato_conversation
93
+ Denshobato::Conversation
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,17 @@
1
+ module Denshobato
2
+ class Notification < ::ActiveRecord::Base
3
+ self.table_name = 'denshobato_notifications'
4
+
5
+ # Associations
6
+ belongs_to :denshobato_message, class_name: 'Denshobato::Message', foreign_key: 'message_id', dependent: :destroy
7
+ belongs_to :denshobato_conversation, class_name: 'Denshobato::Conversation', foreign_key: 'conversation_id', touch: true
8
+
9
+ # Validations
10
+ validates :message_id, :conversation_id, presence: true
11
+ validates :message_id, uniqueness: { scope: :conversation_id }
12
+
13
+ # Aliases
14
+ alias message denshobato_message
15
+ alias conversation denshobato_conversation
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ module Denshobato
2
+ VERSION = '0.0.1'.freeze
3
+ end
data/lib/denshobato.rb ADDED
@@ -0,0 +1,35 @@
1
+ require 'denshobato/version'
2
+ require 'denshobato/engine' if defined?(Rails)
3
+
4
+ # Helpers
5
+ Denshobato.autoload :HelperUtils, 'denshobato/helpers/helper_utils'
6
+ Denshobato.autoload :ViewHelper, 'denshobato/helpers/view_helper'
7
+ Denshobato.autoload :ControllerHelper, 'denshobato/helpers/controller_helper'
8
+
9
+ # View Helpers for messaging
10
+ Denshobato.autoload :ViewMessagingHelper, 'denshobato/helpers/view_messaging_helper'
11
+
12
+ module Denshobato
13
+ if defined?(ActiveRecord::Base)
14
+ require 'denshobato/extenders/core' # denshobato_for method
15
+
16
+ # Active Record Models
17
+ Denshobato.autoload :Conversation, 'denshobato/models/conversation'
18
+ Denshobato.autoload :Message, 'denshobato/models/message'
19
+ Denshobato.autoload :Notification, 'denshobato/models/notification'
20
+ Denshobato.autoload :Blacklist, 'denshobato/models/blacklist'
21
+
22
+ # Add helper methods to core model
23
+ Denshobato.autoload :ConversationHelper, 'denshobato/helpers/core_modules/conversation_helper'
24
+ Denshobato.autoload :MessageHelper, 'denshobato/helpers/core_modules/message_helper'
25
+ Denshobato.autoload :BlacklistHelper, 'denshobato/helpers/core_modules/blacklist_helper'
26
+ Denshobato.autoload :ChatPanelHelper, 'denshobato/helpers/core_modules/chat_panel_helper' if defined?(DenshobatoChatPanel)
27
+ Denshobato.autoload :CoreHelper, 'denshobato/helpers/core_helper'
28
+
29
+ ActiveRecord::Base.extend Denshobato::Extenders::Core
30
+ end
31
+
32
+ # Include Helpers
33
+ ActionView::Base.include Denshobato::ViewHelper if defined?(ActionView::Base)
34
+ ActionController::Base.include Denshobato::ControllerHelper if defined?(ActionController::Base)
35
+ end
@@ -0,0 +1,40 @@
1
+ module Denshobato
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ source_root File.dirname(__FILE__)
5
+ desc 'Add the migrations'
6
+
7
+ def copy_conversations
8
+ p 'Copying migrations'
9
+
10
+ copy_file './migrations/create_conversations.rb', "db/migrate/#{time}_create_denshobato_conversations.rb"
11
+ end
12
+
13
+ def copy_messages
14
+ sleep 1
15
+ copy_file './migrations/create_messages.rb', "db/migrate/#{time}_create_denshobato_messages.rb"
16
+ end
17
+
18
+ def copy_notifications
19
+ sleep 1
20
+ copy_file './migrations/create_notifications.rb', "db/migrate/#{time}_create_denshobato_notifications.rb"
21
+ end
22
+
23
+ def copy_blacklists
24
+ sleep 1
25
+ copy_file './migrations/create_blacklists.rb', "db/migrate/#{time}_create_denshobato_blacklists.rb"
26
+ end
27
+
28
+ def done
29
+ puts 'Denshobato Installed'
30
+ puts 'Run rake db:migrate'
31
+ end
32
+
33
+ private
34
+
35
+ def time
36
+ DateTime.now.to_s(:number)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,8 @@
1
+ class CreateDenshobatoBlacklists < ActiveRecord::Migration
2
+ def change
3
+ create_table :denshobato_blacklists do |t|
4
+ t.references :blocker, polymorphic: true, index: { name: 'blocker_user' }
5
+ t.references :blocked, polymorphic: true, index: { name: 'blocked_user' }
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,11 @@
1
+ class CreateDenshobatoConversations < ActiveRecord::Migration
2
+ def change
3
+ create_table :denshobato_conversations do |t|
4
+ t.boolean :trashed, default: false
5
+ t.references :sender, polymorphic: true, index: { name: 'conversation_polymorphic_sender' }
6
+ t.references :recipient, polymorphic: true, index: { name: 'conversation_polymorphic_recipient' }
7
+
8
+ t.timestamps null: false
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ class CreateDenshobatoMessages < ActiveRecord::Migration
2
+ def change
3
+ create_table :denshobato_messages do |t|
4
+ t.text :body, default: ''
5
+ t.references :author, polymorphic: true, index: { name: 'message_polymorphic_author' }
6
+
7
+ t.timestamps null: false
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ class CreateDenshobatoNotifications < ActiveRecord::Migration
2
+ def change
3
+ create_table :denshobato_notifications do |t|
4
+ t.integer :message_id, index: { name: 'notification_for_message' }
5
+ t.integer :conversation_id, index: { name: 'notification_for_conversation' }
6
+ end
7
+
8
+ add_index :denshobato_notifications, [:message_id, :conversation_id], name: 'unique_messages_for_conversations', unique: true
9
+ end
10
+ end