seivan-generators 0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/CHANGELOG +156 -0
  2. data/Gemfile +2 -0
  3. data/LICENSE +23 -0
  4. data/README.rdoc +38 -0
  5. data/Rakefile +10 -0
  6. data/features/seivan_authentication.feature +52 -0
  7. data/features/seivan_config.feature +17 -0
  8. data/features/seivan_html5_haml.feature +47 -0
  9. data/features/step_definitions/common_steps.rb +44 -0
  10. data/features/step_definitions/rails_setup_steps.rb +6 -0
  11. data/features/support/env.rb +6 -0
  12. data/features/support/matchers.rb +7 -0
  13. data/lib/generators/seivan.rb +15 -0
  14. data/lib/generators/seivan/authentication/USAGE +50 -0
  15. data/lib/generators/seivan/authentication/authentication_generator.rb +123 -0
  16. data/lib/generators/seivan/authentication/templates/controller_authentication.rb +61 -0
  17. data/lib/generators/seivan/authentication/templates/fixtures.yml +24 -0
  18. data/lib/generators/seivan/authentication/templates/migration.rb +20 -0
  19. data/lib/generators/seivan/authentication/templates/sessions_controller.rb +45 -0
  20. data/lib/generators/seivan/authentication/templates/sessions_helper.rb +2 -0
  21. data/lib/generators/seivan/authentication/templates/tests/rspec/sessions_controller.rb +39 -0
  22. data/lib/generators/seivan/authentication/templates/tests/rspec/user.rb +83 -0
  23. data/lib/generators/seivan/authentication/templates/tests/rspec/users_controller.rb +56 -0
  24. data/lib/generators/seivan/authentication/templates/user.rb +42 -0
  25. data/lib/generators/seivan/authentication/templates/users_controller.rb +34 -0
  26. data/lib/generators/seivan/authentication/templates/users_helper.rb +2 -0
  27. data/lib/generators/seivan/config/USAGE +23 -0
  28. data/lib/generators/seivan/config/config_generator.rb +24 -0
  29. data/lib/generators/seivan/config/templates/config.yml +8 -0
  30. data/lib/generators/seivan/config/templates/load_config.rb +2 -0
  31. data/lib/generators/seivan/html5/USAGE +25 -0
  32. data/lib/generators/seivan/html5/html5_generator.rb +32 -0
  33. data/lib/generators/seivan/html5/templates/_errors.html.haml +7 -0
  34. data/lib/generators/seivan/html5/templates/_flashes.html.haml +4 -0
  35. data/lib/generators/seivan/html5/templates/_footer.html.haml +2 -0
  36. data/lib/generators/seivan/html5/templates/_head.html.haml +27 -0
  37. data/lib/generators/seivan/html5/templates/_header.html.haml +1 -0
  38. data/lib/generators/seivan/html5/templates/_javascripts.html.haml +16 -0
  39. data/lib/generators/seivan/html5/templates/_stylesheets.html.haml +8 -0
  40. data/lib/generators/seivan/html5/templates/application.html.haml +15 -0
  41. data/lib/generators/seivan/html5/templates/jquery.js +155 -0
  42. data/lib/generators/seivan/html5/templates/jquery_ujs.js +132 -0
  43. data/test/test_helper.rb +119 -0
  44. data/test/test_nifty_authentication_generator.rb +274 -0
  45. data/test/test_nifty_config_generator.rb +37 -0
  46. data/test/test_nifty_layout_generator.rb +42 -0
  47. metadata +199 -0
@@ -0,0 +1,132 @@
1
+ jQuery(function ($) {
2
+ var csrf_token = $('meta[name=csrf-token]').attr('content'),
3
+ csrf_param = $('meta[name=csrf-param]').attr('content');
4
+
5
+ $.fn.extend({
6
+ /**
7
+ * Triggers a custom event on an element and returns the event result
8
+ * this is used to get around not being able to ensure callbacks are placed
9
+ * at the end of the chain.
10
+ *
11
+ * TODO: deprecate with jQuery 1.4.2 release, in favor of subscribing to our
12
+ * own events and placing ourselves at the end of the chain.
13
+ */
14
+ triggerAndReturn: function (name, data) {
15
+ var event = new $.Event(name);
16
+ this.trigger(event, data);
17
+
18
+ return event.result !== false;
19
+ },
20
+
21
+ /**
22
+ * Handles execution of remote calls firing overridable events along the way
23
+ */
24
+ callRemote: function () {
25
+ var el = this,
26
+ method = el.attr('method') || el.attr('data-method') || 'GET',
27
+ url = el.attr('action') || el.attr('href'),
28
+ dataType = el.attr('data-type') || 'script';
29
+
30
+ if (url === undefined) {
31
+ throw "No URL specified for remote call (action or href must be present).";
32
+ } else {
33
+ if (el.triggerAndReturn('ajax:before')) {
34
+ var data = el.is('form') ? el.serializeArray() : [];
35
+ $.ajax({
36
+ url: url,
37
+ data: data,
38
+ dataType: dataType,
39
+ type: method.toUpperCase(),
40
+ beforeSend: function (xhr) {
41
+ el.trigger('ajax:loading', xhr);
42
+ },
43
+ success: function (data, status, xhr) {
44
+ el.trigger('ajax:success', [data, status, xhr]);
45
+ },
46
+ complete: function (xhr) {
47
+ el.trigger('ajax:complete', xhr);
48
+ },
49
+ error: function (xhr, status, error) {
50
+ el.trigger('ajax:failure', [xhr, status, error]);
51
+ }
52
+ });
53
+ }
54
+
55
+ el.trigger('ajax:after');
56
+ }
57
+ }
58
+ });
59
+
60
+ /**
61
+ * confirmation handler
62
+ */
63
+ $('a[data-confirm],input[data-confirm]').live('click', function () {
64
+ var el = $(this);
65
+ if (el.triggerAndReturn('confirm')) {
66
+ if (!confirm(el.attr('data-confirm'))) {
67
+ return false;
68
+ }
69
+ }
70
+ });
71
+
72
+
73
+ /**
74
+ * remote handlers
75
+ */
76
+ $('form[data-remote]').live('submit', function (e) {
77
+ $(this).callRemote();
78
+ e.preventDefault();
79
+ });
80
+
81
+ $('a[data-remote],input[data-remote]').live('click', function (e) {
82
+ $(this).callRemote();
83
+ e.preventDefault();
84
+ });
85
+
86
+ $('a[data-method]:not([data-remote])').live('click', function (e){
87
+ var link = $(this),
88
+ href = link.attr('href'),
89
+ method = link.attr('data-method'),
90
+ form = $('<form method="post" action="'+href+'"></form>'),
91
+ metadata_input = '<input name="_method" value="'+method+'" type="hidden" />';
92
+
93
+ if (csrf_param != null && csrf_token != null) {
94
+ metadata_input += '<input name="'+csrf_param+'" value="'+csrf_token+'" type="hidden" />';
95
+ }
96
+
97
+ form.hide()
98
+ .append(metadata_input)
99
+ .appendTo('body');
100
+
101
+ e.preventDefault();
102
+ form.submit();
103
+ });
104
+
105
+ /**
106
+ * disable-with handlers
107
+ */
108
+ var disable_with_input_selector = 'input[data-disable-with]';
109
+ var disable_with_form_remote_selector = 'form[data-remote]:has(' + disable_with_input_selector + ')';
110
+ var disable_with_form_not_remote_selector = 'form:not([data-remote]):has(' + disable_with_input_selector + ')';
111
+
112
+ var disable_with_input_function = function () {
113
+ $(this).find(disable_with_input_selector).each(function () {
114
+ var input = $(this);
115
+ input.data('enable-with', input.val())
116
+ .attr('value', input.attr('data-disable-with'))
117
+ .attr('disabled', 'disabled');
118
+ });
119
+ };
120
+
121
+ $(disable_with_form_remote_selector).live('ajax:before', disable_with_input_function);
122
+ $(disable_with_form_not_remote_selector).live('submit', disable_with_input_function);
123
+
124
+ $(disable_with_form_remote_selector).live('ajax:complete', function () {
125
+ $(this).find(disable_with_input_selector).each(function () {
126
+ var input = $(this);
127
+ input.removeAttr('disabled')
128
+ .val(input.data('enable-with'));
129
+ });
130
+ });
131
+
132
+ });
@@ -0,0 +1,119 @@
1
+ require 'test/unit'
2
+
3
+ # Must set before requiring generator libs.
4
+ TMP_ROOT = File.dirname(__FILE__) + "/tmp" unless defined?(TMP_ROOT)
5
+ PROJECT_NAME = "myproject" unless defined?(PROJECT_NAME)
6
+ app_root = File.join(TMP_ROOT, PROJECT_NAME)
7
+ if defined?(APP_ROOT)
8
+ APP_ROOT.replace(app_root)
9
+ else
10
+ APP_ROOT = app_root
11
+ end
12
+ if defined?(RAILS_ROOT)
13
+ RAILS_ROOT.replace(app_root)
14
+ else
15
+ RAILS_ROOT = app_root
16
+ end
17
+
18
+ require 'rubygems'
19
+ gem 'rails', '2.0.2' # getting a Rails.configuration error with 2.1
20
+ gem 'rubigen', '1.4'
21
+ gem 'shoulda', '2.0.6'
22
+ require 'rubigen' # gem install rubigen --version=1.4
23
+ require 'rubigen/helpers/generator_test_helper'
24
+ require 'rails_generator'
25
+ require 'shoulda' # gem install shoulda --version=2.0.6
26
+ require 'mocha'
27
+
28
+ module SeivanGenerators
29
+ module TestHelper
30
+ include RubiGen::GeneratorTestHelper
31
+
32
+ def setup
33
+ bare_setup
34
+ end
35
+
36
+ def teardown
37
+ bare_teardown
38
+ end
39
+
40
+ protected
41
+
42
+ def run_rails_generator(generator, *args)
43
+ options = args.pop if args.last.kind_of? Hash
44
+ options ||= {}
45
+ run_generator(generator.to_s, args, generator_sources, options.reverse_merge(:quiet => true))
46
+ end
47
+
48
+ def generator_sources
49
+ [RubiGen::PathSource.new(:test, File.join(File.dirname(__FILE__), "..", "rails_generators"))]
50
+ end
51
+ end
52
+
53
+ module ShouldaAdditions
54
+ def rails_generator(*args)
55
+ setup do
56
+ run_rails_generator(*args)
57
+ end
58
+ end
59
+
60
+ def should_generate_file(file, &block)
61
+ should "generate file #{file}" do
62
+ yield("foo") if block_given?
63
+ assert_generated_file(file)
64
+ end
65
+ end
66
+
67
+ def should_not_generate_file(file)
68
+ should "not generate file #{file}" do
69
+ assert !File.exists?("#{APP_ROOT}/#{file}"),"The file '#{file}' should not exist"
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ class Thoughtbot::Shoulda::Context
76
+ include SeivanGenerators::ShouldaAdditions
77
+ end
78
+
79
+ # Mock out what we need from AR::Base.
80
+ module ActiveRecord
81
+ class Base
82
+ class << self
83
+ attr_accessor :pluralize_table_names, :columns
84
+
85
+ def add_column(name, type = :string)
86
+ returning ActiveRecord::ConnectionAdapters::Column.new(name, nil) do |column|
87
+ column.type = type
88
+ @columns ||= []
89
+ @columns << column
90
+ end
91
+ end
92
+ end
93
+ self.pluralize_table_names = true
94
+ end
95
+
96
+ module ConnectionAdapters
97
+ class Column
98
+ attr_accessor :name, :default, :type, :limit, :null, :sql_type, :precision, :scale
99
+
100
+ def initialize(name, default, sql_type = nil)
101
+ @name = name
102
+ @default = default
103
+ @type = @sql_type = sql_type
104
+ end
105
+
106
+ def human_name
107
+ @name.humanize
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ # And what we need from ActionView
114
+ module ActionView
115
+ module Helpers
116
+ module ActiveRecordHelper; end
117
+ class InstanceTag; end
118
+ end
119
+ end
@@ -0,0 +1,274 @@
1
+ require File.join(File.dirname(__FILE__), "test_helper.rb")
2
+
3
+ class TestSeivanAuthenticationGenerator < Test::Unit::TestCase
4
+ include SeivanGenerators::TestHelper
5
+
6
+ # Some generator-related assertions:
7
+ # assert_generated_file(name, &block) # block passed the file contents
8
+ # assert_directory_exists(name)
9
+ # assert_generated_class(name, &block)
10
+ # assert_generated_module(name, &block)
11
+ # assert_generated_test_for(name, &block)
12
+ # The assert_generated_(class|module|test_for) &block is passed the body of the class/module within the file
13
+ # assert_has_method(body, *methods) # check that the body has a list of methods (methods with parentheses not supported yet)
14
+ #
15
+ # Other helper methods are:
16
+ # app_root_files - put this in teardown to show files generated by the test method (e.g. p app_root_files)
17
+ # bare_setup - place this in setup method to create the APP_ROOT folder for each test
18
+ # bare_teardown - place this in teardown method to destroy the TMP_ROOT or APP_ROOT folder after each test
19
+ context "" do # empty context so we can use setup block
20
+ setup do
21
+ Rails.stubs(:version).returns("2.0.2")
22
+ Dir.mkdir("#{RAILS_ROOT}/config") unless File.exists?("#{RAILS_ROOT}/config")
23
+ File.open("#{RAILS_ROOT}/config/routes.rb", 'w') do |f|
24
+ f.puts "ActionController::Routing::Routes.draw do |map|\n\nend"
25
+ end
26
+ Dir.mkdir("#{RAILS_ROOT}/app") unless File.exists?("#{RAILS_ROOT}/app")
27
+ Dir.mkdir("#{RAILS_ROOT}/app/controllers") unless File.exists?("#{RAILS_ROOT}/app/controllers")
28
+ File.open("#{RAILS_ROOT}/app/controllers/application.rb", 'w') do |f|
29
+ f.puts "class Application < ActionController::Base\n\nend"
30
+ end
31
+ end
32
+
33
+ teardown do
34
+ FileUtils.rm_rf "#{RAILS_ROOT}/config"
35
+ FileUtils.rm_rf "#{RAILS_ROOT}/app"
36
+ end
37
+
38
+ context "generator without arguments" do
39
+ rails_generator :nifty_authentication
40
+ should_generate_file 'app/models/user.rb'
41
+ should_generate_file 'app/controllers/users_controller.rb'
42
+ should_generate_file 'app/helpers/users_helper.rb'
43
+ should_generate_file 'app/views/users/new.html.erb'
44
+ should_generate_file 'app/controllers/sessions_controller.rb'
45
+ should_generate_file 'app/helpers/sessions_helper.rb'
46
+ should_generate_file 'app/views/sessions/new.html.erb'
47
+ should_generate_file 'lib/authentication.rb'
48
+ should_generate_file 'test/fixtures/users.yml'
49
+ should_generate_file 'test/unit/user_test.rb'
50
+ should_generate_file 'test/functional/users_controller_test.rb'
51
+ should_generate_file 'test/functional/sessions_controller_test.rb'
52
+
53
+ should "generate migration file" do
54
+ assert !Dir.glob("#{RAILS_ROOT}/db/migrate/*.rb").empty?
55
+ end
56
+
57
+ should "generate routes" do
58
+ assert_generated_file "config/routes.rb" do |body|
59
+ assert_match "map.resources :sessions", body
60
+ assert_match "map.resources :users", body
61
+ assert_match "map.login 'login', :controller => 'sessions', :action => 'new'", body
62
+ assert_match "map.logout 'logout', :controller => 'sessions', :action => 'destroy'", body
63
+ assert_match "map.signup 'signup', :controller => 'users', :action => 'new'", body
64
+ end
65
+ end
66
+
67
+ should "include Authentication" do
68
+ application_controller_name = Rails.version >= '2.3.0' ? 'application_controller' : 'application'
69
+ assert_generated_file "app/controllers/#{application_controller_name}.rb" do |body|
70
+ assert_match "include Authentication", body
71
+ end
72
+ end
73
+ end
74
+
75
+ context "generator with user and session names" do
76
+ rails_generator :nifty_authentication, "Account", "CurrentSession"
77
+ should_generate_file 'app/models/account.rb'
78
+ should_generate_file 'app/controllers/accounts_controller.rb'
79
+ should_generate_file 'app/helpers/accounts_helper.rb'
80
+ should_generate_file 'app/views/accounts/new.html.erb'
81
+ should_generate_file 'app/controllers/current_sessions_controller.rb'
82
+ should_generate_file 'app/helpers/current_sessions_helper.rb'
83
+ should_generate_file 'app/views/current_sessions/new.html.erb'
84
+ should_generate_file 'test/fixtures/accounts.yml'
85
+ should_generate_file 'test/unit/account_test.rb'
86
+ should_generate_file 'test/functional/accounts_controller_test.rb'
87
+ should_generate_file 'test/functional/current_sessions_controller_test.rb'
88
+
89
+ should "generate routes" do
90
+ assert_generated_file "config/routes.rb" do |body|
91
+ assert_match "map.resources :current_sessions", body
92
+ assert_match "map.resources :accounts", body
93
+ assert_match "map.login 'login', :controller => 'current_sessions', :action => 'new'", body
94
+ assert_match "map.logout 'logout', :controller => 'current_sessions', :action => 'destroy'", body
95
+ assert_match "map.signup 'signup', :controller => 'accounts', :action => 'new'", body
96
+ end
97
+ end
98
+ end
99
+
100
+ context "generator with shoulda option" do
101
+ rails_generator :nifty_authentication, :test_framework => :shoulda
102
+
103
+ should "have controller and model tests using shoulda syntax" do
104
+ assert_generated_file "test/functional/users_controller_test.rb" do |body|
105
+ assert_match " should ", body
106
+ end
107
+ assert_generated_file "test/functional/sessions_controller_test.rb" do |body|
108
+ assert_match " should ", body
109
+ end
110
+
111
+ assert_generated_file "test/unit/user_test.rb" do |body|
112
+ assert_match " should ", body
113
+ end
114
+ end
115
+ end
116
+
117
+ context "generator with rspec option" do
118
+ rails_generator :nifty_authentication, :test_framework => :rspec
119
+ should_generate_file 'spec/fixtures/users.yml'
120
+ end
121
+
122
+ context "with spec dir" do
123
+ setup do
124
+ Dir.mkdir("#{RAILS_ROOT}/spec") unless File.exists?("#{RAILS_ROOT}/spec")
125
+ end
126
+
127
+ teardown do
128
+ FileUtils.rm_rf "#{RAILS_ROOT}/spec"
129
+ end
130
+
131
+ context "generator without arguments" do
132
+ rails_generator :nifty_authentication
133
+ should_generate_file 'spec/fixtures/users.yml'
134
+ should_generate_file 'spec/models/user_spec.rb'
135
+ should_generate_file 'spec/controllers/users_controller_spec.rb'
136
+ should_generate_file 'spec/controllers/sessions_controller_spec.rb'
137
+ end
138
+
139
+ context "generator with user and session names" do
140
+ rails_generator :nifty_authentication, "Account", "CurrentSessions"
141
+ should_generate_file 'spec/fixtures/accounts.yml'
142
+ should_generate_file 'spec/models/account_spec.rb'
143
+ should_generate_file 'spec/controllers/accounts_controller_spec.rb'
144
+ should_generate_file 'spec/controllers/current_sessions_controller_spec.rb'
145
+ end
146
+
147
+ context "generator with testunit option" do
148
+ rails_generator :nifty_authentication, :test_framework => :testunit
149
+ should_generate_file 'test/fixtures/users.yml'
150
+ end
151
+ end
152
+
153
+ context "generator with haml option" do
154
+ rails_generator :nifty_authentication, :haml => true
155
+
156
+ should_generate_file "app/views/users/new.html.haml"
157
+ should_generate_file "app/views/sessions/new.html.haml"
158
+ end
159
+
160
+ context "generator with authlogic option and custom account name" do
161
+ rails_generator :nifty_authentication, "Account", :authlogic => true
162
+ should_generate_file 'app/models/account.rb'
163
+ should_generate_file 'app/controllers/accounts_controller.rb'
164
+ should_generate_file 'app/helpers/accounts_helper.rb'
165
+ should_generate_file 'app/views/accounts/new.html.erb'
166
+ should_generate_file 'app/controllers/account_sessions_controller.rb'
167
+ should_generate_file 'app/helpers/account_sessions_helper.rb'
168
+ should_generate_file 'app/views/account_sessions/new.html.erb'
169
+ should_generate_file 'test/fixtures/accounts.yml'
170
+ should_generate_file 'test/unit/account_test.rb'
171
+ should_generate_file 'test/functional/accounts_controller_test.rb'
172
+ should_generate_file 'test/functional/account_sessions_controller_test.rb'
173
+ should_generate_file 'lib/authentication.rb'
174
+
175
+ should "only include acts_as_authentic in account model" do
176
+ assert_generated_file "app/models/account.rb" do |body|
177
+ assert_match "acts_as_authentic", body
178
+ assert_no_match(/validates/, body)
179
+ assert_no_match(/def/, body)
180
+ end
181
+ end
182
+
183
+ should "should generate authentication module with current_account_session method" do
184
+ assert_generated_file "lib/authentication.rb" do |body|
185
+ assert_match "def current_account_session", body
186
+ end
187
+ end
188
+
189
+ should "should generate AccountSession model" do
190
+ assert_generated_file "app/models/account_session.rb" do |body|
191
+ assert_match "class AccountSession < Authlogic::Session::Base", body
192
+ end
193
+ end
194
+
195
+ should "should generate restful style actions in sessions controller" do
196
+ assert_generated_file "app/controllers/account_sessions_controller.rb" do |body|
197
+ assert_match "AccountSession.new", body
198
+ end
199
+ end
200
+
201
+ should "should generate form for account session" do
202
+ assert_generated_file "app/views/account_sessions/new.html.erb" do |body|
203
+ assert_match "form_for @account_session", body
204
+ end
205
+ end
206
+
207
+ should "should not include tests in account since authlogic is already tested" do
208
+ assert_generated_file "test/unit/account_test.rb" do |body|
209
+ assert_no_match(/def test/, body)
210
+ end
211
+ end
212
+
213
+ should "should handle session controller tests differently" do
214
+ assert_generated_file "test/functional/account_sessions_controller_test.rb" do |body|
215
+ assert_match "AccountSession.find", body
216
+ end
217
+ end
218
+
219
+ should "generate migration with authlogic columns" do
220
+ file = Dir.glob("#{RAILS_ROOT}/db/migrate/*.rb").first
221
+ assert file, "migration file doesn't exist"
222
+ assert_match(/[0-9]+_create_accounts.rb$/, file)
223
+ assert_generated_file "db/migrate/#{File.basename(file)}" do |body|
224
+ assert_match "class CreateAccounts", body
225
+ assert_match "t.string :username", body
226
+ assert_match "t.string :email", body
227
+ assert_match "t.string :crypted_password", body
228
+ assert_match "t.string :password_salt", body
229
+ assert_match "t.string :persistence_token", body
230
+ assert_match "t.timestamps", body
231
+ assert_no_match(/password_hash/, body)
232
+ end
233
+ end
234
+ end
235
+
236
+ context "generator with authlogic option and custom account and session name" do
237
+ rails_generator :nifty_authentication, "Account", "UserSession", :authlogic => true
238
+ should_generate_file 'app/controllers/user_sessions_controller.rb'
239
+ should_generate_file 'app/helpers/user_sessions_helper.rb'
240
+ should_generate_file 'app/views/user_sessions/new.html.erb'
241
+ should_generate_file 'test/functional/user_sessions_controller_test.rb'
242
+
243
+ should "should generate authentication module with current_user_session method" do
244
+ assert_generated_file "lib/authentication.rb" do |body|
245
+ assert_match "def current_user_session", body
246
+ end
247
+ end
248
+
249
+ should "should generate UserSession model" do
250
+ assert_generated_file "app/models/user_session.rb" do |body|
251
+ assert_match "class UserSession < Authlogic::Session::Base", body
252
+ end
253
+ end
254
+
255
+ should "should generate restful style actions in sessions controller" do
256
+ assert_generated_file "app/controllers/user_sessions_controller.rb" do |body|
257
+ assert_match "UserSession.new", body
258
+ end
259
+ end
260
+
261
+ should "should generate form for user session" do
262
+ assert_generated_file "app/views/user_sessions/new.html.erb" do |body|
263
+ assert_match "form_for @user_session", body
264
+ end
265
+ end
266
+
267
+ should "should handle session controller tests differently" do
268
+ assert_generated_file "test/functional/user_sessions_controller_test.rb" do |body|
269
+ assert_match "UserSession.find", body
270
+ end
271
+ end
272
+ end
273
+ end
274
+ end