diff options
| author | Luc Donnet | 2017-09-12 14:46:35 +0200 |
|---|---|---|
| committer | GitHub | 2017-09-12 14:46:35 +0200 |
| commit | 4aa19d931f8585c061dd3da49e31b2ddbbb1bf6b (patch) | |
| tree | 49e5b796e86f4430cdbf098229490d4ed98e0abb | |
| parent | 38befb74289521600564477cdbabd53b372550a5 (diff) | |
| parent | 9810dd389cff0bd5dbda26e46806f62d28410ff4 (diff) | |
| download | chouette-core-4aa19d931f8585c061dd3da49e31b2ddbbb1bf6b.tar.bz2 | |
Merge pull request #63 from af83/4189-duplicate-route
4189 duplicate route
| -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 |
