diff --git a/app/models/concerns/milestoneable.rb b/app/models/concerns/milestoneable.rb index 2e961c613..7f58a77bb 100644 --- a/app/models/concerns/milestoneable.rb +++ b/app/models/concerns/milestoneable.rb @@ -5,5 +5,7 @@ module Milestoneable has_many :milestones, as: :milestoneable, dependent: :destroy scope :with_milestones, -> { joins(:milestones).distinct } + + has_many :progress_bars, as: :progressable end end diff --git a/app/models/progress_bar.rb b/app/models/progress_bar.rb new file mode 100644 index 000000000..7a7973723 --- /dev/null +++ b/app/models/progress_bar.rb @@ -0,0 +1,28 @@ +class ProgressBar < ActiveRecord::Base + self.inheritance_column = nil + RANGE = 0..100 + + enum kind: %i[primary secondary] + + belongs_to :progressable, polymorphic: true + + translates :title, touch: true + include Globalizable + + validates :progressable, presence: true + validates :kind, presence: true, + uniqueness: { + scope: [:progressable_type, :progressable_id], + conditions: -> { primary } + } + validates :percentage, presence: true, inclusion: RANGE, numericality: { only_integer: true } + + before_validation :assign_progress_bar_to_translations + validates_translation :title, presence: true, unless: :primary? + + private + + def assign_progress_bar_to_translations + translations.each { |translation| translation.globalized_model = self } + end +end diff --git a/app/models/progress_bar/translation.rb b/app/models/progress_bar/translation.rb new file mode 100644 index 000000000..d61f1d47b --- /dev/null +++ b/app/models/progress_bar/translation.rb @@ -0,0 +1,3 @@ +class ProgressBar::Translation < Globalize::ActiveRecord::Translation + delegate :primary?, to: :globalized_model +end diff --git a/config/locales/en/activerecord.yml b/config/locales/en/activerecord.yml index aab700334..90e726039 100644 --- a/config/locales/en/activerecord.yml +++ b/config/locales/en/activerecord.yml @@ -16,6 +16,9 @@ en: milestone/status: one: "Milestone Status" other: "Milestone Statuses" + progress_bar: + one: "Progress bar" + other: "Progress bars" comment: one: "Comment" other: "Comments" @@ -136,6 +139,13 @@ en: milestone/status: name: "Name" description: "Description (optional)" + progress_bar: + kind: "Type" + title: "Title" + percentage: "Current progress" + progress_bar/kind: + primary: "Primary" + secondary: "Secondary" budget/heading: name: "Heading name" price: "Price" diff --git a/config/locales/es/activerecord.yml b/config/locales/es/activerecord.yml index 2c0b210a0..45cfa15b0 100644 --- a/config/locales/es/activerecord.yml +++ b/config/locales/es/activerecord.yml @@ -16,6 +16,9 @@ es: milestone/status: one: "Estado de seguimiento" other: "Estados de seguimiento" + progress_bar: + one: "Barra de progreso" + other: "Barras de progreso" comment: one: "Comentario" other: "Comentarios" @@ -136,6 +139,13 @@ es: milestone/status: name: "Nombre" description: "Descripción (opcional)" + progress_bar: + kind: "Tipo" + title: "Título" + percentage: "Progreso" + progress_bar/kind: + primary: "Principal" + secondary: "Secundaria" budget/heading: name: "Nombre de la partida" price: "Cantidad" diff --git a/db/migrate/20190103132925_create_progress_bars.rb b/db/migrate/20190103132925_create_progress_bars.rb new file mode 100644 index 000000000..899a4819f --- /dev/null +++ b/db/migrate/20190103132925_create_progress_bars.rb @@ -0,0 +1,23 @@ +class CreateProgressBars < ActiveRecord::Migration + def change + create_table :progress_bars do |t| + t.integer :kind + t.integer :percentage + t.references :progressable, polymorphic: true + + t.timestamps null: false + end + + reversible do |change| + change.up do + ProgressBar.create_translation_table!({ + title: :string + }) + end + + change.down do + ProgressBar.drop_translation_table! + end + end + end +end diff --git a/db/schema.rb b/db/schema.rb index e3241fd8d..0d5ed3d1a 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: 20181206153510) do +ActiveRecord::Schema.define(version: 20190103132925) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -1088,6 +1088,26 @@ ActiveRecord::Schema.define(version: 20181206153510) do add_index "polls", ["starts_at", "ends_at"], name: "index_polls_on_starts_at_and_ends_at", using: :btree + create_table "progress_bar_translations", force: :cascade do |t| + t.integer "progress_bar_id", null: false + t.string "locale", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "title" + end + + add_index "progress_bar_translations", ["locale"], name: "index_progress_bar_translations_on_locale", using: :btree + add_index "progress_bar_translations", ["progress_bar_id"], name: "index_progress_bar_translations_on_progress_bar_id", using: :btree + + create_table "progress_bars", force: :cascade do |t| + t.integer "kind" + t.integer "percentage" + t.integer "progressable_id" + t.string "progressable_type" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "proposal_notifications", force: :cascade do |t| t.string "title" t.text "body" diff --git a/spec/factories/milestones.rb b/spec/factories/milestones.rb index ebd436b70..0e4071d15 100644 --- a/spec/factories/milestones.rb +++ b/spec/factories/milestones.rb @@ -11,4 +11,15 @@ FactoryBot.define do description "Milestone description" publication_date { Date.current } end + + factory :progress_bar do + association :progressable, factory: :budget_investment + percentage { rand(0..100) } + kind :primary + + trait(:secondary) do + kind :secondary + sequence(:title) { |n| "Progress bar #{n} title" } + end + end end diff --git a/spec/models/progress_bar_spec.rb b/spec/models/progress_bar_spec.rb new file mode 100644 index 000000000..46b0ce08b --- /dev/null +++ b/spec/models/progress_bar_spec.rb @@ -0,0 +1,97 @@ +require "rails_helper" + +describe ProgressBar do + let(:progress_bar) { build(:progress_bar) } + + it "is valid" do + expect(progress_bar).to be_valid + end + + it "is valid without a title" do + progress_bar.title = nil + + expect(progress_bar).to be_valid + end + + it "is not valid with a custom type" do + expect { progress_bar.kind = "terciary" }.to raise_exception(ArgumentError) + end + + it "is not valid without a percentage" do + progress_bar.percentage = nil + + expect(progress_bar).not_to be_valid + end + + it "is not valid with a non-numeric percentage" do + progress_bar.percentage = "High" + + expect(progress_bar).not_to be_valid + end + + it "is not valid with a non-integer percentage" do + progress_bar.percentage = 22.83 + + expect(progress_bar).not_to be_valid + end + + it "is not valid with a negative percentage" do + progress_bar.percentage = -1 + + expect(progress_bar).not_to be_valid + end + + it "is not valid with a percentage bigger than 100" do + progress_bar.percentage = 101 + + expect(progress_bar).not_to be_valid + end + + it "is valid with an integer percentage within the limits" do + progress_bar.percentage = 0 + + expect(progress_bar).to be_valid + + progress_bar.percentage = 100 + + expect(progress_bar).to be_valid + + progress_bar.percentage = 83 + + expect(progress_bar).to be_valid + end + + it "is not valid without a progressable" do + progress_bar.progressable = nil + + expect(progress_bar).not_to be_valid + end + + it "cannot have another primary progress bar for the same progressable" do + progress_bar.save + duplicate = build(:progress_bar, progressable: progress_bar.progressable) + + expect(duplicate).not_to be_valid + end + + describe "secondary progress bar" do + let(:progress_bar) { build(:progress_bar, :secondary) } + + it "is valid" do + expect(progress_bar).to be_valid + end + + it "is invalid without a title" do + progress_bar.title = nil + + expect(progress_bar).not_to be_valid + end + + it "can have another secondary progress bar for the same progressable" do + progress_bar.save + duplicate = build(:progress_bar, progressable: progress_bar.progressable) + + expect(duplicate).to be_valid + end + end +end