diff --git a/Gemfile b/Gemfile
index b23b6b9..8dd3f6d 100644
--- a/Gemfile
+++ b/Gemfile
@@ -35,6 +35,14 @@ gem 'sdoc', '~> 0.4.0', group: :doc
# Slack Bot
gem 'slack-ruby-bot'
+# HAML
+gem 'haml-rails'
+
+# React
+gem 'react-rails'
+gem 'sprockets-coffee-react'
+gem 'js-routes'
+
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug'
diff --git a/Gemfile.lock b/Gemfile.lock
index 05605b5..adb7e09 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -38,6 +38,10 @@ GEM
tzinfo (~> 1.1)
addressable (2.3.8)
arel (6.0.3)
+ babel-source (5.8.34)
+ babel-transpiler (0.7.0)
+ babel-source (>= 4.0, < 6)
+ execjs (~> 2.0)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
builder (3.2.2)
@@ -45,10 +49,13 @@ GEM
coffee-rails (4.1.0)
coffee-script (>= 2.2.0)
railties (>= 4.0.0, < 5.0)
+ coffee-react (3.0.1)
+ execjs
coffee-script (2.4.1)
coffee-script-source
execjs
coffee-script-source (1.10.0)
+ connection_pool (2.2.0)
debug_inspector (0.0.2)
diff-lcs (1.2.5)
erubis (2.7.0)
@@ -72,7 +79,20 @@ GEM
launchy (~> 2.4)
globalid (0.3.6)
activesupport (>= 4.1.0)
+ haml (4.0.7)
+ tilt
+ haml-rails (0.9.0)
+ actionpack (>= 4.0.1)
+ activesupport (>= 4.0.1)
+ haml (>= 4.0.6, < 5.0)
+ html2haml (>= 1.0.1)
+ railties (>= 4.0.1)
hashie (3.4.3)
+ html2haml (2.0.0)
+ erubis (~> 2.7.0)
+ haml (~> 4.0.0)
+ nokogiri (~> 1.6.0)
+ ruby_parser (~> 3.5)
i18n (0.7.0)
jbuilder (2.3.2)
activesupport (>= 3.0.0, < 5)
@@ -81,6 +101,9 @@ GEM
rails-dom-testing (~> 1.0)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
+ js-routes (1.1.2)
+ railties (>= 3.2)
+ sprockets-rails
json (1.8.3)
launchy (2.4.3)
addressable (~> 2.3)
@@ -125,6 +148,13 @@ GEM
thor (>= 0.18.1, < 2.0)
rake (10.4.2)
rdoc (4.2.0)
+ react-rails (1.5.0)
+ babel-transpiler (>= 0.7.0)
+ coffee-script-source (~> 1.8)
+ connection_pool
+ execjs
+ rails (>= 3.2)
+ tilt
rspec-core (3.4.1)
rspec-support (~> 3.4.0)
rspec-expectations (3.4.0)
@@ -142,6 +172,8 @@ GEM
rspec-mocks (~> 3.4.0)
rspec-support (~> 3.4.0)
rspec-support (3.4.1)
+ ruby_parser (3.7.2)
+ sexp_processor (~> 4.1)
sass (3.4.19)
sass-rails (5.0.4)
railties (>= 4.0.0, < 5.0)
@@ -152,6 +184,7 @@ GEM
sdoc (0.4.1)
json (~> 1.7, >= 1.7.7)
rdoc (~> 4.0)
+ sexp_processor (4.6.0)
slack-ruby-bot (0.4.5)
activesupport
giphy (~> 2.0.2)
@@ -167,6 +200,11 @@ GEM
spring (1.4.4)
sprockets (3.4.1)
rack (> 1, < 3)
+ sprockets-coffee-react (3.0.1)
+ coffee-react (>= 3.0.1)
+ coffee-script
+ sprockets
+ tilt
sprockets-rails (2.3.3)
actionpack (>= 3.0)
activesupport (>= 3.0)
@@ -197,14 +235,18 @@ PLATFORMS
DEPENDENCIES
byebug
coffee-rails (~> 4.1.0)
+ haml-rails
jbuilder (~> 2.0)
jquery-rails
+ js-routes
rails (= 4.2.4)
+ react-rails
rspec-rails
sass-rails (~> 5.0)
sdoc (~> 0.4.0)
slack-ruby-bot
spring
+ sprockets-coffee-react
sqlite3
turbolinks
uglifier (>= 1.3.0)
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index e07c5a8..aeb172a 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -13,4 +13,7 @@
//= require jquery
//= require jquery_ujs
//= require turbolinks
+//= require react
+//= require react_ujs
+//= require js-routes
//= require_tree .
diff --git a/app/assets/javascripts/react/messages/message.js.coffee b/app/assets/javascripts/react/messages/message.js.coffee
new file mode 100644
index 0000000..f207c9b
--- /dev/null
+++ b/app/assets/javascripts/react/messages/message.js.coffee
@@ -0,0 +1,12 @@
+# @cjsx React.DOM
+
+@Message = React.createClass
+ displayName: 'Message'
+ render: ->
+ # let's use this add-on to set the main div's class names
+ cx = React.addons.classSet
+
+ # here we use the calculated classes
+
+ {@props.data.id}: {@props.data.message?.text}
+
diff --git a/app/assets/javascripts/react/messages_section.js.coffee b/app/assets/javascripts/react/messages_section.js.coffee
new file mode 100644
index 0000000..1689643
--- /dev/null
+++ b/app/assets/javascripts/react/messages_section.js.coffee
@@ -0,0 +1,69 @@
+# @cjsx React.DOM
+
+@MessagesSection = React.createClass
+ # Display name used for debugging
+ displayName: 'MessagesSection'
+
+ # Invoked before the component is mounted and provides the initial
+ # state for the render method.
+ getInitialState: ->
+ # We'll change it to true once we fetch data
+ didFetchData: false
+ # The messages JSON array used to display the messages in the view
+ messages: []
+
+ # Invoked right after the component renders
+ componentDidMount: ->
+ # Lets fetch all the messages...
+ @_fetchMessages()
+ setInterval(@_fetchMessages, 1000);
+
+ # AJAX call to our MessagesController
+ _fetchMessages: ()->
+ $.ajax
+ url: Routes.messages_path()
+ .done @_fetchDataDone
+ .fail @_fetchDataFail
+
+ # If the AJAX call is successful...
+ _fetchDataDone: (data, textStatus, jqXHR) ->
+ # We change the state of the component. This will cause the component and
+ # it's children to render again
+ @setState
+ didFetchData: true
+ messages: data
+
+ # If errors in AJAX call...
+ _fetchDataFail: (xhr, status, err) =>
+ console.error @props.url, status, err.toString()
+
+ # How the component is going to be rendered to the user depending on it's
+ # props and state...
+ render: ->
+ # The collection of Message components we are going to display
+ # using the messages stored in the component's state
+ messagesNode = @state.messages.map (message) ->
+ # Message component with a data property containing all the JSON
+ # attributes we are going to use to display it to the user
+
+
+ # HTML displayed if no messages found in it's state
+ noDataNode =
+
+
No messages found...
+
+
+ # Here starts the render result
+
+
+ {
+ # If there are messages render the messages...
+ if @state.messages.length > 0
+ messagesNode
+ # If has fetched data and no messages found, render the
+ # warning message instead
+ else if @state.didFetchData
+ noDataNode
+ }
+
+
diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb
new file mode 100644
index 0000000..95f2992
--- /dev/null
+++ b/app/controllers/home_controller.rb
@@ -0,0 +1,4 @@
+class HomeController < ApplicationController
+ def index
+ end
+end
diff --git a/app/controllers/messages_controller.rb b/app/controllers/messages_controller.rb
new file mode 100644
index 0000000..d7c07ae
--- /dev/null
+++ b/app/controllers/messages_controller.rb
@@ -0,0 +1,8 @@
+class MessagesController < ApplicationController
+ def index
+ data = (1..10).map do |i|
+ { id: i, message: Rails.cache.read(i) }
+ end
+ render json: data
+ end
+end
diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml
new file mode 100644
index 0000000..1566f34
--- /dev/null
+++ b/app/views/home/index.html.haml
@@ -0,0 +1,5 @@
+%section
+ %header
+ %h1 Tattletale
+
+ = react_component 'MessagesSection', {}, :div
diff --git a/bot/say.rb b/bot/say.rb
index c423eab..a6bded6 100644
--- a/bot/say.rb
+++ b/bot/say.rb
@@ -1,5 +1,12 @@
class Say < SlackRubyBot::Commands::Base
+ @id = 0
+
+ def self.next_id
+ @id = @id % 10 + 1
+ end
+
command 'say' do |client, data, match|
+ Rails.cache.write next_id, { text: match['expression'] }
send_message client, data.channel, match['expression']
end
end
diff --git a/config/application.rb b/config/application.rb
index 7267189..99ad675 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -31,5 +31,8 @@ class Application < Rails::Application
# Do not swallow errors in after_commit/after_rollback callbacks.
config.active_record.raise_in_transactional_callbacks = true
+
+ # React config
+ config.react.addons = true
end
end
diff --git a/config/routes.rb b/config/routes.rb
index 3f66539..b454b41 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,56 +1,4 @@
Rails.application.routes.draw do
- # The priority is based upon order of creation: first created -> highest priority.
- # See how all your routes lay out with "rake routes".
-
- # You can have the root of your site routed with "root"
- # root 'welcome#index'
-
- # Example of regular route:
- # get 'products/:id' => 'catalog#view'
-
- # Example of named route that can be invoked with purchase_url(id: product.id)
- # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase
-
- # Example resource route (maps HTTP verbs to controller actions automatically):
- # resources :products
-
- # Example resource route with options:
- # resources :products do
- # member do
- # get 'short'
- # post 'toggle'
- # end
- #
- # collection do
- # get 'sold'
- # end
- # end
-
- # Example resource route with sub-resources:
- # resources :products do
- # resources :comments, :sales
- # resource :seller
- # end
-
- # Example resource route with more complex sub-resources:
- # resources :products do
- # resources :comments
- # resources :sales do
- # get 'recent', on: :collection
- # end
- # end
-
- # Example resource route with concerns:
- # concern :toggleable do
- # post 'toggle'
- # end
- # resources :posts, concerns: :toggleable
- # resources :photos, concerns: :toggleable
-
- # Example resource route within a namespace:
- # namespace :admin do
- # # Directs /admin/products/* to Admin::ProductsController
- # # (app/controllers/admin/products_controller.rb)
- # resources :products
- # end
+ root 'home#index'
+ resources :messages, only: :index
end