cm-admin 0.2.0 → 0.3.0

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.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +12 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +6 -0
  5. data/Gemfile +6 -1
  6. data/Gemfile.lock +118 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +6 -40
  9. data/app/assets/images/logo.png +0 -0
  10. data/{lib/generators/cm_admin/templates/assets/stylesheets → app/assets/stylesheets/cm_admin}/base/auth.scss +16 -6
  11. data/app/assets/stylesheets/cm_admin/base/common.scss +237 -0
  12. data/app/assets/stylesheets/cm_admin/base/filters.scss +199 -0
  13. data/app/assets/stylesheets/cm_admin/base/form.scss +262 -0
  14. data/app/assets/stylesheets/cm_admin/base/main-nav.scss +47 -0
  15. data/app/assets/stylesheets/cm_admin/base/navbar.scss +67 -0
  16. data/app/assets/stylesheets/cm_admin/base/quicksearch.scss +69 -0
  17. data/app/assets/stylesheets/cm_admin/base/scaffold.scss +61 -0
  18. data/app/assets/stylesheets/cm_admin/base/show.scss +54 -0
  19. data/app/assets/stylesheets/cm_admin/base/sidebar.scss +225 -0
  20. data/app/assets/stylesheets/cm_admin/base/table.scss +303 -0
  21. data/app/assets/stylesheets/cm_admin/base/tabs.scss +26 -0
  22. data/app/assets/stylesheets/cm_admin/cm_admin.css.scss +26 -0
  23. data/app/assets/stylesheets/cm_admin/components/_alerts.scss +101 -0
  24. data/app/assets/stylesheets/cm_admin/components/_buttons.scss +135 -0
  25. data/app/assets/stylesheets/cm_admin/components/_dropdown-popup.scss +141 -0
  26. data/app/assets/stylesheets/cm_admin/components/_input.scss +274 -0
  27. data/app/assets/stylesheets/cm_admin/components/_modal.scss +34 -0
  28. data/app/assets/stylesheets/cm_admin/components/_range.scss +20 -0
  29. data/app/assets/stylesheets/cm_admin/components/_status-tag.scss +67 -0
  30. data/app/assets/stylesheets/cm_admin/components/index.scss +7 -0
  31. data/app/assets/stylesheets/cm_admin/dependency/bootstrap.min.css +7 -0
  32. data/app/assets/stylesheets/cm_admin/helpers/_mixins.scss +20 -0
  33. data/app/assets/stylesheets/cm_admin/helpers/_variable.scss +86 -0
  34. data/app/assets/stylesheets/cm_admin/helpers/index.scss +2 -0
  35. data/app/controllers/cm_admin/application_controller.rb +6 -0
  36. data/app/controllers/cm_admin/exports_controller.rb +16 -0
  37. data/app/controllers/cm_admin/main_controller.rb +8 -0
  38. data/app/helpers/cm_admin/application_helper.rb +11 -0
  39. data/app/javascript/packs/cm_admin/application.js +9 -0
  40. data/app/javascript/packs/cm_admin/filters.js +32 -0
  41. data/app/javascript/packs/cm_admin/scaffolds.js +14 -0
  42. data/app/views/cm_admin/main/_cm_pagy_nav.html.slim +23 -0
  43. data/app/views/cm_admin/main/_filters.html.slim +10 -0
  44. data/app/views/cm_admin/main/_table.html.slim +59 -0
  45. data/app/views/cm_admin/main/_top_navbar.html.slim +25 -0
  46. data/app/views/cm_admin/main/dashboard.html.slim +1 -0
  47. data/app/views/cm_admin/main/edit.html.slim +19 -0
  48. data/app/views/cm_admin/main/index.html.slim +9 -0
  49. data/app/views/cm_admin/main/new.html.slim +22 -0
  50. data/app/views/cm_admin/main/show.html.slim +13 -0
  51. data/app/views/layouts/_left_sidebar_nav.html.slim +30 -0
  52. data/app/views/layouts/cm_admin.html.slim +18 -0
  53. data/bin/console +1 -0
  54. data/bin/webpack +18 -0
  55. data/bin/webpack-dev-server +18 -0
  56. data/cm_admin.gemspec +20 -31
  57. data/{lib → config}/.DS_Store +0 -0
  58. data/config/initializers/active_record_extension.rb +9 -0
  59. data/config/routes.rb +18 -0
  60. data/config/webpack/development.js +5 -0
  61. data/config/webpack/environment.js +12 -0
  62. data/config/webpack/production.js +5 -0
  63. data/config/webpack/test.js +5 -0
  64. data/config/webpacker.yml +92 -0
  65. data/lib/c.png +0 -0
  66. data/lib/cm_admin.rb +31 -2
  67. data/lib/cm_admin/constants.rb +33 -0
  68. data/lib/cm_admin/engine.rb +38 -0
  69. data/lib/cm_admin/model.rb +262 -0
  70. data/lib/cm_admin/models/action.rb +22 -0
  71. data/lib/cm_admin/models/actions/blocks.rb +25 -0
  72. data/lib/cm_admin/models/blocks.rb +19 -0
  73. data/lib/cm_admin/models/column.rb +16 -0
  74. data/lib/cm_admin/models/export.rb +43 -0
  75. data/lib/cm_admin/models/field.rb +14 -0
  76. data/lib/cm_admin/models/filter.rb +30 -0
  77. data/lib/cm_admin/utils.rb +67 -0
  78. data/lib/cm_admin/version.rb +1 -1
  79. data/lib/cm_admin/view_helpers.rb +72 -0
  80. data/lib/cm_admin/view_helpers/field_column_helper.rb +23 -0
  81. data/lib/cm_admin/view_helpers/form_field_helper.rb +16 -0
  82. data/lib/cm_admin/view_helpers/form_helper.rb +55 -0
  83. data/lib/cm_admin/view_helpers/navigation_helper.rb +18 -0
  84. data/lib/cm_admin/view_helpers/page_info_helper.rb +21 -0
  85. data/lib/generators/cm_admin/install_generator.rb +10 -32
  86. data/lib/generators/cm_admin/templates/cm_admin_initializer.rb +4 -0
  87. data/lib/tasks/webpack_install.rake +63 -0
  88. data/package.json +18 -0
  89. data/postcss.config.js +12 -0
  90. data/yarn-error.log +44 -0
  91. data/yarn.lock +6903 -0
  92. metadata +123 -48
  93. data/CODE_OF_CONDUCT.md +0 -74
  94. data/lib/generators/cm_admin/USAGE +0 -8
  95. data/lib/generators/cm_admin/templates/assets/images/cm.png +0 -0
  96. data/lib/generators/cm_admin/templates/assets/stylesheets/application.css.scss +0 -33
  97. data/lib/generators/cm_admin/templates/assets/stylesheets/base/_variable.scss +0 -14
  98. data/lib/generators/cm_admin/templates/assets/stylesheets/base/form.scss +0 -125
  99. data/lib/generators/cm_admin/templates/assets/stylesheets/base/input-styles.scss +0 -72
  100. data/lib/generators/cm_admin/templates/assets/stylesheets/base/main-nav.scss +0 -79
  101. data/lib/generators/cm_admin/templates/assets/stylesheets/base/scaffold.scss +0 -95
  102. data/lib/generators/cm_admin/templates/assets/stylesheets/base/show.scss +0 -88
  103. data/lib/generators/cm_admin/templates/assets/stylesheets/base/sidebar.scss +0 -69
  104. data/lib/generators/cm_admin/templates/assets/stylesheets/base/table.scss +0 -246
  105. data/lib/generators/cm_admin/templates/layouts/_navbar.html.slim +0 -8
  106. data/lib/generators/cm_admin/templates/layouts/_side_navbar.html.slim +0 -12
  107. data/lib/generators/cm_admin/templates/layouts/application.html.slim +0 -20
  108. data/lib/generators/cm_admin/templates/layouts/initializer.rb +0 -2
  109. data/lib/generators/cm_admin/templates/views/_form.erb +0 -67
  110. data/lib/generators/cm_admin/templates/views/_table.erb +0 -22
  111. data/lib/generators/cm_admin/templates/views/edit.erb +0 -10
  112. data/lib/generators/cm_admin/templates/views/index.erb +0 -11
  113. data/lib/generators/cm_admin/templates/views/new.erb +0 -10
  114. data/lib/generators/cm_admin/templates/views/reset_password.erb +0 -12
  115. data/lib/generators/cm_admin/templates/views/show.erb +0 -18
  116. data/lib/generators/cm_admin/templates/views/sign_in.erb +0 -18
  117. data/lib/generators/cm_admin/view_generator.rb +0 -78
@@ -0,0 +1,33 @@
1
+ module CmAdmin
2
+ DEFAULT_ACTIONS = {
3
+ index: {
4
+ verb: :get,
5
+ path: '/'
6
+ },
7
+ new: {
8
+ verb: :get,
9
+ path: 'new'
10
+ },
11
+ show: {
12
+ verb: :get,
13
+ path: ':id'
14
+ },
15
+ create: {
16
+ verb: :post,
17
+ path: '/'
18
+ },
19
+ edit: {
20
+ verb: :get,
21
+ path: ':id/edit'
22
+ },
23
+ update: {
24
+ verb: :patch,
25
+ path: ':id'
26
+ },
27
+ destroy: {
28
+ verb: :delete,
29
+ path: ':id'
30
+ }
31
+ }
32
+ REJECTABLE_FIELDS = %w(id created_at updated_at)
33
+ end
@@ -0,0 +1,38 @@
1
+ require 'rails'
2
+ module CmAdmin
3
+ class Engine < Rails::Engine
4
+ isolate_namespace CmAdmin
5
+
6
+ config.app_middleware.use(
7
+ Rack::Static,
8
+ # note! this varies from the webpacker/engine documentation
9
+ urls: ["/cm-admin-packs"], root: CmAdmin::Engine.root.join("public")
10
+ )
11
+
12
+ initializer 'RailsAdmin precompile hook', group: :all do |app|
13
+ app.config.assets.precompile += %w(
14
+ cm_admin/cm_admin.css
15
+ )
16
+ end
17
+
18
+ initializer "webpacker.proxy" do |app|
19
+ insert_middleware = begin
20
+ CmAdmin.webpacker.config.dev_server.present?
21
+ rescue
22
+ nil
23
+ end
24
+ next unless insert_middleware
25
+
26
+ app.middleware.insert_before(
27
+ 0, Webpacker::DevServerProxy, # "Webpacker::DevServerProxy" if Rails version < 5
28
+ ssl_verify_none: true,
29
+ webpacker: CmAdmin.webpacker
30
+ )
31
+ end
32
+
33
+ def mount_path
34
+ CmAdmin::Engine.routes.find_script_name({})
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,262 @@
1
+ require_relative 'constants'
2
+ require_relative 'models/action'
3
+ require_relative 'models/field'
4
+ require_relative 'models/blocks'
5
+ require_relative 'models/column'
6
+ require_relative 'models/filter'
7
+ require_relative 'models/export'
8
+ require 'pagy'
9
+ require 'axlsx'
10
+
11
+
12
+ module CmAdmin
13
+ class Model
14
+ include Pagy::Backend
15
+ include Models::Blocks
16
+ attr_accessor :available_actions, :actions_set, :available_fields, :permitted_fields, :current_action, :params, :filters
17
+ attr_reader :name, :ar_model
18
+
19
+ # Class variable for storing all actions
20
+ # CmAdmin::Model.all_actions
21
+ singleton_class.send(:attr_accessor, :all_actions)
22
+
23
+ def initialize(entity, &block)
24
+ @name = entity.name
25
+ @ar_model = entity
26
+ @available_actions ||= []
27
+ @current_action = nil
28
+ @available_fields ||= {index: [], show: [], edit: [], new: []}
29
+ @params = nil
30
+ @filters ||= []
31
+ instance_eval(&block) if block_given?
32
+ actions unless @actions_set
33
+ $available_actions = @available_actions.dup
34
+ self.class.all_actions.push(@available_actions)
35
+ define_controller
36
+ end
37
+
38
+ class << self
39
+ def all_actions
40
+ @all_actions || []
41
+ end
42
+ end
43
+
44
+ # Insert into actions according to config block
45
+ def actions(only: [], except: [])
46
+ acts = CmAdmin::DEFAULT_ACTIONS.keys
47
+ acts = acts & (Array.new << only).flatten if only.present?
48
+ acts = acts - (Array.new << except).flatten if except.present?
49
+ acts.each do |act|
50
+ action_defaults = CmAdmin::DEFAULT_ACTIONS[act]
51
+ @available_actions << CmAdmin::Models::Action.new(name: act.to_s, verb: action_defaults[:verb], path: action_defaults[:path])
52
+ end
53
+ @actions_set = true
54
+ end
55
+
56
+ def cm_show(&block)
57
+ @current_action = CmAdmin::Models::Action.find_by(self, name: 'show')
58
+ puts "Top of the line"
59
+ yield
60
+ # action.instance_eval(&block)
61
+ puts "End of the line"
62
+ end
63
+
64
+ def cm_index(&block)
65
+ @current_action = CmAdmin::Models::Action.find_by(self, name: 'index')
66
+ yield
67
+ # action.instance_eval(&block)
68
+ end
69
+
70
+ def cm_edit(&block)
71
+ action = CmAdmin::Models::Action.find_by(self, name: 'edit')
72
+ action.instance_eval(&block)
73
+ end
74
+
75
+ def show(params)
76
+ @current_action = CmAdmin::Models::Action.find_by(self, name: 'show')
77
+ @ar_object = @ar_model.find(params[:id])
78
+ end
79
+
80
+ def index(params)
81
+ @current_action = CmAdmin::Models::Action.find_by(self, name: 'index')
82
+ # Based on the params the filter and pagination object to be set
83
+ @ar_object = filter_by(params, filter_params=filter_params(params))
84
+ end
85
+
86
+ def filter_by(params, filter_params={}, sort_params={})
87
+ filtered_result = OpenStruct.new
88
+ sort_column = "users.created_at"
89
+ sort_direction = %w[asc desc].include?(sort_params[:sort_direction]) ? sort_params[:sort_direction] : "asc"
90
+ sort_params = {sort_column: sort_column, sort_direction: sort_direction}
91
+ final_data = filtered_data(filter_params)
92
+ pagy, records = pagy(final_data)
93
+ filtered_result.data = records
94
+ filtered_result.pagy = pagy
95
+ # filtered_result.facets = paginate(page, raw_data.size)
96
+ # filtered_result.sort = sort_params
97
+ # filtered_result.facets.sort = sort_params
98
+ return filtered_result
99
+ end
100
+
101
+ def filtered_data(filter_params)
102
+ records = self.name.constantize.where(nil)
103
+ if filter_params
104
+ filter_params.each do |scope, scope_value|
105
+ records = self.send("cm_#{scope}", scope_value, records)
106
+ end
107
+ end
108
+ records
109
+ end
110
+
111
+ def cm_search(scope_value, records)
112
+ return nil if scope_value.blank?
113
+ table_name = records.table_name
114
+
115
+ @filters.select{|x| x if x.filter_type.eql?(:search)}.each do |filter|
116
+ terms = scope_value.downcase.split(/\s+/)
117
+ terms = terms.map { |e|
118
+ (e.gsub('*', '%').prepend('%') + '%').gsub(/%+/, '%')
119
+ }
120
+ sql = ""
121
+ filter.db_column_name.each.with_index do |column, i|
122
+ sql.concat("#{table_name}.#{column} ILIKE ?")
123
+ sql.concat(' OR ') unless filter.db_column_name.size.eql?(i+1)
124
+ end
125
+
126
+ records = records.where(
127
+ terms.map { |term|
128
+ sql
129
+ }.join(' AND '),
130
+ *terms.map { |e| [e] * filter.db_column_name.size }.flatten
131
+ )
132
+ end
133
+ records
134
+ end
135
+
136
+ def new(params)
137
+ @ar_object = @ar_model.new
138
+ end
139
+
140
+ def edit(params)
141
+ @ar_object = @ar_model.find(params[:id])
142
+ end
143
+
144
+ def update(params)
145
+ @ar_object = @ar_model.find(params[:id])
146
+ @ar_object.update(resource_params(params))
147
+ end
148
+
149
+ def create(params)
150
+ @ar_object = @ar_model.new(resource_params(params))
151
+ end
152
+
153
+ def resource_params(params)
154
+ permittable_fields = @permitted_fields || @ar_model.columns.map(&:name).reject { |i| CmAdmin::REJECTABLE_FIELDS.include?(i) }.map(&:to_sym)
155
+ params.require(self.name.underscore.to_sym).permit(*permittable_fields)
156
+ end
157
+
158
+ def page_title(title)
159
+ if @current_action
160
+ @current_action.page_title = title
161
+ end
162
+ end
163
+
164
+ def page_description(description)
165
+ if @current_action
166
+ @current_action.page_description = description
167
+ end
168
+ end
169
+
170
+ def field(field_name, options={})
171
+ puts "For printing field #{field_name}"
172
+ @available_fields[:show] << CmAdmin::Models::Field.new(field_name, options)
173
+ end
174
+
175
+ def column(field_name, options={})
176
+ unless @available_fields[:index].map{|x| x.db_column_name.to_sym}.include?(field_name)
177
+ puts "For printing column #{field_name}"
178
+ @available_fields[:index] << CmAdmin::Models::Column.new(field_name, options)
179
+ end
180
+ end
181
+
182
+ def all_db_columns(options={})
183
+ field_names = self.instance_variable_get(:@ar_model)&.columns&.map{|x| x.name.to_sym}
184
+ if options.include?(:exclude) && field_names
185
+ excluded_fields = (Array.new << options[:exclude]).flatten.map(&:to_sym)
186
+ field_names -= excluded_fields
187
+ end
188
+ field_names.each do |field_name|
189
+ column field_name
190
+ end
191
+ end
192
+
193
+ def self.find_by(search_hash)
194
+ CmAdmin.cm_admin_models.find { |x| x.name == search_hash[:name] }
195
+ end
196
+
197
+ # Custom actions
198
+ # eg
199
+ # class User < ApplicationRecord
200
+ # cm_admin do
201
+ # custom_action name: 'submit', verb: 'post', path: ':id/submit' do
202
+ # def user_submit
203
+ # Code for action here...
204
+ # end
205
+ # end
206
+ # end
207
+ # end
208
+ def custom_action(name: nil, verb: nil, layout: nil, partial: nil, path: nil, &block)
209
+ @available_actions << CmAdmin::Models::Action.new(name: name, verb: verb, layout: layout, partial: partial, path: path)
210
+ self.class.class_eval(&block)
211
+ end
212
+
213
+ def filter(db_column_name, filter_type, options={})
214
+ @filters << CmAdmin::Models::Filter.new(db_column_name: db_column_name, filter_type: filter_type, options: options)
215
+ end
216
+ private
217
+
218
+ # Controller defined for each model
219
+ # If model is User, controller will be UsersController
220
+ def define_controller
221
+ klass = Class.new(CmAdmin::ApplicationController) do
222
+
223
+ $available_actions.each do |action|
224
+ define_method action.name.to_sym do
225
+
226
+ # controller_name & action_name from ActionController
227
+ @model = CmAdmin::Model.find_by(name: controller_name.classify)
228
+ @model.params = params
229
+ @action = CmAdmin::Models::Action.find_by(@model, name: action_name)
230
+ @ar_object = @model.send(action_name, params)
231
+ respond_to do |format|
232
+ if %w(show index new edit).include?(action_name)
233
+ if request.xhr? && action_name.eql?('index')
234
+ format.html { render partial: '/cm_admin/main/table' }
235
+ else
236
+ format.html { render '/cm_admin/main/'+action_name }
237
+ end
238
+ elsif %w(create update destroy).include?(action_name)
239
+ if @ar_object.save
240
+ format.html { redirect_to CmAdmin::Engine.mount_path + "/#{@model.name.underscore.pluralize}" }
241
+ else
242
+ format.html { render '/cm_admin/main/new' }
243
+ end
244
+ elsif action.layout.present?
245
+ if action.partial.present?
246
+ render partial: action.partial, layout: action.layout
247
+ else
248
+ render layout: action.layout
249
+ end
250
+ end
251
+ end
252
+ end
253
+ end
254
+ end if $available_actions.present?
255
+ CmAdmin.const_set "#{@name}Controller", klass
256
+ end
257
+
258
+ def filter_params(params)
259
+ params.require(:filters).permit(:search) if params[:filters]
260
+ end
261
+ end
262
+ end
@@ -0,0 +1,22 @@
1
+ require_relative 'actions/blocks'
2
+
3
+ module CmAdmin
4
+ module Models
5
+ class Action
6
+ include Actions::Blocks
7
+ attr_accessor :name, :verb, :layout, :partial, :path, :page_title, :page_description
8
+
9
+ def initialize(attributes = {})
10
+ attributes.each do |key, value|
11
+ self.send("#{key.to_s}=", value)
12
+ end
13
+ end
14
+
15
+ class << self
16
+ def find_by(model, search_hash)
17
+ model.available_actions.find { |i| i.name == search_hash[:name] }
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,25 @@
1
+ module CmAdmin
2
+ module Models
3
+ module Actions
4
+ module Blocks
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ attr_accessor :title
9
+ end
10
+
11
+ def set_layout(name)
12
+ self.layout = name
13
+ end
14
+
15
+ def set_partial(name)
16
+ self.partial = name
17
+ end
18
+
19
+ def set_title(name)
20
+ self.title = name
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,19 @@
1
+ module CmAdmin
2
+ module Models
3
+ module Blocks
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ attr_accessor :menu, :title
8
+ end
9
+
10
+ def set_menu(state)
11
+ self.menu = state.to_s == 'on' ? true : false
12
+ end
13
+
14
+ def set_title(name)
15
+ self.title = name
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,16 @@
1
+ module CmAdmin
2
+ module Models
3
+ class Column
4
+ attr_accessor :db_column_name, :column_type, :header, :format, :prefix, :exportable
5
+
6
+ def initialize(db_column_name, attributes = {})
7
+ @db_column_name = db_column_name
8
+ attributes.each do |key, value|
9
+ self.send("#{key.to_s}=", value)
10
+ end
11
+ @exportable = true if attributes[:exportable].nil?
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,43 @@
1
+ module CmAdmin
2
+ module Models
3
+ class Export
4
+ class << self
5
+ def generate_excel(klass_name, columns = [])
6
+ klass = klass_name.constantize
7
+ records = get_records(klass, columns)
8
+ file_path = "#{Rails.root}/tmp/#{klass}_data_#{DateTime.now.strftime("%Y-%m-%d_%H-%M-%S")}.xlsx"
9
+ create_workbook(records, columns, file_path)
10
+ return file_path
11
+ end
12
+
13
+ def get_records(klass, columns)
14
+ records = klass
15
+ deserialized_columns = CmAdmin::Utils.deserialize_csv_columns(columns, :as_json_params)
16
+ # This includes isn't recursve, a full solution should be recursive
17
+ records.includes(deserialized_columns[:include].keys).find_each.as_json(deserialized_columns)
18
+ end
19
+
20
+ def create_workbook(records, class_name, file_path)
21
+ flattened_records = records.map { |record| CmAdmin::Utils.flatten_hash(record) }
22
+ columns = flattened_records.map{|x| x.keys}.flatten.uniq.sort
23
+ size_arr = []
24
+ columns.size.times { size_arr << 22 }
25
+ xl = Axlsx::Package.new
26
+ xl.workbook.add_worksheet do |sheet|
27
+ sheet.add_row columns&.map(&:titleize), b: true
28
+ flattened_records.each do |record|
29
+ sheet.add_row(columns.map { |column| record[column] })
30
+ end
31
+ sheet.column_widths(*size_arr)
32
+ end
33
+ xl.serialize(file_path)
34
+ end
35
+
36
+ def exportable_columns(klass)
37
+ klass.available_fields[:index].map{|x| x.exportable ? x.db_column_name : ""}.reject { |c| c.empty? }
38
+ end
39
+
40
+ end
41
+ end
42
+ end
43
+ end