From cc12ad3b7a65dc56dc94a67f5ff05d316699cc3f Mon Sep 17 00:00:00 2001 From: benji Date: Mon, 9 Jan 2017 13:31:31 +0100 Subject: [PATCH 1/8] Upgrade to ruby 2.3.1 --- .ruby-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ruby-version b/.ruby-version index b1b25a5..2bf1c1c 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.2.2 +2.3.1 From 47486255023d261f9825d024673a4ad107f5c2a0 Mon Sep 17 00:00:00 2001 From: benji Date: Mon, 9 Jan 2017 15:46:43 +0100 Subject: [PATCH 2/8] Make requests for money --- app/assets/javascripts/requests.coffee | 3 ++ app/assets/stylesheets/requests.scss | 3 ++ app/controllers/pages_controller.rb | 1 - app/controllers/requests_controller.rb | 26 +++++++++++++++ app/controllers/transactions_controller.rb | 19 ++++++++--- app/helpers/requests_helper.rb | 2 ++ app/models/request.rb | 33 ++++++++++++++++++++ app/models/transaction.rb | 4 +++ app/models/user.rb | 4 +++ app/models/user_ability.rb | 3 +- app/views/layouts/application.html.haml | 2 ++ app/views/requests/_index.html.haml | 25 +++++++++++++++ app/views/requests/index.html.haml | 3 ++ app/views/transactions/_new.html.haml | 2 +- config/routes.rb | 7 ++++- db/migrate/20170109123717_create_requests.rb | 18 +++++++++++ db/schema.rb | 18 ++++++++++- spec/controllers/requests_controller_spec.rb | 5 +++ spec/factories/requests.rb | 6 ++++ spec/helpers/requests_helper_spec.rb | 15 +++++++++ spec/models/request_spec.rb | 5 +++ 21 files changed, 194 insertions(+), 10 deletions(-) create mode 100644 app/assets/javascripts/requests.coffee create mode 100644 app/assets/stylesheets/requests.scss create mode 100644 app/controllers/requests_controller.rb create mode 100644 app/helpers/requests_helper.rb create mode 100644 app/models/request.rb create mode 100644 app/views/requests/_index.html.haml create mode 100644 app/views/requests/index.html.haml create mode 100644 db/migrate/20170109123717_create_requests.rb create mode 100644 spec/controllers/requests_controller_spec.rb create mode 100644 spec/factories/requests.rb create mode 100644 spec/helpers/requests_helper_spec.rb create mode 100644 spec/models/request_spec.rb diff --git a/app/assets/javascripts/requests.coffee b/app/assets/javascripts/requests.coffee new file mode 100644 index 0000000..24f83d1 --- /dev/null +++ b/app/assets/javascripts/requests.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/stylesheets/requests.scss b/app/assets/stylesheets/requests.scss new file mode 100644 index 0000000..45c818a --- /dev/null +++ b/app/assets/stylesheets/requests.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the requests controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb index fd7c718..42b9a09 100644 --- a/app/controllers/pages_controller.rb +++ b/app/controllers/pages_controller.rb @@ -5,5 +5,4 @@ class PagesController < ApplicationController def landing @statistics = Statistics.new end - end diff --git a/app/controllers/requests_controller.rb b/app/controllers/requests_controller.rb new file mode 100644 index 0000000..3c606b2 --- /dev/null +++ b/app/controllers/requests_controller.rb @@ -0,0 +1,26 @@ +class RequestsController < ApplicationController + load_and_authorize_resource :user, only: :index + + before_action :load_request, only: [:confirm, :decline] + authorize_resource :request, only: [:confirm, :decline] + + def index + @requests = User.find(params[:user_id]).incoming_requests.group_by(&:status) + end + + def confirm + @request.confirm! + redirect_to user_requests_path(@request.debtor) + end + + def decline + @request.decline! + redirect_to user_requests_path(@request.debtor) + end + + private + + def load_request + @request = Request.find params[:request_id] + end +end diff --git a/app/controllers/transactions_controller.rb b/app/controllers/transactions_controller.rb index 2aad2b2..fe4cdc3 100644 --- a/app/controllers/transactions_controller.rb +++ b/app/controllers/transactions_controller.rb @@ -9,13 +9,22 @@ class TransactionsController < ApplicationController def create @transaction = Transaction.new(transaction_params) @transaction.reverse if @transaction.amount < 0 - authorize!(:create, @transaction) - if @transaction.save - render json: @transaction, status: :created + if can? :create, @transaction + if @transaction.save + render json: @transaction, status: :created + else + render json: @transaction.errors.full_messages, + status: :unprocessable_entity + end else - render json: @transaction.errors.full_messages, - status: :unprocessable_entity + request = Request.new @transaction.info + if request.save + render json: request, status: :created + else + render json: request.errors.full_messages, + status: :unprocessable_entity + end end end diff --git a/app/helpers/requests_helper.rb b/app/helpers/requests_helper.rb new file mode 100644 index 0000000..53ac95c --- /dev/null +++ b/app/helpers/requests_helper.rb @@ -0,0 +1,2 @@ +module RequestsHelper +end diff --git a/app/models/request.rb b/app/models/request.rb new file mode 100644 index 0000000..f6c33d7 --- /dev/null +++ b/app/models/request.rb @@ -0,0 +1,33 @@ +class Request < ActiveRecord::Base + belongs_to :debtor, class_name: 'User' + belongs_to :creditor, class_name: 'User' + belongs_to :issuer, polymorphic: true + + validates :amount, numericality: { greater_than: 0 } + validate :different_debtor_creditor + + enum status: [:open, :confirmed, :declined] + + def confirm! + return unless open? + + Transaction.create attributes.symbolize_keys.extract!( + :debtor_id, :creditor_id, :issuer_id, :issuer_type, :amount, :message + ) + update_attributes status: :confirmed + end + + def decline! + return unless open? + + update_attributes status: :declined + end + + private + + def different_debtor_creditor + if self.debtor == self.creditor + self.errors.add :base, "Can't write money to yourself" + end + end +end diff --git a/app/models/transaction.rb b/app/models/transaction.rb index 7d82df8..bc31c92 100644 --- a/app/models/transaction.rb +++ b/app/models/transaction.rb @@ -42,6 +42,10 @@ class Transaction < ActiveRecord::Base self.amount *= -1 end + def info + attributes.symbolize_keys.extract!(:debtor_id, :creditor_id, :issuer_id, :issuer_type, :message, :amount) + end + private def recalculate_balances diff --git a/app/models/user.rb b/app/models/user.rb index d3c234b..65afd6a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -18,6 +18,10 @@ class User < ActiveRecord::Base class_name: 'Transaction', foreign_key: 'creditor_id' has_many :outgoing_transactions, class_name: 'Transaction', foreign_key: 'debtor_id' + has_many :incoming_requests, + class_name: 'Request', foreign_key: 'debtor_id' + has_many :outgoing_requests, + class_name: 'Request', foreign_key: 'debtor_id' has_many :issued_transactions, as: :issuer, class_name: 'Transaction' diff --git a/app/models/user_ability.rb b/app/models/user_ability.rb index 196d854..51fff98 100644 --- a/app/models/user_ability.rb +++ b/app/models/user_ability.rb @@ -5,7 +5,8 @@ class UserAbility return unless user can :manage, :all if user.penning? - can :read, user, id: user.id + can :read, user, id: user.id + can :manage, Request, user_id: user.id can :create, Transaction do |t| t.debtor == user && t.amount <= Rails.application.config.maximum_amount end diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index bdd6542..249bd97 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -17,6 +17,8 @@ - if current_user.penning %li.pure-menu-item =link_to "Zeus", User.zeus, class: "pure-menu-link" + %li.pure-menu-item + = link_to 'Requests', user_requests_path(current_user), class: 'pure-menu-link' .pure-u-1 = render 'partials/flash' = yield diff --git a/app/views/requests/_index.html.haml b/app/views/requests/_index.html.haml new file mode 100644 index 0000000..6b33984 --- /dev/null +++ b/app/views/requests/_index.html.haml @@ -0,0 +1,25 @@ +%h4= title +%table.pure-table + %thead + %tr + %th Peer + %th Issuer + %th Amount + %th Message + - if actions + %th Accept + %th Decline + %tbody + - (requests || []).each do |r| + %tr + %td= r.creditor.name + %td= r.issuer.name + %td= "€#{r.amount/100.0}" + %td= r.message + - if actions + %td + = link_to request_confirm_path(r), method: :post do + %span.glyphicon.glyphicon-ok + %td + = link_to request_decline_path(r), method: :post do + %span.glyphicon.glyphicon-remove diff --git a/app/views/requests/index.html.haml b/app/views/requests/index.html.haml new file mode 100644 index 0000000..e56d57c --- /dev/null +++ b/app/views/requests/index.html.haml @@ -0,0 +1,3 @@ += render 'index', actions: true, title: 'Open Requests', requests: @requests['open'] += render 'index', actions: false, title: 'Confirmed Requests', requests: @requests['confirmed'] += render 'index', actions: false, title: 'Declined Requests', requests: @requests['declined'] diff --git a/app/views/transactions/_new.html.haml b/app/views/transactions/_new.html.haml index cb4be2c..9b2a6db 100644 --- a/app/views/transactions/_new.html.haml +++ b/app/views/transactions/_new.html.haml @@ -13,7 +13,7 @@ %span.input-group-addon %span.glyphicon.glyphicon-euro = f.number_field :euros, value: amount(@transaction.amount), - placeholder: "Amount", step: 0.01, min: (0.01 unless current_user.penning), + placeholder: "Amount", step: 0.01, class: "form-control", size: 20, required: true, max: (Rails.application.config.maximum_amount/100 unless current_user.penning) = f.submit "Send it!", class: "pure-button pure-button-primary btn" diff --git a/config/routes.rb b/config/routes.rb index e4d543e..9c68f28 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -6,7 +6,12 @@ Rails.application.routes.draw do root to: 'pages#landing' resources :transactions, only: [:index, :create] - resources :users, only: [:show, :index] + resources :users, only: [:index, :show] do + resources :requests, only: [:index], shallow: true do + post :confirm + post :decline + end + end get 'datatables/:id' => 'datatables#transactions_for_user', as: "user_transactions" end diff --git a/db/migrate/20170109123717_create_requests.rb b/db/migrate/20170109123717_create_requests.rb new file mode 100644 index 0000000..1f1eca3 --- /dev/null +++ b/db/migrate/20170109123717_create_requests.rb @@ -0,0 +1,18 @@ +class CreateRequests < ActiveRecord::Migration + def change + create_table :requests do |t| + t.references :debtor, index: true, null: false + t.references :creditor, index: true, null: false + t.references :issuer, polymorphic: true, index: true, null: false + t.integer :amount, null: false, default: 0 + t.string :message + + t.integer :status, default: 0 + + t.timestamps null: false + end + + add_foreign_key :request, :users, column: :creditor_id + add_foreign_key :request, :users, column: :debtor_id + end +end diff --git a/db/schema.rb b/db/schema.rb index d784c74..7bbf575 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150914095049) do +ActiveRecord::Schema.define(version: 20170109123717) do create_table "clients", force: :cascade do |t| t.string "name", null: false @@ -23,6 +23,22 @@ ActiveRecord::Schema.define(version: 20150914095049) do add_index "clients", ["key"], name: "index_clients_on_key" add_index "clients", ["name"], name: "index_clients_on_name" + create_table "requests", force: :cascade do |t| + t.integer "debtor_id", null: false + t.integer "creditor_id", null: false + t.integer "issuer_id", null: false + t.string "issuer_type", null: false + t.integer "amount", default: 0, null: false + t.string "message" + t.integer "status", default: 0 + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "requests", ["creditor_id"], name: "index_requests_on_creditor_id" + add_index "requests", ["debtor_id"], name: "index_requests_on_debtor_id" + add_index "requests", ["issuer_type", "issuer_id"], name: "index_requests_on_issuer_type_and_issuer_id" + create_table "transactions", force: :cascade do |t| t.integer "debtor_id", null: false t.integer "creditor_id", null: false diff --git a/spec/controllers/requests_controller_spec.rb b/spec/controllers/requests_controller_spec.rb new file mode 100644 index 0000000..f5fc12f --- /dev/null +++ b/spec/controllers/requests_controller_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe RequestsController, type: :controller do + +end diff --git a/spec/factories/requests.rb b/spec/factories/requests.rb new file mode 100644 index 0000000..e7d79e9 --- /dev/null +++ b/spec/factories/requests.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :request do + + end + +end diff --git a/spec/helpers/requests_helper_spec.rb b/spec/helpers/requests_helper_spec.rb new file mode 100644 index 0000000..a2ab326 --- /dev/null +++ b/spec/helpers/requests_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the RequestsHelper. For example: +# +# describe RequestsHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe RequestsHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/request_spec.rb b/spec/models/request_spec.rb new file mode 100644 index 0000000..8ccca08 --- /dev/null +++ b/spec/models/request_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Request, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end From 4a631bc0c2e57c04c6ceb361c4b799bdb05bc5b1 Mon Sep 17 00:00:00 2001 From: benji Date: Mon, 9 Jan 2017 16:22:58 +0100 Subject: [PATCH 3/8] Add notifications about requests --- app/assets/javascripts/notifications.coffee | 3 +++ app/assets/stylesheets/notifications.scss | 3 +++ app/controllers/notifications_controller.rb | 21 +++++++++++++++++++ app/controllers/requests_controller.rb | 4 ++-- app/helpers/notifications_helper.rb | 2 ++ app/models/notification.rb | 7 +++++++ app/models/request.rb | 4 ++++ app/models/user.rb | 1 + app/models/user_ability.rb | 3 ++- app/views/layouts/application.html.haml | 2 ++ app/views/notifications/_index.html.haml | 15 +++++++++++++ app/views/notifications/index.html.haml | 2 ++ config/routes.rb | 5 ++++- .../20170109150245_create_notifications.rb | 11 ++++++++++ db/schema.rb | 12 ++++++++++- .../notifications_controller_spec.rb | 5 +++++ spec/factories/notifications.rb | 6 ++++++ spec/helpers/notifications_helper_spec.rb | 15 +++++++++++++ spec/models/notification_spec.rb | 5 +++++ 19 files changed, 121 insertions(+), 5 deletions(-) create mode 100644 app/assets/javascripts/notifications.coffee create mode 100644 app/assets/stylesheets/notifications.scss 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/views/notifications/_index.html.haml create mode 100644 app/views/notifications/index.html.haml create mode 100644 db/migrate/20170109150245_create_notifications.rb create mode 100644 spec/controllers/notifications_controller_spec.rb create mode 100644 spec/factories/notifications.rb create mode 100644 spec/helpers/notifications_helper_spec.rb create mode 100644 spec/models/notification_spec.rb diff --git a/app/assets/javascripts/notifications.coffee b/app/assets/javascripts/notifications.coffee new file mode 100644 index 0000000..24f83d1 --- /dev/null +++ b/app/assets/javascripts/notifications.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/stylesheets/notifications.scss b/app/assets/stylesheets/notifications.scss new file mode 100644 index 0000000..c7ddf91 --- /dev/null +++ b/app/assets/stylesheets/notifications.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the notifications controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb new file mode 100644 index 0000000..9f86424 --- /dev/null +++ b/app/controllers/notifications_controller.rb @@ -0,0 +1,21 @@ +class NotificationsController < ApplicationController + load_and_authorize_resource :user, only: :index + + before_action :load_notification, only: :read + authorize_resource :notification, only: :read + + def index + @notifications = @user.notifications.group_by(&:read) + end + + def read + @notification.read! + redirect_to user_notifications_path(@notification.user) + end + + private + + def load_notification + @notification = Notification.find params[:notification_id] + end +end diff --git a/app/controllers/requests_controller.rb b/app/controllers/requests_controller.rb index 3c606b2..57d68ad 100644 --- a/app/controllers/requests_controller.rb +++ b/app/controllers/requests_controller.rb @@ -1,11 +1,11 @@ class RequestsController < ApplicationController - load_and_authorize_resource :user, only: :index + load_and_authorize_resource :user, only: :index before_action :load_request, only: [:confirm, :decline] authorize_resource :request, only: [:confirm, :decline] def index - @requests = User.find(params[:user_id]).incoming_requests.group_by(&:status) + @requests = @user.incoming_requests.group_by(&:status) end def confirm 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/notification.rb b/app/models/notification.rb new file mode 100644 index 0000000..ddad234 --- /dev/null +++ b/app/models/notification.rb @@ -0,0 +1,7 @@ +class Notification < ActiveRecord::Base + belongs_to :user + + def read! + update_attributes read: true + end +end diff --git a/app/models/request.rb b/app/models/request.rb index f6c33d7..5132b3d 100644 --- a/app/models/request.rb +++ b/app/models/request.rb @@ -14,12 +14,16 @@ class Request < ActiveRecord::Base Transaction.create attributes.symbolize_keys.extract!( :debtor_id, :creditor_id, :issuer_id, :issuer_type, :amount, :message ) + + creditor.notifications.create message: "Your request for #{amount} for \"#{message}\" has been accepted by #{debtor.name}." + update_attributes status: :confirmed end def decline! return unless open? + creditor.notifications.create message: "#{debtor.name} refuses to pay #{amount} for \"#{message}\"." update_attributes status: :declined end diff --git a/app/models/user.rb b/app/models/user.rb index 65afd6a..28dfa11 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -22,6 +22,7 @@ class User < ActiveRecord::Base class_name: 'Request', foreign_key: 'debtor_id' has_many :outgoing_requests, class_name: 'Request', foreign_key: 'debtor_id' + has_many :notifications has_many :issued_transactions, as: :issuer, class_name: 'Transaction' diff --git a/app/models/user_ability.rb b/app/models/user_ability.rb index 51fff98..49fe98b 100644 --- a/app/models/user_ability.rb +++ b/app/models/user_ability.rb @@ -6,7 +6,8 @@ class UserAbility can :manage, :all if user.penning? can :read, user, id: user.id - can :manage, Request, user_id: user.id + can :manage, Request, user_id: user.id + can :manage, Notification, user_id: user.id can :create, Transaction do |t| t.debtor == user && t.amount <= Rails.application.config.maximum_amount end diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 249bd97..47a812d 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -19,6 +19,8 @@ =link_to "Zeus", User.zeus, class: "pure-menu-link" %li.pure-menu-item = link_to 'Requests', user_requests_path(current_user), class: 'pure-menu-link' + %li.pure-menu-item + = link_to 'Notifications', user_notifications_path(current_user), class: 'pure-menu-link' .pure-u-1 = render 'partials/flash' = yield diff --git a/app/views/notifications/_index.html.haml b/app/views/notifications/_index.html.haml new file mode 100644 index 0000000..7d52c26 --- /dev/null +++ b/app/views/notifications/_index.html.haml @@ -0,0 +1,15 @@ +%h4= title +%table.pure-table + %thead + %tr + %th Message + - if actions + %th Mark as read + %tbody + - (notifications || []).each do |n| + %tr + %td= n.message + - if actions + %td + = link_to notification_read_path(n), method: :post do + %span.glyphicon.glyphicon-ok diff --git a/app/views/notifications/index.html.haml b/app/views/notifications/index.html.haml new file mode 100644 index 0000000..15012ea --- /dev/null +++ b/app/views/notifications/index.html.haml @@ -0,0 +1,2 @@ += render 'index', actions: true, title: 'Unread Notifications', notifications: @notifications[false] += render 'index', actions: false, title: 'Read Notifications', notifications: @notifications[true] diff --git a/config/routes.rb b/config/routes.rb index 9c68f28..bf9a17a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -7,10 +7,13 @@ Rails.application.routes.draw do resources :transactions, only: [:index, :create] resources :users, only: [:index, :show] do - resources :requests, only: [:index], shallow: true do + resources :requests, only: [:index], shallow: true do post :confirm post :decline end + resources :notifications, only: [:index], shallow: true do + post :read + end end get 'datatables/:id' => 'datatables#transactions_for_user', as: "user_transactions" diff --git a/db/migrate/20170109150245_create_notifications.rb b/db/migrate/20170109150245_create_notifications.rb new file mode 100644 index 0000000..b8b3717 --- /dev/null +++ b/db/migrate/20170109150245_create_notifications.rb @@ -0,0 +1,11 @@ +class CreateNotifications < ActiveRecord::Migration + def change + create_table :notifications do |t| + t.references :user, index: true, null: false + t.string :message + t.boolean :read, default: false + + t.timestamps null: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 7bbf575..3441a84 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170109123717) do +ActiveRecord::Schema.define(version: 20170109150245) do create_table "clients", force: :cascade do |t| t.string "name", null: false @@ -23,6 +23,16 @@ ActiveRecord::Schema.define(version: 20170109123717) do add_index "clients", ["key"], name: "index_clients_on_key" add_index "clients", ["name"], name: "index_clients_on_name" + create_table "notifications", force: :cascade do |t| + t.integer "user_id", null: false + t.string "message" + t.boolean "read", default: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "notifications", ["user_id"], name: "index_notifications_on_user_id" + create_table "requests", force: :cascade do |t| t.integer "debtor_id", null: false t.integer "creditor_id", null: false diff --git a/spec/controllers/notifications_controller_spec.rb b/spec/controllers/notifications_controller_spec.rb new file mode 100644 index 0000000..4212687 --- /dev/null +++ b/spec/controllers/notifications_controller_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe NotificationsController, type: :controller do + +end diff --git a/spec/factories/notifications.rb b/spec/factories/notifications.rb new file mode 100644 index 0000000..23b8f71 --- /dev/null +++ b/spec/factories/notifications.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :notification do + + end + +end diff --git a/spec/helpers/notifications_helper_spec.rb b/spec/helpers/notifications_helper_spec.rb new file mode 100644 index 0000000..1473b13 --- /dev/null +++ b/spec/helpers/notifications_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_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 +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe NotificationsHelper, type: :helper 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..bbdc6e0 --- /dev/null +++ b/spec/models/notification_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Notification, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end From 220a1e66844f22db5709cc9210f400c9980bdd7a Mon Sep 17 00:00:00 2001 From: benji Date: Mon, 9 Jan 2017 16:25:08 +0100 Subject: [PATCH 4/8] Euro signs --- app/models/request.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/request.rb b/app/models/request.rb index 5132b3d..a873316 100644 --- a/app/models/request.rb +++ b/app/models/request.rb @@ -15,7 +15,7 @@ class Request < ActiveRecord::Base :debtor_id, :creditor_id, :issuer_id, :issuer_type, :amount, :message ) - creditor.notifications.create message: "Your request for #{amount} for \"#{message}\" has been accepted by #{debtor.name}." + creditor.notifications.create message: "Your request for €#{amount/100.0} for \"#{message}\" has been accepted by #{debtor.name}." update_attributes status: :confirmed end @@ -23,7 +23,7 @@ class Request < ActiveRecord::Base def decline! return unless open? - creditor.notifications.create message: "#{debtor.name} refuses to pay #{amount} for \"#{message}\"." + creditor.notifications.create message: "#{debtor.name} refuses to pay €#{amount/100.0} for \"#{message}\"." update_attributes status: :declined end From 1e88bdd63accec2bff09f49937ca5e1328fa7bfe Mon Sep 17 00:00:00 2001 From: benji Date: Mon, 9 Jan 2017 16:40:58 +0100 Subject: [PATCH 5/8] Clean redundant code --- app/models/concerns/base_transaction.rb | 31 ++++++++++++++++++++ app/models/concerns/transaction_helpers.rb | 12 ++++++++ app/models/request.rb | 27 ++++++++---------- app/models/transaction.rb | 33 ++++------------------ app/views/requests/_index.html.haml | 2 +- 5 files changed, 60 insertions(+), 45 deletions(-) create mode 100644 app/models/concerns/base_transaction.rb create mode 100644 app/models/concerns/transaction_helpers.rb diff --git a/app/models/concerns/base_transaction.rb b/app/models/concerns/base_transaction.rb new file mode 100644 index 0000000..31c11dc --- /dev/null +++ b/app/models/concerns/base_transaction.rb @@ -0,0 +1,31 @@ +module BaseTransaction + extend ActiveSupport::Concern + include ActionView::Helpers::NumberHelper + + included do + belongs_to :debtor, class_name: 'User' + belongs_to :creditor, class_name: 'User' + belongs_to :issuer, polymorphic: true + + validates :amount, numericality: { greater_than: 0 } + validate :different_debtor_creditor + end + + def info + attributes.symbolize_keys.extract!( + :debtor_id, :creditor_id, :issuer_id, :issuer_type, :message, :amount + ) + end + + def amount_f + number_to_currency amount/100.0, unit: '€' + end + + private + + def different_debtor_creditor + if self.debtor == self.creditor + self.errors.add :base, "Can't write money to yourself" + end + end +end diff --git a/app/models/concerns/transaction_helpers.rb b/app/models/concerns/transaction_helpers.rb new file mode 100644 index 0000000..7187ee9 --- /dev/null +++ b/app/models/concerns/transaction_helpers.rb @@ -0,0 +1,12 @@ +module TransactionHelpers + include ActiveSupport::Concern + + def peer_of(user) + return creditor if user == debtor + return debtor if user == creditor + end + + def is_client_transaction? + issuer_type == 'Client' + end +end diff --git a/app/models/request.rb b/app/models/request.rb index a873316..4c82bd3 100644 --- a/app/models/request.rb +++ b/app/models/request.rb @@ -1,21 +1,13 @@ class Request < ActiveRecord::Base - belongs_to :debtor, class_name: 'User' - belongs_to :creditor, class_name: 'User' - belongs_to :issuer, polymorphic: true - - validates :amount, numericality: { greater_than: 0 } - validate :different_debtor_creditor + include BaseTransaction enum status: [:open, :confirmed, :declined] def confirm! return unless open? - Transaction.create attributes.symbolize_keys.extract!( - :debtor_id, :creditor_id, :issuer_id, :issuer_type, :amount, :message - ) - - creditor.notifications.create message: "Your request for €#{amount/100.0} for \"#{message}\" has been accepted by #{debtor.name}." + Transaction.create info + Notification.create user: creditor,message: confirmed_message update_attributes status: :confirmed end @@ -23,15 +15,18 @@ class Request < ActiveRecord::Base def decline! return unless open? - creditor.notifications.create message: "#{debtor.name} refuses to pay €#{amount/100.0} for \"#{message}\"." + Notification.create user: creditor, message: declined_message + update_attributes status: :declined end private - def different_debtor_creditor - if self.debtor == self.creditor - self.errors.add :base, "Can't write money to yourself" - end + def confirmed_message + "Your request for €#{amount/100.0} for \"#{message}\" has been accepted by #{debtor.name}." + end + + def declined_message + "#{debtor.name} refuses to pay €#{amount/100.0} for \"#{message}\"." end end diff --git a/app/models/transaction.rb b/app/models/transaction.rb index bc31c92..42c2037 100644 --- a/app/models/transaction.rb +++ b/app/models/transaction.rb @@ -15,22 +15,13 @@ # class Transaction < ActiveRecord::Base - belongs_to :debtor, class_name: 'User' - belongs_to :creditor, class_name: 'User' - belongs_to :issuer, polymorphic: true + include BaseTransaction + include TransactionHelpers - after_save :recalculate_balances - after_destroy :recalculate_balances + after_save :recalculate_balances! + after_destroy :recalculate_balances! - validates :amount, numericality: { greater_than: 0 } validates :id_at_client, presence: true, uniqueness: { scope: :issuer_id }, if: :is_client_transaction? - validate :different_debtor_creditor - - - def peer_of(user) - return creditor if user == debtor - return debtor if user == creditor - end def signed_amount_for(user) return -amount if user == debtor @@ -42,24 +33,10 @@ class Transaction < ActiveRecord::Base self.amount *= -1 end - def info - attributes.symbolize_keys.extract!(:debtor_id, :creditor_id, :issuer_id, :issuer_type, :message, :amount) - end - private - def recalculate_balances + def recalculate_balances! creditor.calculate_balance! debtor.calculate_balance! end - - def different_debtor_creditor - if self.debtor == self.creditor - self.errors.add :base, "Can't write money to yourself" - end - end - - def is_client_transaction? - issuer_type == "Client" - end end diff --git a/app/views/requests/_index.html.haml b/app/views/requests/_index.html.haml index 6b33984..3902a64 100644 --- a/app/views/requests/_index.html.haml +++ b/app/views/requests/_index.html.haml @@ -14,7 +14,7 @@ %tr %td= r.creditor.name %td= r.issuer.name - %td= "€#{r.amount/100.0}" + %td= r.amount_f %td= r.message - if actions %td From 51460a97b7fce938b622e0349df9373175e56e69 Mon Sep 17 00:00:00 2001 From: benji Date: Mon, 9 Jan 2017 16:41:24 +0100 Subject: [PATCH 6/8] Annotations --- app/models/notification.rb | 12 ++++++++++++ app/models/request.rb | 16 ++++++++++++++++ spec/factories/notifications.rb | 12 ++++++++++++ spec/factories/requests.rb | 16 ++++++++++++++++ spec/models/notification_spec.rb | 12 ++++++++++++ spec/models/request_spec.rb | 16 ++++++++++++++++ 6 files changed, 84 insertions(+) diff --git a/app/models/notification.rb b/app/models/notification.rb index ddad234..a926d62 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -1,3 +1,15 @@ +# == Schema Information +# +# Table name: notifications +# +# id :integer not null, primary key +# user_id :integer not null +# message :string +# read :boolean default(FALSE) +# created_at :datetime not null +# updated_at :datetime not null +# + class Notification < ActiveRecord::Base belongs_to :user diff --git a/app/models/request.rb b/app/models/request.rb index 4c82bd3..9e85688 100644 --- a/app/models/request.rb +++ b/app/models/request.rb @@ -1,3 +1,19 @@ +# == Schema Information +# +# Table name: requests +# +# id :integer not null, primary key +# debtor_id :integer not null +# creditor_id :integer not null +# issuer_id :integer not null +# issuer_type :string not null +# amount :integer default(0), not null +# message :string +# status :integer default(0) +# created_at :datetime not null +# updated_at :datetime not null +# + class Request < ActiveRecord::Base include BaseTransaction diff --git a/spec/factories/notifications.rb b/spec/factories/notifications.rb index 23b8f71..20df5f0 100644 --- a/spec/factories/notifications.rb +++ b/spec/factories/notifications.rb @@ -1,3 +1,15 @@ +# == Schema Information +# +# Table name: notifications +# +# id :integer not null, primary key +# user_id :integer not null +# message :string +# read :boolean default(FALSE) +# created_at :datetime not null +# updated_at :datetime not null +# + FactoryGirl.define do factory :notification do diff --git a/spec/factories/requests.rb b/spec/factories/requests.rb index e7d79e9..8193437 100644 --- a/spec/factories/requests.rb +++ b/spec/factories/requests.rb @@ -1,3 +1,19 @@ +# == Schema Information +# +# Table name: requests +# +# id :integer not null, primary key +# debtor_id :integer not null +# creditor_id :integer not null +# issuer_id :integer not null +# issuer_type :string not null +# amount :integer default(0), not null +# message :string +# status :integer default(0) +# created_at :datetime not null +# updated_at :datetime not null +# + FactoryGirl.define do factory :request do diff --git a/spec/models/notification_spec.rb b/spec/models/notification_spec.rb index bbdc6e0..964cd26 100644 --- a/spec/models/notification_spec.rb +++ b/spec/models/notification_spec.rb @@ -1,3 +1,15 @@ +# == Schema Information +# +# Table name: notifications +# +# id :integer not null, primary key +# user_id :integer not null +# message :string +# read :boolean default(FALSE) +# created_at :datetime not null +# updated_at :datetime not null +# + require 'rails_helper' RSpec.describe Notification, type: :model do diff --git a/spec/models/request_spec.rb b/spec/models/request_spec.rb index 8ccca08..1aab9ee 100644 --- a/spec/models/request_spec.rb +++ b/spec/models/request_spec.rb @@ -1,3 +1,19 @@ +# == Schema Information +# +# Table name: requests +# +# id :integer not null, primary key +# debtor_id :integer not null +# creditor_id :integer not null +# issuer_id :integer not null +# issuer_type :string not null +# amount :integer default(0), not null +# message :string +# status :integer default(0) +# created_at :datetime not null +# updated_at :datetime not null +# + require 'rails_helper' RSpec.describe Request, type: :model do From 11024261d93985771d9d458c931a6733da56ab01 Mon Sep 17 00:00:00 2001 From: benji Date: Tue, 10 Jan 2017 17:22:11 +0100 Subject: [PATCH 7/8] Add number of requests and notifications in header --- app/views/layouts/application.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 47a812d..3d5c65b 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -18,9 +18,9 @@ %li.pure-menu-item =link_to "Zeus", User.zeus, class: "pure-menu-link" %li.pure-menu-item - = link_to 'Requests', user_requests_path(current_user), class: 'pure-menu-link' + = link_to "Requests (#{current_user.incoming_requests.size})", user_requests_path(current_user), class: 'pure-menu-link' %li.pure-menu-item - = link_to 'Notifications', user_notifications_path(current_user), class: 'pure-menu-link' + = link_to "Notifications (#{current_user.notifications.size})", user_notifications_path(current_user), class: 'pure-menu-link' .pure-u-1 = render 'partials/flash' = yield From a803d005a30850bbbc930f93e77fa5f0f8a221d5 Mon Sep 17 00:00:00 2001 From: benji Date: Wed, 11 Jan 2017 14:34:17 +0100 Subject: [PATCH 8/8] Authorize creating requests for users, and add links for pennings to zeus requests --- app/controllers/transactions_controller.rb | 1 + app/models/user.rb | 2 +- app/models/user_ability.rb | 2 +- app/views/application/_menu.html.haml | 4 ++++ 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/controllers/transactions_controller.rb b/app/controllers/transactions_controller.rb index fe4cdc3..d4d1a45 100644 --- a/app/controllers/transactions_controller.rb +++ b/app/controllers/transactions_controller.rb @@ -19,6 +19,7 @@ class TransactionsController < ApplicationController end else request = Request.new @transaction.info + authorize!(:create, request) if request.save render json: request, status: :created else diff --git a/app/models/user.rb b/app/models/user.rb index 28dfa11..df5aa07 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -47,7 +47,7 @@ class User < ActiveRecord::Base end def self.zeus - find_or_create_by name: 'Zeus' + @@zeus ||= find_or_create_by name: 'Zeus' end end diff --git a/app/models/user_ability.rb b/app/models/user_ability.rb index 49fe98b..1574f44 100644 --- a/app/models/user_ability.rb +++ b/app/models/user_ability.rb @@ -6,7 +6,7 @@ class UserAbility can :manage, :all if user.penning? can :read, user, id: user.id - can :manage, Request, user_id: user.id + can :manage, Request, creditor_id: user.id can :manage, Notification, user_id: user.id can :create, Transaction do |t| t.debtor == user && t.amount <= Rails.application.config.maximum_amount diff --git a/app/views/application/_menu.html.haml b/app/views/application/_menu.html.haml index c5d9fc2..09fe0ac 100644 --- a/app/views/application/_menu.html.haml +++ b/app/views/application/_menu.html.haml @@ -7,6 +7,10 @@ - if current_user.penning %li.pure-menu-item =link_to "Zeus", User.zeus, class: "pure-menu-link" + %li.pure-menu-item + = link_to "Requests (#{User.zeus.incoming_requests.size})", user_requests_path(User.zeus), class: 'pure-menu-link' + %li.pure-menu-item + = link_to "Notifications (#{User.zeus.notifications.size})", user_notifications_path(User.zeus), class: 'pure-menu-link' %li.pure-menu-item = link_to "Requests (#{current_user.incoming_requests.size})", user_requests_path(current_user), class: 'pure-menu-link' %li.pure-menu-item