diff --git a/app/models/budget.rb b/app/models/budget.rb index 6ad8ffe61..024602730 100644 --- a/app/models/budget.rb +++ b/app/models/budget.rb @@ -1,13 +1,15 @@ class Budget < ActiveRecord::Base include Measurable + include Sluggable PHASES = %w(accepting reviewing selecting valuating balloting reviewing_ballots finished).freeze CURRENCY_SYMBOLS = %w(€ $ £ ¥).freeze - validates :name, presence: true + validates :name, presence: true, uniqueness: true validates :phase, inclusion: { in: PHASES } validates :currency_symbol, presence: true + validates :slug, presence: true, format: /\A[a-z0-9\-_]+\z/ has_many :investments, dependent: :destroy has_many :ballots, dependent: :destroy diff --git a/app/models/budget/group.rb b/app/models/budget/group.rb index dd7910950..93d7bba63 100644 --- a/app/models/budget/group.rb +++ b/app/models/budget/group.rb @@ -1,10 +1,14 @@ class Budget class Group < ActiveRecord::Base + include Sluggable + belongs_to :budget has_many :headings, dependent: :destroy validates :budget_id, presence: true - validates :name, presence: true + validates :name, presence: true, uniqueness: { scope: :budget } + validates :slug, presence: true, format: /\A[a-z0-9\-_]+\z/ + end -end \ No newline at end of file +end diff --git a/app/models/budget/heading.rb b/app/models/budget/heading.rb index a81308947..1a232c75e 100644 --- a/app/models/budget/heading.rb +++ b/app/models/budget/heading.rb @@ -1,12 +1,15 @@ class Budget class Heading < ActiveRecord::Base + include Sluggable + belongs_to :group has_many :investments validates :group_id, presence: true - validates :name, presence: true + validates :name, presence: true, uniqueness: { if: :name_exists_in_budget_headings } validates :price, presence: true + validates :slug, presence: true, format: /\A[a-z0-9\-_]+\z/ delegate :budget, :budget_id, to: :group, allow_nil: true @@ -16,5 +19,9 @@ class Budget "#{group.name}: #{name}" end + def name_exists_in_budget_headings + group.budget.headings.where(name: name).any? + end + end end diff --git a/app/models/concerns/sluggable.rb b/app/models/concerns/sluggable.rb new file mode 100644 index 000000000..01e27489d --- /dev/null +++ b/app/models/concerns/sluggable.rb @@ -0,0 +1,11 @@ +module Sluggable + extend ActiveSupport::Concern + + included do + before_validation :generate_slug + end + + def generate_slug + self.slug = name.to_s.parameterize + end +end diff --git a/db/migrate/20170704105112_add_slugs_to_budget_heading_group.rb b/db/migrate/20170704105112_add_slugs_to_budget_heading_group.rb new file mode 100644 index 000000000..85c94e66f --- /dev/null +++ b/db/migrate/20170704105112_add_slugs_to_budget_heading_group.rb @@ -0,0 +1,7 @@ +class AddSlugsToBudgetHeadingGroup < ActiveRecord::Migration + def change + add_column :budgets, :slug, :string + add_column :budget_groups, :slug, :string + add_column :budget_headings, :slug, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index cb9a015e9..0787b5ed1 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: 20170703120055) do +ActiveRecord::Schema.define(version: 20170704105112) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -103,6 +103,7 @@ ActiveRecord::Schema.define(version: 20170703120055) do create_table "budget_groups", force: :cascade do |t| t.integer "budget_id" t.string "name", limit: 50 + t.string "slug" end add_index "budget_groups", ["budget_id"], name: "index_budget_groups_on_budget_id", using: :btree @@ -112,6 +113,7 @@ ActiveRecord::Schema.define(version: 20170703120055) do t.string "name", limit: 50 t.integer "price", limit: 8 t.integer "population" + t.string "slug" end add_index "budget_headings", ["group_id"], name: "index_budget_headings_on_group_id", using: :btree @@ -196,6 +198,7 @@ ActiveRecord::Schema.define(version: 20170703120055) do t.text "description_balloting" t.text "description_reviewing_ballots" t.text "description_finished" + t.string "slug" end create_table "campaigns", force: :cascade do |t| diff --git a/lib/tasks/slugs.rake b/lib/tasks/slugs.rake new file mode 100644 index 000000000..2615c5b4c --- /dev/null +++ b/lib/tasks/slugs.rake @@ -0,0 +1,8 @@ +namespace :slugs do + desc "Generate slug attribute for objects from classes that use Sluggable concern" + task generate: :environment do + %w(Budget Budget::Heading Budget::Group).each do |class_name| + class_name.constantize.all.each(&:generate_slug) + end + end +end diff --git a/spec/models/budget/group_spec.rb b/spec/models/budget/group_spec.rb new file mode 100644 index 000000000..4d2570b59 --- /dev/null +++ b/spec/models/budget/group_spec.rb @@ -0,0 +1,23 @@ +require 'rails_helper' + +describe Budget::Group do + + it_behaves_like "sluggable" + + let(:budget) { create(:budget) } + + describe "name" do + before do + create(:budget_group, budget: budget, name: 'object name') + end + + it "can be repeatead in other budget's groups" do + expect(build(:budget_group, budget: create(:budget), name: 'object name')).to be_valid + end + + it "must be unique among all budget's groups" do + expect(build(:budget_group, budget: budget, name: 'object name')).not_to be_valid + end + end + +end diff --git a/spec/models/budget/heading_spec.rb b/spec/models/budget/heading_spec.rb new file mode 100644 index 000000000..ee2e4c142 --- /dev/null +++ b/spec/models/budget/heading_spec.rb @@ -0,0 +1,28 @@ +require 'rails_helper' + +describe Budget::Heading do + + it_behaves_like "sluggable" + + let(:budget) { create(:budget) } + let(:group) { create(:budget_group, budget: budget) } + + describe "name" do + before do + create(:budget_heading, group: group, name: 'object name') + end + + it "can be repeatead in other budget's groups" do + expect(build(:budget_heading, group: create(:budget_group), name: 'object name')).to be_valid + end + + it "must be unique among all budget's groups" do + expect(build(:budget_heading, group: create(:budget_group, budget: budget), name: 'object name')).not_to be_valid + end + + it "must be unique among all it's group" do + expect(build(:budget_heading, group: group, name: 'object name')).not_to be_valid + end + end + +end diff --git a/spec/models/budget_spec.rb b/spec/models/budget_spec.rb index a1d1a1f42..d3a7574e3 100644 --- a/spec/models/budget_spec.rb +++ b/spec/models/budget_spec.rb @@ -2,6 +2,18 @@ require 'rails_helper' describe Budget do + it_behaves_like "sluggable" + + describe "name" do + before do + create(:budget, name: 'object name') + end + + it "is validated for uniqueness" do + expect(build(:budget, name: 'object name')).not_to be_valid + end + end + describe "description" do it "changes depending on the phase" do budget = create(:budget) diff --git a/spec/models/concerns/sluggable.rb b/spec/models/concerns/sluggable.rb new file mode 100644 index 000000000..bfe76af1b --- /dev/null +++ b/spec/models/concerns/sluggable.rb @@ -0,0 +1,16 @@ +require 'spec_helper' + +shared_examples_for 'sluggable' do + + describe 'generate_slug' do + before do + create(described_class.name.parameterize.tr('-', '_').to_sym, name: "Marlo Brañido Carlo") + end + + context "when a new sluggable is created" do + it "gets a slug string" do + expect(described_class.last.slug).to eq("marlo-branido-carlo") + end + end + end +end