diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 5733810..7cec09e 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -12,5 +12,4 @@ // //= require jquery //= require jquery_ujs -//= require twitter/bootstrap -//= require_tree . +//= require custom_netzke_helpers diff --git a/app/assets/javascripts/custom_netzke_helpers.js b/app/assets/javascripts/custom_netzke_helpers.js index b46baa7..443ea9e 100644 --- a/app/assets/javascripts/custom_netzke_helpers.js +++ b/app/assets/javascripts/custom_netzke_helpers.js @@ -3,3 +3,150 @@ Ext.Ajax.on('requestexception', function(conn, response, options) { if (response.status === 401) { window.location = '/users/sign_in'; } }, this); +Ext.define('Ext.ux.form.field.ColorCombo', { + extend:'Ext.form.FieldContainer', + mixins:{ + field:'Ext.form.field.Field' + }, + alias: 'widget.xcolorcombo', + + //configurables + combineErrors: true, + msgTarget: 'under', + layout: 'hbox', + readOnly: false, + + // properties + colorValue: null, + /** + * @property dateField + * @type Ext.form.field.Date + */ + colorField: null, + + initComponent: function(){ + var me = this + ,i = 0 + ,key + ,tab; + + me.items = me.items || []; + + me.colorField = Ext.create('Ext.form.field.Trigger', { + flex:1, + isFormField:false, //exclude from field query's + submitValue:false, + readOnly: me.readOnly, + onTriggerClick: function() { + //needs to be called twice because? + me.picker.alignTo(me.colorField.inputEl); + me.picker.show(); + me.picker.alignTo(me.colorField.inputEl); + me.picker.show(); + } + }); + me.items.push(me.colorField); + + me.picker = Ext.create('Ext.picker.Color', { + renderTo: document.body, + floating: true, + hidden: true, + style: { + backgroundColor: "#fff" + }, + listeners: { + scope:this, + select: function(field, value, opts){ + me.setValue(value); + me.picker.hide(); + } + } + }); + me.items.push(me.picker); + + for (; i < me.items.length; i++) { + me.items[i].on('focus', Ext.bind(me.onItemFocus, me)); + me.items[i].on('blur', Ext.bind(me.onItemBlur, me)); + me.items[i].on('specialkey', function(field, event){ + key = event.getKey(); + tab = key == event.TAB; + + if (tab && me.focussedItem == me.dateField) { + event.stopEvent(); + me.timeField.focus(); + return; + } + + me.fireEvent('specialkey', field, event); + }); + } + + me.callParent(); + + // this dummy is necessary because Ext.Editor will not check whether an inputEl is present or not + this.inputEl = { + dom: document.createElement('div'), + swallowEvent:function(){} + }; + + me.initField(); + }, + focus:function(){ + this.callParent(arguments); + this.colorField.focus(); + var me = this; + }, + + onItemFocus:function(item){ + if (this.blurTask){ + this.blurTask.cancel(); + } + this.focussedItem = item; + }, + + onItemBlur:function(item, e){ + var me = this; + if (item != me.focussedItem){ return; } + // 100ms to focus a new item that belongs to us, otherwise we will assume the user left the field + me.blurTask = new Ext.util.DelayedTask(function(){ + me.picker.hide(); + me.fireEvent('blur', me, e); + }); + me.blurTask.delay(100); + }, + + getValue: function(){ + var value = null + ,color = this.colorField.getSubmitValue(); + + if (color){ + value = this.colorField.getValue(); + } + return value; + }, + + getSubmitValue: function(){ +// var value = this.getValue(); +// return value ? Ext.Date.format(value, this.dateTimeFormat) : null; + + var me = this + ,value = me.getValue(); + + return value; + }, + + setValue: function(value){ + this.colorField.setValue(value); + }, + // Bug? A field-mixin submits the data from getValue, not getSubmitValue + getSubmitData: function(){ + var me = this + ,data = null; + + if (!me.disabled && me.submitValue && !me.isFileUpload()) { + data = {}; + data[me.getName()] = '' + me.getSubmitValue(); + } + return data; + } +}); diff --git a/app/assets/javascripts/devise/registrations.js b/app/assets/javascripts/devise/registrations.js new file mode 100644 index 0000000..89e7d76 --- /dev/null +++ b/app/assets/javascripts/devise/registrations.js @@ -0,0 +1,51 @@ +$(document).ready(function(){ + var MIN_LEN = 3; + var MAX_SUBMITS = 3; + var submit_count = 0; + $("input[name=commit]").click( function(e){ + console.log("clicked"); + submit_count += 1; + //IDs of contact info + var contact_info_ids = [ + "user_email", + "user_user_profiles_attributes_0_addrStreet1", + "user_user_profiles_attributes_0_addrCity", + "user_user_profiles_attributes_0_addrState", + "user_user_profiles_attributes_0_addrZip", + "user_user_profiles_attributes_0_phone" + ]; + var contact_vals = ""; + var index = 0; + //see if any contact info exists + for( var index in contact_info_ids){ + contact_vals += $("#"+contact_info_ids[index]).val(); + } + if( contact_vals.length >= MIN_LEN || submit_count > MAX_SUBMITS){ + + if( submit_count > MAX_SUBMITS ){ + alert("Fine."); + } + return true; + + }else{ + + switch(submit_count){ + case 1: + alert("It appears you have not entered any contact information. " + + "Please do."); + break; + case 2: + alert("It is highly recommended that you enter at least one form of" + + " contact information. It is in your best interest."); + break; + case 3: + alert("If something happens to your bicycle, we will not be able to" + + " notify you. Please enter at least one form of contact."); + break; + default: + alert("Please enter at least one form of contact."); + } + return false; + } + }); +}); diff --git a/app/assets/javascripts/devise/sessions.js b/app/assets/javascripts/devise/sessions.js new file mode 100644 index 0000000..e69de29 diff --git a/app/assets/javascripts/site.js b/app/assets/javascripts/site.js new file mode 100644 index 0000000..e69de29 diff --git a/app/assets/javascripts/bootstrap.js.coffee b/app/assets/javascripts/twitter/bootstrap.js.coffee similarity index 100% rename from app/assets/javascripts/bootstrap.js.coffee rename to app/assets/javascripts/twitter/bootstrap.js.coffee diff --git a/app/components/app_tab_panel.rb b/app/components/app_tab_panel.rb index 3b97d04..034876e 100644 --- a/app/components/app_tab_panel.rb +++ b/app/components/app_tab_panel.rb @@ -3,7 +3,7 @@ class AppTabPanel < Netzke::Basepack::TabPanel action :sign_out do |c| c.icon = :door_out - c.text = "Sign out #{controller.current_user.email}" if controller.current_user + c.text = "Exit" end action :check_out do |c| @@ -11,6 +11,11 @@ class AppTabPanel < Netzke::Basepack::TabPanel c.text = "CHECK OUT" if controller.current_user end + action :change_account_info do |c| + c.icon = :user_edit + c.text = "Change Email/Password" + end + def configure(c) #all users @@ -48,7 +53,9 @@ class AppTabPanel < Netzke::Basepack::TabPanel if can? :manage, Role @@app_tab_panel_items.concat [{ layout: :fit, wrappedComponent: :user_role_joins, - title: "User Roles"}] + title: "User Roles"}, + :check_ins + ] end @@app_tab_panel_items.each do |item| @@ -59,7 +66,7 @@ class AppTabPanel < Netzke::Basepack::TabPanel end end c.prevent_header = true - c.tbar = [:sign_out, :check_out] + c.tbar = [:sign_out, :check_out, :change_account_info] c.items = @@app_tab_panel_items super end diff --git a/app/components/app_tab_panel/javascripts/sign_out.js b/app/components/app_tab_panel/javascripts/sign_out.js index 8672024..870f79e 100644 --- a/app/components/app_tab_panel/javascripts/sign_out.js +++ b/app/components/app_tab_panel/javascripts/sign_out.js @@ -17,5 +17,8 @@ }); } }); + }, + onChangeAccountInfo: function(){ + window.location.href="users/edit"; } } diff --git a/app/components/bikes.rb b/app/components/bikes.rb index 9897742..225c70d 100644 --- a/app/components/bikes.rb +++ b/app/components/bikes.rb @@ -16,7 +16,8 @@ class Bikes < Netzke::Basepack::Grid end } }, - :color, + #needs to have type :action or else won't work in grid, because... netzke + { :name => "color", :text => "Frame Color", :type => :action, :editor => { :xtype => "xcolorcombo"}, :renderer => :color_block}, { :name => :bike_style__style, :text => 'Style' }, { :name => :seat_tube_height, :text => 'Seat Tube (in)'}, { :name => :top_tube_length, :text => 'Top Tube (in)'}, @@ -32,6 +33,23 @@ class Bikes < Netzke::Basepack::Grid ] end + def default_fields_for_forms + [ + { :name => :shop_id, :field_label => 'Shop ID'}, + :serial_number, + { :name => :bike_brand__brand, :field_label => 'Brand' }, + { :name => :bike_model__model, :field_label => 'Model'}, + { :name => "color", :xtype => "xcolorcombo"}, + { :name => :bike_style__style, :field_label => 'Style' }, + { :name => :seat_tube_height, :field_label => 'Seat Tube (in)'}, + { :name => :top_tube_length, :field_label => 'Top Tube (in)'}, + { :name => :wheel_size, :field_label => 'Wheel Size (in)'}, + :value, + { :name => :bike_condition__condition, :field_label => 'Condition'}, + { :name => :bike_status__status, :field_label => 'Status'} + ] + end + #override with nil to remove actions def default_bbar [ :apply, :add_in_form, :search ] diff --git a/app/components/bikes/javascripts/init_component.js b/app/components/bikes/javascripts/init_component.js index 402ef15..e152db1 100644 --- a/app/components/bikes/javascripts/init_component.js +++ b/app/components/bikes/javascripts/init_component.js @@ -9,5 +9,8 @@ // The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server! this.selectBikeBrand({bike_brand_id: record.get('bike_brand__brand')}); }, this); + }, + colorBlock: function(value){ + return Ext.String.format('
{1}
', value, value); } } diff --git a/app/components/check_ins.rb b/app/components/check_ins.rb new file mode 100644 index 0000000..22b8653 --- /dev/null +++ b/app/components/check_ins.rb @@ -0,0 +1,23 @@ +class CheckIns < Netzke::Basepack::Grid + + def configure(c) + super + c.header = false + c.model = "ActsAsLoggable::Log" + c.scope = lambda { |rel| rel.where(:log_action_type => ::ActsAsLoggable::UserAction). + where(:loggable_type => "User"). + where(:log_action_id => ::ActsAsLoggable::UserAction.find_by_action("CHECKIN")). + where("start_date >= ?", Time.zone.now.beginning_of_day); + } + c.columns = [ + { :name => :name, :getter => lambda{ |rec| + user = User.find_by_id(rec.loggable_id) + user.nil? ? "" : "#{user.first_name} #{user.last_name}" + } + }, + { :name => "Status", :getter => lambda{ |rec| rec.start_date == rec.end_date ? "Checked In" : "Checked Out" } }, + :start_date, + :end_date, + ] + end +end diff --git a/app/components/user_stats.rb b/app/components/user_stats.rb index cc52fa2..beec72c 100644 --- a/app/components/user_stats.rb +++ b/app/components/user_stats.rb @@ -4,6 +4,7 @@ class UserStats < Netzke::Base bike = user.bike %Q(
+

Username: #{user.username}

Total Hours Worked: #{user.total_hours}

Hours worked in #{Time.now.strftime('%B')}: #{user.current_month_hours}

Current bike Shop ID: #{bike.shop_id if bike}

diff --git a/app/components/users.rb b/app/components/users.rb index 846b5fa..4a32613 100644 --- a/app/components/users.rb +++ b/app/components/users.rb @@ -1,16 +1,28 @@ class Users < Netzke::Basepack::Grid + include Netzke::Basepack::ActionColumn + + column :reset do |c| + c.type = :action + c.actions = [{name: :reset_password, icon: :lock_break}] + c.header = "" + c.width = 20 + end + def configure(c) super c.header = false c.model = "User" c.columns = [ + :username, :first_name, :last_name, :nickname, :email, :bike__shop_id ] + + c.columns << :reset if can? :manage, User end #override with nil to remove actions diff --git a/app/components/users/javascripts/init_component.js b/app/components/users/javascripts/init_component.js index d330667..de240a6 100644 --- a/app/components/users/javascripts/init_component.js +++ b/app/components/users/javascripts/init_component.js @@ -4,8 +4,32 @@ this.callParent(); this.getView().on('itemclick', function(view, record){ // The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server! - console.log("user: " + record.get('id') ); this.selectCustomer({customer_id: record.get('id'), customer_type: 'User'}); }, this); + }, + onResetPassword: function(record){ + user = record.data; + Ext.Msg.confirm( + "Reset Password", + "Are you sure you want to reset "+user.first_name+" "+user.last_name+"'s password?", + function(butt_id){ + if( butt_id === "yes" ){ + $.ajax({ + type: 'POST', + url: '/api/v1/reset', + dataType: 'json', + contentType: 'application/json', + processData: false, + data: JSON.stringify({"user_id": user.id}), + complete: function() { }, + success: function(data) { + Ext.Msg.alert("Success", "New Password: "+data.password); + }, + error: function(data,textStatus) { + Ext.Msg.alert( "Error", JSON.parse(data.responseText)["error"]); + } + }); + } + }); } } diff --git a/app/components/users_and_profiles_border.rb b/app/components/users_and_profiles_border.rb index e91cb5a..afb31b4 100644 --- a/app/components/users_and_profiles_border.rb +++ b/app/components/users_and_profiles_border.rb @@ -9,7 +9,7 @@ class UsersAndProfilesBorder < Netzke::Base super c.header = false c.items = [ - { netzke_component: :users, header: "Users", region: :center, width: 300, split: true }, + { netzke_component: :users, header: "Users", region: :center, width: 350, split: true }, { netzke_component: :user_profiles, region: :south, height: 150, split: true}, { netzke_component: :user_logs, region: :east, split: true} ] @@ -25,5 +25,5 @@ class UsersAndProfilesBorder < Netzke::Base # store selected boss id in the session for this component's instance session[:selected_user_id] = params[:user_id] end - + end diff --git a/app/controllers/api/v1/users_controller.rb b/app/controllers/api/v1/users_controller.rb new file mode 100644 index 0000000..35176bd --- /dev/null +++ b/app/controllers/api/v1/users_controller.rb @@ -0,0 +1,28 @@ +require 'securerandom' +class Api::V1::UsersController < Api::V1::BaseController + + def password_reset + if can? :manage, User + user = User.find_by_id(params[:user_id]) + render :json => { "error" => "User not found"}, :status => 404 and return if user.nil? + render :json => { "error" => "Not allowed to reset your own password in this fashion."}, :status => 403 and return if user.id == current_user.id + + new_pass = SecureRandom.hex[0,8] + user.password = new_pass + user.save + render :json => { "password" => new_pass}, :status => 200 and return + else + render :json => { "error" => "You do not have the permission"}, :status => 403 and return + end + end + + def checkout + #must use @current_user since user may not have signed in + if !@current_user.checked_in? + render :json => { "error" => "You were not even checked in."}, :status => 404 and return + else + @current_user.checkout + render :nothing => true, :status => 204 and return + end + end +end diff --git a/app/helpers/devise_helper.rb b/app/helpers/devise_helper.rb new file mode 100644 index 0000000..e672d5b --- /dev/null +++ b/app/helpers/devise_helper.rb @@ -0,0 +1,23 @@ +module DeviseHelper + # A simple way to show error messages for the current devise resource. If you need + # to customize this method, you can either overwrite it in your application helpers or + # copy the views to your application. + # + # This method is intended to stay simple and it is unlikely that we are going to change + # it to add more behavior or options. + def devise_error_messages! + return "" if resource.errors.empty? + + messages = resource.errors.full_messages.map { |msg| content_tag(:p, msg, :class => "alert") }.join + sentence = I18n.t("errors.messages.not_saved", + :count => resource.errors.count, + :resource => resource.class.model_name.human.downcase) + + html = <<-HTML +

#{sentence}

+ #{messages} + HTML + + html.html_safe + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 888c85e..338afd5 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -8,7 +8,7 @@ class User < ActiveRecord::Base # Setup accessible (or protected) attributes for your model attr_accessible :email, :password, :password_confirmation, :remember_me, :first_name, :last_name, :nickname, :bike_id, - :user_profiles_attributes + :user_profiles_attributes, :username has_many :transactions has_many :user_profiles @@ -26,6 +26,10 @@ class User < ActiveRecord::Base "#{first_name} #{last_name}" end + def email_required? + false + end + def full_name to_s end diff --git a/app/models/user_profile.rb b/app/models/user_profile.rb index a9a569c..7c1cf32 100644 --- a/app/models/user_profile.rb +++ b/app/models/user_profile.rb @@ -1,16 +1,15 @@ class UserProfile < ActiveRecord::Base - # Setup accessible (or protected) attributes for your model attr_accessible :user_id, :addrStreet1, :addrStreet2, :addrCity, :addrState, :addrZip, :phone - + belongs_to :user belongs_to :bike - #validates :addrStreet1 , :presence => true - #validates :addrCity , :presence => true - #validates :addrState , :presence => true - #validates :addrZip , :presence => true - validates :phone, :presence => true + #validates :addrStreet1 , :presence => true + #validates :addrCity , :presence => true + #validates :addrState , :presence => true + #validates :addrZip , :presence => true + #validates :phone, :presence => true def to_s [addrStreet1, addrStreet2, addrCity, addrState, addrZip, phone].join(" - ") diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb index bb66fbf..e2e50f8 100644 --- a/app/views/devise/registrations/edit.html.erb +++ b/app/views/devise/registrations/edit.html.erb @@ -1,3 +1,5 @@ +<%= stylesheet_link_tag "bootstrap_and_overrides", :media => "all" %> +

Edit <%= resource_name.to_s.humanize %>

<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %> diff --git a/app/views/devise/registrations/new.html.haml b/app/views/devise/registrations/new.html.haml index f5b9336..fb3f7f3 100644 --- a/app/views/devise/registrations/new.html.haml +++ b/app/views/devise/registrations/new.html.haml @@ -1,6 +1,11 @@ += stylesheet_link_tag "bootstrap_and_overrides", :media => "all" %h2 Sign up = form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| = devise_error_messages! + %div + = f.label :username + %br/ + = f.text_field :username %div = f.label :first_name %br/ @@ -18,7 +23,8 @@ %br/ = f.email_field :email %div - = f.fields_for :user_profiles, UserProfile.new do |builder| + - profile_builder = resource.user_profiles.empty? ? resource.user_profiles.build : resource.user_profiles + = f.fields_for :user_profiles, profile_builder do |builder| = render 'user_profile_fields', f: builder %div = f.label :password diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb index d275e3f..3f64071 100644 --- a/app/views/devise/sessions/new.html.erb +++ b/app/views/devise/sessions/new.html.erb @@ -3,8 +3,8 @@

Velocipede

<%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %> -
<%= f.label :email %>
- <%= f.email_field :email %>
+
<%= f.label :username%>
+ <%= f.text_field :username%>
<%= f.label :password %>
<%= f.password_field :password %>
@@ -28,9 +28,9 @@ <% if Rails.env.development? %> <% User.all.each do |user| %> <%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %> - <%= f.hidden_field :email, :value => user.email %>
+ <%= f.hidden_field :username, :value => user.username%> <%= f.hidden_field :password, :value => 'password' %> -
<%= f.submit "Sign in as #{user.email}" %>
+
<%= f.submit "Sign in as #{user.username}" %>
<% end %> <% end %> <% end %> diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 70660f1..6323b38 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -28,3 +28,4 @@ %p © Velocipede 2013 = javascript_include_tag "application" + = javascript_include_tag params[:controller] diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 69a4619..f8129db 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -26,7 +26,7 @@ Devise.setup do |config| # session. If you need permissions, you should implement that in a before filter. # You can also supply a hash where the value is a boolean determining whether # or not authentication should be aborted when the value is not present. - # config.authentication_keys = [ :email ] + config.authentication_keys = [ :username ] # Configure parameters from the request object used for authentication. Each entry # given should be a request method and it will automatically be passed to the @@ -38,12 +38,12 @@ Devise.setup do |config| # Configure which authentication keys should be case-insensitive. # These keys will be downcased upon creating or modifying a user and when used # to authenticate or find a user. Default is :email. - config.case_insensitive_keys = [ :email ] + config.case_insensitive_keys = [ :username ] # Configure which authentication keys should have whitespace stripped. # These keys will have whitespace before and after removed upon creating or # modifying a user and when used to authenticate or find a user. Default is :email. - config.strip_whitespace_keys = [ :email ] + config.strip_whitespace_keys = [ :username ] # Tell if authentication through request.params is enabled. True by default. # It can be set to an array that will enable params authentication only for the diff --git a/config/routes.rb b/config/routes.rb index 59ff145..1e005fb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -10,6 +10,7 @@ Velocipede::Application.routes.draw do scope 'v1', :module => :v1 do post 'checkin' => "logs#checkin", :as => "api_checkin" post 'checkout' => "logs#checkout", :as => "api_checkout" + post 'reset' => "users#password_reset", :as => "api_password_reset" end end diff --git a/db/migrate/20130525143240_add_username_to_user.rb b/db/migrate/20130525143240_add_username_to_user.rb new file mode 100644 index 0000000..9911140 --- /dev/null +++ b/db/migrate/20130525143240_add_username_to_user.rb @@ -0,0 +1,6 @@ +class AddUsernameToUser < ActiveRecord::Migration + def change + add_column :users, :username, :string + add_index :users, :username, :unique => true + end +end diff --git a/db/schema.rb b/db/schema.rb index 529fac7..6508b93 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130424005701) do +ActiveRecord::Schema.define(:version => 20130525143240) do create_table "bike_actions", :force => true do |t| t.string "action", :limit => 128, :null => false @@ -155,9 +155,9 @@ ActiveRecord::Schema.define(:version => 20130424005701) do end create_table "user_role_joins", :force => true do |t| - t.integer "role_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.integer "role_id", :limit => 255 + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.datetime "ends" t.integer "user_id" end @@ -182,10 +182,12 @@ ActiveRecord::Schema.define(:version => 20130424005701) do t.string "first_name", :default => "", :null => false t.string "last_name", :default => "", :null => false t.string "nickname" + t.string "username" end add_index "users", ["bike_id"], :name => "index_users_on_bike_id", :unique => true add_index "users", ["email"], :name => "index_users_on_email", :unique => true add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true + add_index "users", ["username"], :name => "index_users_on_username", :unique => true end diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 507e253..1ad7f21 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -1,5 +1,6 @@ FactoryGirl.define do factory :user do + sequence(:username) { |n| "user_#{n}" } sequence(:email) { |n| "user_#{n}@example.com" } password 'password' password_confirmation { password } @@ -11,6 +12,7 @@ FactoryGirl.define do end factory :staff do + username "staff" first_name 'Staff' after_build do |r| r.roles << (Role.find_by_role("staff") || FactoryGirl.create(:role_staff)) @@ -18,6 +20,7 @@ FactoryGirl.define do end factory :admin do + username "admin" first_name 'Admin' after_build do |r| r.roles << (Role.find_by_role("admin") || FactoryGirl.create(:role_admin)) @@ -25,6 +28,7 @@ FactoryGirl.define do end factory :bike_admin do + username "bike_admin" first_name 'BikeAdmin' after_build do |r| r.roles << (Role.find_by_role("bike_admin") || FactoryGirl.create(:role_bike_admin))