cream 0.8.6 → 0.8.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/Changelog.txt +6 -1
  2. data/Design Ideas.textile +463 -0
  3. data/Gemfile +1 -0
  4. data/README.textile +158 -34
  5. data/Rakefile +8 -7
  6. data/VERSION +1 -1
  7. data/app/views/cream/menu/_admin_login_items.html.erb +2 -2
  8. data/app/views/cream/menu/_login_items.html.erb +2 -2
  9. data/app/views/cream/menu/_registration_items.html.erb +2 -2
  10. data/config/locales/cream.da.yml +16 -0
  11. data/cream.gemspec +38 -27
  12. data/lib/cream.rb +1 -0
  13. data/lib/cream/configure/after_init/role_config.rb +25 -21
  14. data/lib/cream/configure/rails.rb +8 -5
  15. data/lib/cream/controller/application_controller.rb +22 -0
  16. data/lib/cream/helper/role.rb +102 -11
  17. data/lib/generators/cream/app/app_generator.rb +50 -26
  18. data/lib/generators/cream/full_config/full_config_generator.rb +44 -9
  19. data/lib/generators/cream/helpers/all.rb +1 -0
  20. data/lib/generators/cream/helpers/execute_helper.rb +0 -4
  21. data/lib/generators/cream/helpers/gemfile_helper.rb +28 -0
  22. data/lib/generators/cream/helpers/orm_helper.rb +6 -2
  23. data/lib/generators/cream/helpers/strategy_helper.rb +28 -0
  24. data/lib/generators/cream/views/haml_util.rb +3 -4
  25. data/lib/generators/cream/views/views_generator.rb +13 -13
  26. data/lib/generators/devise/config/app_helper.rb +1 -1
  27. data/lib/generators/devise/config/config_generator.rb +1 -3
  28. data/lib/generators/devise/config/{gem_helper.rb → gem_config_helper.rb} +0 -23
  29. data/lib/generators/devise/customize/customize_generator.rb +49 -0
  30. data/lib/generators/devise/customize/customize_messages.rb +52 -0
  31. data/lib/generators/devise/customize/helpers/query_customizers.rb +43 -0
  32. data/lib/generators/devise/customize/helpers/recover_login.rb +80 -0
  33. data/lib/generators/devise/customize/helpers/username_helper.rb +149 -0
  34. data/lib/generators/devise/users/users_generator.rb +1 -3
  35. data/lib/generators/permits/config/config_generator.rb +1 -3
  36. data/lib/generators/roles/config/config_generator.rb +26 -4
  37. data/sandbox/any_user.rb +98 -0
  38. data/{lib/generators → sandbox}/cream_refactor.rb +0 -0
  39. data/sandbox/str_test.rb +50 -0
  40. data/spec/cream/configure/rails_role_spec.rb +1 -1
  41. data/spec/cream/helper/role_spec.rb +71 -21
  42. data/wiki/Cream-generators-overview.textile +79 -0
  43. data/wiki/How to gollum wiki.txt +13 -0
  44. metadata +107 -72
  45. data/wiki/CONFIG_GENERATOR.txt +0 -21
  46. data/wiki/DESIGN.txt +0 -21
  47. data/wiki/INSTALLATION.txt +0 -6
  48. data/wiki/PERMITS.txt +0 -32
  49. data/wiki/ROLE_STRATEGIES.txt +0 -40
  50. data/wiki/SPEC_NOTES.txt +0 -6
  51. data/wiki/VIEWS_GENERATOR.txt +0 -35
  52. data/wiki/VIEW_HELPERS.txt +0 -162
data/Changelog.txt CHANGED
@@ -1,4 +1,9 @@
1
- 0.8.5 - Jan 4, 2011
1
+ 0.8.6 - Jan 4, 2011
2
+ -----
3
+ Completed major refactoring to streamline generators and DRY things up!
4
+ Needs more testing using alternative class names etc.
5
+
6
+ 0.8.5 - Jan 3, 2011
2
7
  -----
3
8
  Major refactoring to extract common logic in generators in order to make the logic more transparent and more stable and consistent!
4
9
 
@@ -0,0 +1,463 @@
1
+ h1. Create default Guest user
2
+
3
+ <pre>
4
+ class Guest
5
+ def create
6
+ Guest.new
7
+ end
8
+
9
+ def has_role? role
10
+ role == :guest
11
+ end
12
+
13
+ def has_roles? *roles
14
+ role == :guest
15
+ end
16
+ end
17
+ </pre>
18
+
19
+ h2. Add to ApplicationController
20
+
21
+ <pre>
22
+ # http://stackoverflow.com/questions/4275058/using-devise-with-guest-users
23
+
24
+ def current_user
25
+ super || Guest.create # role defaults to 'guest' in the model.
26
+ end
27
+
28
+ def user_signed_in?
29
+ current_user && !current_user.new_record?
30
+ end
31
+
32
+ def user_session
33
+ user_signed_in? ? super : session
34
+ end
35
+ </pre>
36
+
37
+
38
+ h1. Control who can create new users
39
+
40
+ <pre>
41
+ $ mkdir app/controllers/users
42
+ $ touch app/controllers/users/registrations_controller.rb
43
+ </pre>
44
+
45
+ <pre>
46
+ class Users::RegistrationsController < Devise::RegistrationsController
47
+ before_filter :check_permissions, :only => [:new, :create, :cancel]
48
+ skip_before_filter :require_no_authentication
49
+
50
+ def check_permissions
51
+ authorize! :create, resource
52
+ end
53
+ end
54
+ </pre>
55
+
56
+ The check permissions method is really simple. It calls the CanCan method, authorize!, and checks if the current user can create users. We use resource here because devise uses resource to refer to the model that can be authenticated. Also notice how I removed the require_no_authentication filter, a Devise filter which allows access to actions without authentication.
57
+
58
+ <pre>
59
+ # replace devise_for :users with:
60
+ devise_for :users, :controllers => { :registrations => "users/registrations" }
61
+ </pre>
62
+
63
+ At this point if you hit the users/sign_up page when not logged in, you will notice that a CanCan::AccessDenied is thrown. This exception is thrown anytime permission is denied so you should customize it to your liking. I put the handler in my ApplicationController:
64
+
65
+ <pre>
66
+ class ApplicationController < ActionController::Base
67
+ ...
68
+ rescue_from CanCan::AccessDenied do |exception|
69
+ flash[:error] = exception.message
70
+ redirect_to root_url
71
+ end
72
+ ...
73
+ end
74
+ </pre>
75
+
76
+ <pre>
77
+ class UsersController < ApplicationController
78
+ before_filter :get_user, :only => [:index,:new,:edit]
79
+ before_filter :accessible_roles, :only => [:new, :edit, :show, :update, :create]
80
+ load_and_authorize_resource :only => [:show,:new,:destroy,:edit,:update]
81
+
82
+ # GET /users
83
+ # GET /users.xml
84
+ # GET /users.json HTML and AJAX
85
+ #-----------------------------------------------------------------------
86
+ def index
87
+ @users = User.accessible_by(current_ability, :index).limit(20)
88
+ respond_to do |format|
89
+ format.json { render :json => @users }
90
+ format.xml { render :xml => @users }
91
+ format.html
92
+ end
93
+ end
94
+
95
+ # GET /users/new
96
+ # GET /users/new.xml
97
+ # GET /users/new.json HTML AND AJAX
98
+ #-------------------------------------------------------------------
99
+ def new
100
+ respond_to do |format|
101
+ format.json { render :json => @user }
102
+ format.xml { render :xml => @user }
103
+ format.html
104
+ end
105
+ end
106
+
107
+ # GET /users/1
108
+ # GET /users/1.xml
109
+ # GET /users/1.json HTML AND AJAX
110
+ #-------------------------------------------------------------------
111
+ def show
112
+ respond_to do |format|
113
+ format.json { render :json => @user }
114
+ format.xml { render :xml => @user }
115
+ format.html
116
+ end
117
+
118
+ rescue ActiveRecord::RecordNotFound
119
+ respond_to_not_found(:json, :xml, :html)
120
+ end
121
+
122
+ # GET /users/1/edit
123
+ # GET /users/1/edit.xml
124
+ # GET /users/1/edit.json HTML AND AJAX
125
+ #-------------------------------------------------------------------
126
+ def edit
127
+ respond_to do |format|
128
+ format.json { render :json => @user }
129
+ format.xml { render :xml => @user }
130
+ format.html
131
+ end
132
+
133
+ rescue ActiveRecord::RecordNotFound
134
+ respond_to_not_found(:json, :xml, :html)
135
+ end
136
+
137
+ # DELETE /users/1
138
+ # DELETE /users/1.xml
139
+ # DELETE /users/1.json HTML AND AJAX
140
+ #-------------------------------------------------------------------
141
+ def destroy
142
+ @user.destroy!
143
+
144
+ respond_to do |format|
145
+ format.json { respond_to_destroy(:ajax) }
146
+ format.xml { head :ok }
147
+ format.html { respond_to_destroy(:html) }
148
+ end
149
+
150
+ rescue ActiveRecord::RecordNotFound
151
+ respond_to_not_found(:json, :xml, :html)
152
+ end
153
+
154
+ # POST /users
155
+ # POST /users.xml
156
+ # POST /users.json HTML AND AJAX
157
+ #-----------------------------------------------------------------
158
+ def create
159
+ @user = User.new(params[:user])
160
+
161
+ if @user.save
162
+ respond_to do |format|
163
+ format.json { render :json => @user.to_json, :status => 200 }
164
+ format.xml { head :ok }
165
+ format.html { redirect_to :action => :index }
166
+ end
167
+ else
168
+ respond_to do |format|
169
+ format.json { render :text => "Could not create user", :status => :unprocessable_entity } # placeholder
170
+ format.xml { head :ok }
171
+ format.html { render :action => :new, :status => :unprocessable_entity }
172
+ end
173
+ end
174
+ end
175
+ ...
176
+
177
+ # PUT /users/1
178
+ # PUT /users/1.xml
179
+ # PUT /users/1.json HTML AND AJAX
180
+ #----------------------------------------------------------------------------
181
+ def update
182
+ if params[:user][:password].blank?
183
+ [:password,:password_confirmation,:current_password].collect{|p| params[:user].delete(p) }
184
+ else
185
+ @user.errors[:base] << "The password you entered is incorrect" unless @user.valid_password?(params[:user][:current_password])
186
+ end
187
+
188
+ respond_to do |format|
189
+ if @user.errors[:base].empty? and @user.update_attributes(params[:user])
190
+ flash[:notice] = "Your account has been updated"
191
+ format.json { render :json => @user.to_json, :status => 200 }
192
+ format.xml { head :ok }
193
+ format.html { render :action => :edit }
194
+ else
195
+ format.json { render :text => "Could not update user", :status => :unprocessable_entity } #placeholder
196
+ format.xml { render :xml => @user.errors, :status => :unprocessable_entity }
197
+ format.html { render :action => :edit, :status => :unprocessable_entity }
198
+ end
199
+ end
200
+
201
+ rescue
202
+ respond_to_not_found(:js, :xml, :html)
203
+ end
204
+
205
+ # FILTERS
206
+
207
+
208
+ # Get roles accessible by the current user
209
+ #----------------------------------------------------
210
+ def accessible_roles
211
+ @accessible_roles = Role.accessible_by(current_ability,:read)
212
+ end
213
+
214
+ # Make the current user object available to views
215
+ #----------------------------------------
216
+ def get_user
217
+ @current_user = current_user
218
+ end
219
+ end
220
+ </pre>
221
+
222
+ h2. Views
223
+
224
+ <pre>
225
+ <h2>Register User</h2>
226
+
227
+ <%= form_for(@user) do |f| %>
228
+ <%= error_messages(@user,"Could not register user") %>
229
+
230
+ <%= render :partial => 'user_fields', :locals => { :f => f } %>
231
+
232
+ <p><%= f.label :password %></p>
233
+ <p><%= f.password_field :password %></p>
234
+
235
+ <p><%= f.label :password_confirmation %></p>
236
+ <p><%= f.password_field :password_confirmation %></p>
237
+
238
+ <p><%= f.submit "Register" %></p>
239
+ <% end %>
240
+ </pre>
241
+
242
+ h3. partial – _user_fields.html.erb
243
+ <pre>
244
+ <p><%= f.label :first_name %></p>
245
+ <p><%= f.text_field :first_name %></p>
246
+
247
+ <p><%= f.label :last_name %></p>
248
+ <p><%= f.text_field :last_name %></p>
249
+
250
+ <p><%= f.label :email %></p>
251
+ <p><%= f.text_field :email %></p>
252
+
253
+ <% if can? :read, Role %>
254
+ <p><%= f.label :role %></p>
255
+ <ul class="no-pad no-bullets">
256
+ <%= habtm_checkboxes(@user, :role_ids, @accessible_roles, :name) %>
257
+ </ul>
258
+ <% end %>
259
+ </pre>
260
+
261
+ Only allow editing of role attribute if allowed to!
262
+
263
+ h3. Edit existing user
264
+
265
+ <pre>
266
+ <h3><%= @user == @current_user ? "Your Account Settings" : "Edit User" %></h3>
267
+
268
+ <%= form_for(@user, :html => { :method => :put }) do |f| %>
269
+ <%= error_messages(@user,"Could not update user") %>
270
+ <%= render :partial => 'user_fields', :locals => { :f => f } %>
271
+
272
+ <p><%= f.label :password %> <i>(leave blank if you don't want to change it)</i></p>
273
+ <p><%= f.password_field :password %></p>
274
+
275
+ <p><%= f.label :password_confirmation %></p>
276
+ <p><%= f.password_field :password_confirmation %></p>
277
+
278
+ <p><%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i></p>
279
+ <p><%= f.password_field :current_password %></p>
280
+
281
+ <p><%= f.submit "Update" %></p>
282
+ <% end %>
283
+ <%= link_to "Back", :back %>
284
+ </pre>
285
+
286
+ h3. Show User
287
+
288
+ <pre>
289
+ <h3><%= @user.name %></h3>
290
+
291
+ <%= link_to_if(can?(:update,@user), "Edit", edit_user_path(@user)) %> |
292
+ <%= link_to_if(can?(:delete, @user), "Delete", user_path(@user), :confirm => "Are you sure?", :method => :delete) {} %>
293
+
294
+ <table class="one-column-emphasis">
295
+ <tbody>
296
+ <tr>
297
+ <td class="oce-first">Email:</td>
298
+ <td><%= @user.email %></td>
299
+ </tr>
300
+ <tr>
301
+ <td class="oce-first">Role:</td>
302
+ <td><%= @user.roles.first.name %></td>
303
+ </tr>
304
+ <% if can?(:see_timestamps,User) %>
305
+ <tr>
306
+ <td class="oce-first">Created at:</td>
307
+ <td><%= @user.created_at %></td>
308
+ </tr>
309
+ <tr>
310
+ <td class="oce-first">Last Sign In:</td>
311
+ <td><%= @user.last_sign_in_at %></td>
312
+ </tr>
313
+ <tr>
314
+ <td class="oce-first">Sign In Count:</td>
315
+ <td><%= @user.sign_in_count %></td>
316
+ </tr>
317
+ <% end %>
318
+ </tbody>
319
+ </table>
320
+ </pre>
321
+
322
+ h3. Custom can see timestamps!
323
+
324
+ <pre>
325
+ if user.role? :admin
326
+ can :see_timestamps, User
327
+ elsif user.role? :normal
328
+ can :see_timestamps, User, :id => user.id
329
+ end
330
+ </pre>
331
+
332
+ h3. Recaptcha
333
+
334
+ <pre>
335
+ class RegistrationsController < Devise::RegistrationsController
336
+
337
+ def create
338
+ if verify_recaptcha
339
+ super
340
+ else
341
+ build_resource
342
+ clean_up_passwords(resource)
343
+ flash[:error] = "There was an error with the recaptcha code below. Please re-enter the code and click submit."
344
+ render_with_scope :new
345
+ end
346
+ end
347
+ .
348
+ .
349
+ .
350
+ .
351
+ end
352
+ </pre>
353
+
354
+ h3. Recaptcha with Omniauth
355
+
356
+ <pre>
357
+ class RegistrationsController < Devise::RegistrationsController
358
+
359
+ def create
360
+ if session[:omniauth] == nil #OmniAuth
361
+ if verify_recaptcha
362
+ super
363
+ session[:omniauth] = nil unless @user.new_record? #OmniAuth
364
+ else
365
+ build_resource
366
+ clean_up_passwords(resource)
367
+ flash[:error] = "There was an error with the recaptcha code below. Please re-enter the code and click submit."
368
+ render_with_scope :new
369
+ end
370
+ else
371
+ super
372
+ session[:omniauth] = nil unless @user.new_record? #OmniAuth
373
+ end
374
+ end
375
+ </pre>
376
+
377
+ h2. login with username
378
+
379
+ Customizing The Login Requirements
380
+
381
+ Our application currently uses an email address and password to log users in, but if we want to change it so that it asks for a username instead of an email address then we can do so fairly easily.
382
+
383
+ The first thing we need to do is create a username column in the User table, which we can do by generating a migration.
384
+
385
+ $ rails generate migration add_username_to_users username:string
386
+
387
+ That done we’ll run the migration.
388
+
389
+ $ rake db:migrate
390
+
391
+ As we only have one user in the database we can quickly log in to the Rails console and set a value for the username attribute for that user.
392
+
393
+ $ rails c
394
+ Loading development environment (Rails 3.0.0.beta2)
395
+ ruby-1.8.7-p249 > User.first.update_attribute(:username, "eifion")
396
+ => true
397
+
398
+ Now that we’ve modified the database we need to modify devise’s configuration file, uncommenting the config.authentication_keys line and changing its value from :email to :username.
399
+
400
+ /config/initializers/devise.rb
401
+
402
+ 1. config.authentication_keys = [ :username ]
403
+
404
+ config.authentication_keys = [ :username ]
405
+
406
+ With this value set devise will use the username field as the authentication key. We’ll also have to modify the sign in form so that it has a username field instead of the email field.
407
+
408
+ /app/views/devise/sessions/new.html.erb
409
+
410
+ 1. <% title "Sign In" %>
411
+ 2.
412
+ 3. <%= form_for(resource_name, resource, :url => session_path(resource_name)) do |f| %>
413
+ 4. <ol class="formList">
414
+ 5. <li><%= f.label :username %> <%= f.text_field :username %></li>
415
+ 6. <li><%= f.label :password %> <%= f.password_field :password %></li>
416
+ 7. <% if devise_mapping.rememberable? -%>
417
+ 8. <li><%= f.check_box :remember_me %> <%= f.label :remember_me %></li>
418
+ 9. <% end %>
419
+ 10. <li><%= f.submit "Sign in" %></li>
420
+ 11. </ol>
421
+ 12. <% end %>
422
+ 13.
423
+ 14. <%= render :partial => "devise/shared/links" %>
424
+
425
+ <% title "Sign In" %>
426
+
427
+ <%= form_for(resource_name, resource, :url => session_path(resource_name)) do |f| %>
428
+ <ol class="formList">
429
+ <li><%= f.label :username %> <%= f.text_field :username %></li>
430
+ <li><%= f.label :password %> <%= f.password_field :password %></li>
431
+ <% if devise_mapping.rememberable? -%>
432
+ <li><%= f.check_box :remember_me %> <%= f.label :remember_me %></li>
433
+ <% end %>
434
+ <li><%= f.submit "Sign in" %></li>
435
+ </ol>
436
+ <% end %>
437
+
438
+ <%= render :partial => "devise/shared/links" %>
439
+
440
+ The signup form will also need to be modified and we’ll have to add validation to the User model attribute but we won’t show that here.
441
+
442
+ Once we’ve restarted the server so that the configuration changes are picked up we can visit the signin page and login with a username instead of and email address.
443
+
444
+ h2. me.com and gmail style
445
+
446
+ Another way to do this is me.com and gmail style. You allow an email or the username of the email. For public facing accounts, this has more security. Rather than allow some hacker to enter a username and then just guess the password, they would have no clue what the user’s email is. Just to make it easier on the user for logging in, allow a short form of their email to be used e.g “someone@domain.com” or just “someone” for short.
447
+
448
+ before_create :create_login
449
+
450
+ def create_login
451
+ email = self.email.split(/@/)
452
+ login_taken = User.where( :login => email[0]).first
453
+ unless login_taken
454
+ self.login = email[0]
455
+ else
456
+ self.login = self.email
457
+ end
458
+ end
459
+
460
+ def self.find_for_database_authentication(conditions)
461
+ self.where(:login => conditions[:email]).first || self.where(:email => conditions[:email]).first
462
+ end
463
+