Rob van Dijk's Blog

Specify callbacks before specifying the associations

Gepost in rubyonrails door Rob van Dijk op 05/02/2010

Just wasted a full day on this… Suppose you have a User model, with certain Users being children and other Users being parents. With the following code I intended to delete the parent when the child being deleted is the last child of the parent:

class ChildParent < ActiveRecord::Base
  belongs_to :child, :class_name => "User"
  belongs_to :parent, :class_name => "User"
end

class User < ActiveRecord::Base

  has_many :parents, :through => :child_parents_as_child
  has_many :child_parents_as_child, :foreign_key => :child_id, :class_name => "ChildParent", :dependent => :destroy
  has_many :children, :through => :child_parents_as_parent
  has_many :child_parents_as_parent, :foreign_key => :parent_id, :class_name => "ChildParent", :dependent => :destroy

  before_destroy :determine_parents_to_be_destroyed
  after_destroy :destroy_parents

  private
  def determine_parents_to_be_destroyed
    @destroy_parents = []
    self.parents.each do |p|
      @destroy_parents << p unless p.children.size > 1
    end
  end
  def destroy_parents
    @destroy_parents.each do |p|
      p.destroy
    end
  end
end

This didn’t work – @destroy_parents was never populated. Finally I stumbled on the following sentence, clearly documented in the api for ActiveRecord, yet easily missed: *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the callbacks before specifying the associations. Otherwise, you might trigger the loading of a child before the parent has registered the callbacks and they won‘t be inherited.

Moving the before_destroy and after_destroy declarations above the associations solved the problem.

Met de volgende tags:, ,

Using declarative_authorization with subdomains

Gepost in rubyonrails door Rob van Dijk op 24/01/2010

The Rails portal I’m working on uses subdomains to distinguish between schools (i.e. schoolname.schoudercom.nl). Access to various parts of the portal is controlled through roles using declarative_authorization. All this using one app and one database. So far so good.

But what if a teacher in school1 is a parent in school2? So, when this user requests a page for school1 (under subdomain “school1″) his role should be “teacher” and when he requests a page for school2 his role should be “parent”.

Sounds simple enough to accomplish,  until you realize that information about the subdomain is not accessible from the User model. And so also not available in method role_symbols that is called by declarative_authorization.

This separation of logic between the layers of the MVC pattern is a basic Rails aspect. Various workarounds can be found on the Internet, some of which involve class or instance variables or usage of Thread.current. For a typical discussion on this subject see this topic, more information about threading can be found in Thread safety for your Rails.

Having read the available information I finally settled on adding an instance variable to the User model and setting this from a before_filter in ApplicationController. Let’s start with the model definitions, showing only the relevant lines:

class User < ActiveRecord::Base
    has_many :user_roles
    has_many :roles, :through => :user_roles, :conditions => 'school_id=#{self.current_subdomain ? self.current_subdomain.id : -1}'
    attr_accessor :current_subdomain
    # method for declarative_authorization
    def role_symbols
        roles.map do |role|
          role.name.underscore.to_sym
        end
    end
end

class School < ActiveRecord::Base
    has_many :user_roles
end

class Role < ActiveRecord::Base
  has_many :user_roles
  has_many :users, :through => :user_roles
end

class UserRole < ActiveRecord::Base
    belongs_to :user
    belongs_to :role
    belongs_to :school
end

The instance variable is current_subdomain. Note that the :condition for the has_many assocation for roles uses single quotes. When declarative_authorization calls role_symbols, which iterates the roles array, the condition is dynamically executed and the roles are filtered based on subdomain. The single quotes will cause the query to be “lazy evaluated” as mentioned e.g. in this blog.
When no subdomain is available (e.g. for the public portal starting with ‘www’) the ‘-1′ ensures that no roles are assigned.

In ApplicationController add a before_filter and two methods that will set the current_subdomain variable for current_user (provided by AuthLogic in my case):

before_filter :set_current_subdomain

def current_subdomain
    return @current_subdomain if defined?(@current_subdomain)
    @current_subdomain = request.subdomains.join(".")
    @current_subdomain = nil unless (@current_subdomain && @current_subdomain.casecmp("www") != 0 )
    if @current_subdomain
        @current_subdomain = School.find_by_subdomain(@current_subdomain)
    end
    @current_subdomain
end

def set_current_subdomain
    ca = current_subdomain
    current_user.current_subdomain = ca unless current_user.nil?
end

This may be trivial for experienced Rails developers, but I decided to blog about it anyway – for people like me who just started working with Rails a couple of months ago it may be useful information.

Rubyenrails 2009: what’s in it for the customer?

Gepost in rubyonrails door Rob van Dijk op 01/11/2009

The Rubyenrails 2009 Conference, held in Amsterdam as previous years, has just finished. What was new, what was hot and what’s in it for the customer?

One of the nice surprises was the increasing number of large(r) companies that are using Ruby on Rails, to name some examples: Tele2, Nedap, Ordina. Adoption of Rails varies from usage in one department to being the strategic platform of choice.

This nicely coincides with  the inspiring talk by Jeremy Kemper who observes that both Ruby and Rails are not hypes anymore but are becoming mainstream.

The real-life example omroep.nl (presentation by Bart Zonneveld and Sjoerd Tieleman) illustrates what Ruby on Rails can do for the web development industry. Within 6 months a small team built both the site itself and a custom CMS that interfaces with an existing image library. If anything, this example shows both the maturity of the platform and the rapid development cycles that can be achieved.

Other presentations addressed performance (Bart ten Brinke) and security (Jonathan Weiss). They gave various practical pointers for building a performing Ruby on Rails site and increasing security to block various types of attacks (XSS, Session Fixation, CSRF, Javascript Hijacking).

Customers usually don’t care much which database is being used, as long as the functionality of the website works as desired. Perhaps this will be different for MongoDB (presentation by Michael Dirolf), a new document-based schema-free database. Letting go of schemas is quite a step for anybody involved in modern relational databases but it opens up a huge potential for flexibility all the way up to the end-user. An administrator wants a new User field for sub-departments? With MongoDB at the back this will be trivial. Sure, such functionality is possible with a relational database as well, but requires quite a bit more work.

Last but not least Jeremy Kemper and Yehuda Katz highlighted the improvements that Rails 3.0 will bring. Many excellent changes are of technical nature and invisible to customers but the speed-up of the framework (up to 10x for collection rendering) and the improved support for encoding and time zones will likely have a noticable effect on the (international) user experience.

What is your take on the advantages Rails 3.0 will bring?

Yehuda KatzJeremy Kemper and
Met de volgende tags:, , ,
Follow

Get every new post delivered to your Inbox.