Commit bf95a1cf63c63655baf6b5b4c379506288798d41

Authored by Daniela Feitosa
Committed by Antonio Terceiro
1 parent 2cd79dfe

Communities can be moderated by environment admin

  * Added a feature to environment
  * If feature is on, a task is created to environment admin

ActionItem1254
app/controllers/my_profile/memberships_controller.rb
... ... @@ -7,21 +7,17 @@ class MembershipsController < MyProfileController
7 7 end
8 8  
9 9 def new_community
10   - community_data = environment.enabled?('organizations_are_moderated_by_default') ? { :moderated_articles => true } : {}
11   - community_data.merge!(params[:community]) if params[:community]
12   - @community = Community.new(community_data)
13   - @community.environment = environment
14 10 @wizard = params[:wizard].blank? ? false : params[:wizard]
15   - if request.post?
16   - if @community.save
17   - @community.add_admin(profile)
18   - if @wizard
19   - redirect_to :controller => 'search', :action => 'assets', :asset => 'communities', :wizard => true
20   - return
21   - else
22   - redirect_to :action => 'index'
23   - return
24   - end
  11 + @community = Community.new(params[:community])
  12 + @community.environment = environment
  13 + if request.post? && @community.valid?
  14 + @community = Community.create_after_moderation(user, {:environment => environment}.merge(params[:community]))
  15 + if @wizard
  16 + redirect_to :controller => 'search', :action => 'assets', :asset => 'communities', :wizard => true
  17 + return
  18 + else
  19 + redirect_to :action => 'index'
  20 + return
25 21 end
26 22 end
27 23 if @wizard
... ...
app/controllers/public/account_controller.rb
... ... @@ -221,7 +221,7 @@ class AccountController < ApplicationController
221 221  
222 222 def check_url
223 223 @identifier = params[:identifier]
224   - valid = Person.is_available?(@identifier)
  224 + valid = Person.is_available?(@identifier, environment)
225 225 if valid
226 226 @status = _('Available!')
227 227 @status_class = 'available'
... ...
app/models/community.rb
... ... @@ -7,6 +7,21 @@ class Community < Organization
7 7  
8 8 xss_terminate :only => [ :name, :address, :contact_phone, :description ]
9 9  
  10 + before_create do |community|
  11 + community.moderated_articles = true if community.environment.enabled?('organizations_are_moderated_by_default')
  12 + end
  13 +
  14 + def self.create_after_moderation(requestor, attributes = {})
  15 + community = Community.new(attributes)
  16 + if community.environment.enabled?('admin_must_approve_new_communities')
  17 + CreateCommunity.create(attributes.merge(:requestor => requestor))
  18 + else
  19 + community = Community.create(attributes)
  20 + community.add_admin(requestor)
  21 + end
  22 + community
  23 + end
  24 +
10 25 FIELDS = %w[
11 26 description
12 27 language
... ... @@ -20,7 +35,7 @@ class Community < Organization
20 35 super
21 36 self.required_fields.each do |field|
22 37 if self.send(field).blank?
23   - self.errors.add(field, _('%{fn} is mandatory'))
  38 + self.errors.add(field, _('%{fn} is mandatory'))
24 39 end
25 40 end
26 41 end
... ...
app/models/create_community.rb 0 → 100644
... ... @@ -0,0 +1,89 @@
  1 +class CreateCommunity < Task
  2 +
  3 + validates_presence_of :requestor_id, :target_id
  4 + validates_presence_of :name
  5 +
  6 + alias :environment :target
  7 + alias :environment= :target=
  8 +
  9 + serialize :data, Hash
  10 + attr_protected :data
  11 + def data
  12 + self[:data] ||= Hash.new
  13 + end
  14 +
  15 + DATA_FIELDS = Community.fields + ['name', 'closed', 'image_builder', 'tag_list']
  16 +
  17 + DATA_FIELDS.each do |field|
  18 + # getter
  19 + define_method(field) do
  20 + self.data[field.to_sym]
  21 + end
  22 + # setter
  23 + define_method("#{field}=") do |value|
  24 + self.data[field.to_sym] = value
  25 + end
  26 + end
  27 +
  28 + def validate
  29 + self.environment.required_community_fields.each do |field|
  30 + if self.send(field).blank?
  31 + self.errors.add(field, _('%{fn} is mandatory'))
  32 + end
  33 + end
  34 + end
  35 +
  36 + def perform
  37 + community = Community.new
  38 + community_data = self.data.reject do |key, value|
  39 + ! DATA_FIELDS.include?(key.to_s)
  40 + end
  41 +
  42 + community.update_attributes(community_data)
  43 + community.environment = self.environment
  44 + community.save!
  45 + community.add_admin(self.requestor)
  46 + end
  47 +
  48 + def description
  49 + _('%s wants to create community %s.') % [requestor.name, self.name]
  50 + end
  51 +
  52 + def closing_statement
  53 + data[:closing_statement]
  54 + end
  55 +
  56 + def closing_statement= value
  57 + data[:closing_statement] = value
  58 + end
  59 +
  60 + # tells if this request was rejected
  61 + def rejected?
  62 + self.status == Task::Status::CANCELLED
  63 + end
  64 +
  65 + # tells if this request was appoved
  66 + def approved?
  67 + self.status == Task::Status::FINISHED
  68 + end
  69 +
  70 + def target_notification_message
  71 + description + "\n\n" +
  72 + _("User \"%{user}\" just requested to create community %{community}. You have to approve or reject it through the \"Pending Validations\" section in your control panel.\n") % { :user => self.requestor.name, :community => self.name }
  73 + end
  74 +
  75 + def task_created_message
  76 + _("Your request for registering community %{community} at %{environment} was just sent. Environment administrator will receive it and will approve or reject your request according to his methods and creteria.
  77 +
  78 + You will be notified as soon as environment administrator has a position about your request.") % { :community => self.name, :environment => self.target }
  79 + end
  80 +
  81 + def task_cancelled_message
  82 + _("Your request for registering community %{community} at %{environment} was not approved by the environment administrator. The following explanation was given: \n\n%{explanation}") % { :community => self.name, :environment => self.environment, :explanation => self.closing_statement }
  83 + end
  84 +
  85 + def task_finished_message
  86 + _('Your request for registering the community "%{community}" was approved. You can access %{environment} now and start using your new community.') % { :community => self.name, :environment => self.environment }
  87 + end
  88 +
  89 +end
... ...
app/models/environment.rb
... ... @@ -130,6 +130,7 @@ class Environment &lt; ActiveRecord::Base
130 130 'articles_dont_accept_comments_by_default' => _("Articles don't accept comments by default"),
131 131 'organizations_are_moderated_by_default' => _("Organizations have moderated publication by default"),
132 132 'enable_organization_url_change' => _("Allow organizations to change their address"),
  133 + 'admin_must_approve_new_communities' => _("Admin must approve creation of communities"),
133 134 }
134 135 end
135 136  
... ... @@ -662,6 +663,10 @@ class Environment &lt; ActiveRecord::Base
662 663 "home-page-news/#{cache_key}"
663 664 end
664 665  
  666 + def notification_emails
  667 + [contact_email.blank? ? nil : contact_email].compact + admins.map(&:email)
  668 + end
  669 +
665 670 after_create :create_templates
666 671  
667 672 def create_templates
... ...
app/models/profile.rb
... ... @@ -198,8 +198,8 @@ class Profile &lt; ActiveRecord::Base
198 198 @top_level_articles ||= Article.top_level_for(self)
199 199 end
200 200  
201   - def self.is_available?(identifier)
202   - !(identifier =~ IDENTIFIER_FORMAT).nil? && !RESERVED_IDENTIFIERS.include?(identifier) && Profile.find(:first, :conditions => ['environment_id = ? and identifier = ?', Environment.default.id, identifier]).nil?
  201 + def self.is_available?(identifier, environment)
  202 + !(identifier =~ IDENTIFIER_FORMAT).nil? && !RESERVED_IDENTIFIERS.include?(identifier) && Profile.find(:first, :conditions => ['environment_id = ? and identifier = ?', environment.id, identifier]).nil?
203 203 end
204 204  
205 205 validates_presence_of :identifier, :name
... ...
app/models/task_mailer.rb
... ... @@ -14,6 +14,8 @@ class TaskMailer &lt; ActionMailer::Base
14 14  
15 15 recipients task.target.notification_emails
16 16  
  17 + url_for_tasks_list = task.target.kind_of?(Environment) ? '' : url_for(task.target.url.merge(:controller => 'tasks', :action => 'index'))
  18 +
17 19 from self.class.generate_from(task)
18 20 subject '[%s] %s' % [task.requestor.environment.name, task.description]
19 21 body :requestor => task.requestor.name,
... ... @@ -21,7 +23,7 @@ class TaskMailer &lt; ActionMailer::Base
21 23 :message => msg,
22 24 :environment => task.requestor.environment.name,
23 25 :url => url_for(:host => task.requestor.environment.default_hostname, :controller => 'home'),
24   - :tasks_url => url_for(task.target.url.merge(:controller => 'tasks', :action => 'index'))
  26 + :tasks_url => url_for_tasks_list
25 27 end
26 28  
27 29 def invitation_notification(task)
... ...
app/views/task_mailer/target_notification.rhtml
... ... @@ -2,7 +2,7 @@
2 2  
3 3 <%= word_wrap(@message) %>
4 4  
5   -<%= word_wrap(_('Access the address below to see this and other pending actions that need your attention:')) %>
  5 +<%= word_wrap(_('Access your list of tasks or your control panel to see this and other pending actions that need your attention.')) %>
6 6 <%= @tasks_url %>
7 7  
8 8 --
... ...
app/views/tasks/_create_community.rhtml 0 → 100644
... ... @@ -0,0 +1,34 @@
  1 +<h2><%= _('New community') %></h2>
  2 +
  3 +<%= link_to( profile_image(task.requestor, :minor, :border => 0), task.requestor.public_profile_url ) %>
  4 +
  5 +<%= _('%s wants to create community %s.') %
  6 + [content_tag('strong', link_to( task.requestor.name, task.requestor.public_profile_url ) ),
  7 + content_tag('strong', task.name )] %>
  8 +
  9 +<% form_for('task', task, :url => { :action => 'close', :id => task.id } ) do |f| %>
  10 +
  11 + <div>
  12 + <% if !Profile.is_available?(task.name.to_slug, environment) %>
  13 + <p><b><%= _('This name was already taken, this community cannot be approved') %></b></p>
  14 + <%= hidden_field_tag(:decision, 'cancel') %>
  15 + <% else %>
  16 + <%= radio_button_tag(:decision, 'finish', true,
  17 + :id => "decision-finish-#{task.id}") %>
  18 + <label for="<%= "decision-finish-#{task.id}" %>"><b><%= _('Approve') %></b></label>
  19 +
  20 + &nbsp; &nbsp;
  21 +
  22 + <%= radio_button_tag(:decision, 'cancel', false,
  23 + :id => "decision-cancel-#{task.id}") %>
  24 + <label for="<%= "decision-cancel-#{task.id}" %>"><b><%= _('Reject') %></b></label>
  25 + <% end %>
  26 +
  27 + <%= labelled_form_field _('Please provide an explanation for the rejection'), f.text_area(:closing_statement, :style => 'height:200px; width:80%;') %>
  28 +
  29 + </div>
  30 +
  31 + <% button_bar do %>
  32 + <%= submit_button(:ok, _('Ok!')) %>
  33 + <% end %>
  34 +<% end %>
... ...
features/create_community.feature 0 → 100644
... ... @@ -0,0 +1,96 @@
  1 +Feature: create community
  2 + As a noosfero user
  3 + I want to create a community
  4 + In order to interact with other people
  5 +
  6 + Background:
  7 + Given the following users
  8 + | login | name |
  9 + | joaosilva | Joao Silva |
  10 +
  11 + Scenario: a user creates a community
  12 + Given I am logged in as "joaosilva"
  13 + And feature "admin_must_approve_new_communities" is disabled on environment
  14 + And I follow "Control panel"
  15 + And I follow "Manage my groups"
  16 + When I follow "Create a new community"
  17 + And I fill in "Name" with "Fancy community"
  18 + And I press "Create"
  19 + Then I should see "Fancy community"
  20 +
  21 + Scenario: a user creates a community when environment moderates it
  22 + Given I am logged in as "joaosilva"
  23 + And feature "admin_must_approve_new_communities" is enabled on environment
  24 + When I follow "Control panel"
  25 + And I follow "Manage my groups"
  26 + And I follow "Create a new community"
  27 + And I fill in "Name" with "Community for moderation"
  28 + And I press "Create"
  29 + Then I should not see "Community for moderation"
  30 +
  31 + Scenario: a user tries to create a community without a name
  32 + Given I am logged in as "joaosilva"
  33 + And feature "admin_must_approve_new_communities" is disabled on environment
  34 + And I follow "Control panel"
  35 + And I follow "Manage my groups"
  36 + When I follow "Create a new community"
  37 + And I press "Create"
  38 + Then I should see "Creating new community"
  39 +
  40 + Scenario: environment admin receive a task when a user creates a community
  41 + Given I am logged in as admin
  42 + And feature "admin_must_approve_new_communities" is enabled on environment
  43 + When I create community "Community for approval"
  44 + And I follow "Control Panel"
  45 + Then I should see "admin_user wants to create community Community for approval"
  46 +
  47 + Scenario: environment admin accepts new community task
  48 + Given I am logged in as admin
  49 + And feature "admin_must_approve_new_communities" is enabled on environment
  50 + When I create community "Community for approval"
  51 + And I follow "Control Panel"
  52 + And I follow "Process requests"
  53 + And I should see "admin_user wants to create community Community for approval"
  54 + And I choose "Approve"
  55 + When I press "Ok!"
  56 + Then I should not see "admin_user wants to create community Community for approval"
  57 + When I follow "My groups"
  58 + Then I should see "Community for approval"
  59 +
  60 + Scenario: environment admin rejects new community task
  61 + Given I am logged in as admin
  62 + And feature "admin_must_approve_new_communities" is enabled on environment
  63 + When I create community "Community for approval"
  64 + And I follow "Control Panel"
  65 + And I follow "Process requests"
  66 + And I should see "admin_user wants to create community Community for approval"
  67 + And I choose "Reject"
  68 + When I press "Ok!"
  69 + Then I should not see "admin_user wants to create community Community for approval"
  70 + When I follow "My groups"
  71 + Then I should not see "Community for approval"
  72 +
  73 + Scenario: new community is listed after approval
  74 + Given I am logged in as admin
  75 + And feature "admin_must_approve_new_communities" is enabled on environment
  76 + When I create community "Community for approval"
  77 + And I approve community "Community for approval"
  78 + When I follow "My groups"
  79 + Then I should see "Community for approval"
  80 +
  81 + Scenario: new community is not listed after rejection
  82 + Given I am logged in as admin
  83 + And feature "admin_must_approve_new_communities" is enabled on environment
  84 + When I create community "Community for approval"
  85 + And I reject community "Community for approval"
  86 + When I follow "My groups"
  87 + Then I should not see "Community for approval"
  88 +
  89 + Scenario: environment admin accepts new community task but identifier was already taken
  90 + Given I am logged in as admin
  91 + And feature "admin_must_approve_new_communities" is enabled on environment
  92 + And I create community "Community for approval"
  93 + And I create community "Community for approval"
  94 + When I approve community "Community for approval"
  95 + Then I should see "This name was already taken, this community cannot be approved"
  96 + And I should see "admin_user wants to create community Community for approval"
... ...
features/step_definitions/create_community_steps.rb 0 → 100644
... ... @@ -0,0 +1,22 @@
  1 +Given /^I create community "(.+)"$/ do |community|
  2 + click_link('My groups')
  3 + click_link('Create a new community')
  4 + fill_in("Name", :with => community)
  5 + click_button("Create")
  6 +end
  7 +
  8 +Given /^I approve community "(.+)"$/ do |community|
  9 + task = CreateCommunity.all.select {|c| c.name == community}.first
  10 + click_link('Control Panel')
  11 + click_link('Process requests')
  12 + choose("decision-finish-#{task.id}")
  13 + click_button('OK!')
  14 +end
  15 +
  16 +Given /^I reject community "(.+)"$/ do |community|
  17 + task = CreateCommunity.all.select {|c| c.name == community}.first
  18 + click_link('Control Panel')
  19 + click_link('Process requests')
  20 + choose("decision-cancel-#{task.id}")
  21 + click_button('OK!')
  22 +end
... ...
features/step_definitions/noosfero_steps.rb
... ... @@ -62,7 +62,34 @@ Given /^the following products$/ do |table|
62 62 end
63 63  
64 64 Given /^I am logged in as "(.+)"$/ do |username|
  65 + visit('/account/login')
65 66 fill_in("Username", :with => username)
66 67 fill_in("Password", :with => '123456')
67 68 click_button("Log in")
68 69 end
  70 +
  71 +Given /^I am logged in as admin$/ do
  72 + user = User.create!(:login => 'admin_user', :password => '123456', :password_confirmation => '123456', :email => 'admin_user@example.com')
  73 + e = Environment.default
  74 + e.add_admin(user.person)
  75 + visit('/account/login')
  76 + fill_in("Username", :with => user.login)
  77 + fill_in("Password", :with => '123456')
  78 + click_button("Log in")
  79 +end
  80 +
  81 +Given /^I am not logged in$/ do |username|
  82 + visit('/account/logout')
  83 +end
  84 +
  85 +Given /^feature "(.+)" is enabled on environment$/ do |feature|
  86 + e = Environment.default
  87 + e.enable(feature)
  88 + e.save
  89 +end
  90 +
  91 +Given /^feature "(.+)" is disabled on environment$/ do |feature|
  92 + e = Environment.default
  93 + e.disable(feature)
  94 + e.save
  95 +end
... ...
test/fixtures/roles.yml
... ... @@ -73,3 +73,10 @@ environment_administrator:
73 73 system: true
74 74 permissions:
75 75 - perform_task
  76 + - view_environment_admin_panel
  77 + - edit_environment_features
  78 + - edit_environment_design
  79 + - manage_environment_categories
  80 + - manage_environment_roles
  81 + - manage_environment_validators
  82 + - moderate_comments
... ...
test/functional/account_controller_test.rb
... ... @@ -649,6 +649,21 @@ class AccountControllerTest &lt; Test::Unit::TestCase
649 649 assert_redirected_to :controller => 'home', :action => 'index'
650 650 end
651 651  
  652 + should 'check_url is available on environment' do
  653 + env = Environment.create(:name => 'Environment test')
  654 + @controller.expects(:environment).returns(env).at_least_once
  655 + profile = create_user('mylogin').person
  656 + get :check_url, :identifier => 'mylogin'
  657 + assert_equal 'available', assigns(:status_class)
  658 + end
  659 +
  660 + should 'check if url is not available on environment' do
  661 + @controller.expects(:environment).returns(Environment.default).at_least_once
  662 + profile = create_user('mylogin').person
  663 + get :check_url, :identifier => 'mylogin'
  664 + assert_equal 'unavailable', assigns(:status_class)
  665 + end
  666 +
652 667 protected
653 668 def new_user(options = {}, extra_options ={})
654 669 data = {:profile_data => person_data}
... ...
test/functional/memberships_controller_test.rb
... ... @@ -220,6 +220,13 @@ class MembershipsControllerTest &lt; Test::Unit::TestCase
220 220 get :new_community, :profile => profile.identifier
221 221  
222 222 assert_not_nil assigns(:community).environment
  223 + end
  224 +
  225 + should 'set environment' do
  226 + @controller.stubs(:environment).returns(Environment.default).at_least_once
  227 + post :new_community, :profile => profile.identifier, :community => {:name => 'test community'}
  228 +
  229 + assert_not_nil assigns(:community).environment
223 230 end
224 231  
225 232 should 'not show description if isnt enabled when register new community' do
... ...
test/unit/community_test.rb
... ... @@ -2,6 +2,12 @@ require File.dirname(__FILE__) + &#39;/../test_helper&#39;
2 2  
3 3 class CommunityTest < Test::Unit::TestCase
4 4  
  5 + def setup
  6 + @person = create_user('testuser').person
  7 + end
  8 +
  9 + attr_reader :person
  10 +
5 11 should 'inherit from Profile' do
6 12 assert_kind_of Profile, Community.new
7 13 end
... ... @@ -144,5 +150,41 @@ class CommunityTest &lt; Test::Unit::TestCase
144 150 assert_equal [highlighted_t].map(&:slug), c.news(2, true).map(&:slug)
145 151 end
146 152  
  153 + should 'sanitize description' do
  154 + c = Community.create!(:name => 'test_com', :description => '<b>new</b> community')
  155 +
  156 + assert_sanitized c.description
  157 + end
  158 +
  159 + should 'sanitize name' do
  160 + c = Community.create!(:name => '<b>test_com</b>')
  161 +
  162 + assert_sanitized c.name
  163 + end
147 164  
  165 + should 'create a task when creating a community if feature is enabled' do
  166 + env = Environment.default
  167 + env.enable('admin_must_approve_new_communities')
  168 +
  169 + assert_difference CreateCommunity, :count do
  170 + Community.create_after_moderation(person, {:environment => env, :name => 'Example'})
  171 + end
  172 +
  173 + assert_no_difference Community, :count do
  174 + Community.create_after_moderation(person, {:environment => env, :name => 'Example'})
  175 + end
  176 + end
  177 +
  178 + should 'create a community if feature is disabled' do
  179 + env = Environment.default
  180 + env.disable('admin_must_approve_new_communities')
  181 +
  182 + assert_difference Community, :count do
  183 + Community.create_after_moderation(person, {:environment => env, :name => 'Example'})
  184 + end
  185 +
  186 + assert_no_difference CreateCommunity, :count do
  187 + Community.create_after_moderation(person, {:environment => env, :name => 'Example'})
  188 + end
  189 + end
148 190 end
... ...
test/unit/create_community_test.rb 0 → 100644
... ... @@ -0,0 +1,68 @@
  1 +require File.dirname(__FILE__) + '/../test_helper'
  2 +
  3 +class CreateCommunityTest < Test::Unit::TestCase
  4 +
  5 + def setup
  6 + @person = create_user('testing').person
  7 + end
  8 + attr_reader :person
  9 +
  10 + should 'provide needed data' do
  11 + task = CreateCommunity.new
  12 +
  13 + Community.fields + %w[ name closed image_builder tag_list ].each do |field|
  14 + assert task.respond_to?(field)
  15 + assert task.respond_to?("#{field}=")
  16 + end
  17 + end
  18 +
  19 + should 'require a requestor' do
  20 + task = CreateCommunity.new(:name => 'community test', :target => Environment.default)
  21 + task.valid?
  22 +
  23 + assert task.errors.invalid?(:requestor_id)
  24 + task.requestor = person
  25 + task.valid?
  26 + assert !task.errors.invalid?(:requestor_id)
  27 + end
  28 +
  29 + should 'actually create a community when finishing the task and associate the task requestor as its admin' do
  30 +
  31 + task = CreateCommunity.create!({
  32 + :name => 'My new community',
  33 + :requestor => person,
  34 + :target => Environment.default,
  35 + })
  36 +
  37 + assert_difference Community, :count do
  38 + task.finish
  39 + end
  40 +
  41 + assert_equal person, Community['my-new-community'].admins.first
  42 + end
  43 +
  44 + should 'override message methods from Task' do
  45 + specific = CreateCommunity.new
  46 + %w[ task_created_message task_finished_message task_cancelled_message ].each do |method|
  47 + assert_nothing_raised NotImplementedError do
  48 + specific.send(method)
  49 + end
  50 + end
  51 + end
  52 +
  53 + should 'provide a message to be sent to the target' do
  54 + assert_not_nil CreateCommunity.new(:name => 'test comm', :requestor => person).target_notification_message
  55 + end
  56 +
  57 + should 'report as approved when approved' do
  58 + request = CreateCommunity.new
  59 + request.stubs(:status).returns(Task::Status::FINISHED)
  60 + assert request.approved?
  61 + end
  62 +
  63 + should 'report as rejected when rejected' do
  64 + request = CreateCommunity.new
  65 + request.stubs(:status).returns(Task::Status::CANCELLED)
  66 + assert request.rejected?
  67 + end
  68 +end
... ...
test/unit/profile_test.rb
... ... @@ -1462,6 +1462,19 @@ class ProfileTest &lt; Test::Unit::TestCase
1462 1462 assert_equal [event2, event3, event1], profile.events
1463 1463 end
1464 1464  
  1465 + should 'be available if identifier doesnt exist on environment' do
  1466 + p = create_user('identifier-test').person
  1467 +
  1468 + env = Environment.create(:name => 'Environment test')
  1469 + assert_equal true, Profile.is_available?('identifier-test', env)
  1470 + end
  1471 +
  1472 + should 'not be available if identifier exists on environment' do
  1473 + p = create_user('identifier-test').person
  1474 +
  1475 + assert_equal false, Profile.is_available?('identifier-test', Environment.default)
  1476 + end
  1477 +
1465 1478 private
1466 1479  
1467 1480 def assert_invalid_identifier(id)
... ...