qcourses 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/.rspec +1 -0
  2. data/Gemfile +23 -0
  3. data/Gemfile.lock +75 -0
  4. data/LICENSE.txt +20 -0
  5. data/README.rdoc +19 -0
  6. data/Rakefile +52 -0
  7. data/VERSION +1 -0
  8. data/config.ru +3 -0
  9. data/db/migrations/001_initial_database.rb +25 -0
  10. data/db/migrations/002_create_registrations.rb +29 -0
  11. data/demo_app.rb +11 -0
  12. data/lib/factories.rb +111 -0
  13. data/lib/qcourses/configuration.rb +35 -0
  14. data/lib/qcourses/controllers/base.rb +20 -0
  15. data/lib/qcourses/controllers/courses_controller.rb +16 -0
  16. data/lib/qcourses/controllers/events_controller.rb +54 -0
  17. data/lib/qcourses/controllers/registrations_controller.rb +51 -0
  18. data/lib/qcourses/controllers.rb +5 -0
  19. data/lib/qcourses/date_ex.rb +18 -0
  20. data/lib/qcourses/models/course.rb +30 -0
  21. data/lib/qcourses/models/course_repository.rb +90 -0
  22. data/lib/qcourses/models/event.rb +54 -0
  23. data/lib/qcourses/models/location.rb +17 -0
  24. data/lib/qcourses/models/registration.rb +36 -0
  25. data/lib/qcourses/models.rb +28 -0
  26. data/lib/qcourses/qcourses.rake.rb +21 -0
  27. data/lib/qcourses/renderers.rb +26 -0
  28. data/lib/qcourses/resource_paths.rb +45 -0
  29. data/lib/qcourses/string_ex.rb +5 -0
  30. data/lib/qcourses/view_helpers.rb +70 -0
  31. data/lib/qcourses/web_app.rb +29 -0
  32. data/lib/qcourses.rb +21 -0
  33. data/public/css/default.css +91 -0
  34. data/public/img/delete.gif +0 -0
  35. data/public/javascript/jquery-1.7.js +9300 -0
  36. data/public/javascript/jquery-1.7.min.js +4 -0
  37. data/public/javascript/qcourses.js +20 -0
  38. data/spec/factories_spec.rb +131 -0
  39. data/spec/qcourses/configuration_spec.rb +37 -0
  40. data/spec/qcourses/controllers/courses_controller_spec.rb +43 -0
  41. data/spec/qcourses/controllers/events_controller_spec.rb +100 -0
  42. data/spec/qcourses/controllers/registrations_controller_spec.rb +100 -0
  43. data/spec/qcourses/models/course_repository_spec.rb +147 -0
  44. data/spec/qcourses/models/event_spec.rb +74 -0
  45. data/spec/qcourses/models/location_spec.rb +19 -0
  46. data/spec/qcourses/models/registration_spec.rb +55 -0
  47. data/spec/qcourses/renderers_spec.rb +82 -0
  48. data/spec/qcourses/string_ex_spec.rb +11 -0
  49. data/spec/qcourses/view_helpers_spec.rb +90 -0
  50. data/spec/spec_helper.rb +38 -0
  51. data/spec/support/factories.rb +24 -0
  52. data/spec/support/matchers.rb +61 -0
  53. data/spec/support/request_specs.rb +4 -0
  54. data/spec/support/test_files.rb +20 -0
  55. data/views/admin.haml +9 -0
  56. data/views/events/index.haml +18 -0
  57. data/views/events/new.haml +34 -0
  58. data/views/layout.haml +9 -0
  59. data/views/registration.haml +9 -0
  60. data/views/registrations/new.haml +37 -0
  61. data/views/registrations/participant.haml +12 -0
  62. data/views/registrations/success.haml +4 -0
  63. data/views/trainings/error.haml +1 -0
  64. data/views/trainings/index.haml +9 -0
  65. metadata +306 -0
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,23 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+
9
+ gem "sinatra", "~> 1.2.6"
10
+ gem "haml", "~> 3.1.4"
11
+ gem "sequel", "~> 3.33.0"
12
+ gem "sqlite3", "~> 1.3.5"
13
+
14
+ group :development do
15
+ gem "rspec", "~> 2.8"
16
+ gem "rack-test"
17
+ gem "ZenTest", ">= 0"
18
+ gem "rdoc", "~> 3.12"
19
+ gem "bundler", "~> 1.1.0"
20
+ gem "jeweler", "~> 1.8.3"
21
+ gem "simplecov", ">= 0"
22
+ gem "capybara"
23
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,75 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ ZenTest (4.6.2)
5
+ capybara (1.1.2)
6
+ mime-types (>= 1.16)
7
+ nokogiri (>= 1.3.3)
8
+ rack (>= 1.0.0)
9
+ rack-test (>= 0.5.4)
10
+ selenium-webdriver (~> 2.0)
11
+ xpath (~> 0.1.4)
12
+ childprocess (0.3.1)
13
+ ffi (~> 1.0.6)
14
+ diff-lcs (1.1.3)
15
+ ffi (1.0.11)
16
+ git (1.2.5)
17
+ haml (3.1.4)
18
+ jeweler (1.8.3)
19
+ bundler (~> 1.0)
20
+ git (>= 1.2.5)
21
+ rake
22
+ rdoc
23
+ json (1.6.5)
24
+ mime-types (1.17.2)
25
+ multi_json (1.1.0)
26
+ nokogiri (1.5.2)
27
+ rack (1.4.1)
28
+ rack-test (0.6.1)
29
+ rack (>= 1.0)
30
+ rake (0.9.2.2)
31
+ rdoc (3.12)
32
+ json (~> 1.4)
33
+ rspec (2.8.0)
34
+ rspec-core (~> 2.8.0)
35
+ rspec-expectations (~> 2.8.0)
36
+ rspec-mocks (~> 2.8.0)
37
+ rspec-core (2.8.0)
38
+ rspec-expectations (2.8.0)
39
+ diff-lcs (~> 1.1.2)
40
+ rspec-mocks (2.8.0)
41
+ rubyzip (0.9.6.1)
42
+ selenium-webdriver (2.20.0)
43
+ childprocess (>= 0.2.5)
44
+ ffi (~> 1.0)
45
+ multi_json (~> 1.0)
46
+ rubyzip
47
+ sequel (3.33.0)
48
+ simplecov (0.6.1)
49
+ multi_json (~> 1.0)
50
+ simplecov-html (~> 0.5.3)
51
+ simplecov-html (0.5.3)
52
+ sinatra (1.2.6)
53
+ rack (~> 1.1)
54
+ tilt (>= 1.2.2, < 2.0)
55
+ sqlite3 (1.3.5)
56
+ tilt (1.3.3)
57
+ xpath (0.1.4)
58
+ nokogiri (~> 1.3)
59
+
60
+ PLATFORMS
61
+ ruby
62
+
63
+ DEPENDENCIES
64
+ ZenTest
65
+ bundler (~> 1.1.0)
66
+ capybara
67
+ haml (~> 3.1.4)
68
+ jeweler (~> 1.8.3)
69
+ rack-test
70
+ rdoc (~> 3.12)
71
+ rspec (~> 2.8)
72
+ sequel (~> 3.33.0)
73
+ simplecov
74
+ sinatra (~> 1.2.6)
75
+ sqlite3 (~> 1.3.5)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Rob Westgeest
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,19 @@
1
+ = qcourses
2
+
3
+ Description goes here.
4
+
5
+ == Contributing to qcourses
6
+
7
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
8
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
9
+ * Fork the project.
10
+ * Start a feature/bugfix branch.
11
+ * Commit and push until you are happy with your contribution.
12
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2012 Rob Westgeest. See LICENSE.txt for
18
+ further details.
19
+
data/Rakefile ADDED
@@ -0,0 +1,52 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ require 'bundler/setup'
6
+ begin
7
+ Bundler.setup(:default, :development)
8
+ rescue Bundler::BundlerError => e
9
+ $stderr.puts e.message
10
+ $stderr.puts "Run `bundle install` to install missing gems"
11
+ exit e.status_code
12
+ end
13
+ require 'rake'
14
+
15
+ require 'jeweler'
16
+ Jeweler::Tasks.new do |gem|
17
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
18
+ gem.name = "qcourses"
19
+ gem.homepage = "http://github.com/rwestgeest/qcourses"
20
+ gem.license = "MIT"
21
+ gem.summary = %Q{Course management module for QWAN nesta site}
22
+ gem.description = %Q{Course management module for QWAN nesta site}
23
+ gem.email = "rob.westgeest@gmail.com"
24
+ gem.authors = ["Rob Westgeest"]
25
+ gem.files = Dir.glob("{views,spec,lib,db,public}/**/*") + %w{ config.ru demo_app.rb Gemfile Gemfile.lock .rspec LICENCE.txt Rakefile README.rdoc VERSION }
26
+ # dependencies defined in Gemfile
27
+ end
28
+ Jeweler::RubygemsDotOrgTasks.new
29
+
30
+ require 'rspec/core'
31
+ require 'rspec/core/rake_task'
32
+ RSpec::Core::RakeTask.new(:spec) do |spec|
33
+ spec.pattern = FileList['spec/**/*_spec.rb']
34
+ end
35
+
36
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
37
+ spec.pattern = 'spec/**/*_spec.rb'
38
+ spec.rcov = true
39
+ end
40
+
41
+ task :default => :spec
42
+
43
+ require 'rdoc/task'
44
+ Rake::RDocTask.new do |rdoc|
45
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
46
+
47
+ rdoc.rdoc_dir = 'rdoc'
48
+ rdoc.title = "qcourses #{version}"
49
+ rdoc.rdoc_files.include('README*')
50
+ rdoc.rdoc_files.include('lib/**/*.rb')
51
+ end
52
+ require 'qcourses/qcourses.rake'
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.2
data/config.ru ADDED
@@ -0,0 +1,3 @@
1
+ require File.expand_path('demo_app', File.dirname(__FILE__))
2
+
3
+ run DemoApp
@@ -0,0 +1,25 @@
1
+ Sequel.migration do
2
+ up do
3
+ create_table(:events) do
4
+ primary_key :id
5
+ String :course_id
6
+ DateTime :from
7
+ DateTime :to
8
+ foreign_key :location_id, :locations
9
+ index :course_id
10
+ end
11
+ create_table(:locations) do
12
+ primary_key :id
13
+ String :name, limit:100
14
+ String :address, limit: 100
15
+ String :postal_code, limit: 15
16
+ String :city, limit: 100
17
+ String :url
18
+ end
19
+ end
20
+
21
+ down do
22
+ drop_table(:locations)
23
+ drop_table(:events)
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ Sequel.migration do
2
+ up do
3
+ create_table(:companies) do
4
+ primary_key :id
5
+ String :name, limit:100
6
+ String :contact_person, limit:100
7
+ String :contact_email, limit:100
8
+ String :invoice_address, text: true
9
+ String :postal_code, limit:50
10
+ String :city, limit: 100
11
+ end
12
+ create_table(:employees) do
13
+ primary_key :id
14
+ String :name, limit:100
15
+ String :email, limit:100
16
+ foreign_key :company_id, :companies
17
+ end
18
+ create_table(:registrations) do
19
+ primary_key :id
20
+ foreign_key :employee_id, :employees
21
+ foreign_key :event_id, :events
22
+ end
23
+ end
24
+ down do
25
+ drop_table(:registrations)
26
+ drop_table(:employees)
27
+ drop_table(:companies)
28
+ end
29
+ end
data/demo_app.rb ADDED
@@ -0,0 +1,11 @@
1
+ require 'sinatra/base'
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__),'lib'))
3
+ require 'qcourses/web_app'
4
+ Qcourses.configure do |config|
5
+ config.root = File.expand_path('.', File.dirname(__FILE__))
6
+ end
7
+ Qcourses::CourseRepository.on_file_system
8
+
9
+ class DemoApp < Sinatra::Application
10
+ use Qcourses::WebApp
11
+ end
data/lib/factories.rb ADDED
@@ -0,0 +1,111 @@
1
+ module Rubory
2
+ def self.define(name, &definition)
3
+ factories.define(name, &definition)
4
+ end
5
+ def self.create(name)
6
+ factories.create(name)
7
+ end
8
+ def self.build(name)
9
+ factories.build(name)
10
+ end
11
+ def self.attributes_for(name)
12
+ factories.attributes_for(name)
13
+ end
14
+ def self.use_default_module(default_module)
15
+ factories.use_default_module(default_module)
16
+ end
17
+ def self.factories
18
+ @@factories ||= Factories.new
19
+ end
20
+ def self.clear_all
21
+ @@factories = nil
22
+ end
23
+
24
+ class Factories
25
+ def initialize(default_module='::')
26
+ @default_module = default_module
27
+ end
28
+ def use_default_module(default_module)
29
+ @default_module = default_module
30
+ end
31
+ def define(name, &definition)
32
+ factories[name] = CreationDsl.create_factory(name, self, &definition)
33
+ end
34
+ def create(name)
35
+ object = build(name)
36
+ object.save
37
+ object
38
+ end
39
+ def build(name)
40
+ factory(name).build
41
+ end
42
+ def attributes_for(name)
43
+ object = build(name)
44
+ object.values || object.attributes || raise(NotSupportedException.new(":attributes_for is not supported as your model does not support :values or :attributes methods"))
45
+ end
46
+ def factory(name)
47
+ factories[name] || raise(NoSuchFactoryException.new(name.to_s))
48
+ end
49
+
50
+ def factories
51
+ @factories ||= {}
52
+ end
53
+
54
+ def create_instance(class_name, attributes)
55
+ eval(expand_class(class_name)).new attributes
56
+ end
57
+ def expand_class(class_name)
58
+ return @default_module + class_name if @default_module == '::'
59
+ [@default_module, class_name].join '::'
60
+ end
61
+ end
62
+
63
+
64
+ class CreationDsl
65
+ def initialize(factory, context)
66
+ @factory = factory
67
+ @context = context
68
+ end
69
+ def self.create_factory(name,context, &definition)
70
+ f = Factory.new(name, context)
71
+ new(f,context).instance_eval(&definition)
72
+ return f
73
+ end
74
+ def method_missing(method, *args, &creation_block)
75
+ if block_given?
76
+ @factory.value_for(method, &creation_block)
77
+ else
78
+ @factory.value_for(method) { args.first }
79
+ end
80
+ end
81
+ def associate(factory_name)
82
+ @factory.value_for(factory_name) { @context.create(factory_name) }
83
+ end
84
+ end
85
+ class Factory
86
+ def initialize(name, context)
87
+ @context = context
88
+ @name = name
89
+ @attributes = {}
90
+ @counter = 0
91
+ end
92
+ def value_for(attribute, &block)
93
+ @attributes[attribute] = block;
94
+ end
95
+ def build
96
+ @context.create_instance class_name, Hash[ @attributes.to_a.collect{|attribute| [attribute[0], attribute[1].call(count)]} ]
97
+ end
98
+ private
99
+ def count
100
+ @counter+=1
101
+ end
102
+ def class_name
103
+ camelize(@name)
104
+ end
105
+ def camelize(str)
106
+ str.to_s.split("_").map {|w| w.capitalize}.join
107
+ end
108
+ end
109
+ class NoSuchFactoryException < Exception; end
110
+ class NotSupportedException < Exception; end
111
+ end
@@ -0,0 +1,35 @@
1
+ module Qcourses
2
+ module Configuration
3
+ class Error < Exception; end
4
+ class Instance
5
+ attr_accessor :local_root, :views
6
+ attr_writer :root
7
+ def initialize
8
+ @views = 'views'
9
+ end
10
+ def configure(&block)
11
+ self.views= "views/qcourses"
12
+ yield(self)
13
+ end
14
+ def root
15
+ raise Error.new("please configure root first") if @root.nil?
16
+ @root
17
+ end
18
+ end
19
+ def self.new
20
+ Instance.new
21
+ end
22
+ def root
23
+ @@instance.root
24
+ end
25
+ def local_root
26
+ @@instance.local_root
27
+ end
28
+ def views
29
+ @@instance.views
30
+ end
31
+ def self.instance
32
+ @@instance ||= new
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,20 @@
1
+ require 'sinatra/base'
2
+ require 'qcourses/resource_paths'
3
+ require 'haml'
4
+ module Qcourses
5
+ class BaseController < Sinatra::Application
6
+ include ResourcePaths
7
+ include Configuration
8
+ set :root, Proc.new { Qcourses.config.root }
9
+ set :public_folder, File.expand_path('../../../public/', File.dirname(__FILE__))
10
+ resource_admin_on '/admin'
11
+
12
+ helpers do
13
+ include ViewHelpers
14
+ include Renderers
15
+ def javascript(javascript_name)
16
+ haml "%script(src='/javascript/#{javascript_name}' type='text/javascript' )", :layout => false
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,16 @@
1
+ module Qcourses
2
+ class CoursesController < BaseController
3
+ resource '/courses'
4
+
5
+
6
+ get request_url do
7
+ begin
8
+ @courses = CourseRepository.all
9
+ haml :'trainings/index', layout: false
10
+ rescue Exception => e
11
+ haml :'trainings/error', layout: false, locals: { message: e.message }
12
+ end
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,54 @@
1
+ module Qcourses
2
+ class EventsController < BaseController
3
+ resource '/events'
4
+
5
+ get request_url do
6
+ begin
7
+ CourseRepository.all
8
+ @events = Event.all
9
+ haml :'events/index', layout: false
10
+ rescue Exception => e
11
+ haml :'trainings/error', locals: { message: e.message }
12
+ end
13
+ end
14
+
15
+ get admin_request_url do
16
+ begin
17
+ CourseRepository.all
18
+ @events = Event.all
19
+ haml :'events/index', layout: :admin
20
+ rescue Exception => e
21
+ haml :'trainings/error', locals: { message: e.message }
22
+ end
23
+ end
24
+
25
+ get admin_new_request_url do
26
+ begin
27
+ @event = Event.new
28
+ @events = Event.all
29
+ haml :'events/new', layout: :admin
30
+ rescue Exception => e
31
+ haml :'trainings/error', locals: { message: e.message }
32
+ end
33
+ end
34
+
35
+ post admin_request_url do
36
+ @event = Event.create(event_params)
37
+ redirect admin_new_request_url
38
+ end
39
+
40
+ private
41
+ def event_params
42
+ @event_params ||= filter_event_params
43
+ end
44
+ def filter_event_params
45
+ result = params[:event]
46
+ valid_parameters = ["course_id", "from", "to", "location"]
47
+ if (result[:location_id] && result[:location_id] != '0')
48
+ valid_parameters << "location_id"
49
+ valid_parameters.delete 'location'
50
+ end
51
+ result.select {|key| valid_parameters.include?(key)}
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,51 @@
1
+ module Qcourses
2
+ class RegistrationsController < BaseController
3
+ resource '/registrations'
4
+
5
+ get new_request_url(:event_id) do
6
+ @event = Event.opens(params[:event_id])
7
+ @employees = [Employee.new]
8
+ @company = Company.new
9
+ @event && haml(:'/registrations/new', layout: :registration) || error(404)
10
+ end
11
+
12
+ post request_url(:event_id) do
13
+ @event = Event.opens(params[:event_id])
14
+ return error(404) unless @event
15
+
16
+ @employees = participants_params.map {|participant_params| Employee.new(participant_params)}
17
+ @company = Company.new(company_params)
18
+
19
+
20
+ begin
21
+ Registration.db.transaction do
22
+ @company.save
23
+ @employees.each do |employee|
24
+ employee.company = @company
25
+ employee.save
26
+ Registration.create(event: @event, employee:employee)
27
+ end
28
+ end
29
+ redirect "/registrations/success"
30
+ rescue Sequel::ValidationFailed
31
+ haml :'/registrations/new', layout: :registration
32
+ end
33
+
34
+ end
35
+
36
+ get '/registrations/success' do
37
+ haml :'/registrations/success', layout: :registration
38
+ end
39
+
40
+ post '/registrations/add_participant/' do
41
+ haml :'/registrations/participant', :layout => false, locals: { employee: Employee.new }
42
+ end
43
+ private
44
+ def participants_params
45
+ params[:employee].map { |attributes| attributes.select {|key| ["email", "name"].include? key } }
46
+ end
47
+ def company_params
48
+ params[:company]
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,5 @@
1
+ require_relative 'controllers/base'
2
+ require_relative 'controllers/courses_controller'
3
+ require_relative 'controllers/events_controller'
4
+ require_relative 'controllers/registrations_controller'
5
+
@@ -0,0 +1,18 @@
1
+ require 'date'
2
+ class Date
3
+ def self.tomorrow
4
+ today.next
5
+ end
6
+
7
+ def tomorrow
8
+ self.next
9
+ end
10
+
11
+ def self.a_month_from_now
12
+ today.next_month
13
+ end
14
+
15
+ def as_date_string
16
+ to_date.to_s
17
+ end
18
+ end
@@ -0,0 +1,30 @@
1
+ module Qcourses
2
+ class Course
3
+ attr_reader :identification, :name, :subtitle
4
+ def initialize(attributes = {})
5
+ @identification = attributes[:identification]
6
+ @name = attributes[:name]
7
+ @subtitle = attributes[:subtitle]
8
+ end
9
+
10
+ def self.default_attributes(course_identification)
11
+ {identification: course_identification, name: course_identification, subtitle: ''}
12
+ end
13
+
14
+ def inspect
15
+ "Course<#{identification}, #{name}>"
16
+ end
17
+
18
+ def ==(other)
19
+ other.is_a?(Course) && other.identification == identification
20
+ end
21
+
22
+ def eql?(other)
23
+ self == other
24
+ end
25
+
26
+ def hash
27
+ identification.hash
28
+ end
29
+ end
30
+ end