diff options
| -rw-r--r-- | app/assets/stylesheets/main/calendars.sass | 7 | ||||
| -rw-r--r-- | app/controllers/calendars_controller.rb | 64 | ||||
| -rw-r--r-- | app/helpers/breadcrumb_helper.rb | 2 | ||||
| -rw-r--r-- | app/models/calendar.rb | 83 | ||||
| -rw-r--r-- | app/views/calendars/_calendars.html.slim | 2 | ||||
| -rw-r--r-- | app/views/calendars/_form.html.slim | 35 | ||||
| -rw-r--r-- | app/views/calendars/_period_fields.html.slim (renamed from app/views/calendars/_range_fields.html.slim) | 0 | ||||
| -rw-r--r-- | app/views/calendars/edit.html.slim | 2 | ||||
| -rw-r--r-- | app/views/calendars/index.html.slim | 27 | ||||
| -rw-r--r-- | config/locales/calendars.en.yml | 9 | ||||
| -rw-r--r-- | config/locales/calendars.fr.yml | 9 | ||||
| -rw-r--r-- | lib/range_ext.rb | 7 | ||||
| -rw-r--r-- | spec/features/calendars_spec.rb | 35 | ||||
| -rw-r--r-- | spec/models/calendar_spec.rb | 52 |
14 files changed, 196 insertions, 138 deletions
diff --git a/app/assets/stylesheets/main/calendars.sass b/app/assets/stylesheets/main/calendars.sass new file mode 100644 index 000000000..c3f47f733 --- /dev/null +++ b/app/assets/stylesheets/main/calendars.sass @@ -0,0 +1,7 @@ +#calendar_form + .btn + margin: 5px + +#calendar_search_form + button + margin-top: 22px diff --git a/app/controllers/calendars_controller.rb b/app/controllers/calendars_controller.rb index 84b432bf3..563c126e4 100644 --- a/app/controllers/calendars_controller.rb +++ b/app/controllers/calendars_controller.rb @@ -2,42 +2,13 @@ class CalendarsController < BreadcrumbController defaults resource_class: Calendar before_action :check_policy, only: [:edit, :update, :destroy] - def new - new! do - @calendar.date_ranges = [] - @calendar.dates = [] - end - end - - def create - @calendar = current_organisation.calendars.build(calendar_params) - - if @calendar.valid? - respond_with @calendar - else - render action: 'new' - end - end - - def update - update! do |success, failure| - success.html { redirect_to calendar_path(@calendar) } - end - end - - def destroy - destroy! do |success, failure| - success.html { redirect_to calendars_path } - end - end - private def calendar_params params.require(:calendar).permit(:id, :name, :short_name, :shared, ranges_attributes: [:id, :begin, :end, :_destroy], dates: []) end def sort_column - current_organisation.calendars.column_names.include?(params[:sort]) ? params[:sort] : 'short_name' + Calendar.column_names.include?(params[:sort]) ? params[:sort] : 'short_name' end def sort_direction @@ -45,24 +16,27 @@ class CalendarsController < BreadcrumbController end protected - def collection - # if params[:q] - # if params[:q][:shared_eq] - # if params[:q][:shared_eq] == 'all' - # params[:q].delete(:shared_eq) - # else - # params[:q][:shared_eq] = params[:q][:shared_eq] == 'true' - # end - # end - # end + def resource + @calendar = current_organisation.calendars.find_by_id(params[:id]) + end - @q = current_organisation.calendars.search(params[:q]) + def build_resource + super.tap do |calendar| + calendar.organisation = current_organisation + end + end - if sort_column && sort_direction - @calendars ||= @q.result(distinct: true).order(sort_column + ' ' + sort_direction).paginate(page: params[:page]) - else - @calendars ||= @q.result(distinct: true).paginate(page: params[:page]) + def collection + return @calendars if @calendars + if params[:q] + params[:q].delete(:shared_eq) if params[:q][:shared_eq] == '' + params[:q].delete(:short_name_cont) if params[:q][:short_name_cont] == '' end + + @q = current_organisation.calendars.search(params[:q]) + calendars = @q.result(distinct: true) + calendars = calendars.order(sort_column + ' ' + sort_direction) if sort_column && sort_direction + @calendars = calendars.paginate(page: params[:page]) end def check_policy diff --git a/app/helpers/breadcrumb_helper.rb b/app/helpers/breadcrumb_helper.rb index d764ad60f..da8cf8425 100644 --- a/app/helpers/breadcrumb_helper.rb +++ b/app/helpers/breadcrumb_helper.rb @@ -73,7 +73,7 @@ module BreadcrumbHelper end def calendar_breadcrumb(action) - add_breadcrumb Calendar.model_name.human.pluralize + add_breadcrumb I18n.t('calendars.index.title') add_breadcrumb @calendar.name if %i(show edit).include? action end diff --git a/app/models/calendar.rb b/app/models/calendar.rb index a04aa6d0a..40af2d0e3 100644 --- a/app/models/calendar.rb +++ b/app/models/calendar.rb @@ -5,6 +5,13 @@ class Calendar < ActiveRecord::Base validates_uniqueness_of :short_name validate :date_not_in_date_ranges + after_initialize :init_dates_and_date_ranges + + def init_dates_and_date_ranges + self.dates ||= [] + self.date_ranges ||= [] + end + private def date_not_in_date_ranges errors.add(:dates, I18n.t('activerecord.errors.models.calendar.attributes.dates.date_in_date_ranges')) if dates && date_ranges && dates_and_date_ranges_overlap? @@ -20,14 +27,14 @@ class Calendar < ActiveRecord::Base overlap end - class DateRange + class Period include ActiveAttr::Model attribute :id, type: Integer attribute :begin, type: Date attribute :end, type: Date - validates_presence_of :begin, :end + validates :begin, :end, presence: true validate :check_end_greather_than_begin def check_end_greather_than_begin @@ -37,7 +44,7 @@ class Calendar < ActiveRecord::Base end def self.from_range(index, range) - DateRange.new id: index, begin: range.begin, end: range.end + Period.new id: index, begin: range.begin, end: range.end end def range @@ -52,8 +59,8 @@ class Calendar < ActiveRecord::Base other = other.flatten other = other.delete_if { |o| o.id == id } if id - other.any? do |date_range| - if other_range = date_range.range + other.any? do |period| + if other_range = period.range (range & other_range).present? end end @@ -71,6 +78,7 @@ class Calendar < ActiveRecord::Base id.present? end + def mark_for_destruction self._destroy = true end @@ -80,47 +88,49 @@ class Calendar < ActiveRecord::Base end # Required by coocon - def build_date_range - DateRange.new + def build_period + Period.new end - def ranges - @ranges ||= init_ranges + def periods + @periods ||= init_periods end - def init_ranges + def init_periods if date_ranges - date_ranges.each_with_index.map { |r, index| DateRange.from_range(index, r) } + date_ranges.each_with_index.map { |r, index| Period.from_range(index, r) } else [] end end - private :init_ranges + private :init_periods - validate :validate_ranges + validate :validate_periods - def validate_ranges - ranges_are_valid = true + def validate_periods + periods_are_valid = true - unless ranges.all?(&:valid?) - ranges_are_valid = false + unless periods.all?(&:valid?) + periods_are_valid = false end - ranges.each do |range| - if range.intersect?(ranges) - range.errors.add(:base, I18n.t("referentials.errors.overlapped_period")) - ranges_are_valid = false + periods.each do |period| + if period.intersect?(periods) + period.errors.add(:base, I18n.t('calendars.errors.overlapped_period')) + periods_are_valid = false end end - errors.add(:ranges, :invalid) unless ranges_are_valid + unless periods_are_valid + errors.add(:periods, :invalid) + end end - def ranges_attributes=(attributes = {}) - @ranges = [] - attributes.each do |index, range_attribute| - range = DateRange.new(range_attribute.merge(id: index)) - @ranges << range unless range.marked_for_destruction? + def periods_attributes=(attributes = {}) + @periods = [] + attributes.each do |index, period_attribute| + period = Period.new(period_attribute.merge(id: index)) + @periods << period unless period.marked_for_destruction? end date_ranges_will_change! @@ -129,17 +139,23 @@ class Calendar < ActiveRecord::Base before_validation :fill_date_ranges def fill_date_ranges - if @ranges - self.date_ranges = @ranges.map(&:range).compact.sort_by(&:begin) + if @periods + self.date_ranges = @periods.map(&:range).compact.sort_by(&:begin) end end - after_save :clear_ranges + after_save :clear_periods - def clear_ranges - @ranges = nil + def clear_periods + @periods = nil + end + private :clear_periods + + def self.new_from from + from.dup.tap do |metadata| + metadata.referential_id = nil + end end - private :clear_ranges end class Range @@ -149,4 +165,3 @@ class Range end alias_method :&, :intersection end - diff --git a/app/views/calendars/_calendars.html.slim b/app/views/calendars/_calendars.html.slim index 7813ff89e..261052baf 100644 --- a/app/views/calendars/_calendars.html.slim +++ b/app/views/calendars/_calendars.html.slim @@ -8,5 +8,5 @@ = will_paginate @calendars, container: false, renderer: RemoteBootstrapPaginationLinkRenderer - else - = replacement_msg t('calendars.search_no_results') + = replacement_msg t('.search_no_results') diff --git a/app/views/calendars/_form.html.slim b/app/views/calendars/_form.html.slim index d12473b3e..2d36d7d9e 100644 --- a/app/views/calendars/_form.html.slim +++ b/app/views/calendars/_form.html.slim @@ -1,20 +1,23 @@ -= simple_form_for @calendar, html: { class: 'form-horizontal' } do |f| - = f.input :name - = f.input :short_name - / = f.label :dates - / = f.simple_fields_for :dates do |date| - / = render 'date_fields', f: date - / .links - / = link_to_add_association '+', f, :dates - = f.label :date_ranges - = f.simple_fields_for :ranges do |range| - = render 'range_fields', f: range - .links - = link_to_add_association '+', f, :ranges +#calendar_form + .row + .col-xs-8.col-xs-offset-2 + = simple_form_for @calendar, html: { class: 'form-horizontal' } do |f| + = f.input :name + = f.input :short_name + / = f.label :dates + / = f.simple_fields_for :dates do |date| + / = render 'date_fields', f: date + / .links + / = link_to_add_association '+', f, :dates + = f.label :date_ranges + = f.simple_fields_for :periods do |period| + = render 'period_fields', f: period + .links + /= link_to_add_association '+', f, :periods - .form-actions - = f.button :submit, as: :button - = link_to t('cancel'), calendars_path + .form-actions + = f.button :submit, as: :button, class: 'btn btn-info' + = link_to t('cancel'), calendars_path, class: 'btn btn-default' / TODO : cocoon diff --git a/app/views/calendars/_range_fields.html.slim b/app/views/calendars/_period_fields.html.slim index 92cadc4e9..92cadc4e9 100644 --- a/app/views/calendars/_range_fields.html.slim +++ b/app/views/calendars/_period_fields.html.slim diff --git a/app/views/calendars/edit.html.slim b/app/views/calendars/edit.html.slim index f827e2eb6..22645cf24 100644 --- a/app/views/calendars/edit.html.slim +++ b/app/views/calendars/edit.html.slim @@ -1,3 +1,3 @@ -= title_tag t('.title') += title_tag t('.title', calendar: @calendar.name) = render 'form' diff --git a/app/views/calendars/index.html.slim b/app/views/calendars/index.html.slim index 3feca1b1c..57f30eaef 100644 --- a/app/views/calendars/index.html.slim +++ b/app/views/calendars/index.html.slim @@ -1,16 +1,23 @@ = title_tag t('.title') -= search_form_for @q, url: calendars_path, remote: true, html: { method: :get, class: 'form', id: 'search', role: 'form' } do |f| +#calendar_search_form + = search_form_for @q, url: calendars_path, remote: true, html: { method: :get, class: 'form', id: 'search', role: 'form' } do |f| + .panel.panel-default + .panel-heading + .row + .col-md-3 + = f.label t('calendars.activerecord.attributes.calendar.short_name') + = f.search_field :short_name_cont, class: 'form-control' + .col-md-3 + = f.label t('calendars.activerecord.attributes.calendar.shared') + = f.select :shared_eq, [[t('.shared'), true], [t('.not_shared'), false]], { include_blank: '' }, { class: 'form-control', style: 'width: 100%', 'data-select2ed': 'true', 'data-select2ed-placeholder': t('.all') } + .col-md-3 + = f.label t('.date') + = f.date_field :dates_or_date_ranges_cont, class: 'form-control' - .panel.panel-default - .panel-heading - .input-group.col-md-12 - = f.search_field :short_name_cont, placeholder: t('.short_name_cont'), class: 'form-control' - - .input-group-btn - button.btn.btn-primary#search-btn type='submit' - span.fa.fa-search + .col-md-3 + button.btn.btn-primary#search-btn type='submit' + span.fa.fa-search #calendars = render 'calendars' - diff --git a/config/locales/calendars.en.yml b/config/locales/calendars.en.yml index 26aa3a7bd..87ffef141 100644 --- a/config/locales/calendars.en.yml +++ b/config/locales/calendars.en.yml @@ -1,6 +1,5 @@ en: calendars: - search_no_results: No calendar matching your query actions: new: Add a new calendar edit: Edit this calendar @@ -16,6 +15,8 @@ en: date_ranges: Date ranges dates: Dates shared: Shared + errors: + overlapped_periods: Another period is overlapped with this period activerecord: errors: models: @@ -25,7 +26,11 @@ en: date_in_date_ranges: A date can not be in Dates and in Date ranges. index: title: Calendars - short_name_cont: Search by short name + all: All + shared: Shared + not_shared: Not shared + search_no_results: No calendar matching your query + date: Date new: title: "Add a new calendar" edit: diff --git a/config/locales/calendars.fr.yml b/config/locales/calendars.fr.yml index 292566532..7a6fd541f 100644 --- a/config/locales/calendars.fr.yml +++ b/config/locales/calendars.fr.yml @@ -1,6 +1,5 @@ fr: calendars: - search_no_results: "Aucun calendrier ne correspond à votre recherche" actions: new: Ajouter un calendrier edit: Modifier cet calendrier @@ -16,9 +15,15 @@ fr: date_ranges: Intervalles de dates dates: Dates shared: Partagé + errors: + overlapped_periods: Une autre période chevauche cette période index: title: Calendriers - short_name_cont: Recherche par nom court + all: Tous + shared: Partagées + not_shared: Non partagées + search_no_results: "Aucun calendrier ne correspond à votre recherche" + date: Date new: title: "Ajouter un calendrier" edit: diff --git a/lib/range_ext.rb b/lib/range_ext.rb new file mode 100644 index 000000000..5afb44dee --- /dev/null +++ b/lib/range_ext.rb @@ -0,0 +1,7 @@ +class Range + def intersection(other) + return nil if (self.max < other.begin or other.max < self.begin) + [self.begin, other.begin].max..[self.max, other.max].min + end + alias_method :&, :intersection +end diff --git a/spec/features/calendars_spec.rb b/spec/features/calendars_spec.rb new file mode 100644 index 000000000..354e211a3 --- /dev/null +++ b/spec/features/calendars_spec.rb @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +require 'spec_helper' + +describe 'Calendars', type: :feature do + login_user + + let!(:calendars) { Array.new(2) { create :calendar, organisation_id: 1 } } + + describe 'index' do + before(:each) { visit calendars_path } + + it 'displays calendars' do + expect(page).to have_content(calendars.first.short_name) + expect(page).to have_content(calendars.last.short_name) + end + + context 'filtering' do + xit 'supports filtering by short name' do + end + + xit 'supports filtering by shared' do + end + + xit 'supports filtering by date' do + end + end + end + + describe 'show' do + it 'displays calendar' do + visit calendar_path(calendars.first) + expect(page).to have_content(calendars.first.name) + end + end +end diff --git a/spec/models/calendar_spec.rb b/spec/models/calendar_spec.rb index 1143c6615..6cc256582 100644 --- a/spec/models/calendar_spec.rb +++ b/spec/models/calendar_spec.rb @@ -19,70 +19,70 @@ RSpec.describe Calendar, :type => :model do end end - describe 'DateRange' do + describe 'Period' do - subject { date_range } + subject { period } - def date_range(attributes = {}) - return @date_range if attributes.empty? and @date_range - Calendar::DateRange.new(attributes).tap do |date_range| - @date_range = date_range if attributes.empty? + def period(attributes = {}) + return @period if attributes.empty? and @period + Calendar::Period.new(attributes).tap do |period| + @period = period if attributes.empty? end end it 'should support mark_for_destruction (required by cocoon)' do - date_range.mark_for_destruction - expect(date_range).to be_marked_for_destruction + period.mark_for_destruction + expect(period).to be_marked_for_destruction end it 'should support _destroy attribute (required by coocon)' do - date_range._destroy = true - expect(date_range).to be_marked_for_destruction + period._destroy = true + expect(period).to be_marked_for_destruction end it 'should support new_record? (required by cocoon)' do - expect(Calendar::DateRange.new).to be_new_record - expect(date_range(id: 42)).not_to be_new_record + expect(Calendar::Period.new).to be_new_record + expect(period(id: 42)).not_to be_new_record end it 'should cast begin as date attribute' do - expect(date_range(begin: '2016-11-22').begin).to eq(Date.new(2016,11,22)) + expect(period(begin: '2016-11-22').begin).to eq(Date.new(2016,11,22)) end it 'should cast end as date attribute' do - expect(date_range(end: '2016-11-22').end).to eq(Date.new(2016,11,22)) + expect(period(end: '2016-11-22').end).to eq(Date.new(2016,11,22)) end it { is_expected.to validate_presence_of(:begin) } it { is_expected.to validate_presence_of(:end) } it 'should validate that end is greather than or equlals to begin' do - expect(date_range(begin: '2016-11-21', end: '2016-11-22')).to be_valid - expect(date_range(begin: '2016-11-21', end: '2016-11-21')).to be_valid - expect(date_range(begin: '2016-11-22', end: '2016-11-21')).to_not be_valid + expect(period(begin: '2016-11-21', end: '2016-11-22')).to be_valid + expect(period(begin: '2016-11-21', end: '2016-11-21')).to be_valid + expect(period(begin: '2016-11-22', end: '2016-11-21')).to_not be_valid end describe 'intersect?' do it 'should detect date in common with other date_ranges' do - november = date_range(begin: '2016-11-01', end: '2016-11-30') - mid_november_mid_december = date_range(begin: '2016-11-15', end: '2016-12-15') + november = period(begin: '2016-11-01', end: '2016-11-30') + mid_november_mid_december = period(begin: '2016-11-15', end: '2016-12-15') expect(november.intersect?(mid_november_mid_december)).to be(true) end it 'should not intersect when no date is in common' do - november = date_range(begin: '2016-11-01', end: '2016-11-30') - december = date_range(begin: '2016-12-01', end: '2016-12-31') + november = period(begin: '2016-11-01', end: '2016-11-30') + december = period(begin: '2016-12-01', end: '2016-12-31') expect(november.intersect?(december)).to be(false) - january = date_range(begin: '2017-01-01', end: '2017-01-31') + january = period(begin: '2017-01-01', end: '2017-01-31') expect(november.intersect?(december, january)).to be(false) end it 'should not intersect itself' do - date_range = date_range(id: 42, begin: '2016-11-01', end: '2016-11-30') - expect(date_range.intersect?(date_range)).to be(false) + period = period(id: 42, begin: '2016-11-01', end: '2016-11-30') + expect(period.intersect?(period)).to be(false) end end @@ -97,11 +97,11 @@ RSpec.describe Calendar, :type => :model do Range.new(Date.today, Date.tomorrow) ] expected_ranges.each_with_index do |range, index| - calendar.date_ranges << Calendar::DateRange.from_range(index, range) + calendar.date_ranges << Calendar::Period.from_range(index, range) end calendar.valid? - expect(calendar.date_ranges.map { |date_range| date_range.begin..date_range.end }).to eq(expected_ranges) + expect(calendar.date_ranges.map { |period| period.begin..period.end }).to eq(expected_ranges) end end |
