activity_notification 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +56 -0
  4. data/.rspec +3 -0
  5. data/.travis.yml +28 -0
  6. data/Gemfile +8 -0
  7. data/Gemfile.lock +174 -0
  8. data/MIT-LICENSE +20 -0
  9. data/README.md +437 -0
  10. data/Rakefile +19 -0
  11. data/activity_notification.gemspec +33 -0
  12. data/app/controllers/activity_notification/notifications_controller.rb +119 -0
  13. data/app/controllers/activity_notification/notifications_with_devise_controller.rb +29 -0
  14. data/app/mailers/activity_notification/mailer.rb +13 -0
  15. data/app/views/activity_notification/mailer/default/default.html.erb +7 -0
  16. data/app/views/activity_notification/notifications/default/_default.html.erb +36 -0
  17. data/app/views/activity_notification/notifications/default/_index.html.erb +9 -0
  18. data/app/views/activity_notification/notifications/default/destroy.js.erb +2 -0
  19. data/app/views/activity_notification/notifications/default/index.html.erb +17 -0
  20. data/app/views/activity_notification/notifications/default/open.js.erb +2 -0
  21. data/app/views/activity_notification/notifications/default/open_all.js.erb +2 -0
  22. data/app/views/activity_notification/notifications/default/show.html.erb +2 -0
  23. data/config/locales/en.yml +8 -0
  24. data/lib/activity_notification.rb +52 -0
  25. data/lib/activity_notification/apis/notification_api.rb +147 -0
  26. data/lib/activity_notification/common.rb +86 -0
  27. data/lib/activity_notification/config.rb +23 -0
  28. data/lib/activity_notification/controllers/store_controller.rb +30 -0
  29. data/lib/activity_notification/helpers/polymorphic_helpers.rb +32 -0
  30. data/lib/activity_notification/helpers/view_helpers.rb +108 -0
  31. data/lib/activity_notification/mailers/helpers.rb +97 -0
  32. data/lib/activity_notification/models/notifiable.rb +136 -0
  33. data/lib/activity_notification/models/notification.rb +50 -0
  34. data/lib/activity_notification/models/notifier.rb +11 -0
  35. data/lib/activity_notification/models/target.rb +104 -0
  36. data/lib/activity_notification/rails.rb +6 -0
  37. data/lib/activity_notification/rails/routes.rb +105 -0
  38. data/lib/activity_notification/renderable.rb +142 -0
  39. data/lib/activity_notification/roles/acts_as_notifiable.rb +37 -0
  40. data/lib/activity_notification/roles/acts_as_target.rb +30 -0
  41. data/lib/activity_notification/version.rb +3 -0
  42. data/lib/generators/activity_notification/controllers_generator.rb +44 -0
  43. data/lib/generators/activity_notification/install_generator.rb +45 -0
  44. data/lib/generators/activity_notification/migration/migration_generator.rb +17 -0
  45. data/lib/generators/activity_notification/notification/notification_generator.rb +17 -0
  46. data/lib/generators/activity_notification/views_generator.rb +44 -0
  47. data/lib/generators/templates/README +53 -0
  48. data/lib/generators/templates/active_record/migration.rb +18 -0
  49. data/lib/generators/templates/activity_notification.rb +18 -0
  50. data/lib/generators/templates/controllers/README +13 -0
  51. data/lib/generators/templates/controllers/notifications_controller.rb +66 -0
  52. data/lib/generators/templates/controllers/notifications_with_devise_controller.rb +74 -0
  53. data/lib/generators/templates/notification/notification.rb +3 -0
  54. data/lib/tasks/activity_notification_tasks.rake +4 -0
  55. data/spec/concerns/notification_api_spec.rb +531 -0
  56. data/spec/factories/articles.rb +5 -0
  57. data/spec/factories/comments.rb +6 -0
  58. data/spec/factories/notifications.rb +7 -0
  59. data/spec/factories/users.rb +5 -0
  60. data/spec/models/notification_spec.rb +259 -0
  61. data/spec/rails_app/Rakefile +6 -0
  62. data/spec/rails_app/app/controllers/application_controller.rb +5 -0
  63. data/spec/rails_app/app/controllers/concerns/.keep +0 -0
  64. data/spec/rails_app/app/helpers/application_helper.rb +2 -0
  65. data/spec/rails_app/app/mailers/.keep +0 -0
  66. data/spec/rails_app/app/models/.keep +0 -0
  67. data/spec/rails_app/app/models/article.rb +12 -0
  68. data/spec/rails_app/app/models/comment.rb +18 -0
  69. data/spec/rails_app/app/models/concerns/.keep +0 -0
  70. data/spec/rails_app/app/models/user.rb +8 -0
  71. data/spec/rails_app/app/views/layouts/application.html.erb +14 -0
  72. data/spec/rails_app/bin/bundle +3 -0
  73. data/spec/rails_app/bin/rails +4 -0
  74. data/spec/rails_app/bin/rake +4 -0
  75. data/spec/rails_app/bin/setup +29 -0
  76. data/spec/rails_app/config.ru +4 -0
  77. data/spec/rails_app/config/application.rb +20 -0
  78. data/spec/rails_app/config/boot.rb +5 -0
  79. data/spec/rails_app/config/database.yml +25 -0
  80. data/spec/rails_app/config/environment.rb +12 -0
  81. data/spec/rails_app/config/environments/development.rb +44 -0
  82. data/spec/rails_app/config/environments/production.rb +79 -0
  83. data/spec/rails_app/config/environments/test.rb +45 -0
  84. data/spec/rails_app/config/initializers/activity_notification.rb +18 -0
  85. data/spec/rails_app/config/initializers/assets.rb +11 -0
  86. data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  87. data/spec/rails_app/config/initializers/cookies_serializer.rb +3 -0
  88. data/spec/rails_app/config/initializers/devise.rb +274 -0
  89. data/spec/rails_app/config/initializers/filter_parameter_logging.rb +4 -0
  90. data/spec/rails_app/config/initializers/inflections.rb +16 -0
  91. data/spec/rails_app/config/initializers/mime_types.rb +4 -0
  92. data/spec/rails_app/config/initializers/session_store.rb +3 -0
  93. data/spec/rails_app/config/initializers/wrap_parameters.rb +14 -0
  94. data/spec/rails_app/config/routes.rb +5 -0
  95. data/spec/rails_app/config/secrets.yml +22 -0
  96. data/spec/rails_app/db/migrate/20160715050420_create_notifications.rb +18 -0
  97. data/spec/rails_app/db/migrate/20160715050433_create_test_tables.rb +36 -0
  98. data/spec/rails_app/db/schema.rb +73 -0
  99. data/spec/rails_app/public/404.html +67 -0
  100. data/spec/rails_app/public/422.html +67 -0
  101. data/spec/rails_app/public/500.html +66 -0
  102. data/spec/rails_app/public/favicon.ico +0 -0
  103. data/spec/spec_helper.rb +34 -0
  104. metadata +309 -0
@@ -0,0 +1,86 @@
1
+ module ActivityNotification
2
+
3
+ # Used to transform value from metadata to data.
4
+ # Accepts Symbols, which it will send against context.
5
+ # Accepts Procs, which it will execute with controller and context.
6
+ # Both Symbols and Procs will be passed arguments of this method.
7
+ def self.resolve_value(context, thing, *args)
8
+ case thing
9
+ when Symbol
10
+ begin
11
+ context.__send__(thing, *args)
12
+ rescue ArgumentError => e
13
+ context.__send__(thing)
14
+ end
15
+ when Proc
16
+ begin
17
+ thing.call(ActivityNotification.get_controller, context, *args)
18
+ rescue ArgumentError => e
19
+ thing.call(ActivityNotification.get_controller, context)
20
+ end
21
+ when Hash
22
+ thing.dup.tap do |hash|
23
+ hash.each do |key, value|
24
+ hash[key] = ActivityNotification.resolve_value(context, value, *args)
25
+ end
26
+ end
27
+ else
28
+ thing
29
+ end
30
+ end
31
+
32
+ module Common
33
+
34
+ # Used to transform value from metadata to data which belongs model instance.
35
+ # Accepts Symbols, which it will send against this instance.
36
+ # Accepts Procs, which it will execute with this instance.
37
+ # Both Symbols and Procs will be passed arguments of this method.
38
+ def resolve_value(thing, *args)
39
+ case thing
40
+ when Symbol
41
+ begin
42
+ __send__(thing, *args)
43
+ rescue ArgumentError => e
44
+ __send__(thing)
45
+ end
46
+ when Proc
47
+ begin
48
+ thing.call(self, *args)
49
+ rescue ArgumentError => e
50
+ thing.call(self)
51
+ end
52
+ when Hash
53
+ thing.dup.tap do |hash|
54
+ hash.each do |key, value|
55
+ hash[key] = resolve_value(value, *args)
56
+ end
57
+ end
58
+ else
59
+ thing
60
+ end
61
+ end
62
+
63
+ def to_class_name
64
+ self.class.name
65
+ end
66
+
67
+ def to_resource_name
68
+ self.class.name.demodulize.singularize.underscore
69
+ end
70
+
71
+ def to_resources_name
72
+ self.class.name.demodulize.pluralize.underscore
73
+ end
74
+
75
+ #TODO Is it the best solution?
76
+ def printable_type
77
+ "#{self.class.name.demodulize.humanize}"
78
+ end
79
+
80
+ #TODO Is it the best solution?
81
+ def printable_name
82
+ "#{self.printable_type} (#{id})"
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,23 @@
1
+ module ActivityNotification
2
+ # Class used to initialize configuration object.
3
+ class Config
4
+ attr_accessor :enabled,
5
+ :table_name,
6
+ :mailer_sender,
7
+ :mailer,
8
+ :parent_mailer,
9
+ :parent_controller,
10
+ :opened_limit
11
+
12
+ def initialize
13
+ @enabled = true
14
+ @table_name = "notifications"
15
+ @mailer_sender = nil
16
+ @mailer = "ActivityNotification::Mailer"
17
+ @parent_mailer = 'ActionMailer::Base'
18
+ @parent_controller = 'ApplicationController'
19
+ @opened_limit = 10
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,30 @@
1
+ module ActivityNotification
2
+ class << self
3
+ # Setter for remembering controller instance
4
+ def set_controller(controller)
5
+ Thread.current[:activity_notification_controller] = controller
6
+ end
7
+
8
+ # Getter for accessing the controller instance
9
+ def get_controller
10
+ Thread.current[:activity_notification_controller]
11
+ end
12
+ end
13
+
14
+ # Module included in controllers to allow p_a access to controller instance
15
+ module StoreController
16
+ extend ActiveSupport::Concern
17
+
18
+ included do
19
+ around_action :store_controller_for_activity_notification if respond_to?(:around_action)
20
+ around_filter :store_controller_for_activity_notification unless respond_to?(:around_action)
21
+ end
22
+
23
+ def store_controller_for_activity_notification
24
+ ActivityNotification.set_controller(self)
25
+ yield
26
+ ensure
27
+ ActivityNotification.set_controller(nil)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,32 @@
1
+ module ActivityNotification
2
+ module PolymorphicHelpers
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class ::String
7
+ def to_model_name
8
+ singularize.camelize
9
+ end
10
+
11
+ def to_model_class
12
+ to_model_name.classify.constantize
13
+ end
14
+
15
+ def to_resource_name
16
+ singularize.underscore
17
+ end
18
+
19
+ def to_resources_name
20
+ pluralize.underscore
21
+ end
22
+
23
+ def to_boolean(default = nil)
24
+ return true if ['true', '1', 'yes', 'on', 't'].include? self
25
+ return false if ['false', '0', 'no', 'off', 'f'].include? self
26
+ return default
27
+ end
28
+ end
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,108 @@
1
+ # Provides a shortcut from views to the rendering method.
2
+ module ActivityNotification
3
+ # Module extending ActionView::Base and adding `render_notification` helper.
4
+ module ViewHelpers
5
+ # View helper for rendering an notification, calls {ActivityNotification::Notification#render} internally.
6
+ def render_notification notifications, options = {}
7
+ if notifications.is_a? ActivityNotification::Notification
8
+ notifications.render self, options
9
+ elsif notifications.respond_to?(:map)
10
+ return nil if notifications.empty?
11
+ notifications.map {|notification| notification.render self, options.dup }.join.html_safe
12
+ end
13
+ end
14
+ alias_method :render_notifications, :render_notification
15
+
16
+ # View helper for rendering embedded partial template of target's notifications
17
+ def render_notification_of target, options = {}
18
+ return unless target.is_a? ActivityNotification::Target
19
+
20
+ partial_path = options.delete(:partial) || "index"
21
+ partial_root = options[:partial_root] ||
22
+ "activity_notification/notifications/#{target.to_resources_name}"
23
+ partial = select_path(partial_path, partial_root)
24
+ layout = options[:layout].present? ?
25
+ select_path(options.delete(:layout), (options[:layout_root] || "layouts")) :
26
+ nil
27
+ locals = (options[:locals] || {}).merge(target: target)
28
+
29
+ # Prepare content for notifications index
30
+ notification_options = options.merge\
31
+ target: target.to_resources_name,
32
+ partial: options[:notification_partial],
33
+ layout: options[:notification_layout]
34
+ case options[:index_content]
35
+ when :simple
36
+ notification_index = target.notification_index
37
+ when :none
38
+ else
39
+ notification_index = target.notification_index_with_attributes
40
+ end
41
+
42
+ if notification_index.present?
43
+ content_for :notification_index do
44
+ begin
45
+ render_notifications notification_index, notification_options
46
+ rescue ActionView::MissingTemplate => e
47
+ notification_options.delete(:target)
48
+ render_notifications notification_index, notification_options
49
+ end
50
+ end
51
+ end
52
+
53
+ # Render partial index
54
+ begin
55
+ render options.merge(partial: partial, layout: layout, locals: locals)
56
+ rescue ActionView::MissingTemplate => e
57
+ partial_root = "activity_notification/notifications/default"
58
+ partial = select_path(partial_path, partial_root)
59
+ render options.merge(partial: partial, layout: layout, locals: locals)
60
+ end
61
+ end
62
+ alias_method :render_notifications_of, :render_notification_of
63
+
64
+ # Url helper methods
65
+ #TODO Is there any other better solution?
66
+ #TODO Must handle devise namespace
67
+ def notification_path_for(notification, params = {})
68
+ send("#{notification.target.to_resource_name}_notification_path", notification.target, notification, params)
69
+ end
70
+
71
+ def move_notification_path_for(notification, params = {})
72
+ send("move_#{notification.target.to_resource_name}_notification_path", notification.target, notification, params)
73
+ end
74
+
75
+ def open_notification_path_for(notification, params = {})
76
+ send("open_#{notification.target.to_resource_name}_notification_path", notification.target, notification, params)
77
+ end
78
+
79
+ def open_all_notifications_path_for(target, params = {})
80
+ send("open_all_#{target.to_resource_name}_notifications_path", target, params)
81
+ end
82
+
83
+ def notification_url_for(notification, params = {})
84
+ send("#{notification.target.to_resource_name}_notification_url", notification.target, notification, params)
85
+ end
86
+
87
+ def move_notification_url_for(notification, params = {})
88
+ send("move_#{notification.target.to_resource_name}_notification_url", notification.target, notification, params)
89
+ end
90
+
91
+ def open_notification_url_for(notification, params = {})
92
+ send("open_#{notification.target.to_resource_name}_notification_url", notification.target, notification, params)
93
+ end
94
+
95
+ def open_all_notifications_url_for(target, params = {})
96
+ send("open_all_#{target.to_resource_name}_notifications_url", target, params)
97
+ end
98
+
99
+ private
100
+
101
+ def select_path(path, root)
102
+ [root, path].map(&:to_s).join('/')
103
+ end
104
+
105
+ end
106
+
107
+ ActionView::Base.class_eval { include ViewHelpers }
108
+ end
@@ -0,0 +1,97 @@
1
+ module ActivityNotification
2
+ module Mailers
3
+ module Helpers
4
+ extend ActiveSupport::Concern
5
+
6
+ protected
7
+
8
+ # Configure default email options
9
+ def notification_mail(notification, options = {})
10
+ initialize_from_notification(notification)
11
+ headers = headers_for(notification.key, options)
12
+ begin
13
+ mail headers
14
+ rescue ActionView::MissingTemplate => e
15
+ mail headers.merge(template_name: 'default')
16
+ end
17
+ end
18
+
19
+ def initialize_from_notification(notification)
20
+ @notification, @target, @notifiable = notification, notification.target, notification.notifiable
21
+ end
22
+
23
+ def headers_for(key, options)
24
+ if @notifiable.respond_to?(:overriding_notification_email_key) and
25
+ @notifiable.overriding_notification_email_key(@target, key).present?
26
+ key = @notifiable.overriding_notification_email_key(@target, key)
27
+ end
28
+ headers = {
29
+ subject: subject_for(key),
30
+ to: mailer_to(@target),
31
+ from: mailer_sender(@target.to_resource_name),
32
+ reply_to: mailer_reply_to(@target.to_resource_name),
33
+ template_path: template_paths,
34
+ template_name: template_name(key)
35
+ }.merge(options)
36
+
37
+ @email = headers[:to]
38
+ headers
39
+ end
40
+
41
+ def mailer_to(target)
42
+ target.mailer_to
43
+ end
44
+
45
+ def mailer_reply_to(target_type)
46
+ mailer_sender(target_type, :reply_to)
47
+ end
48
+
49
+ def mailer_from(target_type)
50
+ mailer_sender(target_type, :from)
51
+ end
52
+
53
+ def mailer_sender(target_type, sender = :from)
54
+ default_sender = default_params[sender]
55
+ if default_sender.present?
56
+ default_sender.respond_to?(:to_proc) ? instance_eval(&default_sender) : default_sender
57
+ elsif ActivityNotification.config.mailer_sender.is_a?(Proc)
58
+ ActivityNotification.config.mailer_sender.call(target_type)
59
+ else
60
+ ActivityNotification.config.mailer_sender
61
+ end
62
+ end
63
+
64
+ def template_paths
65
+ paths = ['activity_notification/mailer/default']
66
+ paths.unshift("activity_notification/mailer/#{@target.to_resources_name}") if @target.present?
67
+ paths
68
+ end
69
+
70
+ def template_name(key)
71
+ key.gsub('.', '/')
72
+ end
73
+
74
+
75
+ # Set up a subject doing an I18n lookup.
76
+ # At first, it attempts to set a subject based on the current mapping:
77
+ # en:
78
+ # notification:
79
+ # {target}:
80
+ # {key}:
81
+ # mail_subject: '...'
82
+ #
83
+ # If one does not exist, it fallbacks to default:
84
+ # Notification for #{notification.printable_type}
85
+ #
86
+ def subject_for(key)
87
+ k = key.split('.')
88
+ k.unshift('notification') if k.first != 'notification'
89
+ k.insert(1, @target.to_resource_name)
90
+ k = k.join('.')
91
+ I18n.t(:mail_subject, scope: k,
92
+ default: ["Notification of #{@notifiable.printable_type}"])
93
+ end
94
+
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,136 @@
1
+ module ActivityNotification
2
+ module Notifiable
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ include Common
7
+ include ActsAsNotifiable
8
+ include ActionDispatch::Routing::PolymorphicRoutes
9
+ include Rails.application.routes.url_helpers
10
+ class_attribute :_notification_targets,
11
+ :_notification_group,
12
+ :_notifier,
13
+ :_notification_parameters,
14
+ :_notification_email_allowed,
15
+ :_notifiable_path
16
+ set_notifiable_class_defaults
17
+ end
18
+
19
+ def default_url_options
20
+ Rails.application.routes.default_url_options
21
+ end
22
+
23
+ class_methods do
24
+ def set_notifiable_class_defaults
25
+ self._notification_targets = {}
26
+ self._notification_group = {}
27
+ self._notifier = {}
28
+ self._notification_parameters = {}
29
+ self._notification_email_allowed = {}
30
+ self._notifiable_path = {}
31
+ end
32
+ end
33
+
34
+ # Methods to be overriden
35
+
36
+ def notification_targets(target_type, key)
37
+ plural_target_type = target_type.to_s.underscore.pluralize
38
+ plural_target_type_sym = plural_target_type.to_sym
39
+ target_typed_method_name = "notification_#{plural_target_type}"
40
+ if respond_to?(target_typed_method_name)
41
+ send(target_typed_method_name, key)
42
+ else
43
+ if _notification_targets[plural_target_type_sym]
44
+ resolve_value(_notification_targets[plural_target_type_sym], key)
45
+ else
46
+ raise NotImplementedError, "You have to implement #{self.class}##{target_typed_method_name} or set :targets in acts_as_notifiable"
47
+ end
48
+ end
49
+ end
50
+
51
+ def notification_group(target_type, key)
52
+ plural_target_type = target_type.to_s.underscore.pluralize
53
+ plural_target_type_sym = plural_target_type.to_sym
54
+ target_typed_method_name = "notification_group_for_#{plural_target_type}"
55
+ if respond_to?(target_typed_method_name)
56
+ send(target_typed_method_name, key)
57
+ else
58
+ resolve_value(_notification_group[plural_target_type_sym])
59
+ end
60
+ end
61
+
62
+ def notification_parameters(target_type, key)
63
+ plural_target_type = target_type.to_s.underscore.pluralize
64
+ plural_target_type_sym = plural_target_type.to_sym
65
+ target_typed_method_name = "notification_parameters_for_#{plural_target_type}"
66
+ if respond_to?(target_typed_method_name)
67
+ send(target_typed_method_name, key)
68
+ else
69
+ resolve_value(_notification_parameters[plural_target_type_sym], key) || {}
70
+ end
71
+ end
72
+
73
+ def notifier(target_type, key)
74
+ plural_target_type = target_type.to_s.underscore.pluralize
75
+ plural_target_type_sym = plural_target_type.to_sym
76
+ target_typed_method_name = "notifier_for_#{plural_target_type}"
77
+ if respond_to?(target_typed_method_name)
78
+ send(target_typed_method_name, key)
79
+ else
80
+ resolve_value(_notifier[plural_target_type_sym])
81
+ end
82
+ end
83
+
84
+ def notification_email_allowed?(target, key)
85
+ plural_target_type_sym = target.to_resources_name.to_sym
86
+ if _notification_email_allowed[plural_target_type_sym]
87
+ resolve_value(_notification_email_allowed[plural_target_type_sym], target, key)
88
+ else
89
+ true
90
+ end
91
+ end
92
+
93
+ def notifiable_path(target_type, key = nil)
94
+ plural_target_type = target_type.to_s.underscore.pluralize
95
+ plural_target_type_sym = plural_target_type.to_sym
96
+ target_typed_method_name = "notifiable_path_for_#{plural_target_type}"
97
+ if respond_to?(target_typed_method_name)
98
+ send(target_typed_method_name, key)
99
+ elsif _notifiable_path[plural_target_type_sym]
100
+ resolve_value(_notifiable_path[plural_target_type_sym])
101
+ else
102
+ begin
103
+ polymorphic_path(self)
104
+ rescue NoMethodError => e
105
+ raise NotImplementedError, "You have to implement #{self.class}##{__method__}, set :notifiable_path in acts_as_notifiable or set polymorphic_path routing for #{self.class}"
106
+ rescue ActionController::UrlGenerationError => e
107
+ raise NotImplementedError, "You have to implement #{self.class}##{__method__}, set :notifiable_path in acts_as_notifiable or set polymorphic_path routing for #{self.class}"
108
+ end
109
+ end
110
+ end
111
+
112
+ # TODO docs
113
+ # overriding_notification_render_key(target, key)
114
+
115
+ # TODO docs
116
+ # overriding_notification_email_key(target, key)
117
+
118
+ # Wrapper methods of SimpleNotify class methods
119
+
120
+ def notify(target_type, options = {})
121
+ Notification.notify(target_type, self, options)
122
+ end
123
+
124
+ def notify_to(target, options = {})
125
+ Notification.notify_to(target, self, options)
126
+ end
127
+
128
+ def notify_all(targets, options = {})
129
+ Notification.notify_all(targets, self, options)
130
+ end
131
+
132
+ def default_notification_key
133
+ "#{to_resource_name}.default"
134
+ end
135
+ end
136
+ end