<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -1,3 +1,5 @@
+* Change Support: Suggestion grouping, sort by affected users [sb]
+
 * Changed context derived from objects to #class.name.tableize to fix STI [sb]
 
 * Simplified controller authorization with filter_resource_access [sb]</diff>
      <filename>CHANGELOG</filename>
    </modified>
    <modified>
      <diff>@@ -35,7 +35,10 @@ class AuthorizationRulesController &lt; ApplicationController
     @users.sort! {|a, b| a.login &lt;=&gt; b.login }
     
     @privileges = authorization_engine.auth_rules.collect {|rule| rule.privileges.to_a}.flatten.uniq
-    @privileges = @privileges.collect {|priv| Authorization::DevelopmentSupport::AnalyzerEngine::Privilege.for_sym(priv, authorization_engine).descendants.map(&amp;:to_sym) }.flatten.uniq
+    @privileges = @privileges.collect do |priv|
+      priv = Authorization::DevelopmentSupport::AnalyzerEngine::Privilege.for_sym(priv, authorization_engine)
+      (priv.descendants + priv.ancestors).map(&amp;:to_sym)
+    end.flatten.uniq
     @privileges.sort_by {|priv| priv.to_s}
     @privilege = params[:privilege].to_sym rescue @privileges.first
     @contexts = authorization_engine.auth_rules.collect {|rule| rule.contexts.to_a}.flatten.uniq
@@ -64,19 +67,40 @@ class AuthorizationRulesController &lt; ApplicationController
       deserialize_changes(spec).flatten
     end
 
-    users_keys = users_permission.keys
     analyzer = Authorization::DevelopmentSupport::ChangeSupporter.new(authorization_engine)
     
     privilege = params[:privilege].to_sym
     context = params[:context].to_sym
+    all_users = User.all
     @context = context
-    @approaches = analyzer.find_approaches_for(:users =&gt; users_keys, :prohibited_actions =&gt; prohibited_actions) do
+    @approaches = analyzer.find_approaches_for(:users =&gt; all_users, :prohibited_actions =&gt; prohibited_actions) do
       users.each_with_index do |user, idx|
-        args = [privilege, {:context =&gt; context, :user =&gt; user}]
-        assert(users_permission[users_keys[idx]] ? permit?(*args) : !permit?(*args))
+        unless users_permission[all_users[idx]].nil?
+          args = [privilege, {:context =&gt; context, :user =&gt; user}]
+          assert(users_permission[all_users[idx]] ? permit?(*args) : !permit?(*args))
+        end
       end
     end
 
+    @affected_users = @approaches.each_with_object({}) do |approach, memo|
+      memo[approach] = approach.affected_users(authorization_engine, all_users, privilege, context).length
+    end
+    max_affected_users = @affected_users.values.max
+    if params[:affected_users]
+      @approaches = @approaches.sort_by do |approach|
+        affected_users_count = @affected_users[approach]
+        if params[:affected_users] == &quot;many&quot;
+          #approach.weight.to_f / [affected_users_count, 0.1].min
+          approach.weight + (max_affected_users - affected_users_count) * 10
+        else
+          #approach.weight * affected_users_count
+          approach.weight + affected_users_count * 10
+        end
+      end
+    end
+
+    @grouped_approaches = analyzer.group_approaches(@approaches)
+
     respond_to do |format|
       format.js do
         render :partial =&gt; 'suggestions'</diff>
      <filename>app/controllers/authorization_rules_controller.rb</filename>
    </modified>
    <modified>
      <diff>@@ -46,7 +46,7 @@ module AuthorizationRulesHelper
   
   def navigation
     link_to(&quot;Rules&quot;, authorization_rules_path) &lt;&lt; ' | ' &lt;&lt;
-    link_to(&quot;Change Supporter&quot;, change_authorization_rules_path) &lt;&lt; ' | ' &lt;&lt;
+    link_to(&quot;Change Support&quot;, change_authorization_rules_path) &lt;&lt; ' | ' &lt;&lt;
     link_to(&quot;Graphical view&quot;, graph_authorization_rules_path) &lt;&lt; ' | ' &lt;&lt;
     link_to(&quot;Usages&quot;, authorization_usages_path) #&lt;&lt; ' | ' &lt;&lt;
   #  'Edit | ' &lt;&lt;
@@ -118,8 +118,8 @@ module AuthorizationRulesHelper
 
   def prohibit_link (step, text, title, options)
     options[:with_removal] ?
-          ' ' + link_to_function(&quot;[x]&quot;, &quot;prohibit_action('#{serialize_action(step)}', '#{text}')&quot;,
-                    :class =&gt; 'unimportant', :title =&gt; title) :
+          link_to_function(&quot;[x]&quot;, &quot;prohibit_action('#{serialize_action(step)}', '#{text}')&quot;,
+                    :class =&gt; 'prohibit', :title =&gt; title) :
           ''
   end
   
@@ -149,6 +149,10 @@ module AuthorizationRulesHelper
     @changes &amp;&amp; @changes[args[0]] &amp;&amp; @changes[args[0]].include?(args[1..-1])
   end
 
+  def affected_users_count (approach)
+    @affected_users[approach]
+  end
+
   def auth_usage_info_classes (auth_info)
     classes = []
     if auth_info[:controller_permissions]</diff>
      <filename>app/helpers/authorization_rules_helper.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,5 +1,5 @@
 &lt;form&gt;
-&lt;h2&gt;1. Choose permission to change&lt;/h2&gt;
+&lt;h2&gt;Which permission to change?&lt;/h2&gt;
 &lt;p class=&quot;action-options&quot;&gt;
   &lt;label&gt;Privilege&lt;/label&gt;
   &lt;%= select_tag :privilege, options_for_select(@privileges.map(&amp;:to_s).sort, @privilege.to_s) %&gt;
@@ -7,10 +7,19 @@
   &lt;label&gt;On&lt;/label&gt;
   &lt;%= select_tag :context, options_for_select(@contexts.map(&amp;:to_s).sort, @context.to_s) %&gt;
   &lt;br/&gt;
-  &lt;%= link_to_function &quot;Current permissions&quot;, &quot;show_current_permissions()&quot;, :class =&gt; 'unimportant' %&gt;
+  &lt;label&gt;&lt;/label&gt;
+  &lt;%= link_to_function &quot;Show current permissions&quot;, &quot;show_current_permissions()&quot;, :class =&gt; 'unimportant' %&gt;
+  &lt;br/&gt;&lt;br/&gt;
+  How many users should be &lt;strong&gt;affected&lt;/strong&gt;?
+  &lt;br/&gt;
+  &lt;label&gt;&lt;/label&gt;
+  &lt;%= radio_button_tag :affected_users, :few, params[:affected_users] == 'few' %&gt;
+  &lt;label class=&quot;inline&quot;&gt;A &lt;strong&gt;few&lt;/strong&gt; users&lt;/label&gt;
+  &lt;%= radio_button_tag :affected_users, :many, params[:affected_users] == 'many' %&gt;
+  &lt;label class=&quot;inline&quot;&gt;&lt;strong&gt;Many&lt;/strong&gt; users&lt;/label&gt;
 &lt;/p&gt;
 
-&lt;h2&gt;2. Whose permission should be changed?&lt;/h2&gt;
+&lt;h2&gt;Whose permission should be changed?&lt;/h2&gt;
 &lt;table class=&quot;change-options&quot;&gt;
   &lt;thead&gt;
     &lt;tr&gt;</diff>
      <filename>app/views/authorization_rules/_change.erb</filename>
    </modified>
    <modified>
      <diff>@@ -32,6 +32,6 @@
     }
 &lt;% end %&gt;
 &lt;div id=&quot;graph-container&quot; style=&quot;display:none&quot;&gt;
-&lt;%= link_to_function &quot;Hide&quot;, &quot;$('graph-container').hide()&quot; %&gt;&lt;br/&gt;
-&lt;object id=&quot;graph&quot; data=&quot;&quot; type=&quot;image/svg+xml&quot; style=&quot;max-width:100%&quot;/&gt;
+&lt;%= link_to_function &quot;Hide&quot;, &quot;$('graph-container').hide()&quot;, :class =&gt; 'important' %&gt;&lt;br/&gt;
+&lt;object id=&quot;graph&quot; data=&quot;&quot; type=&quot;image/svg+xml&quot; style=&quot;max-width:100%;margin-top: 0.5em&quot;/&gt;
 &lt;/div&gt;
\ No newline at end of file</diff>
      <filename>app/views/authorization_rules/_show_graph.erb</filename>
    </modified>
    <modified>
      <diff>@@ -4,21 +4,45 @@
   &lt;% if @approaches.first.changes.empty? %&gt;
     &lt;p&gt;No changes necessary.&lt;/p&gt;
   &lt;% else %&gt;
-    &lt;p&gt;Suggested changes (&lt;%= link_to_function &quot;show&quot;, &quot;show_suggest_graph('#{serialize_changes(@approaches.first)}', '#{serialize_relevant_roles(@approaches.first)}', '#{@context}', relevant_user_ids())&quot; %&gt;):&lt;/p&gt;
-    &lt;%= render &quot;suggestion&quot;, :object =&gt; @approaches.first %&gt;
+    &lt;p class=&quot;unimportant&quot;&gt;
+      &lt;%= pluralize(@approaches.length, 'approach') %&gt; in
+      &lt;%= pluralize(@grouped_approaches.length, 'group') %&gt;
+      &lt;%= params[:affected_users] ? &quot;&#8211; #{params[:affected_users] == 'few' ? &quot;fewer&quot; : &quot;most&quot;} affected users first&quot; : &quot;&quot; %&gt;
+    &lt;/p&gt;
+    &lt;ul&gt;
+      &lt;% @grouped_approaches.each_with_index do |grouped_approach, group_index| %&gt;
+        &lt;% ([grouped_approach.approach] + grouped_approach.similar_approaches).each_with_index do |approach, index| %&gt;
+          &lt;li &lt;%= (group_index &lt; 3 || params[:show_all]) &amp;&amp; index == 0 ? '' : 'style=&quot;display: none&quot;' %&gt; class=&quot;&lt;%= index == 0 ? 'primary' : &quot;secondary&quot; %&gt; group-&lt;%= group_index %&gt;&quot;&gt;
+            &lt;!--&lt;span class=&quot;ord&quot;&gt;&lt;%= (index + 1) %&gt;&lt;/span&gt;--&gt;
+            &lt;span class=&quot;unimportant&quot;&gt;Affected users&lt;/span&gt; &lt;strong&gt;&lt;%= affected_users_count(approach) %&gt;&lt;/strong&gt; &amp;nbsp;
+            &lt;span class=&quot;unimportant&quot;&gt;Complexity&lt;/span&gt; &lt;strong&gt;&lt;%= approach.weight %&gt;&lt;/strong&gt; &amp;nbsp;
+            &lt;%= link_to_function &quot;Diagram&quot;, &quot;show_suggest_graph('#{serialize_changes(approach)}', '#{serialize_relevant_roles(approach)}', '#{@context}', relevant_user_ids())&quot;, :class =&gt; &quot;show-approach&quot; %&gt;
+            &lt;ul&gt;
+              &lt;% approach.changes.each do |action| %&gt;
+                &lt;% (action.to_a[0].is_a?(Enumerable) ? action.to_a : [action.to_a]).each do |step| %&gt;
+                  &lt;li&gt;
+                    &lt;%= describe_step(step.to_a, :with_removal =&gt; true) %&gt;
+                  &lt;/li&gt;
+                &lt;% end %&gt;
+              &lt;% end %&gt;
+            &lt;/ul&gt;
+            &lt;% if index == 0 %&gt;
+              &lt;% if grouped_approach.similar_approaches.empty? %&gt;
+                &lt;span class=&quot;unimportant show-others-in-group&quot;&gt;No further suggestions in this group&lt;/span&gt;
+              &lt;% else %&gt;
+                &lt;%= link_to_function &quot;Show further #{pluralize grouped_approach.similar_approaches.length, &quot;suggestion&quot;} in this group&quot;, &quot;show_group_approaches(#{group_index})&quot;, :class =&gt; &quot;show-others-in-group&quot; %&gt;
+              &lt;% end %&gt;
+            &lt;% end %&gt;
+            &lt;%= link_to_function &quot;Hide further suggestions&quot;, &quot;hide_group_approaches(#{group_index})&quot; if index &gt; 0 and index == grouped_approach.similar_approaches.length %&gt;
+          &lt;/li&gt;
+          &lt;% if index == 0 and group_index == 2 and @grouped_approaches.length &gt; 3 %&gt;
+            &lt;li &lt;%= !params[:show_all] ? '' : 'style=&quot;display: none&quot;' %&gt;&gt;&lt;%= link_to_function &quot;Show further #{pluralize(@grouped_approaches.length - 3, 'group')}&quot;, &quot;show_all_groups(); $(this).up().hide()&quot; %&gt;&lt;/li&gt;
+          &lt;% end %&gt;
+        &lt;% end %&gt;
+      &lt;% end %&gt;
+    &lt;/ul&gt;
+
   &lt;% end %&gt;
 &lt;% else %&gt;
   &lt;p&gt;&lt;strong&gt;No approach found.&lt;/strong&gt;&lt;/p&gt;
 &lt;% end %&gt;
-
-&lt;% if @approaches.length &gt; 1 %&gt;
-  &lt;p &lt;%= !params[:show_all] ? '' : 'style=&quot;display: none&quot;' %&gt;&gt;&lt;a href=&quot;#&quot; onclick=&quot;$(this).up().hide();$('more-suggestions').show();return false&quot;&gt;Show other &lt;%= pluralize(@approaches.length - 1, 'approach') %&gt;&lt;/a&gt;&lt;/p&gt;
-  &lt;div id=&quot;more-suggestions&quot; &lt;%= params[:show_all] ? '' : 'style=&quot;display: none&quot;' %&gt;&gt;
-    &lt;% @approaches[1..-1].each_with_index do |approach, index| %&gt;
-      &lt;p&gt;
-        &lt;%= (index + 2).ordinalize %&gt; best approach (&lt;%= pluralize(approach.changes.length, 'step') %&gt;, &lt;%= link_to_function &quot;show&quot;, &quot;show_suggest_graph('#{serialize_changes(approach)}', '#{serialize_relevant_roles(approach)}', '#{@context}', relevant_user_ids())&quot; %&gt;)
-        &lt;%= render &quot;suggestion&quot;, :object =&gt; approach %&gt;
-      &lt;/p&gt;
-    &lt;% end %&gt;
-  &lt;/div&gt;
-&lt;% end %&gt;</diff>
      <filename>app/views/authorization_rules/_suggestions.erb</filename>
    </modified>
    <modified>
      <diff>@@ -4,12 +4,13 @@
 &lt;%= link_to_function &quot;Hide&quot;, &quot;$(this).up().hide()&quot;, :class =&gt; 'important' %&gt;
 &lt;%= link_to_function &quot;Toggle stacked roles&quot;, &quot;toggle_graph_params('suggest-graph', 'stacked_roles');&quot; %&gt;
 &lt;%= link_to_function &quot;Toggle only users' roles&quot;, &quot;toggle_graph_params('suggest-graph', 'only_relevant_roles');&quot; %&gt;&lt;br/&gt;
-&lt;object id=&quot;suggest-graph&quot; data=&quot;&quot; type=&quot;image/svg+xml&quot; style=&quot;max-width: 98%;max-height: 95%&quot;/&gt;
+&lt;object id=&quot;suggest-graph&quot; data=&quot;&quot; type=&quot;image/svg+xml&quot; style=&quot;max-width: 98%;max-height: 95%;margin-top: 0.5em&quot;/&gt;
 &lt;/div&gt;
 &lt;%= render 'show_graph' %&gt;
 
 &lt;style type=&quot;text/css&quot;&gt;
   .action-options label { display: block; float: left; width: 7em; padding-bottom: 0.5em }
+  .action-options label.inline { display: inline; float: none }
   .action-options select { float: left; }
   .action-options br { clear: both; }
   .action-options { margin-bottom: 2em }
@@ -23,21 +24,33 @@
   .submit { margin-top: 0 }
   .submit input { font-weight: bold; font-size: 120% }
   #suggest-result { 
-    position: absolute; left: 60%; right: 10px;
-    border-left: 2px solid grey;
-    padding-left: 1em;
+    position: absolute; left: 55%; right: 10px;
+    border-left: 1px solid grey;
+    padding-left: 2em;
     z-index: 10;
   }
+  #suggest-result&gt;ul { padding-left: 0 }
+  #suggest-result&gt;ul&gt;li { list-style: none;  margin-bottom: 1em }
+  #suggest-result&gt;ul&gt;li.secondary { padding-left: 2em }
+  #suggest-result li { padding-left: 0 }
+  #suggest-result ul ul { padding-left: 2em }
+  #suggest-result .ord { float: left; display: block; width: 2em; font-weight: bold; color: grey }
+  .show-approach, .show-others-in-group { visibility: hidden }
+  .prohibit { text-decoration: none; visibility: hidden }
+  li:hover .show-approach, li:hover .prohibit,
+    li:hover .show-others-in-group { visibility: visible }
   #suggest-graph-container {
-    background: white; border:1px solid #ccc;
+    background: white; border:1px solid grey;
     position:fixed; z-index: 20;
-    left:10%; bottom: 10%; right: 10%; top: 10%
+    left:10%; bottom: 10%; right: 10%; top: 10%;
+    padding: 0.5em
   }
   #graph-container {
-    background: white; margin: 1em; border:1px solid #ccc;
+    background: white; margin: 1em; border:1px solid grey; padding: 0.5em;
     max-width:50%; position:fixed; z-index: 20; right:0;
   }
-  .unimportant, .remove { color: grey }
+  .unimportant, .remove, .prohibit { color: grey }
+  .important { font-weight: bold }
   .remove { cursor: pointer }
 &lt;/style&gt;
 &lt;% javascript_tag do %&gt;
@@ -72,8 +85,9 @@
     function install_change_observers () {
         $$('#change select').each(function (el) {
             el.observe('change', function (event) {
+                var form = $('change').down('form');
                 new Ajax.Updater({success: 'change'}, '&lt;%= url_for %&gt;', {
-                  parameters: { context: $F('context'), privilege: $F('privilege') },
+                  parameters: Form.serializeElements(form.select('select').concat(form.getInputs('radio', 'affected_users')), true),
                   method: 'get',
                   onComplete: function () {
                       install_change_observers();
@@ -98,6 +112,20 @@
         show_graph($F('privilege'), $F('context')/*, relevant_user_ids()*/);
     }
 
+    function show_all_groups () {
+      $$('#suggest-result&gt;ul&gt;li.primary').invoke(&quot;show&quot;);
+    }
+
+    function show_group_approaches (group_no) {
+      $$('#suggest-result&gt;ul&gt;li.secondary.group-' + group_no).invoke(&quot;show&quot;);
+      $$('#suggest-result&gt;ul&gt;li.primary.group-' + group_no + ' a.show-others-in-group').invoke(&quot;hide&quot;);
+    }
+
+    function hide_group_approaches (group_no) {
+      $$('#suggest-result&gt;ul&gt;li.secondary.group-' + group_no).invoke(&quot;hide&quot;);
+      $$('#suggest-result&gt;ul&gt;li.primary.group-' + group_no + ' a.show-others-in-group').invoke(&quot;show&quot;);
+    }
+
     function relevant_user_ids () {
       return $$('#change .user_id').collect(function (el) {
         return el.innerHTML;</diff>
      <filename>app/views/authorization_rules/change.html.erb</filename>
    </modified>
    <modified>
      <diff>@@ -8,7 +8,6 @@ module Authorization
     #   * Objective function:
     #     * affected user count,
     #     * as specific as possible (roles, privileges)
-    #       -&gt; counter-productive?
     #     * as little changes as necessary
     #   * Modify role, privilege hierarchy
     #   * Merge, split roles
@@ -18,7 +17,7 @@ module Authorization
     #   * group similar candidates: only show abstract methods?
     #   * restructure GUI layout: more room for analyzing suggestions
     #   * changelog, previous tests, etc.
-    #   * different permissions in tests
+    #   * multiple permissions in tests
     # * Evaluation of approaches with Analyzer algorithms
     # * Authorization constraints
     #
@@ -41,6 +40,11 @@ module Authorization
     #
     class ChangeSupporter &lt; AbstractAnalyzer
 
+      # Returns a list of possible approaches for changes to the current
+      # authorization rules that achieve a given goal.  The goal is given as
+      # permission tests in the block.  The instance method +users+ is available
+      # when the block is executed to refer to the then-current users, whose
+      # roles might have changed as one suggestion.
       def find_approaches_for (options, &amp;tests)
         @prohibited_actions = (options[:prohibited_actions] || []).to_set
 
@@ -63,10 +67,29 @@ module Authorization
         end
 
         # remove subsets
-
         suggestions.sort!
       end
 
+      # Returns an array of GroupedApproaches for the given array of approaches.
+      # Only groups directly adjacent approaches
+      def group_approaches (approaches)
+        approaches.each_with_object([]) do |approach, grouped|
+          if grouped.last and grouped.last.approach.similar_to(approach)
+            grouped.last.similar_approaches &lt;&lt; approach
+          else
+            grouped &lt;&lt; GroupedApproach.new(approach)
+          end
+        end
+      end
+
+      class GroupedApproach
+        attr_accessor :approach, :similar_approaches
+        def initialize (approach)
+          @approach = approach
+          @similar_approaches = []
+        end
+      end
+
       class ApproachChecker
         attr_reader :users, :failed_tests
 
@@ -119,6 +142,15 @@ module Authorization
           res
         end
 
+        def affected_users (original_engine, original_users, privilege, context)
+          (0...@users.length).select do |i|
+            original_engine.permit?(privilege, :context =&gt; context,
+              :skip_attribute_test =&gt; true, :user =&gt; original_users[i]) !=
+                @engine.permit?(privilege, :context =&gt; context,
+                  :skip_attribute_test =&gt; true, :user =&gt; @users[i])
+          end.collect {|i| original_users[i]}
+        end
+
         def initialize_copy (other)
           @engine = @engine.clone
           @users = @users.clone
@@ -171,7 +203,17 @@ module Authorization
         end
 
         def sort_value
-          changes.sum(&amp;:weight) + @failed_tests.length
+          weight + @failed_tests.length
+        end
+
+        def weight
+          changes.sum(&amp;:weight)
+        end
+
+        def similar_to (other)
+          other.weight == weight and
+              other.changes.map {|change| change.class.name}.sort ==
+                changes.map {|change| change.class.name}.sort
         end
 
         def inspect</diff>
      <filename>lib/declarative_authorization/development_support/change_supporter.rb</filename>
    </modified>
    <modified>
      <diff>@@ -594,4 +594,92 @@ class ChangeSupporterTest &lt; Test::Unit::TestCase
     assert_not_equal 0, approaches.length
     assert !approaches.any? {|approach| approach.steps.any? {|step| step.class == Authorization::DevelopmentSupport::ChangeSupporter::RemoveRoleFromUserAction}}
   end
+
+  def test_affected_users
+    reader = Authorization::Reader::DSLReader.new
+    reader.parse %{
+      authorization do
+        role :test_role do
+        end
+        role :test_role_2 do
+          includes :test_role
+        end
+      end
+    }
+    engine = Authorization::Engine.new(reader)
+    analyzer = Authorization::DevelopmentSupport::ChangeSupporter.new(engine)
+
+    user_to_extend_permissions = MockUser.new(:test_role_2)
+    another_user = MockUser.new(:test_role)
+    all_users = [user_to_extend_permissions, another_user]
+
+    approaches = analyzer.find_approaches_for(:users =&gt; all_users) do
+      assert permit?(:read, :context =&gt; :permissions, :user =&gt; users[0])
+      assert !permit?(:read, :context =&gt; :permissions, :user =&gt; users[1])
+    end
+
+    assert approaches.any? {|approach|
+        approach.steps.any? {|step| step.class == Authorization::DevelopmentSupport::ChangeSupporter::AssignPrivilegeToRoleAction} &amp;&amp;
+          approach.affected_users(engine, all_users, :read, :permissions).length == 1 }
+  end
+
+  def test_affected_users_with_user_change
+    reader = Authorization::Reader::DSLReader.new
+    reader.parse %{
+      authorization do
+        role :test_role do
+        end
+        role :test_role_2 do
+          has_permission_on :permissions, :to =&gt; :read
+        end
+      end
+    }
+    engine = Authorization::Engine.new(reader)
+    analyzer = Authorization::DevelopmentSupport::ChangeSupporter.new(engine)
+
+    user_to_extend_permissions = MockUser.new(:test_role)
+    another_user = MockUser.new(:test_role)
+    all_users = [user_to_extend_permissions, another_user]
+
+    approaches = analyzer.find_approaches_for(:users =&gt; all_users) do
+      assert permit?(:read, :context =&gt; :permissions, :user =&gt; users.first)
+      assert !permit?(:read, :context =&gt; :permissions, :user =&gt; users[1])
+    end
+
+    assert approaches.any? {|approach|
+        approach.changes.first.class == Authorization::DevelopmentSupport::ChangeSupporter::AssignRoleToUserAction &amp;&amp;
+            approach.affected_users(engine, all_users, :read, :permissions).length == 1 }
+  end
+
+  def test_group_approaches
+    reader = Authorization::Reader::DSLReader.new
+    reader.parse %{
+      authorization do
+        role :test_role do
+          includes :test_role_2
+        end
+        role :test_role_2 do
+          has_permission_on :permissions, :to =&gt; :read
+        end
+      end
+    }
+    engine = Authorization::Engine.new(reader)
+    analyzer = Authorization::DevelopmentSupport::ChangeSupporter.new(engine)
+
+    user_to_extend_permissions = MockUser.new()
+    another_user = MockUser.new()
+    all_users = [user_to_extend_permissions, another_user]
+
+    approaches = analyzer.find_approaches_for(:users =&gt; all_users) do
+      assert permit?(:read, :context =&gt; :permissions, :user =&gt; users.first)
+    end
+
+    assert approaches.first.similar_to(approaches[1]),
+        &quot;First two approaches should be similar&quot;
+
+    grouped_approaches = analyzer.group_approaches(approaches)
+    assert_equal 2, grouped_approaches.length
+    assert grouped_approaches.first.approach
+    assert_equal 1, grouped_approaches.first.similar_approaches.length
+  end
 end</diff>
      <filename>test/development_support/change_supporter_test.rb</filename>
    </modified>
  </modified>
  <removed type="array">
    <removed>
      <filename>app/views/authorization_rules/_suggestion.erb</filename>
    </removed>
  </removed>
  <parents type="array">
    <parent>
      <id>95dbc6813c8b8f3f77c906bc2a100822400ba83b</id>
    </parent>
  </parents>
  <author>
    <name>Steffen Bartsch</name>
    <email>sbartsch@tzi.org</email>
  </author>
  <url>http://github.com/stffn/declarative_authorization/commit/51b8bd1979612fa66547ed5cb5c71d843234521a</url>
  <id>51b8bd1979612fa66547ed5cb5c71d843234521a</id>
  <committed-date>2009-09-10T01:47:36-07:00</committed-date>
  <authored-date>2009-08-27T05:24:10-07:00</authored-date>
  <message>Change support: suggestions: grouping, sorting by affected users</message>
  <tree>d693b048bdcbba1b8fdef99e9ffbc71a7af020af</tree>
  <committer>
    <name>Steffen Bartsch</name>
    <email>sbartsch@tzi.org</email>
  </committer>
</commit>
