bigbluebutton_rails 0.0.5 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +2 -0
 - data/.rvmrc +6 -0
 - data/.travis.yml +4 -0
 - data/CHANGELOG.rdoc +9 -0
 - data/Gemfile +18 -0
 - data/Gemfile.lock +85 -65
 - data/README.rdoc +23 -0
 - data/Rakefile +17 -6
 - data/app/controllers/bigbluebutton/rooms_controller.rb +79 -58
 - data/app/controllers/bigbluebutton/servers_controller.rb +41 -24
 - data/app/models/bigbluebutton_room.rb +45 -5
 - data/app/models/bigbluebutton_server.rb +5 -7
 - data/app/views/bigbluebutton/rooms/external.html.erb +24 -0
 - data/app/views/bigbluebutton/rooms/join_mobile.html.erb +1 -1
 - data/app/views/bigbluebutton/servers/_activity_list.html.erb +7 -7
 - data/app/views/bigbluebutton/servers/activity.html.erb +1 -1
 - data/bigbluebutton_rails.gemspec +2 -12
 - data/config/locales/en.yml +3 -0
 - data/lib/bigbluebutton_rails/rails/routes.rb +12 -6
 - data/lib/bigbluebutton_rails/utils.rb +8 -0
 - data/lib/bigbluebutton_rails/version.rb +1 -1
 - data/lib/bigbluebutton_rails.rb +1 -0
 - data/spec/bigbluebutton_rails_spec.rb +13 -0
 - data/spec/controllers/bigbluebutton/rooms_controller_exception_handling_spec.rb +114 -0
 - data/spec/controllers/bigbluebutton/rooms_controller_json_responses_spec.rb +167 -0
 - data/spec/controllers/bigbluebutton/rooms_controller_spec.rb +213 -457
 - data/spec/controllers/bigbluebutton/servers_controller_json_responses_spec.rb +148 -0
 - data/spec/controllers/bigbluebutton/servers_controller_spec.rb +50 -76
 - data/spec/factories/bigbluebutton_room.rb +1 -0
 - data/spec/models/bigbluebutton_room_spec.rb +141 -17
 - data/spec/models/bigbluebutton_server_spec.rb +27 -36
 - data/spec/rails_app/app/controllers/application_controller.rb +4 -3
 - data/spec/rails_app/config/initializers/rack_hotfix.rb +13 -0
 - data/spec/rails_app/features/join_external_bigbluebutton_rooms.feature +19 -0
 - data/spec/rails_app/features/manage_bigbluebutton_rooms.feature +3 -3
 - data/spec/rails_app/features/manage_bigbluebutton_servers.feature +2 -2
 - data/spec/rails_app/features/step_definitions/bigbluebutton_room_steps.rb +29 -3
 - data/spec/rails_app/features/step_definitions/bigbluebutton_server_steps.rb +2 -7
 - data/spec/rails_app/features/step_definitions/common_steps.rb +15 -2
 - data/spec/rails_app/features/step_definitions/web_steps.rb +7 -3
 - data/spec/rails_app/features/support/application_controller.rb +12 -0
 - data/spec/rails_app/features/support/content.rb +10 -0
 - data/spec/rails_app/features/support/env.rb +4 -4
 - data/spec/rails_app/features/support/env_gem.rb +2 -1
 - data/spec/rails_app/features/support/forms.rb +12 -0
 - data/spec/rails_app/features/support/paths.rb +17 -8
 - data/spec/rails_app/features/support/selectors.rb +57 -0
 - data/spec/rails_app/features/support/within.rb +7 -0
 - data/spec/routing/bigbluebutton/rooms_routing_spec.rb +60 -52
 - data/spec/routing/bigbluebutton/servers_routing_spec.rb +8 -8
 - data/spec/spec_helper.rb +5 -0
 - data/spec/support/controllers/bigbluebutton/rooms_controller.rb +7 -0
 - data/spec/support/matchers/shoulda/be_boolean.rb +1 -1
 - data/spec/support/shared_examples/rooms_controller.rb +37 -0
 - metadata +24 -110
 - data/spec/rails_app/app/helpers/application_helper.rb +0 -2
 
| 
         @@ -1,14 +1,15 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            class Bigbluebutton::ServersController < ApplicationController
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
              respond_to :html
         
     | 
| 
       4 
     | 
    
         
            -
              respond_to :json, :only => [:index, :show, :new, :create, :update, :destroy]
         
     | 
| 
      
 4 
     | 
    
         
            +
              respond_to :json, :only => [:index, :show, :new, :create, :update, :destroy, :activity]
         
     | 
| 
      
 5 
     | 
    
         
            +
              before_filter :find_server, :only => [:show, :edit, :activity, :update, :destroy]
         
     | 
| 
       5 
6 
     | 
    
         | 
| 
       6 
7 
     | 
    
         
             
              def index
         
     | 
| 
       7 
8 
     | 
    
         
             
                respond_with(@servers = BigbluebuttonServer.all)
         
     | 
| 
       8 
9 
     | 
    
         
             
              end
         
     | 
| 
       9 
10 
     | 
    
         | 
| 
       10 
11 
     | 
    
         
             
              def show
         
     | 
| 
       11 
     | 
    
         
            -
                respond_with(@server 
     | 
| 
      
 12 
     | 
    
         
            +
                respond_with(@server)
         
     | 
| 
       12 
13 
     | 
    
         
             
              end
         
     | 
| 
       13 
14 
     | 
    
         | 
| 
       14 
15 
     | 
    
         
             
              def new
         
     | 
| 
         @@ -16,31 +17,43 @@ class Bigbluebutton::ServersController < ApplicationController 
     | 
|
| 
       16 
17 
     | 
    
         
             
              end
         
     | 
| 
       17 
18 
     | 
    
         | 
| 
       18 
19 
     | 
    
         
             
              def edit
         
     | 
| 
       19 
     | 
    
         
            -
                respond_with(@server 
     | 
| 
      
 20 
     | 
    
         
            +
                respond_with(@server)
         
     | 
| 
       20 
21 
     | 
    
         
             
              end
         
     | 
| 
       21 
22 
     | 
    
         | 
| 
       22 
23 
     | 
    
         
             
              def activity
         
     | 
| 
       23 
     | 
    
         
            -
                 
     | 
| 
       24 
     | 
    
         
            -
                 
     | 
| 
       25 
     | 
    
         
            -
             
     | 
| 
       26 
     | 
    
         
            -
             
     | 
| 
       27 
     | 
    
         
            -
             
     | 
| 
       28 
     | 
    
         
            -
             
     | 
| 
       29 
     | 
    
         
            -
                 
     | 
| 
       30 
     | 
    
         
            -
             
     | 
| 
       31 
     | 
    
         
            -
                   
     | 
| 
      
 24 
     | 
    
         
            +
                error = false
         
     | 
| 
      
 25 
     | 
    
         
            +
                begin
         
     | 
| 
      
 26 
     | 
    
         
            +
                  @server.fetch_meetings
         
     | 
| 
      
 27 
     | 
    
         
            +
                  @server.meetings.each do |meeting|
         
     | 
| 
      
 28 
     | 
    
         
            +
                    meeting.fetch_meeting_info
         
     | 
| 
      
 29 
     | 
    
         
            +
                  end
         
     | 
| 
      
 30 
     | 
    
         
            +
                rescue BigBlueButton::BigBlueButtonException => e
         
     | 
| 
      
 31 
     | 
    
         
            +
                  error = true
         
     | 
| 
      
 32 
     | 
    
         
            +
                  message = e.to_s
         
     | 
| 
       32 
33 
     | 
    
         
             
                end
         
     | 
| 
       33 
34 
     | 
    
         | 
| 
       34 
     | 
    
         
            -
                #  
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
       37 
     | 
    
         
            -
                  render :partial => 'activity_list'
         
     | 
| 
      
 35 
     | 
    
         
            +
                # update_list works only for html
         
     | 
| 
      
 36 
     | 
    
         
            +
                if params[:update_list] && (params[:format].nil? || params[:format].to_s == "html")
         
     | 
| 
      
 37 
     | 
    
         
            +
                  render :html => 'activity_list', :server => @server
         
     | 
| 
       38 
38 
     | 
    
         
             
                  return
         
     | 
| 
       39 
39 
     | 
    
         
             
                end
         
     | 
| 
       40 
40 
     | 
    
         | 
| 
       41 
     | 
    
         
            -
                 
     | 
| 
       42 
     | 
    
         
            -
             
     | 
| 
       43 
     | 
    
         
            -
             
     | 
| 
      
 41 
     | 
    
         
            +
                respond_with @server.meetings do |format|
         
     | 
| 
      
 42 
     | 
    
         
            +
                  # we return/render the fetched meetings even in case of error
         
     | 
| 
      
 43 
     | 
    
         
            +
                  # but we set the error message in the response
         
     | 
| 
      
 44 
     | 
    
         
            +
                  if error
         
     | 
| 
      
 45 
     | 
    
         
            +
                    flash[:error] = message
         
     | 
| 
      
 46 
     | 
    
         
            +
                    format.html { render :activity }
         
     | 
| 
      
 47 
     | 
    
         
            +
                    format.json {
         
     | 
| 
      
 48 
     | 
    
         
            +
                      array = @server.meetings
         
     | 
| 
      
 49 
     | 
    
         
            +
                      array.insert(0, { :message => message })
         
     | 
| 
      
 50 
     | 
    
         
            +
                      render :json => array, :status => :error
         
     | 
| 
      
 51 
     | 
    
         
            +
                    }
         
     | 
| 
      
 52 
     | 
    
         
            +
                  else
         
     | 
| 
      
 53 
     | 
    
         
            +
                    format.html
         
     | 
| 
      
 54 
     | 
    
         
            +
                    format.json
         
     | 
| 
      
 55 
     | 
    
         
            +
                  end
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
       44 
57 
     | 
    
         
             
              end
         
     | 
| 
       45 
58 
     | 
    
         | 
| 
       46 
59 
     | 
    
         
             
              def create
         
     | 
| 
         @@ -54,15 +67,13 @@ class Bigbluebutton::ServersController < ApplicationController 
     | 
|
| 
       54 
67 
     | 
    
         
             
                    }
         
     | 
| 
       55 
68 
     | 
    
         
             
                    format.json { render :json => @server, :status => :created }
         
     | 
| 
       56 
69 
     | 
    
         
             
                  else
         
     | 
| 
       57 
     | 
    
         
            -
                    format.html { render : 
     | 
| 
      
 70 
     | 
    
         
            +
                    format.html { render :new }
         
     | 
| 
       58 
71 
     | 
    
         
             
                    format.json { render :json => @server.errors.full_messages, :status => :unprocessable_entity }
         
     | 
| 
       59 
72 
     | 
    
         
             
                  end
         
     | 
| 
       60 
73 
     | 
    
         
             
                end
         
     | 
| 
       61 
74 
     | 
    
         
             
              end
         
     | 
| 
       62 
75 
     | 
    
         | 
| 
       63 
76 
     | 
    
         
             
              def update
         
     | 
| 
       64 
     | 
    
         
            -
                @server = BigbluebuttonServer.find_by_param(params[:id])
         
     | 
| 
       65 
     | 
    
         
            -
             
     | 
| 
       66 
77 
     | 
    
         
             
                respond_with @server do |format|
         
     | 
| 
       67 
78 
     | 
    
         
             
                  if @server.update_attributes(params[:bigbluebutton_server])
         
     | 
| 
       68 
79 
     | 
    
         
             
                    format.html {
         
     | 
| 
         @@ -71,14 +82,13 @@ class Bigbluebutton::ServersController < ApplicationController 
     | 
|
| 
       71 
82 
     | 
    
         
             
                    }
         
     | 
| 
       72 
83 
     | 
    
         
             
                    format.json { head :ok }
         
     | 
| 
       73 
84 
     | 
    
         
             
                  else
         
     | 
| 
       74 
     | 
    
         
            -
                    format.html { render : 
     | 
| 
      
 85 
     | 
    
         
            +
                    format.html { render :edit }
         
     | 
| 
       75 
86 
     | 
    
         
             
                    format.json { render :json => @server.errors.full_messages, :status => :unprocessable_entity }
         
     | 
| 
       76 
87 
     | 
    
         
             
                  end
         
     | 
| 
       77 
88 
     | 
    
         
             
                end
         
     | 
| 
       78 
89 
     | 
    
         
             
              end
         
     | 
| 
       79 
90 
     | 
    
         | 
| 
       80 
91 
     | 
    
         
             
              def destroy
         
     | 
| 
       81 
     | 
    
         
            -
                @server = BigbluebuttonServer.find_by_param(params[:id])
         
     | 
| 
       82 
92 
     | 
    
         
             
                @server.destroy
         
     | 
| 
       83 
93 
     | 
    
         | 
| 
       84 
94 
     | 
    
         
             
                respond_with do |format|
         
     | 
| 
         @@ -86,4 +96,11 @@ class Bigbluebutton::ServersController < ApplicationController 
     | 
|
| 
       86 
96 
     | 
    
         
             
                  format.json { head :ok }
         
     | 
| 
       87 
97 
     | 
    
         
             
                end
         
     | 
| 
       88 
98 
     | 
    
         
             
              end
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
              protected
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
              def find_server
         
     | 
| 
      
 103 
     | 
    
         
            +
                @server = BigbluebuttonServer.find_by_param(params[:id])
         
     | 
| 
      
 104 
     | 
    
         
            +
              end
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
       89 
106 
     | 
    
         
             
            end
         
     | 
| 
         @@ -131,7 +131,7 @@ class BigbluebuttonRoom < ActiveRecord::Base 
     | 
|
| 
       131 
131 
     | 
    
         
             
                unless response.nil?
         
     | 
| 
       132 
132 
     | 
    
         
             
                  self.attendee_password = response[:attendeePW]
         
     | 
| 
       133 
133 
     | 
    
         
             
                  self.moderator_password = response[:moderatorPW]
         
     | 
| 
       134 
     | 
    
         
            -
                  self.save
         
     | 
| 
      
 134 
     | 
    
         
            +
                  self.save unless self.new_record?
         
     | 
| 
       135 
135 
     | 
    
         
             
                end
         
     | 
| 
       136 
136 
     | 
    
         | 
| 
       137 
137 
     | 
    
         
             
                response
         
     | 
| 
         @@ -140,13 +140,17 @@ class BigbluebuttonRoom < ActiveRecord::Base 
     | 
|
| 
       140 
140 
     | 
    
         
             
              # Returns the URL to join this room.
         
     | 
| 
       141 
141 
     | 
    
         
             
              # username:: Name of the user
         
     | 
| 
       142 
142 
     | 
    
         
             
              # role:: Role of the user in this room. Can be <tt>[:moderator, :attendee]</tt>
         
     | 
| 
      
 143 
     | 
    
         
            +
              # password:: Password to be use (in case role == nil)
         
     | 
| 
       143 
144 
     | 
    
         
             
              #
         
     | 
| 
       144 
145 
     | 
    
         
             
              # Uses the API but does not require a request to the server.
         
     | 
| 
       145 
     | 
    
         
            -
              def join_url(username, role)
         
     | 
| 
       146 
     | 
    
         
            -
                 
     | 
| 
      
 146 
     | 
    
         
            +
              def join_url(username, role, password=nil)
         
     | 
| 
      
 147 
     | 
    
         
            +
                case role
         
     | 
| 
      
 148 
     | 
    
         
            +
                when :moderator
         
     | 
| 
       147 
149 
     | 
    
         
             
                  self.server.api.join_meeting_url(self.meetingid, username, self.moderator_password)
         
     | 
| 
       148 
     | 
    
         
            -
                 
     | 
| 
      
 150 
     | 
    
         
            +
                when :attendee
         
     | 
| 
       149 
151 
     | 
    
         
             
                  self.server.api.join_meeting_url(self.meetingid, username, self.attendee_password)
         
     | 
| 
      
 152 
     | 
    
         
            +
                else
         
     | 
| 
      
 153 
     | 
    
         
            +
                  self.server.api.join_meeting_url(self.meetingid, username, password)
         
     | 
| 
       150 
154 
     | 
    
         
             
                end
         
     | 
| 
       151 
155 
     | 
    
         
             
              end
         
     | 
| 
       152 
156 
     | 
    
         | 
| 
         @@ -191,6 +195,42 @@ class BigbluebuttonRoom < ActiveRecord::Base 
     | 
|
| 
       191 
195 
     | 
    
         
             
                self.param
         
     | 
| 
       192 
196 
     | 
    
         
             
              end
         
     | 
| 
       193 
197 
     | 
    
         | 
| 
      
 198 
     | 
    
         
            +
              # The join logic
         
     | 
| 
      
 199 
     | 
    
         
            +
              # A moderator can create the meeting and join
         
     | 
| 
      
 200 
     | 
    
         
            +
              # An attendee can only join if the meeting is running
         
     | 
| 
      
 201 
     | 
    
         
            +
              def perform_join(username, role, request=nil)
         
     | 
| 
      
 202 
     | 
    
         
            +
                fetch_is_running?
         
     | 
| 
      
 203 
     | 
    
         
            +
             
     | 
| 
      
 204 
     | 
    
         
            +
                # if the current user is a moderator, create the room (if needed)
         
     | 
| 
      
 205 
     | 
    
         
            +
                # and join it
         
     | 
| 
      
 206 
     | 
    
         
            +
                if role == :moderator
         
     | 
| 
      
 207 
     | 
    
         
            +
                  add_domain_to_logout_url(request.protocol, request.host) unless request.nil?
         
     | 
| 
      
 208 
     | 
    
         
            +
                  send_create unless is_running?
         
     | 
| 
      
 209 
     | 
    
         
            +
                  ret = join_url(username, role)
         
     | 
| 
      
 210 
     | 
    
         
            +
             
     | 
| 
      
 211 
     | 
    
         
            +
                # normal user only joins if the conference is running
         
     | 
| 
      
 212 
     | 
    
         
            +
                # if it's not, wait for a moderator to create the conference
         
     | 
| 
      
 213 
     | 
    
         
            +
                else
         
     | 
| 
      
 214 
     | 
    
         
            +
                  ret = join_url(username, role) if is_running?
         
     | 
| 
      
 215 
     | 
    
         
            +
                end
         
     | 
| 
      
 216 
     | 
    
         
            +
             
     | 
| 
      
 217 
     | 
    
         
            +
                ret
         
     | 
| 
      
 218 
     | 
    
         
            +
              end
         
     | 
| 
      
 219 
     | 
    
         
            +
             
     | 
| 
      
 220 
     | 
    
         
            +
              # add a domain name and/or protocol to the logout_url if needed
         
     | 
| 
      
 221 
     | 
    
         
            +
              def add_domain_to_logout_url(protocol, host)
         
     | 
| 
      
 222 
     | 
    
         
            +
                unless logout_url.nil?
         
     | 
| 
      
 223 
     | 
    
         
            +
                  url = logout_url.downcase
         
     | 
| 
      
 224 
     | 
    
         
            +
                  unless url.nil? or url =~ /^[a-z]+:\/\//           # matches the protocol
         
     | 
| 
      
 225 
     | 
    
         
            +
                    unless url =~ /^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*/  # matches the host domain
         
     | 
| 
      
 226 
     | 
    
         
            +
                      url = host + url
         
     | 
| 
      
 227 
     | 
    
         
            +
                    end
         
     | 
| 
      
 228 
     | 
    
         
            +
                    url = protocol + url
         
     | 
| 
      
 229 
     | 
    
         
            +
                  end
         
     | 
| 
      
 230 
     | 
    
         
            +
                  update_attributes(:logout_url => url.downcase)
         
     | 
| 
      
 231 
     | 
    
         
            +
                end
         
     | 
| 
      
 232 
     | 
    
         
            +
              end
         
     | 
| 
      
 233 
     | 
    
         
            +
             
     | 
| 
       194 
234 
     | 
    
         
             
              protected
         
     | 
| 
       195 
235 
     | 
    
         | 
| 
       196 
236 
     | 
    
         
             
              def init
         
     | 
| 
         @@ -219,7 +259,7 @@ class BigbluebuttonRoom < ActiveRecord::Base 
     | 
|
| 
       219 
259 
     | 
    
         | 
| 
       220 
260 
     | 
    
         
             
              def random_voice_bridge
         
     | 
| 
       221 
261 
     | 
    
         
             
                value = (70000 + ActiveSupport::SecureRandom.random_number(9999)).to_s
         
     | 
| 
       222 
     | 
    
         
            -
                count =  
     | 
| 
      
 262 
     | 
    
         
            +
                count = 1
         
     | 
| 
       223 
263 
     | 
    
         
             
                while not BigbluebuttonRoom.find_by_voice_bridge(value).nil? and count < 10
         
     | 
| 
       224 
264 
     | 
    
         
             
                  count += 1
         
     | 
| 
       225 
265 
     | 
    
         
             
                  value = (70000 + ActiveSupport::SecureRandom.random_number(9999)).to_s
         
     | 
| 
         @@ -1,4 +1,4 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            require ' 
     | 
| 
      
 1 
     | 
    
         
            +
            require 'bigbluebutton_api'
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            class BigbluebuttonServer < ActiveRecord::Base
         
     | 
| 
       4 
4 
     | 
    
         
             
              has_many :rooms,
         
     | 
| 
         @@ -65,18 +65,16 @@ class BigbluebuttonServer < ActiveRecord::Base 
     | 
|
| 
       65 
65 
     | 
    
         
             
                response[:meetings].each do |attr|
         
     | 
| 
       66 
66 
     | 
    
         
             
                  room = BigbluebuttonRoom.find_by_server_id_and_meetingid(self.id, attr[:meetingID])
         
     | 
| 
       67 
67 
     | 
    
         
             
                  if room.nil?
         
     | 
| 
       68 
     | 
    
         
            -
                    room = BigbluebuttonRoom. 
     | 
| 
       69 
     | 
    
         
            -
             
     | 
| 
       70 
     | 
    
         
            -
             
     | 
| 
       71 
     | 
    
         
            -
             
     | 
| 
      
 68 
     | 
    
         
            +
                    room = BigbluebuttonRoom.new(:server => self, :meetingid => attr[:meetingID],
         
     | 
| 
      
 69 
     | 
    
         
            +
                                                 :name => attr[:meetingID], :attendee_password => attr[:attendeePW],
         
     | 
| 
      
 70 
     | 
    
         
            +
                                                 :moderator_password => attr[:moderatorPW], :external => true,
         
     | 
| 
      
 71 
     | 
    
         
            +
                                                 :randomize_meetingid => false, :private => true)
         
     | 
| 
       72 
72 
     | 
    
         
             
                  else
         
     | 
| 
       73 
73 
     | 
    
         
             
                    room.update_attributes(:attendee_password => attr[:attendeePW],
         
     | 
| 
       74 
74 
     | 
    
         
             
                                           :moderator_password => attr[:moderatorPW])
         
     | 
| 
       75 
75 
     | 
    
         
             
                  end
         
     | 
| 
       76 
76 
     | 
    
         
             
                  room.running = attr[:running]
         
     | 
| 
       77 
77 
     | 
    
         | 
| 
       78 
     | 
    
         
            -
                  # TODO What if the update/save above fails?
         
     | 
| 
       79 
     | 
    
         
            -
             
     | 
| 
       80 
78 
     | 
    
         
             
                  @meetings << room
         
     | 
| 
       81 
79 
     | 
    
         
             
                end
         
     | 
| 
       82 
80 
     | 
    
         
             
              end
         
     | 
| 
         @@ -0,0 +1,24 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            <h1>Join the meeting <%= @room.name %></h1>
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            <p id="error"><%= flash[:error] %></p>
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            <p>This meeting was created from another application.<br/>You must provide a name and a password to join:</p>
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            <%= form_tag external_bigbluebutton_server_rooms_path(@server) do %>
         
     | 
| 
      
 8 
     | 
    
         
            +
              <%= hidden_field_tag "meeting", params[:meeting] %>
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              <div class="field">
         
     | 
| 
      
 11 
     | 
    
         
            +
                Name:<br />
         
     | 
| 
      
 12 
     | 
    
         
            +
                <% if bigbluebutton_user.nil? %>
         
     | 
| 
      
 13 
     | 
    
         
            +
                  <%= text_field_tag "user[name]", "" %>
         
     | 
| 
      
 14 
     | 
    
         
            +
                <% else %>
         
     | 
| 
      
 15 
     | 
    
         
            +
                  <%= text_field_tag "user[name]", bigbluebutton_user.name %>
         
     | 
| 
      
 16 
     | 
    
         
            +
                <% end %>
         
     | 
| 
      
 17 
     | 
    
         
            +
              </div>
         
     | 
| 
      
 18 
     | 
    
         
            +
              <div class="field">
         
     | 
| 
      
 19 
     | 
    
         
            +
                Password:<br />
         
     | 
| 
      
 20 
     | 
    
         
            +
                <%= password_field_tag "user[password]", "" %>
         
     | 
| 
      
 21 
     | 
    
         
            +
              </div>
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
              <%= submit_tag "Submit" %>
         
     | 
| 
      
 24 
     | 
    
         
            +
            <% end %>
         
     | 
| 
         @@ -3,7 +3,7 @@ 
     | 
|
| 
       3 
3 
     | 
    
         
             
            <div style="text-align:center; width:250px;">
         
     | 
| 
       4 
4 
     | 
    
         
             
              <a href="<%= @join_url %>"><b>Click here</b><br/>to join from a mobile device</a><br><br/>
         
     | 
| 
       5 
5 
     | 
    
         
             
              <span>Or scan the code below:</span><br/>
         
     | 
| 
       6 
     | 
    
         
            -
              <img src="<%= qrcode_url(@ 
     | 
| 
      
 6 
     | 
    
         
            +
              <img src="<%= qrcode_url(@qrcode_url) %>" style="float:left; border:1px solid #ccc;"/>
         
     | 
| 
       7 
7 
     | 
    
         
             
            </div>
         
     | 
| 
       8 
8 
     | 
    
         | 
| 
       9 
9 
     | 
    
         | 
| 
         @@ -1,16 +1,16 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            <%  
     | 
| 
      
 1 
     | 
    
         
            +
            <% server.meetings.each do |room| %>
         
     | 
| 
       2 
2 
     | 
    
         
             
              <div class="bbbrails_meeting_description">
         
     | 
| 
       3 
3 
     | 
    
         
             
                <div class="title_bar">
         
     | 
| 
       4 
4 
     | 
    
         
             
                  <% unless room.new_record? %>
         
     | 
| 
       5 
5 
     | 
    
         
             
                    <div class="button_bar">
         
     | 
| 
       6 
     | 
    
         
            -
                      <%= link_to 'Show', bigbluebutton_server_room_path( 
     | 
| 
       7 
     | 
    
         
            -
                      <%= link_to 'Edit', edit_bigbluebutton_server_room_path( 
     | 
| 
       8 
     | 
    
         
            -
                      <%= link_to 'Destroy', bigbluebutton_server_room_path( 
     | 
| 
      
 6 
     | 
    
         
            +
                      <%= link_to 'Show', bigbluebutton_server_room_path(server, room) %>
         
     | 
| 
      
 7 
     | 
    
         
            +
                      <%= link_to 'Edit', edit_bigbluebutton_server_room_path(server, room) %>
         
     | 
| 
      
 8 
     | 
    
         
            +
                      <%= link_to 'Destroy', bigbluebutton_server_room_path(server, room), :confirm => 'Are you sure?', :method => :delete %>
         
     | 
| 
       9 
9 
     | 
    
         
             
                    </div>
         
     | 
| 
       10 
10 
     | 
    
         
             
                    <div class="button_bar">
         
     | 
| 
       11 
     | 
    
         
            -
                      <%= link_to 'Join', join_bigbluebutton_server_room_path( 
     | 
| 
       12 
     | 
    
         
            -
                      <%= link_to 'Mobile Join', join_mobile_bigbluebutton_server_room_path( 
     | 
| 
       13 
     | 
    
         
            -
                      <%= link_to 'End', end_bigbluebutton_server_room_path( 
     | 
| 
      
 11 
     | 
    
         
            +
                      <%= link_to 'Join', join_bigbluebutton_server_room_path(server, room) %>
         
     | 
| 
      
 12 
     | 
    
         
            +
                      <%= link_to 'Mobile Join', join_mobile_bigbluebutton_server_room_path(server, room) %>
         
     | 
| 
      
 13 
     | 
    
         
            +
                      <%= link_to 'End', end_bigbluebutton_server_room_path(server, room), :confirm => 'Are you sure?' %>
         
     | 
| 
       14 
14 
     | 
    
         
             
                    </div>
         
     | 
| 
       15 
15 
     | 
    
         
             
                  <% end %>
         
     | 
| 
       16 
16 
     | 
    
         
             
                  <b><%= room.name %></b> (ID: <%= room.meetingid %>) -
         
     | 
    
        data/bigbluebutton_rails.gemspec
    CHANGED
    
    | 
         @@ -15,16 +15,6 @@ Gem::Specification.new do |s| 
     | 
|
| 
       15 
15 
     | 
    
         
             
              s.test_files    = `git ls-files -- {test,spec,features}/*`.split("\n")
         
     | 
| 
       16 
16 
     | 
    
         
             
              s.require_paths = ["lib"]
         
     | 
| 
       17 
17 
     | 
    
         | 
| 
       18 
     | 
    
         
            -
              s. 
     | 
| 
       19 
     | 
    
         
            -
              s. 
     | 
| 
       20 
     | 
    
         
            -
             
     | 
| 
       21 
     | 
    
         
            -
              s.add_development_dependency("rspec-rails", "~> 2.6.0")
         
     | 
| 
       22 
     | 
    
         
            -
              s.add_development_dependency("factory_girl", "~> 1.3.2")
         
     | 
| 
       23 
     | 
    
         
            -
              s.add_development_dependency("sqlite3-ruby", "~> 1.3.3")
         
     | 
| 
       24 
     | 
    
         
            -
              s.add_development_dependency("generator_spec", "~> 0.8.2")
         
     | 
| 
       25 
     | 
    
         
            -
              s.add_development_dependency("shoulda-matchers", "~> 1.0.0.beta")
         
     | 
| 
       26 
     | 
    
         
            -
              s.add_development_dependency("forgery", "~> 0.3.7")
         
     | 
| 
       27 
     | 
    
         
            -
              s.add_development_dependency("cucumber-rails", "~> 0.5.2")
         
     | 
| 
       28 
     | 
    
         
            -
              s.add_development_dependency("database_cleaner", "~> 0.6.7")
         
     | 
| 
       29 
     | 
    
         
            -
              s.add_development_dependency("rdoc", "~> 3.6.0")
         
     | 
| 
      
 18 
     | 
    
         
            +
              s.add_dependency("rails", ">= 3.0.3")
         
     | 
| 
      
 19 
     | 
    
         
            +
              s.add_dependency("bigbluebutton-api-ruby", "~> 0.0.11")
         
     | 
| 
       30 
20 
     | 
    
         
             
            end
         
     | 
    
        data/config/locales/en.yml
    CHANGED
    
    | 
         @@ -27,6 +27,9 @@ en: 
     | 
|
| 
       27 
27 
     | 
    
         
             
                    auth:
         
     | 
| 
       28 
28 
     | 
    
         
             
                      failure: "Authentication failure."
         
     | 
| 
       29 
29 
     | 
    
         
             
                      not_running: "The meeting is not running. You must wait for a moderator to join."
         
     | 
| 
      
 30 
     | 
    
         
            +
                      wrong_params: "Wrong params in your request."
         
     | 
| 
      
 31 
     | 
    
         
            +
                    external:
         
     | 
| 
      
 32 
     | 
    
         
            +
                      blank_meetingid: "You must specify the meetingid of the meeting you want to join"
         
     | 
| 
       30 
33 
     | 
    
         
             
                    param_format: 'you can only use letters, numbers and the symbols "-" or "_" (not at the begin or end)'
         
     | 
| 
       31 
34 
     | 
    
         | 
| 
       32 
35 
     | 
    
         
             
              activerecord:
         
     | 
| 
         @@ -83,12 +83,18 @@ module ActionDispatch::Routing 
     | 
|
| 
       83 
83 
     | 
    
         
             
                    resources :servers, :controller => BigbluebuttonRails.controllers[:servers] do
         
     | 
| 
       84 
84 
     | 
    
         
             
                      get :activity, :on => :member
         
     | 
| 
       85 
85 
     | 
    
         
             
                      resources :rooms, :controller => BigbluebuttonRails.controllers[:rooms] do
         
     | 
| 
       86 
     | 
    
         
            -
                         
     | 
| 
       87 
     | 
    
         
            -
             
     | 
| 
       88 
     | 
    
         
            -
             
     | 
| 
       89 
     | 
    
         
            -
                         
     | 
| 
       90 
     | 
    
         
            -
                         
     | 
| 
       91 
     | 
    
         
            -
             
     | 
| 
      
 86 
     | 
    
         
            +
                        collection do
         
     | 
| 
      
 87 
     | 
    
         
            +
                          get :external
         
     | 
| 
      
 88 
     | 
    
         
            +
                          post :external, :action => :external_auth
         
     | 
| 
      
 89 
     | 
    
         
            +
                        end
         
     | 
| 
      
 90 
     | 
    
         
            +
                        member do
         
     | 
| 
      
 91 
     | 
    
         
            +
                          get :join
         
     | 
| 
      
 92 
     | 
    
         
            +
                          get :running
         
     | 
| 
      
 93 
     | 
    
         
            +
                          get :end
         
     | 
| 
      
 94 
     | 
    
         
            +
                          get :invite
         
     | 
| 
      
 95 
     | 
    
         
            +
                          get :join_mobile
         
     | 
| 
      
 96 
     | 
    
         
            +
                          post :join, :action => :auth
         
     | 
| 
      
 97 
     | 
    
         
            +
                        end
         
     | 
| 
       92 
98 
     | 
    
         
             
                      end
         
     | 
| 
       93 
99 
     | 
    
         
             
                    end
         
     | 
| 
       94 
100 
     | 
    
         
             
                  end
         
     | 
    
        data/lib/bigbluebutton_rails.rb
    CHANGED
    
    | 
         @@ -4,6 +4,7 @@ require 'classes/bigbluebutton_attendee' 
     | 
|
| 
       4 
4 
     | 
    
         | 
| 
       5 
5 
     | 
    
         
             
            module BigbluebuttonRails
         
     | 
| 
       6 
6 
     | 
    
         
             
              require 'bigbluebutton_rails/rails'
         
     | 
| 
      
 7 
     | 
    
         
            +
              require 'bigbluebutton_rails/utils'
         
     | 
| 
       7 
8 
     | 
    
         
             
              require 'bigbluebutton_rails/controller_methods'
         
     | 
| 
       8 
9 
     | 
    
         
             
              require 'bigbluebutton_rails/rails/routes'
         
     | 
| 
       9 
10 
     | 
    
         
             
              require 'bigbluebutton_rails/exceptions'
         
     | 
| 
         @@ -8,4 +8,17 @@ describe BigbluebuttonRails do 
     | 
|
| 
       8 
8 
     | 
    
         
             
              it "should have an engine" do
         
     | 
| 
       9 
9 
     | 
    
         
             
                BigbluebuttonRails::Engine.new.should be_a_kind_of(Rails::Engine)
         
     | 
| 
       10 
10 
     | 
    
         
             
              end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
              describe "#value_to_boolean" do
         
     | 
| 
      
 13 
     | 
    
         
            +
                it { BigbluebuttonRails::value_to_boolean("true").should be_true }
         
     | 
| 
      
 14 
     | 
    
         
            +
                it { BigbluebuttonRails::value_to_boolean("1").should be_true }
         
     | 
| 
      
 15 
     | 
    
         
            +
                it { BigbluebuttonRails::value_to_boolean(1).should be_true }
         
     | 
| 
      
 16 
     | 
    
         
            +
                it { BigbluebuttonRails::value_to_boolean(true).should be_true }
         
     | 
| 
      
 17 
     | 
    
         
            +
                it { BigbluebuttonRails::value_to_boolean("t").should be_true }
         
     | 
| 
      
 18 
     | 
    
         
            +
                it { BigbluebuttonRails::value_to_boolean("false").should be_false }
         
     | 
| 
      
 19 
     | 
    
         
            +
                it { BigbluebuttonRails::value_to_boolean("0").should be_false }
         
     | 
| 
      
 20 
     | 
    
         
            +
                it { BigbluebuttonRails::value_to_boolean(0).should be_false }
         
     | 
| 
      
 21 
     | 
    
         
            +
                it { BigbluebuttonRails::value_to_boolean(false).should be_false }
         
     | 
| 
      
 22 
     | 
    
         
            +
                it { BigbluebuttonRails::value_to_boolean("f").should be_false }
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
       11 
24 
     | 
    
         
             
            end
         
     | 
| 
         @@ -0,0 +1,114 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'spec_helper'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            describe Bigbluebutton::RoomsController do
         
     | 
| 
      
 4 
     | 
    
         
            +
              render_views
         
     | 
| 
      
 5 
     | 
    
         
            +
              let(:server) { Factory.create(:bigbluebutton_server) }
         
     | 
| 
      
 6 
     | 
    
         
            +
              let(:room) { Factory.create(:bigbluebutton_room, :server => server) }
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              # make sure that the exceptions thrown by bigbluebutton-api-ruby are treated by the controller
         
     | 
| 
      
 9 
     | 
    
         
            +
              context "exception handling" do
         
     | 
| 
      
 10 
     | 
    
         
            +
                let(:bbb_error_msg) { "err msg" }
         
     | 
| 
      
 11 
     | 
    
         
            +
                let(:bbb_error) { BigBlueButton::BigBlueButtonException.new(bbb_error_msg) }
         
     | 
| 
      
 12 
     | 
    
         
            +
                let(:http_referer) { bigbluebutton_server_path(mocked_server) }
         
     | 
| 
      
 13 
     | 
    
         
            +
                before {
         
     | 
| 
      
 14 
     | 
    
         
            +
                  mock_server_and_api
         
     | 
| 
      
 15 
     | 
    
         
            +
                  request.env["HTTP_REFERER"] = http_referer
         
     | 
| 
      
 16 
     | 
    
         
            +
                }
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                describe "#destroy" do
         
     | 
| 
      
 19 
     | 
    
         
            +
                  it "catches exception on is_meeting_running" do
         
     | 
| 
      
 20 
     | 
    
         
            +
                    mocked_api.should_receive(:is_meeting_running?) { raise bbb_error }
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  it "catches exception on end_meeting" do
         
     | 
| 
      
 24 
     | 
    
         
            +
                    mocked_api.should_receive(:is_meeting_running?).and_return(true)
         
     | 
| 
      
 25 
     | 
    
         
            +
                    mocked_api.should_receive(:end_meeting) { raise bbb_error }
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                  after :each do
         
     | 
| 
      
 29 
     | 
    
         
            +
                    delete :destroy, :server_id => mocked_server.to_param, :id => room.to_param
         
     | 
| 
      
 30 
     | 
    
         
            +
                    should respond_with(:redirect)
         
     | 
| 
      
 31 
     | 
    
         
            +
                    should redirect_to bigbluebutton_server_rooms_url
         
     | 
| 
      
 32 
     | 
    
         
            +
                    should set_the_flash.to(bbb_error_msg)
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                describe "#running" do
         
     | 
| 
      
 37 
     | 
    
         
            +
                  before { mocked_api.should_receive(:is_meeting_running?) { raise bbb_error } }
         
     | 
| 
      
 38 
     | 
    
         
            +
                  before(:each) { get :running, :server_id => mocked_server.to_param, :id => room.to_param }
         
     | 
| 
      
 39 
     | 
    
         
            +
                  it { should respond_with(:success) }
         
     | 
| 
      
 40 
     | 
    
         
            +
                  it { response.body.should == build_running_json(false, bbb_error_msg) }
         
     | 
| 
      
 41 
     | 
    
         
            +
                  it { should set_the_flash.to(bbb_error_msg) }
         
     | 
| 
      
 42 
     | 
    
         
            +
                end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                describe "#end" do
         
     | 
| 
      
 45 
     | 
    
         
            +
                  it "catches exception on is_meeting_running" do
         
     | 
| 
      
 46 
     | 
    
         
            +
                    mocked_api.should_receive(:is_meeting_running?) { raise bbb_error }
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                  it "catches exception on end_meeting" do
         
     | 
| 
      
 50 
     | 
    
         
            +
                    mocked_api.should_receive(:is_meeting_running?).and_return(true)
         
     | 
| 
      
 51 
     | 
    
         
            +
                    mocked_api.should_receive(:end_meeting) { raise bbb_error }
         
     | 
| 
      
 52 
     | 
    
         
            +
                  end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                  after :each do
         
     | 
| 
      
 55 
     | 
    
         
            +
                    get :end, :server_id => mocked_server.to_param, :id => room.to_param
         
     | 
| 
      
 56 
     | 
    
         
            +
                    should respond_with(:redirect)
         
     | 
| 
      
 57 
     | 
    
         
            +
                    should redirect_to(http_referer)
         
     | 
| 
      
 58 
     | 
    
         
            +
                    should set_the_flash.to(bbb_error_msg)
         
     | 
| 
      
 59 
     | 
    
         
            +
                  end
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                describe "#join" do
         
     | 
| 
      
 63 
     | 
    
         
            +
                  before { controller.stub(:bigbluebutton_user) { Factory.build(:user) } }
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                  context "as moderator" do
         
     | 
| 
      
 66 
     | 
    
         
            +
                    before { controller.should_receive(:bigbluebutton_role).with(room).and_return(:moderator) }
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                    it "catches exception on is_meeting_running" do
         
     | 
| 
      
 69 
     | 
    
         
            +
                      mocked_api.should_receive(:is_meeting_running?) { raise bbb_error }
         
     | 
| 
      
 70 
     | 
    
         
            +
                    end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                    it "catches exception on create_meeting" do
         
     | 
| 
      
 73 
     | 
    
         
            +
                      mocked_api.should_receive(:is_meeting_running?).and_return(false)
         
     | 
| 
      
 74 
     | 
    
         
            +
                      mocked_api.should_receive(:create_meeting) { raise bbb_error }
         
     | 
| 
      
 75 
     | 
    
         
            +
                    end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                    it "catches exception on join_meeting_url" do
         
     | 
| 
      
 78 
     | 
    
         
            +
                      mocked_api.should_receive(:is_meeting_running?).and_return(true)
         
     | 
| 
      
 79 
     | 
    
         
            +
                      mocked_api.should_receive(:join_meeting_url) { raise bbb_error }
         
     | 
| 
      
 80 
     | 
    
         
            +
                    end
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                    after :each do
         
     | 
| 
      
 83 
     | 
    
         
            +
                      get :join, :server_id => mocked_server.to_param, :id => room.to_param
         
     | 
| 
      
 84 
     | 
    
         
            +
                      should respond_with(:redirect)
         
     | 
| 
      
 85 
     | 
    
         
            +
                      should redirect_to(http_referer)
         
     | 
| 
      
 86 
     | 
    
         
            +
                      should set_the_flash.to(bbb_error_msg)
         
     | 
| 
      
 87 
     | 
    
         
            +
                    end
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                  end
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
                  context "as moderator" do
         
     | 
| 
      
 92 
     | 
    
         
            +
                    before { controller.should_receive(:bigbluebutton_role).with(room).and_return(:attendee) }
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                    it "catches exception on is_meeting_running" do
         
     | 
| 
      
 95 
     | 
    
         
            +
                      mocked_api.should_receive(:is_meeting_running?) { raise bbb_error }
         
     | 
| 
      
 96 
     | 
    
         
            +
                    end
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
                    it "catches exception on join_meeting_url" do
         
     | 
| 
      
 99 
     | 
    
         
            +
                      mocked_api.should_receive(:is_meeting_running?).and_return(true)
         
     | 
| 
      
 100 
     | 
    
         
            +
                      mocked_api.should_receive(:join_meeting_url) { raise bbb_error }
         
     | 
| 
      
 101 
     | 
    
         
            +
                    end
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
                    after :each do
         
     | 
| 
      
 104 
     | 
    
         
            +
                      get :join, :server_id => mocked_server.to_param, :id => room.to_param
         
     | 
| 
      
 105 
     | 
    
         
            +
                      should respond_with(:redirect)
         
     | 
| 
      
 106 
     | 
    
         
            +
                      should redirect_to(http_referer)
         
     | 
| 
      
 107 
     | 
    
         
            +
                      should set_the_flash.to(bbb_error_msg)
         
     | 
| 
      
 108 
     | 
    
         
            +
                    end
         
     | 
| 
      
 109 
     | 
    
         
            +
                  end
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
                end # #join
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
              end
         
     | 
| 
      
 114 
     | 
    
         
            +
            end
         
     |