Thursday, May 29, 2008

Project Organization

All three of my open source projects have been moved to stonean.com.

For individual resources:

Classy Inheritance

Lockdown

Stage

The above links take you to the wiki, but there's a forum and issue tracking system setup for each account as well.

I would like to thank Jean-Philippe Lang and the Redmine team for the Redmine project management system.

Also, RailsPlayground has been great. Superb customer service. Special thanks to Joe Clarke.

Classy Inheritance

So depends_on graduated to a project called Classy Inheritance. Why the name difference? Well depends_on represents just one type of relationship. I expect other relationships to be added as needs arise. One such method I could see is "composed_of", where the object wouldn't be required.

Lockdown will be using Classy Inheritance for its User/Profile relationship. This update will be coming soon.

As always, if you have any suggestions for Classy Inheritance, Lockdown or Stage, just let me know.

thanks,
andy

Wednesday, May 28, 2008

depends_on update(2)

This update allows for non-polymorphic associations. For instance a user belongs_to a profile. To define this association add the following to the User model:
depends_on :profile, :attrs => [:first_name, :last_name, :email]
Notice an ":as" option has not been passed because this isn't a polymorphic association. So instead of adding the has_one call, a belongs_to reference is added.

The original post has the updated code.

Getting time to put this into a gem...

depends_on update

I've updated the code for depends_on.

This update adds a dynamic method, so if you had the call:
 depends_on :content, :attrs => [:name, :url ], :as => :presentable

Then you would get a "find_with_content" class method that does the :include for you.

git checkout

This definitely falls in the duh! category:

If you delete a file and want to restore it to the trunk/master version, for subversion I would do:
svn update

to do the same with git:
git checkout <file>

Seems like this should have been obvious, but I guess I was expecting to find an update or restore or some other git method than checkout.

Tuesday, May 27, 2008

depends_on

For one of my projects I had the need for a polymorphic association in which one object required the existence of the other. I had written a specific version of this, but decided it could easily be abstracted. I didn't go with the built in ActiveRecord polymorphic features as I wanted to strip it down to the basics to see if it would work. Why? I would love to use this in DataMapper at some point.

module Stonean
module DependsOn
def self.included(base)
base.extend Stonean::DependsOn::ClassMethods
end

module ClassMethods
def depends_on(model_sym, options = {})
define_relationship(model_sym,options)

validates_presence_of model_sym
validates_associated model_sym

# Before save functionality to create/update the requisite object
define_save_method(model_sym, options[:as])

define_find_method(model_sym)


options[:attrs].each{|attr| define_accessors(model_sym, attr)}
end

#model_instance = instance_variable_get("@#{model_sym}")
private
def define_relationship(model_sym, options)
if options[:as]
has_one model_sym, polymorphic_constraints(options[:as])
else
belongs_to model_sym
end
end

def define_save_method(model_sym, polymorphic_name = nil)
define_method "save_requisite_#{model_sym}" do
if polymorphic_name
eval("self.#{model_sym}.#{polymorphic_name}_type = self.class.name")
eval("self.#{model_sym}.#{polymorphic_name}_id = self.id")
end

eval("self.#{model_sym}.save")
end

before_save "save_requisite_#{model_sym}".to_sym
end

def define_find_method(model_sym)
self.class.send :define_method, "find_with_#{model_sym}" do |*args|
eval <<-CODE
if args[1] && args[1].is_a?(Hash)
if args[1].has_key?(:include)
inc_val = args[1][:include]
new_val = inc_val.is_a?(Array) ? inc_val.push(:#{:model_sym}) : [inc_val, :#{model_sym}]
args[1][:include] = new_val
else
args[1].merge({:include => :#{model_sym}})
end
else
args << {:include => :#{model_sym}}
end
find(*args)
CODE
end
end

def define_accessors(model_sym, attr)
define_method attr do
eval("self.#{model_sym} ? self.#{model_sym}.#{attr} : nil")
end

define_method "#{attr}=" do |val|
model_defined = eval("self.#{model_sym}")

unless model_defined
klass = model_sym.to_s.classify
eval("self.#{model_sym} = #{klass}.new")
end

eval("self.#{model_sym}.#{attr}= val")
end
end

def polymorphic_constraints(polymorphic_name)
{ :foreign_key => "#{polymorphic_name}_id",
:conditions => "#{polymorphic_name}_type = '#{self.name}'"}
end

end # ClassMethods
end # DependsOn module
end # Stonean module
ActiveRecord::Base.send :include, Stonean::DependsOn


Here's an example, but first a little information. Content is a model that all "presentable" objects in my cms depend on. It holds the name and url attributes so when you are calling message.name, you are really calling message.content.name.
class Message < ActiveRecord::Base
depends_on :content, :attrs => [:name, :url], :as => :presentable
end

This means your form and views can use these methods and not worry about the underlying association. for example:
# This form tag was used for descriptive purposes.
text_field_tag "message[name]", message.name

Now you don't have to do anything special in your views or controller to build and save the depends_on object.

You also get a find_with_<dependent_model> class method. This means if you have a Picture model that depends_on Image, you would get a Picture.find_with_image method.

This example is very specific, but with a little modification, can be used to implement a class table inheritance architecture. I definitely plan on doing this soon.

I will be releasing this as a gem in the near future, but for now you can just copy the code above and add it into your config/initializers directory.

If you have any ideas or suggestions, I would love to hear them.

thanks,
andy

Sunday, May 25, 2008

Stage Update: 0.4.0

This release addresses the use of namespaces such as "admin". This release is targeted at Rails only.

For more information on Stage check out the RubyForge site.

Thursday, May 22, 2008

Git and Tagging

Just a quick personal note on how to tag so I don't have to look it up again. I'm not sure if this is right.?

git tag -a rel_0.0.0
git push --tags

Friday, May 9, 2008

Announcing Lockdown

If you don't care about how Lockdown came to be and just want to read about how it works, go to the RubyForge site.

Over the past few years, I have been refining a security system for RubyOnRails. I've used it in every Rails app I've ever written. However there aren't many similarities between this public release and the older versions as a lot of refactoring has been done over time.

The original reason behind Lockdowns creation was twofold:
  1. The existing security systems worked off of the principle "allow all access unless restricted". I think that is the opposite of how a security system should work. Therefore, Lockdown works off of the principle "restrict all access unless allowed".
  2. It was not possible to administer the security rules via an user interface, it was all code based. This posed two issues for me:
    1. I had to modify code in order to add rules/change access. Yuck. I like management screens (they aren't required with Lockdown, just an option).
    2. The systems I saw required code to be placed in each of the controllers to defined the access for that controller. Code everywhere. Yuck again. I wanted a central place to manage my access rules.

So I built what I wanted and all was looking pretty good until I ran into RSpec. This threw a big cold bucket of water on the DRYness I try to achieve. Since tests worked off of the test database and migrations were excluded (only structure), that meant I would have to redefine all my security rules as mock objects. That goes way beyond a Yuck! to #uck!. I simply refused to do this extra work (repeatedly). So I went about refactoring Lockdown to play nice in this situation.

Where it was fully dependent on the database, it is now more code based and the init.rb file is only place to define Permissions and the main place to define UserGroups. This means you don't have to redefine your Permissions or UserGroups for your tests. Now I'm back on track...

I then made the decision to really focus on releasing this to the community. I had always intended on doing this but realized it needed more work to simplify the installation and use. I won't bore you with the stages, but I will just say that there were a few.

I think the community will benefit from Lockdown as much as Lockdown will benefit from the community. So, kick the tires, take it for a spin and let me know what you think.

There is a google group setup for your questions.

Right now, it's really focused on RubyonRails. However, it is architected for Merb support, I just haven't completed it. A Merb release will be coming soon.

thanks,
andy