neofiles 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +1 -0
  3. data/README.md +417 -0
  4. data/Rakefile +40 -0
  5. data/app/assets/images/neofiles/loading.gif +0 -0
  6. data/app/assets/images/neofiles/swf-thumb-100x100.png +0 -0
  7. data/app/assets/images/neofiles/watermark.png +0 -0
  8. data/app/assets/javascripts/neofiles/index.js.coffee +3 -0
  9. data/app/assets/javascripts/neofiles/jquery.fileupload.js +1128 -0
  10. data/app/assets/javascripts/neofiles/jquery.iframe-transport.js +172 -0
  11. data/app/assets/javascripts/neofiles/jquery.neofiles.js.coffee +191 -0
  12. data/app/assets/stylesheets/neofiles/index.css.scss +3 -0
  13. data/app/assets/stylesheets/neofiles/neofiles.css.scss +149 -0
  14. data/app/controllers/concerns/neofiles/not_found.rb +21 -0
  15. data/app/controllers/neofiles/admin_controller.rb +228 -0
  16. data/app/controllers/neofiles/admin_test_controller.rb +5 -0
  17. data/app/controllers/neofiles/files_controller.rb +28 -0
  18. data/app/controllers/neofiles/images_controller.rb +130 -0
  19. data/app/helpers/neofiles/neofiles_helper.rb +188 -0
  20. data/app/models/neofiles/file.rb +319 -0
  21. data/app/models/neofiles/file_chunk.rb +18 -0
  22. data/app/models/neofiles/image.rb +119 -0
  23. data/app/models/neofiles/swf.rb +45 -0
  24. data/app/views/neofiles/admin/_file_compact.html.haml +85 -0
  25. data/app/views/neofiles/admin/file_compact.html.haml +1 -0
  26. data/app/views/neofiles/admin_test/file_compact.erb +7 -0
  27. data/config/locales/ru.yml +21 -0
  28. data/config/routes.rb +1 -0
  29. data/lib/neofiles.rb +94 -0
  30. data/lib/neofiles/engine.rb +33 -0
  31. data/lib/neofiles/version.rb +3 -0
  32. metadata +131 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f5872fc7a18fd718ba418406742eff52c13a95f4
4
+ data.tar.gz: e677b851a4e6b89cb71d87360f22ba22a1488577
5
+ SHA512:
6
+ metadata.gz: b170149ccb7201d112291695d16fb0595902d4fe180f4e1f8541aab49897f4b149d3c522f825adda3fb91520fb7de7d756169f6d1740665344d9d55126077395
7
+ data.tar.gz: d0b896968b9be2b52274a0bec910508a9a44004adb5c0b6d8ded9ae7948be2abe4f8af44fd9666117c736c98c198e7c3560e305113d7de05654e42b0bb17ca91
data/LICENSE ADDED
@@ -0,0 +1 @@
1
+ Закрытая лицензия
@@ -0,0 +1,417 @@
1
+ Neofiles
2
+ ========
3
+
4
+ Neofiles is a filesystem-like gem for storing and managing files, mainly for organizing attachments to Active Models
5
+ in Rails applications: avatars, logotypes, product images etc.
6
+
7
+ It is simple and powerful, but has some prerequisites. If you accept them it can keep the file management subsystem of
8
+ your application lightweight and headache-free. If you find yourself fighting it instead of enjoying it, maybe it is not
9
+ fitted for your task, try switching to more classic alternatives like Paperclip or Carrierwave.
10
+
11
+ The classical approach of storing files in a local filesystem and postprocessing them at the moment of upload/save has
12
+ the following drawbacks:
13
+
14
+ 1. Hard to copy/backup/shard the entire file set or its part.
15
+ 1. Almost impossible to make batch queries like finding a subset of files by some criteria.
16
+ 1. Complex metadata handling via a companion DB model + usually gems do not provide such functionality, DIY.
17
+ 1. Hard to change postprocessing rules, say when design changes and you need an avatar 20px larger — oops, we need to
18
+ travel through all the objects and resave originals or thumbnails (if originals are available, of course, which
19
+ is not always the case).
20
+ 1. The last point naturally leads to another: why at all a model needs to know how its logo must be resized or otherwise
21
+ postprocessed for the sake of design or whatever? This is completely irrelevant to the model itself and should be
22
+ defined elsewhere.
23
+
24
+ Neofiles addresses these issues in following ways:
25
+
26
+ ***Database storage***: all files are stored in MongoDB database, thus providing standard (and very efficient)
27
+ mechanisms for backup, sharding, replication, monitoring & control, you name it. Most importantly, all this is done
28
+ by standard and well described tools, no need to put tricky cron jobs at night to backup several Gb.
29
+
30
+ ***File models***: host (owner) models only store IDs of file objects `Neofiles::File` which is actually a metadata
31
+ container and a handle to real file bytes (a collection of `Neofiles::FileChunk`). The file + chunks concept is called
32
+ Mongo GridFS.
33
+
34
+ ***File model is environment agnostic***: it does not know anything about its URL, thumbnail size and similar things.
35
+ It only stores data and metadata. To actually stream file bytes to clients there is a set of controllers (and of course
36
+ appropriate routes must be set up).
37
+
38
+ ***No postprocessing at file save***: the only thing can be done is cropping extremely large images, like resizing
39
+ them to some max value to avoid storing unnecessarily detailed pics and keep disk & memory usage low.
40
+
41
+ ***Postprocessing by request***: real job like resizing an avatar to 100x100px thumbnail is done by an HTTP request
42
+ at runtime, e.g. the real file URL might be `/neofiles/serve-image/ID`, whilst the thumbnail's is
43
+ `/neofiles/serve-image/ID/100x100` — all this is done in `Neofiles::ImagesController`, dedicated to serving and modifying
44
+ images. Any special logic for handling videos, audios, pdfs or whatever can be achieved in similar way (you have to code
45
+ it, only images are supported out of the box).
46
+
47
+ ***Rely on webserver cache***: to keep things simple and not worry about caches and deleting thumbnails, the image
48
+ streaming controller always generates a fresh copy of postprocessed original, be it an original with watermarks or
49
+ a resized thumbnail. The task to cache the result is a burden of frontend webserver, like Nginx.
50
+
51
+ ***Immutability***: technically, any file content and metadata can be changed, and any file can be completely deleted.
52
+ But deleting is useless as MongoDB does not reallocate deleted space to avoid partitioning. And if we agree that a file
53
+ can not be changed after it is uploaded (file immutability) we strongly simplify our lives as now we always have unique
54
+ correspondence between file ID and its content.
55
+
56
+ For example, we can tell Nginx to cache forever any image or its derivative simply by its URL of form
57
+ `/neofiles/serve-image/ID(/WxH)` since we know that no mater what, the content represented by this URL is always the same.
58
+ (Well, we can change watermarks — but this happens rarely, and eventually the whole cache will be updated).
59
+
60
+ Or on upload we can check if the same file exists in DB (by md5 hash) and if it is, create new file metadata pointing
61
+ to the same set of file chunks (byte content).
62
+
63
+ So, Neofiles does not automatically delete or alter existing files, only their metadata, which is ok.
64
+
65
+ ***With all this in mind***, Neofiles can be viewed as a remote filesystem, where files with their metadata
66
+ are addressed and used by their IDs, as in `belongs_to/has_many`, and real file fetching (and postprocessing) is done
67
+ via HTTP requests to Content Delivery Network represented by a set of streaming controllers + caching frontend webserver
68
+ setup.
69
+
70
+ Installation, dependencies
71
+ --------------------------
72
+
73
+ Add Neofiles and its dependencies to your gemfile:
74
+
75
+ ``` ruby
76
+ gem 'neofiles', git: 'git@bitbucket.org:neolabs-kz/gem_neo_files.git'
77
+ gem 'ruby-imagespec', git: 'git://github.com/dim/ruby-imagespec.git'
78
+ gem 'mini_magick', '3.7.0'
79
+ ```
80
+
81
+ ***ruby-imagespec*** is needed to get an image file dimensions & content type.
82
+
83
+ ***mini_magick*** does resizing & watermarking. Actually it is a lightweight wrapper around command-line utility
84
+ ImageMagick, which must be installed also. Refer to the gem's description for installation instructions.
85
+
86
+ Also, you must have installed MongoDB with its default driver ***mongoid*** (5 version at least) and Rails framework
87
+ with HAML templating engine. By default the gem needs `neofiles` mongoid client defined in `config/mongoid.yml`, which
88
+ can be changed in ***Configuration*** section.
89
+
90
+ Next, include CSS & JS files where needed (say, application.js or admin.js)...
91
+
92
+ ``` javascript
93
+ #= require jquery # neofiles requires jquery
94
+ #= require neofiles
95
+ ```
96
+
97
+ ... and application.css/admin.css or whatever place you need it in:
98
+
99
+ ``` css
100
+ *= require neofiles
101
+ ```
102
+
103
+ The last step is to set up routing which is as simple as adding a line to the `routes.rb`:
104
+
105
+ ``` ruby
106
+ instance_eval &Neofiles.routes_proc
107
+ ```
108
+
109
+ It produces the following routes:
110
+
111
+ ```
112
+ $ rake routes | grep neofiles
113
+ neofiles_file_compact GET /neofiles/admin/file_compact(.:format) neofiles/admin#file_compact
114
+ neofiles_file_save POST /neofiles/admin/file_save(.:format) neofiles/admin#file_save
115
+ neofiles_file_remove POST /neofiles/admin/file_remove(.:format) neofiles/admin#file_remove
116
+ neofiles_file_update POST /neofiles/admin/file_update(.:format) neofiles/admin#file_update
117
+ neofiles_redactor_upload POST /neofiles/admin/redactor-upload(.:format) neofiles/admin#redactor_upload
118
+ neofiles_redactor_list GET /neofiles/admin/redactor-list/:owner_type/:owner_id/:type(.:format) neofiles/admin#redactor_list
119
+ neofiles_file GET /neofiles/serve/:id(.:format) neofiles/files#show
120
+ neofiles_image GET /neofiles/serve-image/:id(/:format(/c:crop)(/q:quality)) neofiles/images#show {:format=>/[1-9]\d*x[1-9]\d*/, :crop=>/[10]/, :quality=>/[1-9]\d*/}
121
+ neofiles_image_nowm GET /neofiles/nowm-serve-image/:id(.:format) neofiles/images#show {:nowm=>true}
122
+ ```
123
+
124
+ Routes `/neofiles/admin/*` form AJAX backend for file manipulations. The last 3 routes are for streaming controllers.
125
+
126
+ Usage with CCK
127
+ --------------
128
+
129
+ Neofiles gem is used mainly with two other gems: cck & cck_forms (CCK stands for Content Construction Kit).
130
+
131
+ Using these gems together provides straightforward and extremely easy way to handle file storage and manipulation.
132
+
133
+ ***As cck & cck_forms are yet to be published (soon), I will only show the basic usage.***
134
+
135
+ ``` ruby
136
+ # in model, say app/models/user.rb
137
+ class User
138
+ include Mongoid::Document
139
+
140
+ field :avatar, type: Cck::ParameterTypeClass::Image
141
+ field :slider, type: Cck::ParameterTypeClass::Album
142
+ end
143
+
144
+ # in form view, say app/views/admin/users/edit.haml
145
+ = form_for @user do |f|
146
+ .form-group
147
+ label= Avatar image:
148
+ = f.standalone_cck_field :avatar
149
+
150
+ .form-group
151
+ %label= Slider photos:
152
+ = f.standalone_cck_field :slider
153
+
154
+ # everywhere else where you want to use avatar or slider photos, say on user profile page app/views/users/show.haml
155
+ .user-profile
156
+ .avatar
157
+ = neofiles_img_tag @user.avatar.value, width=100, height=100, {crop: 1}, {alt: "#{@user.name}'s avatar", class: 'avatar-img'}
158
+
159
+ .slider
160
+ = @user.slider.value.each do |img| # img is Neofiles::Image instance
161
+ .slider-img
162
+ = neofiles_img_link img, width=800, height=300
163
+ ```
164
+
165
+ Note, that fields `avatar` and `slider` are wrappers around real `Neofiles::Image` values, so you need to unwrap them
166
+ with call to `.value`.
167
+
168
+ Standalone usage
169
+ ----------------
170
+
171
+ The whole idea is to handle `Neofiles::File` instances via their IDs, without direct files manipulation.
172
+
173
+ In your MongoDB model create field to store file ID:
174
+
175
+ ``` ruby
176
+ class User
177
+ include Mongoid::Document
178
+
179
+ belongs_to :avatar, class_name: 'Neofiles::Image'
180
+ belongs_to :cv, class_name: 'Neofiles::File'
181
+
182
+ # OR
183
+ #
184
+ # field :avatar_id, type: BSON::ObjectID # or type: String
185
+ #
186
+ # def avatar
187
+ # Neofiles::Image.where(id: avatar_id).first if avatar_id.present?
188
+ # end
189
+ #
190
+ # def avatar=(other)
191
+ # self.avatar_id = other ? other.id : nil
192
+ # end
193
+ end
194
+ ```
195
+
196
+ With ActiveRecord, create ID field in database schema and define getter and setter to construct a `Neofiles::File`
197
+ instance like in example above.
198
+
199
+ Next, to build an edit form, construct an AJAX request to special action `Neofiles::AdminController#file_compact`, it
200
+ will generate HTML subform for file upload/edit. The subform contains all HTML & JS needed to asynchronously upload/edit
201
+ or delete file. Most importantly it contains so-called ***transfer input*** (hidden field) which gets populated with ID
202
+ of the newly created `Neofiles::File` instance on file upload (also it is emptied when a file is deleted). The key is this
203
+ input should have its `name` attribute in context of the outer form (of the host object) — you pass that name as an AJAX
204
+ parameter. Hence when a user saves the outer form, the ID value will be persisted alongside other host object fields.
205
+
206
+ ``` haml
207
+ = form_for @user do |f|
208
+ .form-group
209
+ %label= Avatar image
210
+ #avatar-container
211
+ = f.hidden_field :avatar_id
212
+
213
+ - file_compact_path = neofiles_file_compact_path(id: @user.avatar_id, input_name: 'user[avatar]')
214
+ :javascript
215
+ $(function() {
216
+ $("#avatar-container").load("#{file_compact_path}", null, function() {
217
+ $(this).children().unwrap();
218
+ });
219
+ })
220
+ ```
221
+
222
+ The reason for inserting default hidden field is that an AJAX request can take long to load or even fail, and all this
223
+ time there will be no input with name `user[avatar]`, so controller may be confused and nullify that field if the "Save"
224
+ button is pressed by an impatient user.
225
+
226
+ Now, we can freely use the value stored, either directly of via helper:
227
+
228
+ ``` haml
229
+ - pic = @user.avatar
230
+ - if pic
231
+ This user has an avatar image of type #{pic.content_type} and of size #{pic.length} bytes.
232
+ %br
233
+ The original filename was #{pic.filename}, MD5 hash is #{pic.md5}.
234
+ %br
235
+ The image is:
236
+ %img{src: neofiles_image_url(pic, format: '100x100', crop: '1', q: '90'), width: 100, height: 100}
237
+
238
+ / OR EQUIVALENTLY
239
+
240
+ = neofiles_img_tag pic, 100, 100, crop: 1, q: 90
241
+ ```
242
+
243
+ Delivering bytes
244
+ ----------------
245
+
246
+ To actually get file bytes to users Neofiles offers two controllers — `FilesController` and `ImagesController` — and
247
+ three routes: `neofiles_file_url`, `neofiles_image_url` and `neofiles_image_nowm_url`.
248
+
249
+ The `FilesController` delivers bytes as-is, the `ImagesController` allows resizing & watermarking. `nowm` stands for "no
250
+ watermark" and is used when an admin is logged in (see ***Watermarking*** section).
251
+
252
+ The actual URLs look like this: `/neofiles/serve-file/FILE_ID`, `/neofiles/serve-image/IMAGE_ID/...`,
253
+ `/neofiles/nowm-serve-image/IMAGE_ID/...` (prefixed with domain and protocol).
254
+
255
+ When you request an image, you have several parameters to pass along:
256
+
257
+ * `format`: a string of form `WxH`, where `W` and `H` are max width and height correspondingly, that is the returned
258
+ image will be no greater than that size.
259
+ * `crop`: a string, if `'1'` is passed, the image will be cropped and the aspect ratio of the resulting image will
260
+ always be `W/H` (from the `format` parameter). Otherwise (default) the original aspect ratio will be preserved.
261
+ * `q`: an integer from 1 till 100. If passed, the image will be forced to JPEG format with the specified quality.
262
+
263
+ It is ok to request an image via the `FilesController` as it is smart enough to redirect to the correct path.
264
+
265
+ ***Production notes***:
266
+
267
+ 1. It may be good to put the burden of serving files to a different server than where your main application resides.
268
+ Create a new environment called `neofiles` and setup its deploy accordingly (leave only Neofiles and MongoDB related
269
+ things).
270
+
271
+ 1. Server from point (1) forms naturally a simple CDN: give it a proper name, say `strg.domain.com`, set it inside
272
+ your neofiles config as `cdns` and all your files will be downloaded faster since browser can now send more
273
+ parallel requests.
274
+
275
+ If the server is powerful enough create even more domains like `str1/2/3...` pointing to it to get
276
+ even more speed.
277
+
278
+ 1. (1) + (2) lead to the rule: always use `*_url` route helpers instead of `*_path` ones, as the former takes into
279
+ account `cdns`.
280
+
281
+ 1. Make sure your session cookies (or other means of identifying admins) are available to CDN domains since they need
282
+ to check for `is_admin?`.
283
+
284
+ 1. You ***must*** set up caching in front of streaming controllers, with one exception: `/neofiles/nowm-serve-image`
285
+ must always hit the application as it checks if an admin is logged in. If you cache it you will give everyone
286
+ watermarkless cached copies of image originals. Example Nginx config:
287
+
288
+ server {
289
+ listen 80;
290
+ server_name strg1.domain.com strg2.domain.com;
291
+ root /var/www/neofiles/current/public;
292
+
293
+ location /neofiles/serve {
294
+
295
+ if ($http_if_modified_since) {
296
+ return 304;
297
+ }
298
+ if ($http_if_none_match) {
299
+ return 304;
300
+ }
301
+
302
+ expires max;
303
+ add_header Cache-Control public;
304
+ add_header Last-Modified "Sat, 1 Jan 2012 00:00:00 GMT";
305
+
306
+ proxy_cache_valid 200 30d;
307
+ proxy_cache_valid 404 301 302 304 5m;
308
+ proxy_cache_key "$request_uri";
309
+ proxy_ignore_headers "Expires" "Cache-Control" "Set-Cookie";
310
+
311
+ proxy_cache neofiles;
312
+ proxy_pass http://neofiles;
313
+ }
314
+ }
315
+
316
+ upstream neofiles {
317
+ server unix:/var/run/neofiles.sock;
318
+ }
319
+
320
+ View helpers
321
+ ------------
322
+
323
+ The following view helpers are available, by example:
324
+
325
+ ``` haml
326
+ - file = Neofiles::File.first
327
+ - image = Neofiles::Image.first
328
+ - swf = Neofiles::Swf.first
329
+
330
+ # <img src="/neofiles/serve-image/..." alt="..." width...>
331
+ = neofiles_img_tag image, 100, 100, {crop: 1}, {alt: '...'}
332
+
333
+ # <a href="/neofiles/serve-image/..." title="See fullsize"><img src=....></a>
334
+ = neofiles_img_link image, 100, 100, {crop: 0, q: 50}, {title: 'See fullsize'}, {alt: '...}
335
+
336
+ # <a href="/neofiles/serve-file/..." title="Download it!">My CV</a>
337
+ = neofiles_link file, "My CV", title: "Download it!"
338
+
339
+ # <object id="bnr_object_1" width... classid...>...</object>
340
+ = swf_embed 'bnr_object_1', neofiles_image_url(swf), swf.width, swf.height, '#f00', ''
341
+ ```
342
+
343
+ Watermarks
344
+ ----------
345
+
346
+ By default this gem watermarks images with a single watermark (at the bottom) from `app/assets/images/neofiles/watermark.png`
347
+ (it is a 1x1 transparent pixel by default). You can change this by redefining the config option `config.neofiles.watermarker`.
348
+ Its value must be a proc with signature `image, no_watermark:, watermark_width:, watermark_height:`. The arguments
349
+ are:
350
+
351
+ * `image`: a `MiniMagick::Image` instance to be watermarked.
352
+ * `no_watermark`: if this is `true` the proc must return the image's blob intact
353
+ * `watermark_width`, `watermark_height`: the image dimensions
354
+
355
+ The returned value must be converted to blob by the `MiniMagick::Image#to_blob` method.
356
+
357
+ As admins usually need watermarkless originals of images, you can specify the current admin via `config.neofiles.current_admin`.
358
+ If that proc returns something truthy a request to `/neofiles/nowm-serve-image/...` will not stamp watermarks. Otherwise
359
+ it will return an HTTP 403 Forbidden response. Check out watermarks caching problems in ***Delivering bytes*** section.
360
+
361
+ Configuration
362
+ -------------
363
+
364
+ Neofiles offers the following config options which can be set in `config/application.rb` or `config/environments/*.rb`:
365
+
366
+ ``` ruby
367
+ # array of CDN strings like 'http://strg1.example.com'
368
+ config.neofiles.cdns = []
369
+
370
+ # if you need some special logic done before file save IN ADMIN CONTROLLER, put it here
371
+ # `file` is a Neofiles::File instance
372
+ config.neofiles.before_save = ->(file) do
373
+ ...
374
+ end
375
+
376
+ # if you have a notion of "admin" (whatever it is), put here a logic to get the current "admin" object,
377
+ # it is used only when deciding if the current user can access watermarkless version of an image
378
+ # (`context` is a controller instance where the proc is called, so with Devise you can just do `context.current_admin`)
379
+ config.neofiles.current_admin = ->(context) do
380
+ ...
381
+ end
382
+
383
+ # mongo specific settings — override them if you need to store data in some other database and/or collections
384
+ config.neofiles.mongo_files_collection = 'files.files'
385
+ config.neofiles.mongo_chunks_collection = 'files.chunks'
386
+ config.neofiles.mongo_client = 'neofiles'
387
+ config.neofiles.mongo_default_chunk_size = 4.megabytes
388
+
389
+ # image related settings
390
+ config.neofiles.image_rotate_exif = true # rotate image, if exif contains orientation info
391
+ config.neofiles.image_clean_exif = true # clean all exif fields on save
392
+ config.neofiles.image_max_dimensions = nil # resize huge originals to meaningful size: [w, h], {width: w, height: h}, wh
393
+ config.neofiles.image_max_crop_width = 2000 # users can request resizing only up to this width
394
+ config.neofiles.image_max_crop_height = 2000 # users can request resizing only up to this height
395
+
396
+ # default watermarker — redefine to set special watermarking logic
397
+ # by default, watermark only images larger than 300x300 with a watermark at the bottom center, taken from
398
+ # app/assets/images/neofiles/watermark.png
399
+ config.neofiles.watermarker = ->(image, no_watermark: false, watermark_width:, watermark_height:) do
400
+ ...
401
+ end
402
+ ```
403
+
404
+ Roadmap, TODOs
405
+ --------------
406
+
407
+ * Move HTML-building methods `admin_compact_view` to views or helper.
408
+ * Add proper authorization for admin controllers.
409
+ * Speed up image resize (`Image#save_file` has unnecessary steps).
410
+ * Move airbrake `notify_airbrake` calls to config.
411
+ * Configurable WYCIWYG editor (on/off, ability to use other than proprietary RadactorJS).
412
+ * Transform controller exceptions into meaningful error messages to users.
413
+
414
+ License
415
+ -------
416
+
417
+ Released under the [MIT License](http://www.opensource.org/licenses/MIT).
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'Cck'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
24
+ load 'rails/tasks/engine.rake'
25
+
26
+
27
+
28
+ Bundler::GemHelper.install_tasks
29
+
30
+ require 'rake/testtask'
31
+
32
+ Rake::TestTask.new(:test) do |t|
33
+ t.libs << 'lib'
34
+ t.libs << 'test'
35
+ t.pattern = 'test/**/*_test.rb'
36
+ t.verbose = false
37
+ end
38
+
39
+
40
+ task :default => :test