denshobato 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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