From a2c0d92bebcb693eacb437e0d531e63486613c86 Mon Sep 17 00:00:00 2001 From: okeen Date: Fri, 2 Sep 2011 22:46:48 +0200 Subject: [PATCH] Added player notifications --- app/controllers/notifications_controller.rb | 60 +++++++ app/helpers/notifications_helper.rb | 2 + app/models/confirmable.rb | 11 +- app/models/game.rb | 2 +- app/models/notification.rb | 19 +++ app/models/notification_type.rb | 59 +++++++ app/models/player.rb | 10 +- app/models/result.rb | 2 +- app/views/notifications/_form.html.erb | 33 ++++ app/views/notifications/edit.html.erb | 6 + app/views/notifications/index.html.erb | 29 ++++ app/views/notifications/new.html.erb | 5 + app/views/notifications/show.html.erb | 25 +++ .../players/_player_session_panel.html.erb | 4 +- config/application.rb | 2 +- config/routes.rb | 8 +- ...0110902180233_create_notification_types.rb | 13 ++ .../20110902180309_create_notifications.rb | 16 ++ db/schema.rb | 17 +- public/images/gritter-long.png | Bin 0 -> 6299 bytes public/images/gritter.png | Bin 0 -> 4880 bytes public/images/ie-spacer.gif | Bin 0 -> 43 bytes public/images/trees.jpg | Bin 0 -> 274003 bytes public/javascripts/jquery.gritter.min.js | 11 ++ public/javascripts/session.js | 66 ++++++-- public/stylesheets/jquery.gritter.css | 89 ++++++++++ public/stylesheets/player_session_panel.css | 3 + .../sass/player_session_panel.scss | 4 + .../notifications_controller_spec.rb | 157 ++++++++++++++++++ spec/helpers/notifications_helper_spec.rb | 15 ++ spec/models/notification_spec.rb | 5 + spec/models/notification_type_spec.rb | 5 + spec/requests/notifications_spec.rb | 11 ++ spec/routing/notifications_routing_spec.rb | 35 ++++ .../views/notifications/edit.html.erb_spec.rb | 24 +++ .../notifications/index.html.erb_spec.rb | 32 ++++ spec/views/notifications/new.html.erb_spec.rb | 24 +++ .../views/notifications/show.html.erb_spec.rb | 24 +++ 38 files changed, 806 insertions(+), 22 deletions(-) create mode 100644 app/controllers/notifications_controller.rb create mode 100644 app/helpers/notifications_helper.rb create mode 100644 app/models/notification.rb create mode 100644 app/models/notification_type.rb create mode 100644 app/views/notifications/_form.html.erb create mode 100644 app/views/notifications/edit.html.erb create mode 100644 app/views/notifications/index.html.erb create mode 100644 app/views/notifications/new.html.erb create mode 100644 app/views/notifications/show.html.erb create mode 100644 db/migrate/20110902180233_create_notification_types.rb create mode 100644 db/migrate/20110902180309_create_notifications.rb create mode 100755 public/images/gritter-long.png create mode 100755 public/images/gritter.png create mode 100755 public/images/ie-spacer.gif create mode 100755 public/images/trees.jpg create mode 100755 public/javascripts/jquery.gritter.min.js create mode 100755 public/stylesheets/jquery.gritter.css create mode 100644 spec/controllers/notifications_controller_spec.rb create mode 100644 spec/helpers/notifications_helper_spec.rb create mode 100644 spec/models/notification_spec.rb create mode 100644 spec/models/notification_type_spec.rb create mode 100644 spec/requests/notifications_spec.rb create mode 100644 spec/routing/notifications_routing_spec.rb create mode 100644 spec/views/notifications/edit.html.erb_spec.rb create mode 100644 spec/views/notifications/index.html.erb_spec.rb create mode 100644 spec/views/notifications/new.html.erb_spec.rb create mode 100644 spec/views/notifications/show.html.erb_spec.rb diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb new file mode 100644 index 0000000..7f4e101 --- /dev/null +++ b/app/controllers/notifications_controller.rb @@ -0,0 +1,60 @@ +class NotificationsController < ApplicationController + before_filter :authenticate_player!, :load_player + # GET /notifications + # GET /notifications.xml + def index + @notifications = @player.notifications.unread.all + + respond_to do |format| + format.json { render :json => @notifications } + end + end + + # GET /notifications/1 + # GET /notifications/1.xml + def show + @notification = @player.notifications.find(params[:id]) + + respond_to do |format| + format.html # show.html.erb + format.xml { render :xml => @notification } + end + end + + + # PUT /notifications/1 + # PUT /notifications/1.xml + def update + @notification = @player.notifications.find(params[:id]) + + respond_to do |format| + if @notification.update_attributes(params[:notification]) + format.html { redirect_to(@notification, :notice => 'Notification was successfully updated.') } + format.xml { head :ok } + else + format.html { render :action => "edit" } + format.xml { render :xml => @notification.errors, :status => :unprocessable_entity } + end + end + end + + # DELETE /notifications/1 + # DELETE /notifications/1.xml + def destroy + @notification = @player.notifications.find(params[:id]) + @notification.destroy + + respond_to do |format| + format.html { redirect_to(notifications_url) } + format.xml { head :ok } + end + end + + private + + def load_player + @player = current_player + end + + +end diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb new file mode 100644 index 0000000..7342393 --- /dev/null +++ b/app/helpers/notifications_helper.rb @@ -0,0 +1,2 @@ +module NotificationsHelper +end diff --git a/app/models/confirmable.rb b/app/models/confirmable.rb index 2de23b1..7e34dc8 100644 --- a/app/models/confirmable.rb +++ b/app/models/confirmable.rb @@ -6,7 +6,8 @@ module Confirmable after_create :set_initial_status, :create_confirmations_if_needed, - :deliver_ask_email + :deliver_ask_email, + :create_ask_notifications default_scope where(:status => "confirmed") @@ -47,6 +48,14 @@ def create_confirmations_if_needed end end + def create_ask_notifications + confirmating_player_groups.each do |notificating_group| + notificating_group.players.each do |player| + player.notifications.create NotificationType.NEW_TEAM + end + end + end + def mailer "#{self.class}Mailer".constantize end diff --git a/app/models/game.rb b/app/models/game.rb index 9ae9865..010ad31 100644 --- a/app/models/game.rb +++ b/app/models/game.rb @@ -116,7 +116,7 @@ def create_facebook_game_event(initiator_player,facebook_token) private def confirmating_player_groups - teams + self.team2 end def create_playground_request_if_needed diff --git a/app/models/notification.rb b/app/models/notification.rb new file mode 100644 index 0000000..3a9636b --- /dev/null +++ b/app/models/notification.rb @@ -0,0 +1,19 @@ +class Notification < ActiveRecord::Base + belongs_to :notification_type + belongs_to :player + + serialize :params + + default_scope includes(:notification_type) + + scope :unread, where(:read => false) + + def type + notification_type.name + end + + def as_json(options = {}) + super(:only => [:id, :created_at, :params, :read], + :methods => [:type]) + end +end diff --git a/app/models/notification_type.rb b/app/models/notification_type.rb new file mode 100644 index 0000000..ee6dba1 --- /dev/null +++ b/app/models/notification_type.rb @@ -0,0 +1,59 @@ +class NotificationType < ActiveRecord::Base + belongs_to :notification + + scope :named, lambda {|name| where('name = ?', name)} + + + def self.create_all_notification_types + NotificationType.create(:name => "new_player") + NotificationType.create(:name => "new_team") + NotificationType.create(:name => "team_accepted") + NotificationType.create(:name => "new_game") + NotificationType.create(:name => "game_accepted") + NotificationType.create(:name => "new_result") + NotificationType.create(:name => "result_accepted") + end + create_all_notification_types if self.all.blank?; + + + def NotificationType.NEW_PLAYER + { + :notification_type_id => NotificationType.named("new_player").first.id, + :params => { + :title => "Welcome to Padelotron", + :message => "As your first actions, yo can create a new team, invite friends or check today's games in your area", + :urgent => true + } + } + end + + def NotificationType.NEW_TEAM + { + :notification_type_id => NotificationType.named("new_team").first.id, + :params => { + :title => "New Team Request", + :message => "You have received an offer to join a team"} + } + end + + def NotificationType.TEAM_ACCEPTED + { + :notification_type_id => NotificationType.named("team_accepted").first.id, + :params => { + :title => "Team Joined", + :message => "You have joined a new team" + } + } + end + + def NotificationType.NEW_GAME + { + :notification_type_id => NotificationType.named("new_game").first.id, + :params => { + :title => "Team Joined", + :message => "You have joined a new team" + } + } + end + +end diff --git a/app/models/player.rb b/app/models/player.rb index ac38bae..2ed1dea 100644 --- a/app/models/player.rb +++ b/app/models/player.rb @@ -3,13 +3,13 @@ class Player < ActiveRecord::Base validates :email, :presence => true, :uniqueness => true has_many :teams, :finder_sql => 'select * from teams t where t.player1_id = #{id} or t.player2_id = #{id}' - + has_many :notifications has_many :player_games, :class_name => "Game", :finder_sql => 'select * from games ' include Statable devise :database_authenticatable, :omniauthable, :rememberable - before_create :init_devise_password + before_create :init_devise_password,:create_welcome_notification before_create :geocode_with_gmaps before_update :geocode_with_gmaps @@ -79,4 +79,10 @@ def get_geocode_attribute_value(loc, att) def init_devise_password password = Devise.friendly_token[0,20] end + + def create_welcome_notifications + notification= NotificationType.NEW_PLAYER + notification[:params][:name] = self.name + @player.notifications.create notification + end end diff --git a/app/models/result.rb b/app/models/result.rb index 516de33..f2a600a 100644 --- a/app/models/result.rb +++ b/app/models/result.rb @@ -67,7 +67,7 @@ def rejection_ask_message private def confirmating_player_groups - game.teams + game.winner == team1 ? team2 : team1 end end diff --git a/app/views/notifications/_form.html.erb b/app/views/notifications/_form.html.erb new file mode 100644 index 0000000..c057348 --- /dev/null +++ b/app/views/notifications/_form.html.erb @@ -0,0 +1,33 @@ +<%= form_for(@notification) do |f| %> + <% if @notification.errors.any? %> +
+

<%= pluralize(@notification.errors.count, "error") %> prohibited this notification from being saved:

+ + +
+ <% end %> + +
+ <%= f.label :player %>
+ <%= f.text_field :player %> +
+
+ <%= f.label :notification_type %>
+ <%= f.text_field :notification_type %> +
+
+ <%= f.label :params %>
+ <%= f.text_area :params %> +
+
+ <%= f.label :read %>
+ <%= f.check_box :read %> +
+
+ <%= f.submit %> +
+<% end %> diff --git a/app/views/notifications/edit.html.erb b/app/views/notifications/edit.html.erb new file mode 100644 index 0000000..92c805b --- /dev/null +++ b/app/views/notifications/edit.html.erb @@ -0,0 +1,6 @@ +

Editing notification

+ +<%= render 'form' %> + +<%= link_to 'Show', @notification %> | +<%= link_to 'Back', notifications_path %> diff --git a/app/views/notifications/index.html.erb b/app/views/notifications/index.html.erb new file mode 100644 index 0000000..90f7665 --- /dev/null +++ b/app/views/notifications/index.html.erb @@ -0,0 +1,29 @@ +

Listing notifications

+ + + + + + + + + + + + +<% @notifications.each do |notification| %> + + + + + + + + + +<% end %> +
PlayerNotification typeParamsRead
<%= notification.player %><%= notification.notification_type %><%= notification.params %><%= notification.read %><%= link_to 'Show', notification %><%= link_to 'Edit', edit_notification_path(notification) %><%= link_to 'Destroy', notification, :confirm => 'Are you sure?', :method => :delete %>
+ +
+ +<%= link_to 'New Notification', new_notification_path %> diff --git a/app/views/notifications/new.html.erb b/app/views/notifications/new.html.erb new file mode 100644 index 0000000..e6fb48d --- /dev/null +++ b/app/views/notifications/new.html.erb @@ -0,0 +1,5 @@ +

New notification

+ +<%= render 'form' %> + +<%= link_to 'Back', notifications_path %> diff --git a/app/views/notifications/show.html.erb b/app/views/notifications/show.html.erb new file mode 100644 index 0000000..9c5e4e9 --- /dev/null +++ b/app/views/notifications/show.html.erb @@ -0,0 +1,25 @@ +

<%= notice %>

+ +

+ Player: + <%= @notification.player %> +

+ +

+ Notification type: + <%= @notification.notification_type %> +

+ +

+ Params: + <%= @notification.params %> +

+ +

+ Read: + <%= @notification.read %> +

+ + +<%= link_to 'Edit', edit_notification_path(@notification) %> | +<%= link_to 'Back', notifications_path %> diff --git a/app/views/players/_player_session_panel.html.erb b/app/views/players/_player_session_panel.html.erb index c225676..ce4f90a 100644 --- a/app/views/players/_player_session_panel.html.erb +++ b/app/views/players/_player_session_panel.html.erb @@ -1,4 +1,6 @@
+
<%= player_photo(player) %>
@@ -17,7 +19,7 @@ <%= link_to "Profile", player_home_path %>
  • - <%= link_to "News", teams_path %> + <%= link_to "News", teams_path, :id=>"notifications_button" %>
  • diff --git a/config/application.rb b/config/application.rb index 79ba226..e9890ec 100644 --- a/config/application.rb +++ b/config/application.rb @@ -30,7 +30,7 @@ class Application < Rails::Application # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.default_locale = :de # JavaScript files you want as :defaults (application.js is always included). - config.action_view.javascript_expansions[:defaults] = %w(jquery rails underscore json2 backbone jquery.cookie) + config.action_view.javascript_expansions[:defaults] = %w(jquery rails underscore json2 backbone jquery.cookie jquery.gritter.min) config.action_view.javascript_expansions[:jquery_ui] = "https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.15/jquery-ui.js" config.action_view.javascript_expansions[:google] = "https://www.google.com/jsapi" diff --git a/config/routes.rb b/config/routes.rb index cfc29b9..166617b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,5 @@ Padelotron::Application.routes.draw do + match 'players/:id/graph_code' => 'graph#graph_code', :as => :graph_code match 'players/:id/graph_games_played' => 'graph#graph_games_played', :as => :graph_games_played @@ -43,8 +44,11 @@ get "sign_in", :to => "devise/sessions#new" get "sign_out", :to => "devise/sessions#destroy" end - resources :players - + resources :players do + + end + resources :notifications + devise_for :customers, :controllers => { :registrations => "customers", :confirmation => "customers/confirmations"} devise_scope :customers do diff --git a/db/migrate/20110902180233_create_notification_types.rb b/db/migrate/20110902180233_create_notification_types.rb new file mode 100644 index 0000000..97c894f --- /dev/null +++ b/db/migrate/20110902180233_create_notification_types.rb @@ -0,0 +1,13 @@ +class CreateNotificationTypes < ActiveRecord::Migration + def self.up + create_table :notification_types do |t| + t.string :name + + t.timestamps + end + end + + def self.down + drop_table :notification_types + end +end diff --git a/db/migrate/20110902180309_create_notifications.rb b/db/migrate/20110902180309_create_notifications.rb new file mode 100644 index 0000000..0ffe456 --- /dev/null +++ b/db/migrate/20110902180309_create_notifications.rb @@ -0,0 +1,16 @@ +class CreateNotifications < ActiveRecord::Migration + def self.up + create_table :notifications do |t| + t.references :player + t.references :notification_type + t.text :params + t.boolean :read, :default => false + + t.timestamps + end + end + + def self.down + drop_table :notifications + end +end diff --git a/db/schema.rb b/db/schema.rb index b788ebb..5583197 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20110902144146) do +ActiveRecord::Schema.define(:version => 20110902180309) do create_table "achievement_types", :force => true do |t| t.string "name" @@ -86,6 +86,21 @@ t.integer "playground_id" end + create_table "notification_types", :force => true do |t| + t.string "name" + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "notifications", :force => true do |t| + t.integer "player_id" + t.integer "notification_type_id" + t.text "params" + t.boolean "read", :default => false + t.datetime "created_at" + t.datetime "updated_at" + end + create_table "places", :force => true do |t| t.string "name" t.float "latitude" diff --git a/public/images/gritter-long.png b/public/images/gritter-long.png new file mode 100755 index 0000000000000000000000000000000000000000..578b89104feb2460a821b8543be29c50008666b6 GIT binary patch literal 6299 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 Zf3P5-;PGuo@D~?gbJgKWjfL<1{{eTAuA%?{ literal 0 HcmV?d00001 diff --git a/public/images/ie-spacer.gif b/public/images/ie-spacer.gif new file mode 100755 index 0000000000000000000000000000000000000000..5bfd67a2d6f72ac3a55cbfcea5866e841d22f5d9 GIT binary patch literal 43 mcmZ?wbhEHbWMp7uXkdT>#h)yUAf^t80Ld^gF}W}@SOWlZ0R#L1 literal 0 HcmV?d00001 diff --git a/public/images/trees.jpg b/public/images/trees.jpg new file mode 100755 index 0000000000000000000000000000000000000000..7caf603ad6d94ee3e2360b3d5ae0c9f70d4a28f4 GIT binary patch literal 274003 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

    ssq9+(rC7ICAowr_Rx&h|!5L z<+t)@yKRhON`!gIsuqh2pIGz<=Sn(s8go7S`Y7X)mty~{g$K|5?Zl7qE3FKPOiVt6 zpZ_Km3qF-x4`E||Vd<3ga=A&AnH({vmV9<5JYfuYXfMF*5o_XJXW6#+$iLMOT9omV1(Q@5h{8ll`)K z*e+%9!)wsjUGaE#XSbMLkws(NT4x9#2^G zw0_uGUG*x;nqyn9?VOg}riY;YkHv=1qf-gF4u&UeqXkYWnIEqszp2`;>2f~Xkl5-- zujwQCm(Z5-SzRq$_-DOziqMBv;+H z_HXx}+j;V`XV<6Aw2XS{`|iC)de`HMYXl}tz8BbUO*(EF#8Da_lVFcm;nzRP%uOd+ z@3Kf_>16-0pL;cNX5xdHAa6_km^%5H^dI(wA_4magI?N(FNd<@8p51M#xO0IkHf~ppkgHJY{Tm@a6PaS{4;1OQ6?b}`_CHg<}hn-YCMN?2jTR@ZK;2F8}mk_mHHSIkxd8<^tG^RgRr0vZcQcBZcKYLq zE63~@U*@)2Cb*UK73lCU>5L|_hDYcht3I*#7?A*UNOo?1`G+ z1h1#_8AE4^G*f9Aft+4ttzhZzl9O*G8vabxkqhOWu{)n}uWvEi;kEP=HEkE8xX%jb z9|zO(+k~DhPFv&=d3|(!!)io_HD!9|p?IS5c_F_F(-SuN?*`=M8o&P~NM7PP8I&^1 zl&Gxh_4VtZg=0;eA8V4sguny0Ve_g#Cj@nyICCsEkEgwg?n(LW73w@#e_V(0<1uT) zgE3v3vD)9>=YF`fq@uG{Z~2ykv#a9WhcnCWouo4FcIaB45r0?H(F_EzMpYN5tn{4F zKXgB?##yHDr?#)k_wU@6;6+17Qp{%sUc)VvvVF3()8%?{)eom z29z$E{!}k~y-B9y$C>go(eJ41?{EK>5tf+)FPxKLTWwwVs5@mQM?92Clvp03>|M^7@IRqd80SG_<0uX=z1Rwwb2tWV=5P$##AOHaf zKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_ z009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz z00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_< z0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb z2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$## zAOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;| zfB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U< z00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa z0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV= z5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHaf zKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_ z009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz z00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_< z0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb z2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$## zAOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;| zfB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U< z00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa z0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV= z5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHaf zKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_ z009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz z00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_< z0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb h2tWV=5P$##AOHafKmY;|fB*y_009U<;QwcV{{_lI#9;sc literal 0 HcmV?d00001 diff --git a/public/javascripts/jquery.gritter.min.js b/public/javascripts/jquery.gritter.min.js new file mode 100755 index 0000000..9ca8edd --- /dev/null +++ b/public/javascripts/jquery.gritter.min.js @@ -0,0 +1,11 @@ +/* + * Gritter for jQuery + * http://www.boedesign.com/ + * + * Copyright (c) 2011 Jordan Boesch + * Dual licensed under the MIT and GPL licenses. + * + * Date: July 9, 2011 + * Version: 1.7.1 + */ +(function(b){b.gritter={};b.gritter.options={position:"",fade_in_speed:"medium",fade_out_speed:1000,time:6000};b.gritter.add=function(f){try{return a.add(f||{})}catch(d){var c="Gritter Error: "+d;(typeof(console)!="undefined"&&console.error)?console.error(c,f):alert(c)}};b.gritter.remove=function(d,c){a.removeSpecific(d,c||{})};b.gritter.removeAll=function(c){a.stop(c||{})};var a={position:"",fade_in_speed:"",fade_out_speed:"",time:"",_custom_timer:0,_item_count:0,_is_setup:0,_tpl_close:'

    ',_tpl_item:'',_tpl_wrap:'
    ',add:function(g){if(!g.title||!g.text){throw'You need to fill out the first 2 params: "title" and "text"'}if(!this._is_setup){this._runSetup()}var i=g.title,n=g.text,e=g.image||"",l=g.sticky||false,m=g.class_name||"",k=b.gritter.options.position,d=g.time||"";this._verifyWrapper();this._item_count++;var f=this._item_count,j=this._tpl_item;b(["before_open","after_open","before_close","after_close"]).each(function(p,q){a["_"+q+"_"+f]=(b.isFunction(g[q]))?g[q]:function(){}});this._custom_timer=0;if(d){this._custom_timer=d}var c=(e!="")?'':"",h=(e!="")?"gritter-with-image":"gritter-without-image";j=this._str_replace(["[[username]]","[[text]]","[[close]]","[[image]]","[[number]]","[[class_name]]","[[item_class]]"],[i,n,this._tpl_close,c,this._item_count,h,m],j);this["_before_open_"+f]();b("#gritter-notice-wrapper").addClass(k).append(j);var o=b("#gritter-item-"+this._item_count);o.fadeIn(this.fade_in_speed,function(){a["_after_open_"+f](b(this))});if(!l){this._setFadeTimer(o,f)}b(o).bind("mouseenter mouseleave",function(p){if(p.type=="mouseenter"){if(!l){a._restoreItemIfFading(b(this),f)}}else{if(!l){a._setFadeTimer(b(this),f)}}a._hoverState(b(this),p.type)});return f},_countRemoveWrapper:function(c,d,f){d.remove();this["_after_close_"+c](d,f);if(b(".gritter-item-wrapper").length==0){b("#gritter-notice-wrapper").remove()}},_fade:function(f,c,h,d){var h=h||{},g=(typeof(h.fade)!="undefined")?h.fade:true;fade_out_speed=h.speed||this.fade_out_speed,manual_close=d;this["_before_close_"+c](f,manual_close);if(d){f.unbind("mouseenter mouseleave")}if(g){f.animate({opacity:0},fade_out_speed,function(){f.animate({height:0},300,function(){a._countRemoveWrapper(c,f,manual_close)})})}else{this._countRemoveWrapper(c,f)}},_hoverState:function(d,c){if(c=="mouseenter"){d.addClass("hover");d.find(".gritter-close").show();d.find(".gritter-close").click(function(){var e=d.attr("id").split("-")[2];a.removeSpecific(e,{},d,true)})}else{d.removeClass("hover");d.find(".gritter-close").hide()}},removeSpecific:function(c,g,f,d){if(!f){var f=b("#gritter-item-"+c)}this._fade(f,c,g||{},d)},_restoreItemIfFading:function(d,c){clearTimeout(this["_int_id_"+c]);d.stop().css({opacity:""})},_runSetup:function(){for(opt in b.gritter.options){this[opt]=b.gritter.options[opt]}this._is_setup=1},_setFadeTimer:function(f,d){var c=(this._custom_timer)?this._custom_timer:this.time;this["_int_id_"+d]=setTimeout(function(){a._fade(f,d)},c)},stop:function(e){var c=(b.isFunction(e.before_close))?e.before_close:function(){};var f=(b.isFunction(e.after_close))?e.after_close:function(){};var d=b("#gritter-notice-wrapper");c(d);d.fadeOut(function(){b(this).remove();f()})},_str_replace:function(v,e,o,n){var k=0,h=0,t="",m="",g=0,q=0,l=[].concat(v),c=[].concat(e),u=o,d=c instanceof Array,p=u instanceof Array;u=[].concat(u);if(n){this.window[n]=0}for(k=0,g=u.length;k 0){ + $.when( $.ajax("/notifications.json")).then(function(data, status){ + $("#notifications_button").addClass("with_notifications"); + this.set({"notifications": data}); + var urgentNotifications = _(data).find(function(notif){ + return notif.params.urgent == true; + }); + for (var i=0; i notification.id.to_s + assigns(:notification).should eq(notification) + end + end + + describe "GET new" do + it "assigns a new notification as @notification" do + get :new + assigns(:notification).should be_a_new(Notification) + end + end + + describe "GET edit" do + it "assigns the requested notification as @notification" do + notification = Notification.create! valid_attributes + get :edit, :id => notification.id.to_s + assigns(:notification).should eq(notification) + end + end + + describe "POST create" do + describe "with valid params" do + it "creates a new Notification" do + expect { + post :create, :notification => valid_attributes + }.to change(Notification, :count).by(1) + end + + it "assigns a newly created notification as @notification" do + post :create, :notification => valid_attributes + assigns(:notification).should be_a(Notification) + assigns(:notification).should be_persisted + end + + it "redirects to the created notification" do + post :create, :notification => valid_attributes + response.should redirect_to(Notification.last) + end + end + + describe "with invalid params" do + it "assigns a newly created but unsaved notification as @notification" do + # Trigger the behavior that occurs when invalid params are submitted + Notification.any_instance.stub(:save).and_return(false) + post :create, :notification => {} + assigns(:notification).should be_a_new(Notification) + end + + it "re-renders the 'new' template" do + # Trigger the behavior that occurs when invalid params are submitted + Notification.any_instance.stub(:save).and_return(false) + post :create, :notification => {} + response.should render_template("new") + end + end + end + + describe "PUT update" do + describe "with valid params" do + it "updates the requested notification" do + notification = Notification.create! valid_attributes + # Assuming there are no other notifications in the database, this + # specifies that the Notification created on the previous line + # receives the :update_attributes message with whatever params are + # submitted in the request. + Notification.any_instance.should_receive(:update_attributes).with({'these' => 'params'}) + put :update, :id => notification.id, :notification => {'these' => 'params'} + end + + it "assigns the requested notification as @notification" do + notification = Notification.create! valid_attributes + put :update, :id => notification.id, :notification => valid_attributes + assigns(:notification).should eq(notification) + end + + it "redirects to the notification" do + notification = Notification.create! valid_attributes + put :update, :id => notification.id, :notification => valid_attributes + response.should redirect_to(notification) + end + end + + describe "with invalid params" do + it "assigns the notification as @notification" do + notification = Notification.create! valid_attributes + # Trigger the behavior that occurs when invalid params are submitted + Notification.any_instance.stub(:save).and_return(false) + put :update, :id => notification.id.to_s, :notification => {} + assigns(:notification).should eq(notification) + end + + it "re-renders the 'edit' template" do + notification = Notification.create! valid_attributes + # Trigger the behavior that occurs when invalid params are submitted + Notification.any_instance.stub(:save).and_return(false) + put :update, :id => notification.id.to_s, :notification => {} + response.should render_template("edit") + end + end + end + + describe "DELETE destroy" do + it "destroys the requested notification" do + notification = Notification.create! valid_attributes + expect { + delete :destroy, :id => notification.id.to_s + }.to change(Notification, :count).by(-1) + end + + it "redirects to the notifications list" do + notification = Notification.create! valid_attributes + delete :destroy, :id => notification.id.to_s + response.should redirect_to(notifications_url) + end + end + +end diff --git a/spec/helpers/notifications_helper_spec.rb b/spec/helpers/notifications_helper_spec.rb new file mode 100644 index 0000000..f97959e --- /dev/null +++ b/spec/helpers/notifications_helper_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +# Specs in this file have access to a helper object that includes +# the NotificationsHelper. For example: +# +# describe NotificationsHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# helper.concat_strings("this","that").should == "this that" +# end +# end +# end +describe NotificationsHelper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/notification_spec.rb b/spec/models/notification_spec.rb new file mode 100644 index 0000000..2fc117a --- /dev/null +++ b/spec/models/notification_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe Notification do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/notification_type_spec.rb b/spec/models/notification_type_spec.rb new file mode 100644 index 0000000..31ae8c4 --- /dev/null +++ b/spec/models/notification_type_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe NotificationType do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/requests/notifications_spec.rb b/spec/requests/notifications_spec.rb new file mode 100644 index 0000000..07db7c0 --- /dev/null +++ b/spec/requests/notifications_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe "Notifications" do + describe "GET /notifications" do + it "works! (now write some real specs)" do + # Run the generator again with the --webrat flag if you want to use webrat methods/matchers + get notifications_path + response.status.should be(200) + end + end +end diff --git a/spec/routing/notifications_routing_spec.rb b/spec/routing/notifications_routing_spec.rb new file mode 100644 index 0000000..421a443 --- /dev/null +++ b/spec/routing/notifications_routing_spec.rb @@ -0,0 +1,35 @@ +require "spec_helper" + +describe NotificationsController do + describe "routing" do + + it "routes to #index" do + get("/notifications").should route_to("notifications#index") + end + + it "routes to #new" do + get("/notifications/new").should route_to("notifications#new") + end + + it "routes to #show" do + get("/notifications/1").should route_to("notifications#show", :id => "1") + end + + it "routes to #edit" do + get("/notifications/1/edit").should route_to("notifications#edit", :id => "1") + end + + it "routes to #create" do + post("/notifications").should route_to("notifications#create") + end + + it "routes to #update" do + put("/notifications/1").should route_to("notifications#update", :id => "1") + end + + it "routes to #destroy" do + delete("/notifications/1").should route_to("notifications#destroy", :id => "1") + end + + end +end diff --git a/spec/views/notifications/edit.html.erb_spec.rb b/spec/views/notifications/edit.html.erb_spec.rb new file mode 100644 index 0000000..9903d7b --- /dev/null +++ b/spec/views/notifications/edit.html.erb_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe "notifications/edit.html.erb" do + before(:each) do + @notification = assign(:notification, stub_model(Notification, + :player => "", + :notification_type => nil, + :params => "MyText", + :read => false + )) + end + + it "renders the edit notification form" do + render + + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "form", :action => notifications_path(@notification), :method => "post" do + assert_select "input#notification_player", :name => "notification[player]" + assert_select "input#notification_notification_type", :name => "notification[notification_type]" + assert_select "textarea#notification_params", :name => "notification[params]" + assert_select "input#notification_read", :name => "notification[read]" + end + end +end diff --git a/spec/views/notifications/index.html.erb_spec.rb b/spec/views/notifications/index.html.erb_spec.rb new file mode 100644 index 0000000..a432dd4 --- /dev/null +++ b/spec/views/notifications/index.html.erb_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +describe "notifications/index.html.erb" do + before(:each) do + assign(:notifications, [ + stub_model(Notification, + :player => "", + :notification_type => nil, + :params => "MyText", + :read => false + ), + stub_model(Notification, + :player => "", + :notification_type => nil, + :params => "MyText", + :read => false + ) + ]) + end + + it "renders a list of notifications" do + render + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "tr>td", :text => "".to_s, :count => 2 + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "tr>td", :text => nil.to_s, :count => 2 + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "tr>td", :text => "MyText".to_s, :count => 2 + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "tr>td", :text => false.to_s, :count => 2 + end +end diff --git a/spec/views/notifications/new.html.erb_spec.rb b/spec/views/notifications/new.html.erb_spec.rb new file mode 100644 index 0000000..e521cc6 --- /dev/null +++ b/spec/views/notifications/new.html.erb_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe "notifications/new.html.erb" do + before(:each) do + assign(:notification, stub_model(Notification, + :player => "", + :notification_type => nil, + :params => "MyText", + :read => false + ).as_new_record) + end + + it "renders new notification form" do + render + + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "form", :action => notifications_path, :method => "post" do + assert_select "input#notification_player", :name => "notification[player]" + assert_select "input#notification_notification_type", :name => "notification[notification_type]" + assert_select "textarea#notification_params", :name => "notification[params]" + assert_select "input#notification_read", :name => "notification[read]" + end + end +end diff --git a/spec/views/notifications/show.html.erb_spec.rb b/spec/views/notifications/show.html.erb_spec.rb new file mode 100644 index 0000000..7cbea25 --- /dev/null +++ b/spec/views/notifications/show.html.erb_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe "notifications/show.html.erb" do + before(:each) do + @notification = assign(:notification, stub_model(Notification, + :player => "", + :notification_type => nil, + :params => "MyText", + :read => false + )) + end + + it "renders attributes in

    " do + render + # Run the generator again with the --webrat flag if you want to use webrat matchers + rendered.should match(//) + # Run the generator again with the --webrat flag if you want to use webrat matchers + rendered.should match(//) + # Run the generator again with the --webrat flag if you want to use webrat matchers + rendered.should match(/MyText/) + # Run the generator again with the --webrat flag if you want to use webrat matchers + rendered.should match(/false/) + end +end