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