diff options
| -rw-r--r-- | app/controllers/routes_controller.rb | 10 | ||||
| -rw-r--r-- | app/decorators/route_decorator.rb | 12 | ||||
| -rw-r--r-- | app/models/chouette/route.rb | 27 | ||||
| -rw-r--r-- | app/models/chouette/stop_point.rb | 15 | ||||
| -rw-r--r-- | app/models/stop_area_copy.rb | 2 | ||||
| -rw-r--r-- | app/policies/route_policy.rb | 4 | ||||
| -rw-r--r-- | config/locales/routes.en.yml | 2 | ||||
| -rw-r--r-- | config/locales/routes.fr.yml | 2 | ||||
| -rw-r--r-- | config/routes.rb | 1 | ||||
| -rw-r--r-- | spec/controllers/routes_controller_spec.rb | 51 | ||||
| -rw-r--r-- | spec/models/chouette/route/route_duplication_spec.rb | 52 | ||||
| -rw-r--r-- | spec/models/chouette/stop_point_spec.rb | 19 | ||||
| -rw-r--r-- | spec/policies/route_policy_spec.rb | 4 | ||||
| -rw-r--r-- | spec/routing/routes_routing_spec.rb | 12 | ||||
| -rw-r--r-- | spec/support/helpers/model_compare_helpers.rb | 15 | 
15 files changed, 195 insertions, 33 deletions
| diff --git a/app/controllers/routes_controller.rb b/app/controllers/routes_controller.rb index 7ba2c1a58..04f63c112 100644 --- a/app/controllers/routes_controller.rb +++ b/app/controllers/routes_controller.rb @@ -69,11 +69,11 @@ class RoutesController < ChouetteController      end    end - # def update -   # update! do |success, failure| -   #   success.html { redirect_to referential_line_path(@referential,@line) } -   # end - # end +  def duplicate +    route = Chouette::Route.find(params[:id]).duplicate +    redirect_to edit_referential_line_route_path(@referential, route.line, route) +  end +    protected    alias_method :route, :resource diff --git a/app/decorators/route_decorator.rb b/app/decorators/route_decorator.rb index 484c3db04..46cb6cd5f 100644 --- a/app/decorators/route_decorator.rb +++ b/app/decorators/route_decorator.rb @@ -58,6 +58,18 @@ class RouteDecorator < Draper::Decorator        )      end +    if h.policy(object).duplicate? +      links << Link.new( +        content: h.t('routes.duplicate.title'), +        href: h.duplicate_referential_line_route_path( +          context[:referential], +          context[:line], +          object +        ), +        method: :post +      ) +    end +      links    end  end diff --git a/app/models/chouette/route.rb b/app/models/chouette/route.rb index e8c7755c7..49493d5b5 100644 --- a/app/models/chouette/route.rb +++ b/app/models/chouette/route.rb @@ -16,6 +16,7 @@ class Chouette::Route < Chouette::TridentActiveRecord    end    belongs_to :line +  belongs_to :opposite_route, :class_name => 'Chouette::Route', :foreign_key => :opposite_route_id    has_many :routing_constraint_zones    has_many :journey_patterns, :dependent => :destroy @@ -30,7 +31,6 @@ class Chouette::Route < Chouette::TridentActiveRecord        Chouette::Route.vehicle_journeys_timeless(proxy_association.owner.journey_patterns.pluck( :departure_stop_point_id))      end    end -  belongs_to :opposite_route, :class_name => 'Chouette::Route', :foreign_key => :opposite_route_id    has_many :stop_points, -> { order("position") }, :dependent => :destroy do      def find_by_stop_area(stop_area)        stop_area_ids = Integer === stop_area ? [stop_area] : (stop_area.children_in_depth + [stop_area]).map(&:id) @@ -56,12 +56,13 @@ class Chouette::Route < Chouette::TridentActiveRecord    end    has_many :stop_areas, -> { order('stop_points.position ASC') }, :through => :stop_points do      def between(departure, arrival) -      departure, arrival = [departure, arrival].collect do |endpoint| +      departure, arrival = [departure, arrival].map do |endpoint|          String === endpoint ? Chouette::StopArea.find_by_objectid(endpoint) : endpoint        end        proxy_owner.stop_points.between(departure, arrival).includes(:stop_area).collect(&:stop_area)      end    end +    accepts_nested_attributes_for :stop_points, :allow_destroy => :true    validates_presence_of :name @@ -75,6 +76,28 @@ class Chouette::Route < Chouette::TridentActiveRecord    after_commit :journey_patterns_control_route_sections +  def duplicate +    overrides = { +      'opposite_route_id' => nil +    } +    keys_for_create = attributes.keys - %w{id objectid created_at updated_at} +    atts_for_create = attributes +      .slice(*keys_for_create) +      .merge(overrides) +    new_route = self.class.create!(atts_for_create) +    duplicate_stop_points(for_route: new_route) +    new_route +  end + +  def duplicate_stop_points(for_route:) +    stop_points.each(&duplicate_stop_point(for_route: for_route)) +  end +  def duplicate_stop_point(for_route:) +    -> stop_point do +      stop_point.duplicate(for_route: for_route) +    end +  end +    def local_id      "IBOO-#{self.referential.id}-#{self.line.objectid.local_id}-#{self.id}"    end diff --git a/app/models/chouette/stop_point.rb b/app/models/chouette/stop_point.rb index 8fe79dc0c..89c492b91 100644 --- a/app/models/chouette/stop_point.rb +++ b/app/models/chouette/stop_point.rb @@ -20,6 +20,11 @@ module Chouette      validates_presence_of :stop_area      validate :stop_area_id_validation +    def stop_area_id_validation +      if stop_area_id.nil? +        errors.add(:stop_area_id, I18n.t("stop_areas.errors.empty")) +      end +    end      scope :default_order, -> { order("position") } @@ -34,10 +39,12 @@ module Chouette        end      end -    def stop_area_id_validation -      if stop_area_id.nil? -        errors.add(:stop_area_id, I18n.t("stop_areas.errors.empty")) -      end +    def duplicate(for_route:) +      keys_for_create = attributes.keys - %w{id objectid created_at updated_at} +      atts_for_create = attributes +        .slice(*keys_for_create) +        .merge('route_id' => for_route.id) +      self.class.create!(atts_for_create)      end      def self.area_candidates diff --git a/app/models/stop_area_copy.rb b/app/models/stop_area_copy.rb index 0fa56ff68..d3eb78557 100644 --- a/app/models/stop_area_copy.rb +++ b/app/models/stop_area_copy.rb @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -  class StopAreaCopy    include ActiveModel::Validations    include ActiveModel::Conversion diff --git a/app/policies/route_policy.rb b/app/policies/route_policy.rb index 786b0acf4..7e9fe251a 100644 --- a/app/policies/route_policy.rb +++ b/app/policies/route_policy.rb @@ -16,4 +16,8 @@ class RoutePolicy < ApplicationPolicy    def update?      !archived? && organisation_match? && user.has_permission?('routes.update')    end + +  def duplicate? +    create? +  end  end diff --git a/config/locales/routes.en.yml b/config/locales/routes.en.yml index 3099d4ab1..e94adf490 100644 --- a/config/locales/routes.en.yml +++ b/config/locales/routes.en.yml @@ -32,6 +32,8 @@ en:        stop_area_name: "Stop area name"        for_boarding: "Boarding"        for_alighting: "Alighting" +    duplicate: +      title: "Duplicate route"      route:        no_journey_pattern: "No Journey pattern"        wayback: diff --git a/config/locales/routes.fr.yml b/config/locales/routes.fr.yml index 0af2832a2..a494e60ec 100644 --- a/config/locales/routes.fr.yml +++ b/config/locales/routes.fr.yml @@ -32,6 +32,8 @@ fr:        stop_area_name: "Nom de l'arrêt"        for_boarding: "Montée"        for_alighting: "Descente" +    duplicate: +      title: "Dupliquer l'itinéraire"      route:        no_journey_pattern: "Pas de mission"        wayback: diff --git a/config/routes.rb b/config/routes.rb index fa892c908..8f8989cab 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -117,6 +117,7 @@ ChouetteIhm::Application.routes.draw do          member do            get 'edit_boarding_alighting'            put 'save_boarding_alighting' +          post 'duplicate', to: 'routes#duplicate'          end          resource :journey_patterns_collection, :only => [:show, :update]          resources :journey_patterns do diff --git a/spec/controllers/routes_controller_spec.rb b/spec/controllers/routes_controller_spec.rb index 000b799db..336f20945 100644 --- a/spec/controllers/routes_controller_spec.rb +++ b/spec/controllers/routes_controller_spec.rb @@ -1,7 +1,9 @@ -RSpec.describe RoutesController, :type => :controller do +Route = Chouette::Route + +RSpec.describe RoutesController, type: :controller do    login_user -  let!(:route) { create(:route) } +  let(:route) { create(:route) }    it { is_expected.to be_kind_of(ChouetteController) } @@ -10,6 +12,7 @@ RSpec.describe RoutesController, :type => :controller do        # expect(response).to redirect_to( referential_line_path(referential,route.line) )      end    end +    shared_examples_for "line and referential linked" do      it "assigns route.line as @line" do        expect(assigns[:line]).to eq(route.line) @@ -19,6 +22,7 @@ RSpec.describe RoutesController, :type => :controller do        expect(assigns[:referential]).to eq(referential)      end    end +    shared_examples_for "route, line and referential linked" do      it "assigns route as @route" do        expect(assigns[:route]).to eq(route) @@ -28,8 +32,8 @@ RSpec.describe RoutesController, :type => :controller do    describe "GET /index" do      before(:each) do -      get :index, :line_id => route.line_id, -          :referential_id => referential.id +      get :index, line_id: route.line_id, +          referential_id: referential.id      end      it_behaves_like "line and referential linked" @@ -38,9 +42,9 @@ RSpec.describe RoutesController, :type => :controller do    describe "POST /create" do      before(:each) do -      post :create, :line_id => route.line_id, -          :referential_id => referential.id, -          :route => { :name => "changed"} +      post :create, line_id: route.line_id, +          referential_id: referential.id, +          route: { name: "changed"}      end      it_behaves_like "line and referential linked" @@ -49,9 +53,9 @@ RSpec.describe RoutesController, :type => :controller do    describe "PUT /update" do      before(:each) do -      put :update, :id => route.id, :line_id => route.line_id, -          :referential_id => referential.id, -          :route => route.attributes +      put :update, id: route.id, line_id: route.line_id, +          referential_id: referential.id, +          route: route.attributes      end      it_behaves_like "route, line and referential linked" @@ -60,9 +64,9 @@ RSpec.describe RoutesController, :type => :controller do    describe "GET /show" do      before(:each) do -      get :show, :id => route.id, -          :line_id => route.line_id, -          :referential_id => referential.id +      get :show, id: route.id, +          line_id: route.line_id, +          referential_id: referential.id      end      it_behaves_like "route, line and referential linked" @@ -71,11 +75,22 @@ RSpec.describe RoutesController, :type => :controller do        expect(assigns[:map]).to be_an_instance_of(RouteMap)        expect(assigns[:map].route).to eq(route)      end - -    #it "assigns route.stop_points.paginate(:page => nil) as @stop_points" do -    #  expect(assigns[:stop_points]).to eq(route.stop_points.paginate(:page => nil)) -    #end    end -end +  describe "POST /duplicate" do +    let!( :route_prime ){ route } + +    it "creates a new route" do +      expect do +        post :duplicate, +          referential_id: route.line.line_referential_id, +          line_id: route.line_id, +          id: route.id +      end.to change { Route.count }.by(1) + +      expect(Route.last.name).to eq(route.name) +      expect(Route.last.published_name).to eq(route.published_name) +    end +  end +end diff --git a/spec/models/chouette/route/route_duplication_spec.rb b/spec/models/chouette/route/route_duplication_spec.rb new file mode 100644 index 000000000..6645b909f --- /dev/null +++ b/spec/models/chouette/route/route_duplication_spec.rb @@ -0,0 +1,52 @@ +# From Chouette import what we need ™ +Route     = Chouette::Route +StopArea  = Chouette::StopArea +StopPoint = Chouette::StopPoint + +RSpec.describe Route do + +  let!( :route ){ create :route } + +  context '#duplicate' do  +    describe 'properties' do +      it 'same attribute values' do +        route.duplicate +        expect( values_for_create(Route.last, except: %w{objectid}) ).to eq( values_for_create( route, except: %w{objectid} ) ) +      end +      it 'and others cannot' do +        expect{ route.duplicate name: 'YAN', line_id: 42  }.to raise_error(ArgumentError) +      end +      it 'same associated stop_areeas' do +        expect( route.duplicate.stop_areas.pluck(:id) ).to eq(route.stop_areas.pluck(:id)) +      end +    end + +    describe 'side_effects' do +      it { +        expect{ route.duplicate }.to change{Route.count}.by(1) +      } +      it 'duplicates its stop points' do +        expect{ route.duplicate }.to change{StopPoint.count}.by(route.stop_points.count) +      end +      it 'does bot duplicate the stop areas' do +        expect{ route.duplicate }.not_to change{StopArea.count} +      end +    end + +    describe 'is idempotent, concerning' do +      let( :first_duplicate ){ route.duplicate  } +      let( :second_duplicate ){ first_duplicate.reload.duplicate } + +      it 'the required attributes' do +        expect( values_for_create(first_duplicate, except: %w{objectid}) ).to eq( values_for_create( second_duplicate, except: %w{objectid} ) ) +      end  + +      it 'the stop areas' do +        expect( first_duplicate.stop_areas.pluck(:id) ).to eq( route.stop_areas.pluck(:id) ) +        expect( second_duplicate.stop_areas.pluck(:id) ).to eq( first_duplicate.stop_areas.pluck(:id) ) +      end + +    end +  end + +end diff --git a/spec/models/chouette/stop_point_spec.rb b/spec/models/chouette/stop_point_spec.rb index 5eae8caf0..329e76a75 100644 --- a/spec/models/chouette/stop_point_spec.rb +++ b/spec/models/chouette/stop_point_spec.rb @@ -1,6 +1,7 @@ -require 'spec_helper' +# From Chouette import what we need ™ +StopPoint = Chouette::StopPoint -describe Chouette::StopPoint, :type => :model do +describe StopPoint, :type => :model do    let!(:vehicle_journey) { create(:vehicle_journey)}    subject { Chouette::Route.find( vehicle_journey.route_id).stop_points.first } @@ -38,4 +39,18 @@ describe Chouette::StopPoint, :type => :model do        expect(jpsp_stop_point_ids(@vehicle.journey_pattern_id)).not_to include(@stop_point.id)      end    end + +  describe '#duplicate' do +    let!( :new_route ){ create :route } + +    it 'creates a new instance' do +      expect{ subject.duplicate(for_route: new_route) }.to change{ StopPoint.count }.by(1) +    end +    it 'new instance has a new route' do +      expect(subject.duplicate(for_route: new_route).route).to eq(new_route) +    end +    it 'and old stop_area' do +      expect(subject.duplicate(for_route: new_route).stop_area).to eq(subject.stop_area) +    end +  end  end diff --git a/spec/policies/route_policy_spec.rb b/spec/policies/route_policy_spec.rb index 243d85acb..d7edceaef 100644 --- a/spec/policies/route_policy_spec.rb +++ b/spec/policies/route_policy_spec.rb @@ -6,6 +6,10 @@ RSpec.describe RoutePolicy, type: :policy do      it_behaves_like 'permitted policy and same organisation', 'routes.create', archived: true    end +  permissions :duplicate? do +    it_behaves_like 'permitted policy and same organisation', 'routes.create', archived: true +  end +    permissions :destroy? do      it_behaves_like 'permitted policy and same organisation', 'routes.destroy', archived: true    end diff --git a/spec/routing/routes_routing_spec.rb b/spec/routing/routes_routing_spec.rb new file mode 100644 index 000000000..311de9f39 --- /dev/null +++ b/spec/routing/routes_routing_spec.rb @@ -0,0 +1,12 @@ +RSpec.describe "routes for Routes", type: :routing do +  context "routes /referentials/:id/lines/:id/routes/:id/duplicate" do + +    let( :controller ){ {controller: 'routes', referential_id: ':referential_id', line_id: ':line_id', id: ':id'}  } + +    it 'with method post to #post_duplicate' do +      expect( +        post: '/referentials/:referential_id/lines/:line_id/routes/:id/duplicate' +      ).to route_to controller.merge(action: 'duplicate') +    end +  end +end diff --git a/spec/support/helpers/model_compare_helpers.rb b/spec/support/helpers/model_compare_helpers.rb new file mode 100644 index 000000000..a10892af0 --- /dev/null +++ b/spec/support/helpers/model_compare_helpers.rb @@ -0,0 +1,15 @@ +module Support::ModelCompareHelpers +   +  def values_for_create obj, **overrides +    except = overrides.delete(:except) || [] +    keys = obj.attributes.keys - except - %w{id created_at updated_at} +    overrides.inject(obj.attributes.slice(*keys)){ |atts, (k,v)| +      atts.merge k.to_s => v +    } +  end + +end + +RSpec.configure do | rspec | +  rspec.include Support::ModelCompareHelpers, type: :model +end | 
