├── .gitignore ├── Gemfile ├── Gemfile.lock ├── README.md ├── Rakefile ├── app ├── assets │ ├── config │ │ └── manifest.js │ ├── images │ │ └── .keep │ ├── javascripts │ │ ├── application.js │ │ ├── cable.js │ │ └── channels │ │ │ └── .keep │ └── stylesheets │ │ └── application.scss ├── channels │ └── application_cable │ │ ├── channel.rb │ │ └── connection.rb ├── controllers │ ├── application_controller.rb │ ├── concerns │ │ └── .keep │ ├── conversations_controller.rb │ ├── messages_controller.rb │ ├── sessions_controller.rb │ └── users_controller.rb ├── jobs │ └── application_job.rb ├── mailers │ └── application_mailer.rb ├── models │ ├── application_record.rb │ ├── concerns │ │ └── .keep │ ├── conversation.rb │ ├── message.rb │ └── user.rb ├── uploaders │ └── image_uploader.rb └── views │ ├── application │ ├── _header.html.erb │ └── _notices.html.erb │ ├── conversations │ └── index.html.erb │ ├── layouts │ ├── application.html.erb │ ├── mailer.html.erb │ └── mailer.text.erb │ ├── messages │ └── index.html.erb │ ├── sessions │ └── new.html.erb │ └── users │ └── new.html.erb ├── bin ├── bundle ├── rails ├── rake ├── setup ├── spring └── update ├── config.ru ├── config ├── application.rb ├── boot.rb ├── cable.yml ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── application_controller_renderer.rb │ ├── assets.rb │ ├── backtrace_silencers.rb │ ├── carrierwave.rb │ ├── cookies_serializer.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── mime_types.rb │ ├── new_framework_defaults.rb │ ├── session_store.rb │ └── wrap_parameters.rb ├── locales │ ├── devise.en.yml │ └── en.yml ├── puma.rb ├── routes.rb ├── secrets.yml └── spring.rb ├── db ├── images │ ├── chetan.png │ ├── ed.jpg │ ├── jason.jpg │ ├── jules.jpg │ ├── mike.png │ ├── toni.jpg │ └── will.jpg ├── migrate │ ├── 20160808105118_create_users.rb │ ├── 20160808110229_create_conversations.rb │ └── 20160808111922_create_messages.rb ├── schema.rb └── seeds.rb ├── lib ├── assets │ └── .keep └── tasks │ └── .keep ├── log └── .keep ├── public ├── 404.html ├── 422.html ├── 500.html ├── apple-touch-icon-precomposed.png ├── apple-touch-icon.png ├── favicon.ico └── robots.txt ├── screen-shot-1.png ├── screen-shot-2.png ├── test ├── controllers │ ├── .keep │ ├── conversations_controller_test.rb │ ├── messages_controller_test.rb │ ├── sessions_controller_test.rb │ └── users_controller_test.rb ├── fixtures │ ├── .keep │ ├── files │ │ └── .keep │ └── messages.yml ├── helpers │ └── .keep ├── integration │ └── .keep ├── mailers │ └── .keep ├── models │ ├── .keep │ └── message_test.rb └── test_helper.rb └── tmp └── .keep /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all logfiles and tempfiles. 11 | /log/* 12 | /tmp/* 13 | !/log/.keep 14 | !/tmp/.keep 15 | 16 | # Ignore Byebug command history file. 17 | .byebug_history 18 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rails', '~> 5.0.0' 4 | gem 'pg', '~> 0.18' 5 | gem 'puma', '~> 3.0' 6 | gem 'bootstrap-sass', '~> 3.3.6' 7 | gem 'sass-rails', '~> 5.0' 8 | gem 'uglifier', '>= 1.3.0' 9 | gem 'coffee-rails', '~> 4.2' 10 | gem 'bcrypt', '~> 3.1.7' 11 | gem 'carrierwave' 12 | gem 'fog', require: 'fog/aws' 13 | gem 'rmagick' 14 | 15 | gem 'jquery-rails' 16 | gem 'turbolinks', '~> 5' 17 | gem 'jbuilder', '~> 2.5' 18 | 19 | group :development, :test do 20 | gem 'byebug', platform: :mri 21 | end 22 | 23 | group :development do 24 | gem 'web-console' 25 | gem 'listen', '~> 3.0.5' 26 | gem 'spring' 27 | gem 'spring-watcher-listen', '~> 2.0.0' 28 | end 29 | 30 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] 31 | 32 | ruby "2.3.1" 33 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (2.3.2) 5 | actioncable (5.0.0) 6 | actionpack (= 5.0.0) 7 | nio4r (~> 1.2) 8 | websocket-driver (~> 0.6.1) 9 | actionmailer (5.0.0) 10 | actionpack (= 5.0.0) 11 | actionview (= 5.0.0) 12 | activejob (= 5.0.0) 13 | mail (~> 2.5, >= 2.5.4) 14 | rails-dom-testing (~> 2.0) 15 | actionpack (5.0.0) 16 | actionview (= 5.0.0) 17 | activesupport (= 5.0.0) 18 | rack (~> 2.0) 19 | rack-test (~> 0.6.3) 20 | rails-dom-testing (~> 2.0) 21 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 22 | actionview (5.0.0) 23 | activesupport (= 5.0.0) 24 | builder (~> 3.1) 25 | erubis (~> 2.7.0) 26 | rails-dom-testing (~> 2.0) 27 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 28 | activejob (5.0.0) 29 | activesupport (= 5.0.0) 30 | globalid (>= 0.3.6) 31 | activemodel (5.0.0) 32 | activesupport (= 5.0.0) 33 | activerecord (5.0.0) 34 | activemodel (= 5.0.0) 35 | activesupport (= 5.0.0) 36 | arel (~> 7.0) 37 | activesupport (5.0.0) 38 | concurrent-ruby (~> 1.0, >= 1.0.2) 39 | i18n (~> 0.7) 40 | minitest (~> 5.1) 41 | tzinfo (~> 1.1) 42 | arel (7.1.1) 43 | autoprefixer-rails (6.3.7) 44 | execjs 45 | bcrypt (3.1.11) 46 | bootstrap-sass (3.3.7) 47 | autoprefixer-rails (>= 5.2.1) 48 | sass (>= 3.3.4) 49 | builder (3.2.2) 50 | byebug (9.0.5) 51 | carrierwave (0.10.0) 52 | activemodel (>= 3.2.0) 53 | activesupport (>= 3.2.0) 54 | json (>= 1.7) 55 | mime-types (>= 1.16) 56 | coffee-rails (4.2.1) 57 | coffee-script (>= 2.2.0) 58 | railties (>= 4.0.0, < 5.2.x) 59 | coffee-script (2.4.1) 60 | coffee-script-source 61 | execjs 62 | coffee-script-source (1.10.0) 63 | concurrent-ruby (1.0.2) 64 | debug_inspector (0.0.2) 65 | erubis (2.7.0) 66 | excon (0.51.0) 67 | execjs (2.7.0) 68 | ffi (1.9.14) 69 | fission (0.5.0) 70 | CFPropertyList (~> 2.2) 71 | fog (1.38.0) 72 | fog-aliyun (>= 0.1.0) 73 | fog-atmos 74 | fog-aws (>= 0.6.0) 75 | fog-brightbox (~> 0.4) 76 | fog-cloudatcost (~> 0.1.0) 77 | fog-core (~> 1.32) 78 | fog-dynect (~> 0.0.2) 79 | fog-ecloud (~> 0.1) 80 | fog-google (<= 0.1.0) 81 | fog-json 82 | fog-local 83 | fog-openstack 84 | fog-powerdns (>= 0.1.1) 85 | fog-profitbricks 86 | fog-rackspace 87 | fog-radosgw (>= 0.0.2) 88 | fog-riakcs 89 | fog-sakuracloud (>= 0.0.4) 90 | fog-serverlove 91 | fog-softlayer 92 | fog-storm_on_demand 93 | fog-terremark 94 | fog-vmfusion 95 | fog-voxel 96 | fog-vsphere (>= 0.4.0) 97 | fog-xenserver 98 | fog-xml (~> 0.1.1) 99 | ipaddress (~> 0.5) 100 | fog-aliyun (0.1.0) 101 | fog-core (~> 1.27) 102 | fog-json (~> 1.0) 103 | ipaddress (~> 0.8) 104 | xml-simple (~> 1.1) 105 | fog-atmos (0.1.0) 106 | fog-core 107 | fog-xml 108 | fog-aws (0.11.0) 109 | fog-core (~> 1.38) 110 | fog-json (~> 1.0) 111 | fog-xml (~> 0.1) 112 | ipaddress (~> 0.8) 113 | fog-brightbox (0.11.0) 114 | fog-core (~> 1.22) 115 | fog-json 116 | inflecto (~> 0.0.2) 117 | fog-cloudatcost (0.1.2) 118 | fog-core (~> 1.36) 119 | fog-json (~> 1.0) 120 | fog-xml (~> 0.1) 121 | ipaddress (~> 0.8) 122 | fog-core (1.42.0) 123 | builder 124 | excon (~> 0.49) 125 | formatador (~> 0.2) 126 | fog-dynect (0.0.3) 127 | fog-core 128 | fog-json 129 | fog-xml 130 | fog-ecloud (0.3.0) 131 | fog-core 132 | fog-xml 133 | fog-google (0.1.0) 134 | fog-core 135 | fog-json 136 | fog-xml 137 | fog-json (1.0.2) 138 | fog-core (~> 1.0) 139 | multi_json (~> 1.10) 140 | fog-local (0.3.0) 141 | fog-core (~> 1.27) 142 | fog-openstack (0.1.10) 143 | fog-core (>= 1.40) 144 | fog-json (>= 1.0) 145 | ipaddress (>= 0.8) 146 | fog-powerdns (0.1.1) 147 | fog-core (~> 1.27) 148 | fog-json (~> 1.0) 149 | fog-xml (~> 0.1) 150 | fog-profitbricks (0.0.5) 151 | fog-core 152 | fog-xml 153 | nokogiri 154 | fog-rackspace (0.1.1) 155 | fog-core (>= 1.35) 156 | fog-json (>= 1.0) 157 | fog-xml (>= 0.1) 158 | ipaddress (>= 0.8) 159 | fog-radosgw (0.0.5) 160 | fog-core (>= 1.21.0) 161 | fog-json 162 | fog-xml (>= 0.0.1) 163 | fog-riakcs (0.1.0) 164 | fog-core 165 | fog-json 166 | fog-xml 167 | fog-sakuracloud (1.7.5) 168 | fog-core 169 | fog-json 170 | fog-serverlove (0.1.2) 171 | fog-core 172 | fog-json 173 | fog-softlayer (1.1.3) 174 | fog-core 175 | fog-json 176 | fog-storm_on_demand (0.1.1) 177 | fog-core 178 | fog-json 179 | fog-terremark (0.1.0) 180 | fog-core 181 | fog-xml 182 | fog-vmfusion (0.1.0) 183 | fission 184 | fog-core 185 | fog-voxel (0.1.0) 186 | fog-core 187 | fog-xml 188 | fog-vsphere (1.0.0) 189 | fog-core 190 | rbvmomi (~> 1.8) 191 | fog-xenserver (0.2.3) 192 | fog-core 193 | fog-xml 194 | fog-xml (0.1.2) 195 | fog-core 196 | nokogiri (~> 1.5, >= 1.5.11) 197 | formatador (0.2.5) 198 | globalid (0.3.7) 199 | activesupport (>= 4.1.0) 200 | i18n (0.7.0) 201 | inflecto (0.0.2) 202 | ipaddress (0.8.3) 203 | jbuilder (2.6.0) 204 | activesupport (>= 3.0.0, < 5.1) 205 | multi_json (~> 1.2) 206 | jquery-rails (4.1.1) 207 | rails-dom-testing (>= 1, < 3) 208 | railties (>= 4.2.0) 209 | thor (>= 0.14, < 2.0) 210 | json (2.0.2) 211 | listen (3.0.8) 212 | rb-fsevent (~> 0.9, >= 0.9.4) 213 | rb-inotify (~> 0.9, >= 0.9.7) 214 | loofah (2.0.3) 215 | nokogiri (>= 1.5.9) 216 | mail (2.6.4) 217 | mime-types (>= 1.16, < 4) 218 | method_source (0.8.2) 219 | mime-types (3.1) 220 | mime-types-data (~> 3.2015) 221 | mime-types-data (3.2016.0521) 222 | mini_portile2 (2.1.0) 223 | minitest (5.9.0) 224 | multi_json (1.12.1) 225 | nio4r (1.2.1) 226 | nokogiri (1.6.8) 227 | mini_portile2 (~> 2.1.0) 228 | pkg-config (~> 1.1.7) 229 | pg (0.18.4) 230 | pkg-config (1.1.7) 231 | puma (3.6.0) 232 | rack (2.0.1) 233 | rack-test (0.6.3) 234 | rack (>= 1.0) 235 | rails (5.0.0) 236 | actioncable (= 5.0.0) 237 | actionmailer (= 5.0.0) 238 | actionpack (= 5.0.0) 239 | actionview (= 5.0.0) 240 | activejob (= 5.0.0) 241 | activemodel (= 5.0.0) 242 | activerecord (= 5.0.0) 243 | activesupport (= 5.0.0) 244 | bundler (>= 1.3.0, < 2.0) 245 | railties (= 5.0.0) 246 | sprockets-rails (>= 2.0.0) 247 | rails-dom-testing (2.0.1) 248 | activesupport (>= 4.2.0, < 6.0) 249 | nokogiri (~> 1.6.0) 250 | rails-html-sanitizer (1.0.3) 251 | loofah (~> 2.0) 252 | railties (5.0.0) 253 | actionpack (= 5.0.0) 254 | activesupport (= 5.0.0) 255 | method_source 256 | rake (>= 0.8.7) 257 | thor (>= 0.18.1, < 2.0) 258 | rake (11.2.2) 259 | rb-fsevent (0.9.7) 260 | rb-inotify (0.9.7) 261 | ffi (>= 0.5.0) 262 | rbvmomi (1.8.2) 263 | builder 264 | nokogiri (>= 1.4.1) 265 | trollop 266 | rmagick (2.15.4) 267 | sass (3.4.22) 268 | sass-rails (5.0.6) 269 | railties (>= 4.0.0, < 6) 270 | sass (~> 3.1) 271 | sprockets (>= 2.8, < 4.0) 272 | sprockets-rails (>= 2.0, < 4.0) 273 | tilt (>= 1.1, < 3) 274 | spring (1.7.2) 275 | spring-watcher-listen (2.0.0) 276 | listen (>= 2.7, < 4.0) 277 | spring (~> 1.2) 278 | sprockets (3.7.0) 279 | concurrent-ruby (~> 1.0) 280 | rack (> 1, < 3) 281 | sprockets-rails (3.1.1) 282 | actionpack (>= 4.0) 283 | activesupport (>= 4.0) 284 | sprockets (>= 3.0.0) 285 | thor (0.19.1) 286 | thread_safe (0.3.5) 287 | tilt (2.0.5) 288 | trollop (2.1.2) 289 | turbolinks (5.0.1) 290 | turbolinks-source (~> 5) 291 | turbolinks-source (5.0.0) 292 | tzinfo (1.2.2) 293 | thread_safe (~> 0.1) 294 | uglifier (3.0.1) 295 | execjs (>= 0.3.0, < 3) 296 | web-console (3.3.1) 297 | actionview (>= 5.0) 298 | activemodel (>= 5.0) 299 | debug_inspector 300 | railties (>= 5.0) 301 | websocket-driver (0.6.4) 302 | websocket-extensions (>= 0.1.0) 303 | websocket-extensions (0.1.2) 304 | xml-simple (1.1.5) 305 | 306 | PLATFORMS 307 | ruby 308 | 309 | DEPENDENCIES 310 | bcrypt (~> 3.1.7) 311 | bootstrap-sass (~> 3.3.6) 312 | byebug 313 | carrierwave 314 | coffee-rails (~> 4.2) 315 | fog 316 | jbuilder (~> 2.5) 317 | jquery-rails 318 | listen (~> 3.0.5) 319 | pg (~> 0.18) 320 | puma (~> 3.0) 321 | rails (~> 5.0.0) 322 | rmagick 323 | sass-rails (~> 5.0) 324 | spring 325 | spring-watcher-listen (~> 2.0.0) 326 | turbolinks (~> 5) 327 | tzinfo-data 328 | uglifier (>= 1.3.0) 329 | web-console 330 | 331 | RUBY VERSION 332 | ruby 2.3.1p112 333 | 334 | BUNDLED WITH 335 | 1.12.5 336 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Creating an internal messaging system in Rails 5: a walkthrough 2 | 3 | > **Note:** This app was lifted almost line by line from [an excellent article by Dana Muller](https://medium.com/@danamulder/tutorial-create-a-simple-messaging-system-on-rails-d9b94b0fbca1#.bheztdsw0). I've made a few changes to her solution, but I highly recommend reading the article. 4 | 5 | > **Note:** This guide assumes you've already implemented a user authentication system with BCrypt. 6 | 7 | ## Overview 8 | 9 | ![inbox](https://raw.githubusercontent.com/mickyginger/rails-conversations/master/screen-shot-1.png) 10 | 11 | ![messges](https://raw.githubusercontent.com/mickyginger/rails-conversations/master/screen-shot-2.png) 12 | 13 | The system requires 3 models: `User`, `Message` and `Conversation`. The conversation is simply a container for messages. It is essentially a relationship between two users. The system we are going to create will effectively mimic a phone text message system, where the user clicks on the name of a contact and all of the messages between those two people will be displayed there. 14 | 15 | ## Setup 16 | 17 | We will not be using `scaffold` at all throughout this walkthrough, since we only need a few, very specific views. 18 | 19 | ## Conversation model 20 | 21 | The conversation model and respective database table is quite unusual, so we're going to build it piecemeal, starting with a custom migration: 22 | 23 | `rails g migration CreateConversations` 24 | 25 | This will give you the following migration: 26 | ```ruby 27 | class CreateConversations < ActiveRecord::Migration[5.0] 28 | def change 29 | create_table :conversations do |t| 30 | end 31 | end 32 | end 33 | ``` 34 | Update it as follows: 35 | 36 | ```ruby 37 | class CreateConversations < ActiveRecord::Migration[5.0] 38 | def change 39 | create_table :conversations do |t| 40 | t.integer :sender_id 41 | t.integer :receiver_id 42 | 43 | t.timestamps 44 | end 45 | end 46 | end 47 | ``` 48 | 49 | Then run the migration with `rake db:migrate` 50 | 51 | Since the sender_id *and* the receiver_id will both be user ids, we need to construct a very specific model. First we'll manually create the model file: 52 | 53 | `touch app/models/conversation.rb` 54 | 55 | Then flesh out the model: 56 | 57 | ```ruby 58 | class Conversation < ApplicationRecord 59 | belongs_to :sender, class_name: "User", foreign_key: "sender_id" 60 | belongs_to :receiver, class_name: "User", foreign_key: "receiver_id" 61 | end 62 | ``` 63 | 64 | Here, we've indicated that the `sender_id` and `receiver_id` are both ids from the user table. So when we do `conversation.sender` or `conversation.receiver`, ActiveRecord will attempt to find a user with those ids. 65 | 66 | But wait, there's more... 67 | 68 | A conversation will have many messages, so we can add that relationship to the model now as well: 69 | 70 | ```ruby 71 | class Conversation < ApplicationRecord 72 | belongs_to :sender, class_name: "User", foreign_key: "sender_id" 73 | belongs_to :receiver, class_name: "User", foreign_key: "receiver_id" 74 | has_many :messages, dependent: :destroy 75 | end 76 | ``` 77 | 78 | We're also going to add a validation as well. We want to make sure that only one conversation is created between two users, regardless of who is the `receiver` and who is the `sender`. 79 | 80 | If Sarah starts a conversation with Bradley, Sarah is the `sender` and Bradley the `receiver`. If Bradley wants to send Sarah a message, he should use the same conversation that has already been set up between those two users, even though he is now the `sender`. 81 | 82 | ```ruby 83 | class Conversation < ApplicationRecord 84 | belongs_to :sender, class_name: "User", foreign_key: "sender_id" 85 | belongs_to :receiver, class_name: "User", foreign_key: "receiver_id" 86 | has_many :messages, dependent: :destroy 87 | 88 | validates_uniqueness_of :sender_id, scope: :receiver_id 89 | end 90 | ``` 91 | 92 | The `scope` option limits the uniqueness check. 93 | 94 | > **Note:** more info on ActiveRecord validations can be found in the [official docs](http://guides.rubyonrails.org/active_record_validations.html) 95 | 96 | Finally, we need to add a class method `between` which will allow us to find the conversation between to users, regardless of who is the `sender` and who is the `receiver`. To create a class method with ActiveRecord, we use the `scope` method, like so: 97 | 98 | ```ruby 99 | class Conversation < ApplicationRecord 100 | belongs_to :sender, class_name: "User", foreign_key: "sender_id" 101 | belongs_to :receiver, class_name: "User", foreign_key: "receiver_id" 102 | has_many :messages, dependent: :destroy 103 | 104 | validates_uniqueness_of :sender_id, scope: :receiver_id 105 | 106 | scope :between, -> (sender_id,receiver_id) do 107 | where("(conversations.sender_id = ? AND conversations.receiver_id = ?) OR (conversations.receiver_id = ? AND conversations.sender_id = ?)", sender_id, receiver_id, sender_id, receiver_id) 108 | end 109 | end 110 | ``` 111 | 112 | Here we have created some custom SQL. `Conversation.between` will take two arguments, both user ids, and try to find a conversation that has either one as sender OR receiver. We be using this later. 113 | 114 | ## Message model 115 | 116 | This will be a lot simpler! We can generate the model directly like so: 117 | 118 | `rails g model Message body:text conversation:references user:references read:boolean` 119 | 120 | Before running the migration that is created, we need to set `read` to be `false` by default. Amend the migration like so: 121 | 122 | ```ruby 123 | class CreateMessages < ActiveRecord::Migration[5.0] 124 | def change 125 | create_table :messages do |t| 126 | t.text :body 127 | t.references :conversation, foreign_key: true 128 | t.references :user, foreign_key: true 129 | t.boolean :read, default: false 130 | 131 | t.timestamps 132 | end 133 | end 134 | end 135 | ``` 136 | 137 | Then `rake db:migrate` 138 | 139 | We're just going to add a validation to the message model to ensure all of the fields are filled in: 140 | 141 | ```ruby 142 | class Message < ApplicationRecord 143 | belongs_to :conversation 144 | belongs_to :user 145 | 146 | validates_presence_of :body, :conversation_id, :user_id 147 | end 148 | ``` 149 | 150 | While we're here, we can add some formatting to the timestamp, so we can display the time of the message in a more human readable way: 151 | 152 | ```ruby 153 | class Message < ApplicationRecord 154 | belongs_to :conversation 155 | belongs_to :user 156 | 157 | validates_presence_of :body, :conversation_id, :user_id 158 | 159 | private 160 | def message_time 161 | created_at.strftime("%d/%m/%y at %l:%M %p") 162 | end 163 | end 164 | ``` 165 | 166 | Done and done. 167 | 168 | ## Routes 169 | 170 | The routing for this system is actually very straightforward. We want a `conversations` index page, which will display all the conversations a user has. We also want a `conversations_messages` index page that will display all the messages for that conversation. 171 | 172 | We can do that very simply like so: 173 | 174 | ```ruby 175 | # config/routes.rb 176 | resources :conversations, only: [:index, :create] do 177 | resources :messages, only: [:index, :create] 178 | end 179 | ``` 180 | 181 | You can see how that has affected your app's routes by typing `rails routes` in the terminal. 182 | 183 | ## Conversations controller 184 | 185 | The conversations controller will basically handle showing all conversations, and creating new conversations when needed. It will only need `index` and `create` methods. 186 | 187 | Lets make this now: 188 | 189 | `rails g controller conversations index` 190 | 191 | This will create the controller and an `index.html.erb` file in `app/views/conversations/` 192 | 193 | Let's flesh out the controller: 194 | 195 | ```ruby 196 | class ConversationsController < ApplicationController 197 | before_action :authenticate 198 | 199 | def index 200 | @conversations = Conversation.where("sender_id = ? OR receiver_id = ?", current_user.id, current_user.id) 201 | @users = User.where.not(id: current_user.id) 202 | end 203 | end 204 | ``` 205 | 206 | For the index, we will display a list of all the conversations that the current user has on the go, and a list of the users that are signed up to the app. 207 | 208 | Notice that I have added the `authenticate` method as a `before_action`. The user **must** be logged in to view their messages! 209 | 210 | Let's now add the `create` method. First we will check if a conversation already exists between the two users. If it does we will redirect the user to that conversation's messages index page. If not, we will create the conversation and then redirect the user. 211 | 212 | ```ruby 213 | class ConversationsController < ApplicationController 214 | before_action :authenticate 215 | 216 | def index 217 | @users = User.where.not(id: current_user.id) 218 | @conversations = Conversation.where("sender_id = ? OR receiver_id = ?", current_user.id, current_user.id) 219 | end 220 | 221 | def create 222 | if Conversation.between(params[:sender_id], params[:receiver_id]).present? 223 | @conversation = Conversation.between(params[:sender_id], params[:receiver_id]).first 224 | else 225 | @conversation = Conversation.create!(conversation_params) 226 | end 227 | 228 | redirect_to conversation_messages_path(@conversation) 229 | end 230 | 231 | private 232 | def conversation_params 233 | params.permit(:sender_id, :receiver_id) 234 | end 235 | end 236 | ``` 237 | This is where the `between` method we created in the model comes in to play. 238 | 239 | ### The view 240 | 241 | There's only one view we need to create here. Here's a skeleton view which you can amend and style to your liking: 242 | 243 | ```erb 244 |

Inbox

245 | 251 | 252 | 253 |

Users

254 | 259 | ``` 260 | 261 | ## Messages controller 262 | 263 | As before we can generate the controller like so: 264 | 265 | `rails g controller messages index` 266 | 267 | Here's the completed controller. Take a look, then I'll talk you through it: 268 | 269 | ```ruby 270 | class MessagesController < ApplicationController 271 | before_action :authenticate 272 | 273 | before_action do 274 | @conversation = Conversation.find(params[:conversation_id]) 275 | end 276 | 277 | def index 278 | @messages = @conversation.messages 279 | 280 | @messages.where("user_id != ? AND read = ?", current_user.id, false).update_all(read: true) 281 | 282 | @message = @conversation.messages.new 283 | end 284 | 285 | def create 286 | @message = @conversation.messages.new(message_params) 287 | @message.user = current_user 288 | 289 | if @message.save 290 | redirect_to conversation_messages_path(@conversation) 291 | end 292 | end 293 | 294 | private 295 | def message_params 296 | params.require(:message).permit(:body, :user_id) 297 | end 298 | end 299 | ``` 300 | 301 | We start by ensuring the user is logged in. Since we are always going to need the current conversation regardless of what we are doing, we can use the `before_action` hook to pull that from the database. 302 | 303 | In the `index` method, we can pull out all of the messages from the conversation. Since all the messages will be on the page, we can assume the user has read all the unread messages that were sent to her, so we update the `read` attribute of any messages sent by the other user to be `true`. After that, we create a new message, ready for the user to add the content. 304 | 305 | The `create` method is standard, we save the message and redirect to the same page, so the user can see their new message has been added to the conversation. 306 | 307 | ### The view 308 | 309 | Again, a skeleton view for your consideration: 310 | 311 | ```erb 312 | 322 | 323 | <%= form_for [@conversation, @message] do |f| %> 324 |
325 | <%= f.text_area :body, placeholder: "Your message" %> 326 |
327 | 328 | <%= f.submit "Send" %> 329 | <% end %> 330 | ``` 331 | 332 | The only thing here that's unusual is the `form_for` tag. We're passing two models. This will mean that the form points to the correct url, eg: `/conversations/1/messages/` 333 | 334 | ## Cleaning up our views 335 | 336 | The ideal goal with the MVC design pattern is to have the model take care of all the data, and as much logic as possible, leaving our views and controllers as clean as possible. We can remove the `receiver` variable from the conversations index page and move the logic into the model: 337 | 338 | ```ruby 339 | # app/models/conversation.rb 340 | class Conversation < ApplicationRecord 341 | belongs_to :sender, class_name: "User", foreign_key: "sender_id" 342 | belongs_to :receiver, class_name: "User", foreign_key: "receiver_id" 343 | has_many :messages, dependent: :destroy 344 | 345 | validates_uniqueness_of :sender_id, scope: :receiver_id 346 | 347 | scope :between, -> (sender_id, receiver_id) do 348 | where("(conversations.sender_id = ? AND conversations.receiver_id = ?) OR (conversations.receiver_id = ? AND conversations.sender_id = ?)", sender_id, receiver_id, sender_id, receiver_id) 349 | end 350 | 351 | def recipient(current_user) 352 | self.sender_id == current_user.id ? self.receiver : self.sender 353 | end 354 | 355 | end 356 | ``` 357 | 358 | We've created a new method `recipient`, which will return the other user (ie. not the `current_user`) from the conversation. Unfortunately we have to pass `current_user` into the method since `current_user` is not available in the model by design. 359 | 360 | > **Note:** Some more info about that from [http://stackoverflow.com/questions/2513383/access-current-user-in-model](StackOverflow) 361 | 362 | Now we can use it in our view 363 | 364 | ```erb 365 |

Inbox

366 | 367 | 374 | 375 | ... 376 | 377 | ``` 378 | 379 | Ah, that's much nicer 380 | 381 | ## Unread message count 382 | 383 | Finally, lets display the number of unread messages in a conversation. Again, not **all** the unread messages, just the unread messages that were posted by the other user. Let's make a new method in the model to handle that: 384 | 385 | ```ruby 386 | class Conversation < ApplicationRecord 387 | belongs_to :sender, class_name: "User", foreign_key: "sender_id" 388 | belongs_to :receiver, class_name: "User", foreign_key: "receiver_id" 389 | has_many :messages, dependent: :destroy 390 | 391 | validates_uniqueness_of :sender_id, scope: :receiver_id 392 | 393 | scope :between, -> (sender_id, receiver_id) do 394 | where("(conversations.sender_id = ? AND conversations.receiver_id = ?) OR (conversations.receiver_id = ? AND conversations.sender_id = ?)", sender_id, receiver_id, sender_id, receiver_id) 395 | end 396 | 397 | def recipient(current_user) 398 | self.sender_id == current_user.id ? self.receiver : self.sender 399 | end 400 | 401 | def unread_message_count(current_user) 402 | self.messages.where("user_id != ? AND read = ?", current_user.id, false).count 403 | end 404 | 405 | end 406 | ``` 407 | 408 | Great, let's update the view 409 | 410 | ```erb 411 |

Inbox

412 | 413 | 423 | ``` 424 | 425 | If there are unread messages (ie. the `unread_message_count` is not 0), then we can display them on the screen. 426 | 427 | > **Note:** if you're using bootstrap [http://getbootstrap.com/components/#list-group-badges](a list group with badges) might be useful here. -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative 'config/application' 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../javascripts .js 3 | //= link_directory ../stylesheets .css 4 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickyginger/rails-conversations/aaf95e6d2ce8d7ba24d83d364156843cde970310/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. JavaScript code in this file should be added after the last require_* statement. 9 | // 10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require turbolinks 16 | //= require bootstrap-sprockets 17 | //= require_tree ./channels 18 | //= require_tree . 19 | 20 | $(document).on('turbolinks:load', function() { 21 | 22 | var $ul = $('#users'); 23 | var $lis = $ul.find('li'); 24 | 25 | $('#search').on('keyup', function() { 26 | var input = this; 27 | $lis.each(function() { 28 | var searchString = $(input).val().toLowerCase(); 29 | var textToMatch = $(this).data('searchstring').toLowerCase(); 30 | 31 | if(textToMatch.match(searchString)) { 32 | $(this).show(); 33 | } else { 34 | $(this).hide(); 35 | } 36 | }); 37 | }); 38 | }); 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /app/assets/javascripts/cable.js: -------------------------------------------------------------------------------- 1 | // Action Cable provides the framework to deal with WebSockets in Rails. 2 | // You can generate new channels where WebSocket features live using the rails generate channel command. 3 | // 4 | //= require action_cable 5 | //= require_self 6 | //= require_tree ./channels 7 | 8 | (function() { 9 | this.App || (this.App = {}); 10 | 11 | // App.cable = ActionCable.createConsumer(); 12 | 13 | }).call(this); 14 | -------------------------------------------------------------------------------- /app/assets/javascripts/channels/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickyginger/rails-conversations/aaf95e6d2ce8d7ba24d83d364156843cde970310/app/assets/javascripts/channels/.keep -------------------------------------------------------------------------------- /app/assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | @import "bootstrap-sprockets"; 2 | @import "bootstrap"; 3 | 4 | img.user-img { 5 | border-radius: 4px; 6 | } 7 | 8 | .navbar img.user-img { 9 | margin: 7px 0; 10 | } -------------------------------------------------------------------------------- /app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Channel < ActionCable::Channel::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Connection < ActionCable::Connection::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery with: :exception 3 | 4 | helper_method :current_user, :logged_in? 5 | def current_user 6 | @current_user ||= User.find(session[:user_id]) if session[:user_id] 7 | end 8 | 9 | def logged_in? 10 | !!current_user 11 | end 12 | 13 | def authenticate 14 | unless logged_in? 15 | flash[:error] = "You must be logged in to do that!" 16 | redirect_to login_path 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickyginger/rails-conversations/aaf95e6d2ce8d7ba24d83d364156843cde970310/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/conversations_controller.rb: -------------------------------------------------------------------------------- 1 | class ConversationsController < ApplicationController 2 | before_action :authenticate 3 | 4 | def index 5 | @conversations = Conversation.where("sender_id = ? OR receiver_id = ?", current_user.id, current_user.id) 6 | @users = User.where.not(id: current_user.id) 7 | end 8 | 9 | def create 10 | if Conversation.between(params[:sender_id], params[:receiver_id]).present? 11 | @conversation = Conversation.between(params[:sender_id], params[:receiver_id]).first 12 | else 13 | @conversation = Conversation.create!(conversation_params) 14 | end 15 | 16 | redirect_to conversation_messages_path(@conversation) 17 | end 18 | 19 | private 20 | def conversation_params 21 | params.permit(:sender_id, :receiver_id) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/controllers/messages_controller.rb: -------------------------------------------------------------------------------- 1 | class MessagesController < ApplicationController 2 | before_action do 3 | @conversation = Conversation.find(params[:conversation_id]) 4 | end 5 | 6 | def index 7 | @conversation.messages.where("user_id != ? AND read = ?", current_user.id, false).update_all(read: true) 8 | @message = @conversation.messages.new 9 | end 10 | 11 | def create 12 | @message = @conversation.messages.new(message_params) 13 | @message.user = current_user 14 | 15 | if @message.save 16 | ActionCable.server.broadcast "messages", { conversation_id: @conversation.id } 17 | redirect_to conversation_messages_path(@conversation) 18 | end 19 | end 20 | 21 | private 22 | def message_params 23 | params.require(:message).permit(:body) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/controllers/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | class SessionsController < ApplicationController 2 | def new 3 | end 4 | 5 | def create 6 | user = User.find_by_email(params[:email]) 7 | if user && user.authenticate(params[:password]) 8 | session[:user_id] = user.id 9 | redirect_to root_path, notice: "Welcome back!" 10 | else 11 | flash.now.alert = "Invalid login credentials." 12 | render "new" 13 | end 14 | end 15 | 16 | def destroy 17 | session.delete :user_id 18 | redirect_to root_path, notice: "Logged out!" 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | class UsersController < ApplicationController 2 | def new 3 | @user = User.new 4 | end 5 | 6 | def create 7 | @user = User.new user_params 8 | if @user.save 9 | flash[:success] = "Thanks for registering!" 10 | redirect_to root_path 11 | else 12 | render 'new' 13 | end 14 | end 15 | 16 | private 17 | 18 | def user_params 19 | params.require(:user).permit(:username, :email, :password, :password_confirmation) 20 | end 21 | end -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | end 3 | -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: 'from@example.com' 3 | layout 'mailer' 4 | end 5 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickyginger/rails-conversations/aaf95e6d2ce8d7ba24d83d364156843cde970310/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/conversation.rb: -------------------------------------------------------------------------------- 1 | class Conversation < ApplicationRecord 2 | belongs_to :sender, class_name: "User", foreign_key: "sender_id" 3 | belongs_to :receiver, class_name: "User", foreign_key: "receiver_id" 4 | has_many :messages, dependent: :destroy 5 | 6 | validates_uniqueness_of :sender_id, scope: :receiver_id 7 | 8 | scope :between, -> (sender_id, receiver_id) do 9 | where("(conversations.sender_id = ? AND conversations.receiver_id = ?) OR (conversations.receiver_id = ? AND conversations.sender_id = ?)", sender_id, receiver_id, sender_id, receiver_id) 10 | end 11 | 12 | def recipient(current_user) 13 | self.sender_id == current_user.id ? self.receiver : self.sender 14 | end 15 | 16 | def unread_message_count(current_user) 17 | self.messages.where("user_id != ? AND read = ?", current_user.id, false).count 18 | end 19 | 20 | end -------------------------------------------------------------------------------- /app/models/message.rb: -------------------------------------------------------------------------------- 1 | class Message < ApplicationRecord 2 | belongs_to :conversation 3 | belongs_to :user 4 | 5 | validates_presence_of :body, :conversation_id, :user_id 6 | 7 | def time 8 | created_at.strftime("%d/%m/%y at %l:%M %p") 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ApplicationRecord 2 | has_secure_password 3 | validates :email, presence: true, uniqueness: true 4 | validates :password_confirmation, presence: true 5 | 6 | mount_uploader :image, ImageUploader 7 | end 8 | -------------------------------------------------------------------------------- /app/uploaders/image_uploader.rb: -------------------------------------------------------------------------------- 1 | class ImageUploader < CarrierWave::Uploader::Base 2 | include CarrierWave::RMagick 3 | 4 | def store_dir 5 | "uploads/" 6 | end 7 | 8 | # Create different versions of your uploaded files: 9 | version :thumb do 10 | process :resize_to_fit => [35, 35] 11 | end 12 | 13 | # Add a white list of extensions which are allowed to be uploaded. 14 | def extension_white_list 15 | %w(jpg jpeg gif png) 16 | end 17 | 18 | # Override the filename of the uploaded files: 19 | # def filename 20 | # "something.jpg" if original_filename 21 | # end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /app/views/application/_header.html.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/application/_notices.html.erb: -------------------------------------------------------------------------------- 1 | <% if notice %> 2 |
<%= notice %>
3 | <% end %> 4 | 5 | <% if alert %> 6 |
<%= alert %>
7 | <% end %> -------------------------------------------------------------------------------- /app/views/conversations/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | 13 |
14 |
15 |
16 | 17 |
18 | 25 |
26 |
27 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Conversations 5 | <%= csrf_meta_tags %> 6 | 7 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> 8 | <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> 9 | 10 | <%= action_cable_meta_tag %> 11 | 12 | 13 | 14 | <%= render "header" %> 15 |
16 | <%= render "notices" %> 17 | <%= yield %> 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /app/views/messages/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% @conversation.messages.each do |message| %> 3 | <% if message.body %> 4 |
5 |
6 | <%= image_tag message.user.image.thumb, class: "media-object user-img", alt: message.user.username %> 7 |
8 |
9 |
<%= message.user.username %> <%= message.time %>
10 |

<%= message.body %>

11 |
12 |
13 | <% end %> 14 | <% end %> 15 |
16 | 17 | <%= form_for [@conversation, @message] do |f| %> 18 |
19 | <%= f.text_area :body, autofocus: true, placeholder: "Your message", class: "form-control", rows: 5 %> 20 |
21 | 22 | <%= f.submit "Send", class: "btn btn-primary pull-right" %> 23 | <% end %> -------------------------------------------------------------------------------- /app/views/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 |

Login

2 | <%= form_tag login_path do %> 3 |
4 | <%= label_tag :email %> 5 | <%= text_field_tag :email, nil, class: "form-control" %> 6 |
7 |
8 | <%= label_tag :password %> 9 | <%= password_field_tag :password, nil, class: "form-control" %> 10 |
11 | <%= submit_tag "Login", class: "btn btn-primary pull-right" %> 12 | <% end %> -------------------------------------------------------------------------------- /app/views/users/new.html.erb: -------------------------------------------------------------------------------- 1 |

Sign Up

2 | <%= form_for @user, url: register_path do |f| %> 3 | <% if @user.errors.any? %> 4 | <% for message in @user.errors.full_messages %> 5 | 6 | <% end %> 7 | <% end %> 8 | 9 |
10 | <%= f.label :username %> 11 | <%= f.text_field :username %> 12 |
13 |
14 | <%= f.label :email %> 15 | <%= f.text_field :email %> 16 |
17 |
18 | <%= f.label :password %> 19 | <%= f.password_field :password %> 20 |
21 |
22 | <%= f.label :password_confirmation %> 23 | <%= f.password_field :password_confirmation %> 24 |
25 | <%= f.submit "Register", class: "btn btn-primary pull-right" %> 26 | <% end %> 27 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | APP_PATH = File.expand_path('../config/application', __dir__) 8 | require_relative '../config/boot' 9 | require 'rails/commands' 10 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | require_relative '../config/boot' 8 | require 'rake' 9 | Rake.application.run 10 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a starting point to setup your application. 15 | # Add necessary setup steps to this file. 16 | 17 | puts '== Installing dependencies ==' 18 | system! 'gem install bundler --conservative' 19 | system('bundle check') || system!('bundle install') 20 | 21 | # puts "\n== Copying sample files ==" 22 | # unless File.exist?('config/database.yml') 23 | # cp 'config/database.yml.sample', 'config/database.yml' 24 | # end 25 | 26 | puts "\n== Preparing database ==" 27 | system! 'bin/rails db:setup' 28 | 29 | puts "\n== Removing old logs and tempfiles ==" 30 | system! 'bin/rails log:clear tmp:clear' 31 | 32 | puts "\n== Restarting application server ==" 33 | system! 'bin/rails restart' 34 | end 35 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads spring without using Bundler, in order to be fast. 4 | # It gets overwritten when you run the `spring binstub` command. 5 | 6 | unless defined?(Spring) 7 | require 'rubygems' 8 | require 'bundler' 9 | 10 | if (match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m)) 11 | Gem.paths = { 'GEM_PATH' => [Bundler.bundle_path.to_s, *Gem.path].uniq.join(Gem.path_separator) } 12 | gem 'spring', match[1] 13 | require 'spring/binstub' 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a way to update your development environment automatically. 15 | # Add necessary update steps to this file. 16 | 17 | puts '== Installing dependencies ==' 18 | system! 'gem install bundler --conservative' 19 | system('bundle check') || system!('bundle install') 20 | 21 | puts "\n== Updating database ==" 22 | system! 'bin/rails db:migrate' 23 | 24 | puts "\n== Removing old logs and tempfiles ==" 25 | system! 'bin/rails log:clear tmp:clear' 26 | 27 | puts "\n== Restarting application server ==" 28 | system! 'bin/rails restart' 29 | end 30 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative 'config/environment' 4 | 5 | run Rails.application 6 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative 'boot' 2 | 3 | require 'rails/all' 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | require 'carrierwave/orm/activerecord' 10 | 11 | module Conversations 12 | class Application < Rails::Application 13 | config.generators do |g| 14 | g.assets false 15 | g.helper false 16 | g.jbuilder false 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: async 6 | 7 | production: 8 | adapter: redis 9 | url: <%= ENV['REDISTOGO_URL'] %> 10 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # PostgreSQL. Versions 9.1 and up are supported. 2 | # 3 | # Install the pg driver: 4 | # gem install pg 5 | # On OS X with Homebrew: 6 | # gem install pg -- --with-pg-config=/usr/local/bin/pg_config 7 | # On OS X with MacPorts: 8 | # gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config 9 | # On Windows: 10 | # gem install pg 11 | # Choose the win32 build. 12 | # Install PostgreSQL and put its /bin directory on your path. 13 | # 14 | # Configure Using Gemfile 15 | # gem 'pg' 16 | # 17 | default: &default 18 | adapter: postgresql 19 | encoding: unicode 20 | # For details on connection pooling, see rails configuration guide 21 | # http://guides.rubyonrails.org/configuring.html#database-pooling 22 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 23 | 24 | development: 25 | <<: *default 26 | database: conversations_development 27 | 28 | # The specified database role being used to connect to postgres. 29 | # To create additional roles in postgres see `$ createuser --help`. 30 | # When left blank, postgres will use the default role. This is 31 | # the same name as the operating system user that initialized the database. 32 | #username: conversations 33 | 34 | # The password associated with the postgres role (username). 35 | #password: 36 | 37 | # Connect on a TCP socket. Omitted by default since the client uses a 38 | # domain socket that doesn't need configuration. Windows does not have 39 | # domain sockets, so uncomment these lines. 40 | #host: localhost 41 | 42 | # The TCP port the server listens on. Defaults to 5432. 43 | # If your server runs on a different port number, change accordingly. 44 | #port: 5432 45 | 46 | # Schema search path. The server defaults to $user,public 47 | #schema_search_path: myapp,sharedapp,public 48 | 49 | # Minimum log levels, in increasing order: 50 | # debug5, debug4, debug3, debug2, debug1, 51 | # log, notice, warning, error, fatal, and panic 52 | # Defaults to warning. 53 | #min_messages: notice 54 | 55 | # Warning: The database defined as "test" will be erased and 56 | # re-generated from your development database when you run "rake". 57 | # Do not set this db to the same as development or production. 58 | test: 59 | <<: *default 60 | database: conversations_test 61 | 62 | # As with config/secrets.yml, you never want to store sensitive information, 63 | # like your database password, in your source code. If your source code is 64 | # ever seen by anyone, they now have access to your database. 65 | # 66 | # Instead, provide the password as a unix environment variable when you boot 67 | # the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database 68 | # for a full rundown on how to provide these environment variables in a 69 | # production deployment. 70 | # 71 | # On Heroku and other platform providers, you may have a full connection URL 72 | # available as an environment variable. For example: 73 | # 74 | # DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" 75 | # 76 | # You can use this database configuration with: 77 | # 78 | # production: 79 | # url: <%= ENV['DATABASE_URL'] %> 80 | # 81 | production: 82 | <<: *default 83 | database: conversations_production 84 | username: conversations 85 | password: <%= ENV['CONVERSATIONS_DATABASE_PASSWORD'] %> 86 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports. 13 | config.consider_all_requests_local = true 14 | 15 | # Enable/disable caching. By default caching is disabled. 16 | if Rails.root.join('tmp/caching-dev.txt').exist? 17 | config.action_controller.perform_caching = true 18 | 19 | config.cache_store = :memory_store 20 | config.public_file_server.headers = { 21 | 'Cache-Control' => 'public, max-age=172800' 22 | } 23 | else 24 | config.action_controller.perform_caching = false 25 | 26 | config.cache_store = :null_store 27 | end 28 | 29 | # Don't care if the mailer can't send. 30 | config.action_mailer.raise_delivery_errors = false 31 | 32 | config.action_mailer.perform_caching = false 33 | 34 | config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } 35 | 36 | # Print deprecation notices to the Rails logger. 37 | config.active_support.deprecation = :log 38 | 39 | # Raise an error on page load if there are pending migrations. 40 | config.active_record.migration_error = :page_load 41 | 42 | # Debug mode disables concatenation and preprocessing of assets. 43 | # This option may cause significant delays in view rendering with a large 44 | # number of complex assets. 45 | config.assets.debug = true 46 | 47 | # Suppress logger output for asset requests. 48 | config.assets.quiet = true 49 | 50 | # Raises error for missing translations 51 | # config.action_view.raise_on_missing_translations = true 52 | 53 | # Use an evented file watcher to asynchronously detect changes in source code, 54 | # routes, locales, etc. This feature depends on the listen gem. 55 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker 56 | end 57 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Disable serving static files from the `/public` folder by default since 18 | # Apache or NGINX already handles this. 19 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? 20 | 21 | # Compress JavaScripts and CSS. 22 | config.assets.js_compressor = :uglifier 23 | # config.assets.css_compressor = :sass 24 | 25 | # Do not fallback to assets pipeline if a precompiled asset is missed. 26 | config.assets.compile = false 27 | 28 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 29 | 30 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 31 | # config.action_controller.asset_host = 'http://assets.example.com' 32 | 33 | # Specifies the header that your server uses for sending files. 34 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 35 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 36 | 37 | # Mount Action Cable outside main process or domain 38 | # config.action_cable.mount_path = nil 39 | config.action_cable.url = "wss://rails-conversations.herokuapp.com/cable" 40 | config.action_cable.allowed_request_origins = [ 41 | 'https://rails-conversations.herokuapp.com/', 42 | /http:\/\/rails-conversations.herokuapp.*/ 43 | ] 44 | 45 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 46 | # config.force_ssl = true 47 | 48 | # Use the lowest log level to ensure availability of diagnostic information 49 | # when problems arise. 50 | config.log_level = :debug 51 | 52 | # Prepend all log lines with the following tags. 53 | config.log_tags = [ :request_id ] 54 | 55 | # Use a different cache store in production. 56 | # config.cache_store = :mem_cache_store 57 | 58 | # Use a real queuing backend for Active Job (and separate queues per environment) 59 | # config.active_job.queue_adapter = :resque 60 | # config.active_job.queue_name_prefix = "conversations_#{Rails.env}" 61 | config.action_mailer.perform_caching = false 62 | 63 | # Ignore bad email addresses and do not raise email delivery errors. 64 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 65 | # config.action_mailer.raise_delivery_errors = false 66 | 67 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 68 | # the I18n.default_locale when a translation cannot be found). 69 | config.i18n.fallbacks = true 70 | 71 | # Send deprecation notices to registered listeners. 72 | config.active_support.deprecation = :notify 73 | 74 | # Use default logging formatter so that PID and timestamp are not suppressed. 75 | config.log_formatter = ::Logger::Formatter.new 76 | 77 | # Use a different logger for distributed setups. 78 | # require 'syslog/logger' 79 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') 80 | 81 | if ENV["RAILS_LOG_TO_STDOUT"].present? 82 | logger = ActiveSupport::Logger.new(STDOUT) 83 | logger.formatter = config.log_formatter 84 | config.logger = ActiveSupport::TaggedLogging.new(logger) 85 | end 86 | 87 | # Do not dump schema after migrations. 88 | config.active_record.dump_schema_after_migration = false 89 | end 90 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure public file server for tests with Cache-Control for performance. 16 | config.public_file_server.enabled = true 17 | config.public_file_server.headers = { 18 | 'Cache-Control' => 'public, max-age=3600' 19 | } 20 | 21 | # Show full error reports and disable caching. 22 | config.consider_all_requests_local = true 23 | config.action_controller.perform_caching = false 24 | 25 | # Raise exceptions instead of rendering exception templates. 26 | config.action_dispatch.show_exceptions = false 27 | 28 | # Disable request forgery protection in test environment. 29 | config.action_controller.allow_forgery_protection = false 30 | config.action_mailer.perform_caching = false 31 | 32 | # Tell Action Mailer not to deliver emails to the real world. 33 | # The :test delivery method accumulates sent emails in the 34 | # ActionMailer::Base.deliveries array. 35 | config.action_mailer.delivery_method = :test 36 | 37 | # Print deprecation notices to the stderr. 38 | config.active_support.deprecation = :stderr 39 | 40 | # Raises error for missing translations 41 | # config.action_view.raise_on_missing_translations = true 42 | end 43 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ApplicationController.renderer.defaults.merge!( 4 | # http_host: 'example.org', 5 | # https: false 6 | # ) 7 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Add additional assets to the asset load path 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 11 | # Rails.application.config.assets.precompile += %w( search.js ) 12 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/initializers/carrierwave.rb: -------------------------------------------------------------------------------- 1 | # sample config/initializers/carrierwave.rb 2 | CarrierWave.configure do |config| 3 | if Rails.env.development? || Rails.env.test? 4 | config.storage = :file 5 | else 6 | config.storage = :fog 7 | config.fog_credentials = { 8 | provider: 'AWS', 9 | aws_access_key_id: ENV['AWS_ACCESS_KEY'], 10 | aws_secret_access_key: ENV['AWS_SECRET_KEY'], 11 | region: 'eu-west-1' 12 | } 13 | config.fog_directory = ENV['AWS_BUCKET_NAME'] 14 | end 15 | end -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Specify a serializer for the signed and encrypted cookie jars. 4 | # Valid options are :json, :marshal, and :hybrid. 5 | Rails.application.config.action_dispatch.cookies_serializer = :json 6 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /config/initializers/new_framework_defaults.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | # 3 | # This file contains migration options to ease your Rails 5.0 upgrade. 4 | # 5 | # Read the Rails 5.0 release notes for more info on each option. 6 | 7 | # Enable per-form CSRF tokens. Previous versions had false. 8 | Rails.application.config.action_controller.per_form_csrf_tokens = true 9 | 10 | # Enable origin-checking CSRF mitigation. Previous versions had false. 11 | Rails.application.config.action_controller.forgery_protection_origin_check = true 12 | 13 | # Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`. 14 | # Previous versions had false. 15 | ActiveSupport.to_time_preserves_timezone = true 16 | 17 | # Require `belongs_to` associations by default. Previous versions had false. 18 | Rails.application.config.active_record.belongs_to_required_by_default = true 19 | 20 | # Do not halt callback chains when a callback returns false. Previous versions had true. 21 | ActiveSupport.halt_callback_chains_on_return_false = false 22 | 23 | # Configure SSL options to enable HSTS with subdomains. Previous versions had false. 24 | Rails.application.config.ssl_options = { hsts: { subdomains: true } } 25 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: '_conversations_session' 4 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /config/locales/devise.en.yml: -------------------------------------------------------------------------------- 1 | # Additional translations at https://github.com/plataformatec/devise/wiki/I18n 2 | 3 | en: 4 | devise: 5 | confirmations: 6 | confirmed: "Your email address has been successfully confirmed." 7 | send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes." 8 | send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes." 9 | failure: 10 | already_authenticated: "You are already signed in." 11 | inactive: "Your account is not activated yet." 12 | invalid: "Invalid %{authentication_keys} or password." 13 | locked: "Your account is locked." 14 | last_attempt: "You have one more attempt before your account is locked." 15 | not_found_in_database: "Invalid %{authentication_keys} or password." 16 | timeout: "Your session expired. Please sign in again to continue." 17 | unauthenticated: "You need to sign in or sign up before continuing." 18 | unconfirmed: "You have to confirm your email address before continuing." 19 | mailer: 20 | confirmation_instructions: 21 | subject: "Confirmation instructions" 22 | reset_password_instructions: 23 | subject: "Reset password instructions" 24 | unlock_instructions: 25 | subject: "Unlock instructions" 26 | password_change: 27 | subject: "Password Changed" 28 | omniauth_callbacks: 29 | failure: "Could not authenticate you from %{kind} because \"%{reason}\"." 30 | success: "Successfully authenticated from %{kind} account." 31 | passwords: 32 | no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." 33 | send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes." 34 | send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." 35 | updated: "Your password has been changed successfully. You are now signed in." 36 | updated_not_active: "Your password has been changed successfully." 37 | registrations: 38 | destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon." 39 | signed_up: "Welcome! You have signed up successfully." 40 | signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated." 41 | signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked." 42 | signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account." 43 | update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address." 44 | updated: "Your account has been updated successfully." 45 | sessions: 46 | signed_in: "Signed in successfully." 47 | signed_out: "Signed out successfully." 48 | already_signed_out: "Signed out successfully." 49 | unlocks: 50 | send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes." 51 | send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes." 52 | unlocked: "Your account has been unlocked successfully. Please sign in to continue." 53 | errors: 54 | messages: 55 | already_confirmed: "was already confirmed, please try signing in" 56 | confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" 57 | expired: "has expired, please request a new one" 58 | not_found: "not found" 59 | not_locked: "was not locked" 60 | not_saved: 61 | one: "1 error prohibited this %{resource} from being saved:" 62 | other: "%{count} errors prohibited this %{resource} from being saved:" 63 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum, this matches the default thread size of Active Record. 6 | # 7 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i 8 | threads threads_count, threads_count 9 | 10 | # Specifies the `port` that Puma will listen on to receive requests, default is 3000. 11 | # 12 | port ENV.fetch("PORT") { 3000 } 13 | 14 | # Specifies the `environment` that Puma will run in. 15 | # 16 | environment ENV.fetch("RAILS_ENV") { "development" } 17 | 18 | # Specifies the number of `workers` to boot in clustered mode. 19 | # Workers are forked webserver processes. If using threads and workers together 20 | # the concurrency of the application would be max `threads` * `workers`. 21 | # Workers do not work on JRuby or Windows (both of which do not support 22 | # processes). 23 | # 24 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 25 | 26 | # Use the `preload_app!` method when specifying a `workers` number. 27 | # This directive tells Puma to first boot the application and load code 28 | # before forking the application. This takes advantage of Copy On Write 29 | # process behavior so workers use less memory. If you use this option 30 | # you need to make sure to reconnect any threads in the `on_worker_boot` 31 | # block. 32 | # 33 | # preload_app! 34 | 35 | # The code in the `on_worker_boot` will be called if you are using 36 | # clustered mode by specifying a number of `workers`. After each worker 37 | # process is booted this block will be run, if you are using `preload_app!` 38 | # option you will want to use this block to reconnect to any threads 39 | # or connections that may have been created at application boot, Ruby 40 | # cannot share connections between processes. 41 | # 42 | # on_worker_boot do 43 | # ActiveRecord::Base.establish_connection if defined?(ActiveRecord) 44 | # end 45 | 46 | # Allow puma to be restarted by `rails restart` command. 47 | plugin :tmp_restart 48 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | 3 | root 'conversations#index' 4 | 5 | resources :conversations, only: [:index, :create] do 6 | resources :messages, only: [:index, :create] 7 | end 8 | 9 | get '/register', to: 'users#new' 10 | post '/register', to: 'users#create' 11 | get '/login', to: 'sessions#new' 12 | post '/login', to: 'sessions#create' 13 | delete '/logout', to: 'sessions#destroy' 14 | end 15 | -------------------------------------------------------------------------------- /config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rails secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: cd00f43b3fc3cde58262f76ef639f97462155cb00c6b19ec1734748ea531f1d798639a759bd6566cd136f0a2caf341abf3c615ebfc8fa5b9af13a9b6607410f7 15 | 16 | test: 17 | secret_key_base: 81be0d790068e7ce4cebe4ef0473c4e1623bf05e8a2830e3018efcb42f0caf541fa652de8ca936ce863dc53547fb0f84b254224732ffbb44c5aae8aba81c46c6 18 | 19 | # Do not keep production secrets in the repository, 20 | # instead read values from the environment. 21 | production: 22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 23 | -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | %w( 2 | .ruby-version 3 | .rbenv-vars 4 | tmp/restart.txt 5 | tmp/caching-dev.txt 6 | ).each { |path| Spring.watch(path) } 7 | -------------------------------------------------------------------------------- /db/images/chetan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickyginger/rails-conversations/aaf95e6d2ce8d7ba24d83d364156843cde970310/db/images/chetan.png -------------------------------------------------------------------------------- /db/images/ed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickyginger/rails-conversations/aaf95e6d2ce8d7ba24d83d364156843cde970310/db/images/ed.jpg -------------------------------------------------------------------------------- /db/images/jason.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickyginger/rails-conversations/aaf95e6d2ce8d7ba24d83d364156843cde970310/db/images/jason.jpg -------------------------------------------------------------------------------- /db/images/jules.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickyginger/rails-conversations/aaf95e6d2ce8d7ba24d83d364156843cde970310/db/images/jules.jpg -------------------------------------------------------------------------------- /db/images/mike.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickyginger/rails-conversations/aaf95e6d2ce8d7ba24d83d364156843cde970310/db/images/mike.png -------------------------------------------------------------------------------- /db/images/toni.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickyginger/rails-conversations/aaf95e6d2ce8d7ba24d83d364156843cde970310/db/images/toni.jpg -------------------------------------------------------------------------------- /db/images/will.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickyginger/rails-conversations/aaf95e6d2ce8d7ba24d83d364156843cde970310/db/images/will.jpg -------------------------------------------------------------------------------- /db/migrate/20160808105118_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration[5.0] 2 | def change 3 | create_table :users do |t| 4 | t.string :username 5 | t.string :name 6 | t.string :email 7 | t.string :image 8 | t.string :password_digest 9 | 10 | t.timestamps 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20160808110229_create_conversations.rb: -------------------------------------------------------------------------------- 1 | class CreateConversations < ActiveRecord::Migration[5.0] 2 | def change 3 | create_table :conversations do |t| 4 | t.integer :sender_id 5 | t.integer :receiver_id 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20160808111922_create_messages.rb: -------------------------------------------------------------------------------- 1 | class CreateMessages < ActiveRecord::Migration[5.0] 2 | def change 3 | create_table :messages do |t| 4 | t.text :body 5 | t.references :conversation, foreign_key: true 6 | t.references :user, foreign_key: true 7 | t.boolean :read, default: false 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # Note that this schema.rb definition is the authoritative source for your 6 | # database schema. If you need to create the application database on another 7 | # system, you should be using db:schema:load, not running all the migrations 8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 9 | # you'll amass, the slower it'll run and the greater likelihood for issues). 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(version: 20160808111922) do 14 | 15 | # These are extensions that must be enabled in order to support this database 16 | enable_extension "plpgsql" 17 | 18 | create_table "conversations", force: :cascade do |t| 19 | t.integer "sender_id" 20 | t.integer "receiver_id" 21 | t.datetime "created_at", null: false 22 | t.datetime "updated_at", null: false 23 | end 24 | 25 | create_table "messages", force: :cascade do |t| 26 | t.text "body" 27 | t.integer "conversation_id" 28 | t.integer "user_id" 29 | t.boolean "read", default: false 30 | t.datetime "created_at", null: false 31 | t.datetime "updated_at", null: false 32 | t.index ["conversation_id"], name: "index_messages_on_conversation_id", using: :btree 33 | t.index ["user_id"], name: "index_messages_on_user_id", using: :btree 34 | end 35 | 36 | create_table "users", force: :cascade do |t| 37 | t.string "username" 38 | t.string "name" 39 | t.string "email" 40 | t.string "image" 41 | t.string "password_digest" 42 | t.datetime "created_at", null: false 43 | t.datetime "updated_at", null: false 44 | end 45 | 46 | add_foreign_key "messages", "conversations" 47 | add_foreign_key "messages", "users" 48 | end 49 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | ["users", "conversations", "messages"].each do |table_name| 2 | ActiveRecord::Base.connection.execute("TRUNCATE #{table_name} RESTART IDENTITY CASCADE") 3 | end 4 | 5 | User.create([ 6 | { 7 | username: "mickyginger", 8 | email: "mike.hayden@ga.co", 9 | password: "password", 10 | password_confirmation: "password", 11 | image: File.open(Rails.root.join 'db/images/mike.png'), 12 | name: "Mike Hayden" 13 | },{ 14 | username: "julesjam", 15 | email: "jules@ga.co", 16 | password: "password", 17 | password_confirmation: "password", 18 | image: File.open(Rails.root.join 'db/images/jules.jpg'), 19 | name: "Jules Wyatt" 20 | },{ 21 | username: "jasonlai", 22 | email: "jason@ga.co", 23 | password: "password", 24 | password_confirmation: "password", 25 | image: File.open(Rails.root.join 'db/images/jason.jpg'), 26 | name: "Jason Lai" 27 | },{ 28 | username: "steadyx", 29 | email: "ed@ga.co", 30 | password: "password", 31 | password_confirmation: "password", 32 | image: File.open(Rails.root.join 'db/images/ed.jpg'), 33 | name: "Edward Kemp" 34 | },{ 35 | username: "willcook", 36 | email: "will@ga.co", 37 | password: "password", 38 | password_confirmation: "password", 39 | image: File.open(Rails.root.join 'db/images/will.jpg'), 40 | name: "Will Cook" 41 | },{ 42 | username: "toni155", 43 | email: "toni@ga.co", 44 | password: "password", 45 | password_confirmation: "password", 46 | image: File.open(Rails.root.join 'db/images/toni.jpg'), 47 | name: "Antonio Rossi" 48 | },{ 49 | username: "chetanbarot", 50 | email: "chetan@ga.co", 51 | password: "password", 52 | password_confirmation: "password", 53 | image: File.open(Rails.root.join 'db/images/chetan.png'), 54 | name: "Chetan Barot" 55 | } 56 | ]) -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickyginger/rails-conversations/aaf95e6d2ce8d7ba24d83d364156843cde970310/lib/assets/.keep -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickyginger/rails-conversations/aaf95e6d2ce8d7ba24d83d364156843cde970310/lib/tasks/.keep -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickyginger/rails-conversations/aaf95e6d2ce8d7ba24d83d364156843cde970310/log/.keep -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickyginger/rails-conversations/aaf95e6d2ce8d7ba24d83d364156843cde970310/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickyginger/rails-conversations/aaf95e6d2ce8d7ba24d83d364156843cde970310/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickyginger/rails-conversations/aaf95e6d2ce8d7ba24d83d364156843cde970310/public/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /screen-shot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickyginger/rails-conversations/aaf95e6d2ce8d7ba24d83d364156843cde970310/screen-shot-1.png -------------------------------------------------------------------------------- /screen-shot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickyginger/rails-conversations/aaf95e6d2ce8d7ba24d83d364156843cde970310/screen-shot-2.png -------------------------------------------------------------------------------- /test/controllers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickyginger/rails-conversations/aaf95e6d2ce8d7ba24d83d364156843cde970310/test/controllers/.keep -------------------------------------------------------------------------------- /test/controllers/conversations_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ConversationsControllerTest < ActionDispatch::IntegrationTest 4 | test "should get index" do 5 | get conversations_index_url 6 | assert_response :success 7 | end 8 | 9 | end 10 | -------------------------------------------------------------------------------- /test/controllers/messages_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class MessagesControllerTest < ActionDispatch::IntegrationTest 4 | test "should get index" do 5 | get messages_index_url 6 | assert_response :success 7 | end 8 | 9 | end 10 | -------------------------------------------------------------------------------- /test/controllers/sessions_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SessionsControllerTest < ActionDispatch::IntegrationTest 4 | test "should get new" do 5 | get sessions_new_url 6 | assert_response :success 7 | end 8 | 9 | end 10 | -------------------------------------------------------------------------------- /test/controllers/users_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class UsersControllerTest < ActionDispatch::IntegrationTest 4 | test "should get new" do 5 | get users_new_url 6 | assert_response :success 7 | end 8 | 9 | end 10 | -------------------------------------------------------------------------------- /test/fixtures/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickyginger/rails-conversations/aaf95e6d2ce8d7ba24d83d364156843cde970310/test/fixtures/.keep -------------------------------------------------------------------------------- /test/fixtures/files/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickyginger/rails-conversations/aaf95e6d2ce8d7ba24d83d364156843cde970310/test/fixtures/files/.keep -------------------------------------------------------------------------------- /test/fixtures/messages.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | body: MyText 5 | conversation: one 6 | user: one 7 | read: false 8 | 9 | two: 10 | body: MyText 11 | conversation: two 12 | user: two 13 | read: false 14 | -------------------------------------------------------------------------------- /test/helpers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickyginger/rails-conversations/aaf95e6d2ce8d7ba24d83d364156843cde970310/test/helpers/.keep -------------------------------------------------------------------------------- /test/integration/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickyginger/rails-conversations/aaf95e6d2ce8d7ba24d83d364156843cde970310/test/integration/.keep -------------------------------------------------------------------------------- /test/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickyginger/rails-conversations/aaf95e6d2ce8d7ba24d83d364156843cde970310/test/mailers/.keep -------------------------------------------------------------------------------- /test/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickyginger/rails-conversations/aaf95e6d2ce8d7ba24d83d364156843cde970310/test/models/.keep -------------------------------------------------------------------------------- /test/models/message_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class MessageTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV['RAILS_ENV'] ||= 'test' 2 | require File.expand_path('../../config/environment', __FILE__) 3 | require 'rails/test_help' 4 | 5 | class ActiveSupport::TestCase 6 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. 7 | fixtures :all 8 | 9 | # Add more helper methods to be used by all tests here... 10 | end 11 | -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickyginger/rails-conversations/aaf95e6d2ce8d7ba24d83d364156843cde970310/tmp/.keep --------------------------------------------------------------------------------