Austin Story

Ruby, Rails and Javascript Blog

Powered by Genesis

How to create a User and an Account in one form with Devise

November 22, 2015 By Austin Story Leave a Comment

Most of my apps these days that are pure html with a backend are written in Ruby on Rails and use the Devise engine to handle password and account creation type things.

For this example we will be assume a few things

  • You have rails 4+ and Devise Setup
  • Devise routes are setting the registration controller as your custom controller.
  • models are User and Account
  • User belongs_to Account (so User has an account_id)
  • User responds to set_admin to deal with however admin is setup.

I was reading through the source the other day and saw this block option in the RegistrationsController for devise

[ruby]

class Devise::RegistrationsController

# POST /resource
def create
build_resource(sign_up_params)
resource.save

yield resource if block_given?
#other devise stuff
respond_with resource
end
end
[/ruby]

 

[ruby]yield resource if block_given[/ruby]

means that we can pass some code in as a block (which is just an anonymous function) that will receive the resource as a parameter.

 

In your resources controller you can override like this

[ruby]

class RegistrationsController < Devise::RegistrationsController
def create
super {|user| user.create_account }
end
end

[/ruby]

So with this code, super sends the block up the chain.  So the result is that when we get to the yield resource, the resources receives the message ‘create_account’.

This is great, now we are able to delegate all of our registration logic to devise.  But wait, what if we now want to set the user as admin on the account and make sure that it is all wrapped up in a transaction?  That is easy, just expand the block.

[ruby]
def create
ActiveRecord::Base.transaction do
super do |user|
user.create_account
user.set_admin
end
end
end
[/ruby]

At this point, we have fulfilled the feature that we create the account along with the user and set that user as the admin on the account, so ship the feature!

Filed Under: Devise, Ruby, Ruby on Rails Tagged With: Devise, Rails, ruby, ruby on Rails, Ruby super

Testing Mulitenancy with Minitest and Capybara

February 16, 2015 By Austin Story Leave a Comment

Lately I have used an outside in approach to building my web application in ruby on rails.  What that means is that I am driving my application development by building integration tests.  I picked up this technique a year or so ago from thoughtbot.  So far, i have really enjoyed the insight it has given me and how much less code I write than when i was doing things by building models first.

Many of the applications I end up building as a consultant are multi-tenant application.  So there are many customers that have to live in strict or limited scope from each other.  I have built a really simple helper that I have been using and wanted to share it below in my TestCase class description

[code]

class ActiveSupport::TestCaseActiveRecord::Migration.check_pending!
include Warden::Test::Helpers
fixtures :all

def as_user(user)
login_as users(user.to_sym)
yield
logout
end
end

[/code]

 

It is super simple and I was a little hesitant to put it out there.  All it does is a login the user, execute the block that is passed in and then log the user out.  In a multitenant app though it is really imporant to check your scopes based on the type of user that is logging in (admin, super admin, org admin, user, anonymous, etc).  On top of that it adds to some very understandable tests.  Ozark and nixa are just two random cities in my area.

 

[code]

scenario "Employee cannot visit other organizations tickets" do
as_user(:ozark_employee) do
ozark_ticket = organizations(:ozark).tickets.first
visit ticket_path(ozark_ticket)

page.text.must_include unauthorized_error
end
end

scenario "Can edit own tickets" do
as_user(:ozark_employee) do
visit tickets_path
click_link 'Edit'
fill_in 'Description', :with => 'Some new description xxx'
click_button 'Update Ticket'

page.text.must_include 'successfully updated'
page.text.must_include 'Some new description xxx'
end
end

[/code]

This one simple helper has really tied in with my multitenant outside in development and sped up my dev time when building out how different types of users should experience an application.

Filed Under: Minitest, Programming, Testing Tagged With: bdd, minitest, ruby on Rails, tdd

Pundit and Rolify Testing with Rails

September 21, 2014 By Austin Story Leave a Comment

Just ran into an oddity that I didn’t expect (due to a false assumptions) when testing permissions and wanted to send it out to the world.

I have an app with two types of roles; :admin or :user. I am using Rolify in combination with Pundit to perform the role based authorization of REST actions.

I mixin a module containing general purpose authorization classes. I do it this way because permissions schemes tend to change alot and centralization makes it easier to change. You could easily just add these to your pundit policy classes.

[ruby]
def is_admin?
user.has_role?(:admin)
end

def is_user?
user.has_role?(:user)
end

def is_allowed?
user.has_role?(:user, record)
end
[/ruby]

Pretty straight-forward. An administrator has the role of :admin, a user has a role of :user and a :user is only allowed to access records that they are explicitly granted permission on.

My error was the assumption that user.has_role?(:user) would return true if a user had a role of :user explicitly set on any object in the system. In a pundit action it would look something like this.

[ruby]
def index?
is_admin? || is_user?
end
[/ruby]

The problem is that this was returning false unless someone had a :user role set in general, not on a specific instance of a record.

I think code clears this up further, here is the error.

[ruby]
#Assign the user role of :user on a specific object instance
User.first.add_role(:user, SomeObject.first)
User.first.has_role?(:user) #= false

#Assign the user a role of :user
User.first.add_role(:user)
User.first.has_role?(:user) #= true
[/ruby]

So i had to alter this to use see if the User had the role of :user on any object in the system.

[ruby]
#Old definition, broken
def is_user?
user.has_role?(:user)
end

#New definition, fixed!
def is_user?
SomeObject.find_roles(:user, user).any?
end
[/ruby]

The new is_user? returns true when the user has a role of :user on any SomeObject in the system. Which is exactly what we need.

Lesson learned, I assumed that the method worked a certain way and lost several hours of debugging in my unit tests. I normally will go straight to IRB when i run into anomalies but because i strongly help my assumption I didn’t.

Filed Under: Integrations, Minitest, Ruby, Ruby on Rails Tagged With: Pundit, Rails, Rolify, ruby on Rails

  • 1
  • 2
  • 3
  • 4
  • Next Page »

Categories

  • AngularJS
  • Books
  • Devise
  • Elasticsearch
  • ES6
  • Information Security
  • Integrations
  • Javascript
  • Linux
  • Minitest
  • PhoneGap
  • Programming
  • React
  • Redux
  • Ruby
  • Ruby on Rails
  • Stripe
  • Testing
  • Theory
  • TypeScript
  • Uncategorized
  • Vue
  • Webpack