This evening I decided to write some UML for the permission sub-system that I'd been working on. I decided that I really should have it in dot format because 1) I want to learn it better, 2) its plain text and thus more "Pragmatic", 3) there are rails gems (i.e. railroad) that can generate dot format. So I wrote the dot file (with inspiration from a railroad output). During the process, I decided to include the method signatures, which definitely helps to explain what's going on.
Then, I decided that I'd really like my classes to be able to generate this UML (in particular I wanted the method signature). So with a little thinking, I started to roll my own, but it gets a bit complicated parsing the source file and determining what's going on. Fortunately, after 30 minutes, I realized that RDoc does the hard-work for me, and all I need to do is parse the html.
Enter RdocInspector. It takes a string and finds and collects the Public Class methods, Public Instance methods, and Protected Instance methods (as well as their arguements). The string is the result of File.read(some_rdoc_html_file).
My UML automation work is not yet done, but the issue of what methods are explicitly defined in a given file has been solved.
Showing posts with label ruby. Show all posts
Showing posts with label ruby. Show all posts
Wednesday, August 08, 2007
Wednesday, July 18, 2007
Mocha
So my previous post talked about overriding delegation. Then I remembered, Mocha. An honest to goodness stubbing/mocking library. Rather obviously, when testing units, I really don't want to fiddle around with the interaction of objects in each and every instance.
So instead of my convoluted example of conditionally_overriding_delegation, I can do the following (but hey you may have already known this):
Even in this case, Mocha is rather useful, I don't have to worry about testing the coupling that occurs due to delegation, and can test the objects responsibilities. Of course, now its time to look more into Mocha's documentation. It looks like there is some rather amazing stuff that can be done.
So instead of my convoluted example of conditionally_overriding_delegation, I can do the following (but hey you may have already known this):
class AbstractUnit
extend Forwardable
def_delegate :foo, :name
def has_name?
!(name.nil? || name.blank?)
end
end
class AbstractUnitTest < Test::Unit::TestCase
def setup
@object = AbstractUnit.new
end
# Perhaps a reasonable test for delegation?
def test_should_delegate_name_to_foo
assert_equal @object.name, @object.foo.name
end
def test_has_name_should_be_false_if_name_is_empty
@object.stubs(:name).returns('')
assert ! @object.has_name?
end
def test_has_name_should_be_false_if_name_is_nil
@object.stubs(:name).returns(nil)
assert ! @object.has_name?
end
def test_has_name_should_be_false_if_name_is_blank
@object.stubs(:name).returns('hello world')
assert @object.has_name?
end
end
Even in this case, Mocha is rather useful, I don't have to worry about testing the coupling that occurs due to delegation, and can test the objects responsibilities. Of course, now its time to look more into Mocha's documentation. It looks like there is some rather amazing stuff that can be done.
Monday, July 16, 2007
assert_interface
I've been reading quite a bit on programming these days. And one of the things I'm liking more and more is the concept of programming for the interface.
Now I use Ruby and not Java or C# or other languages, so the idea of the interface is a bit different. I can implement them, kind of, via a module.
However, I want to make sure that my objects quack appropriately. Enter assert_interface(object, module). The object is assumed to be an
instance of a class. The module is assumed to be "well-formed" for interfaces
Now I use Ruby and not Java or C# or other languages, so the idea of the interface is a bit different. I can implement them, kind of, via a module.
However, I want to make sure that my objects quack appropriately. Enter assert_interface(object, module). The object is assumed to be an
instance of a class. The module is assumed to be "well-formed" for interfaces
module WellFormedInterface
def self.included(base)
base.send(:include, InstanceMethods)
base.extend(ClassMethods)
end
module ClassMethods
end
module InstanceMethods
end
end
class AbstractUnit
include WellFormedInterface
end
class Test::Unit::TestCase
# Given an object (i.e. Foo.new), and a module (i.e. SomethingCool)
# assert that all methods explicitly defined in the module
# are present in the object.
#
# This isn't the end all test, as you are still responsible for
# testing the implementation of the interface
def assert_interface(object, mod)
methods = {object => [], object.class => []}
mod::InstanceMethods.instance_methods(false).each do |method|
methods[object] << method unless object.respond_to?(method)
end
mod::ClassMethods.instance_methods(false).each do |method|
methods[object.class] << method unless object.class.respond_to?(method)
end
msg = []
methods.each_pair do |key, value|
msg << "Expected #{key} to respond to the following :#{value.join(' :')}" unless value.empty?
end
if msg.empty?
assert true, 'Hey, it all worked'
else
flunk msg.join('. ')
end
end
end
Monday, May 21, 2007
Corrupt Session?
The system that I have been working on for the past while had a particularly vexing problem. Every morning, I had to restart the mongrel cluster. The reason, each morning the login process just stopped working. It was especially annoying, because I had one shot each day to attempt to fix it (to apply the fix, I had to restart the server). After a two weeks of various attempts at correcting the problem, we finally stumbled upon the problem.
I was assuming that a session would gracefully expire 12 hours after first being created. In fact, what was happening is the session would expire 12 hours after the server restarted. So once twelve hours would pass, each request would generate a new session that would in essence be immediately expired. Not exactly the behavior I was looking for.
So if you are looking for corrupt session, session corruption, or unresponsive mongrel it may just be related to the code above.
class ApplicationController < ActionController::Base
session :session_expires => 12.hours.from_now
end
I was assuming that a session would gracefully expire 12 hours after first being created. In fact, what was happening is the session would expire 12 hours after the server restarted. So once twelve hours would pass, each request would generate a new session that would in essence be immediately expired. Not exactly the behavior I was looking for.
So if you are looking for corrupt session, session corruption, or unresponsive mongrel it may just be related to the code above.
Wednesday, May 09, 2007
A "Best Migration Practice"
So today, I discovered a "good idea" for migrations.
Put your database schema changes in one migration, then your data changes in the next. This becomes self-evident when you mix the two and watch as part way through your migration, the data change fails and suddenly your tables have been altered.
Put your database schema changes in one migration, then your data changes in the next. This becomes self-evident when you mix the two and watch as part way through your migration, the data change fails and suddenly your tables have been altered.
Sunday, April 01, 2007
Complex views and conventional trickery
This past month, I've been working on a Content Management System (CMS). The CMS is comprised of many sites, each of which have many subwebs (permissioned collections), each site has many pages, each page has many content blocks. The navigation tree of a subweb is a polymorphic relation. So when generating the navigation in the CMS Admin, I use the following method:
In the above example, node the send(*node.route_for_show). I ask the node (which is either a subweb or a page to tell me how to generate the path.
So the module knows the method to generate its path, but can't actually build the URL. So it passes the information back to the helper, which can generate the path.
I don't know if this is the best way to do it, but its working for me (and I've used this pattern for rendering links for another polymorphic relation).
module PagesHelper
# Generate a link to the show page of navigation node
def link_to_node(node)
link_to( h(node.display_title), send(*node.route_for_show), :class => node.published? ? 'published' : 'unpublished' )
end
end
In the above example, node the send(*node.route_for_show). I ask the node (which is either a subweb or a page to tell me how to generate the path.
class Page < ActiveRecord::Base
def route_for_show
return :page_path, subweb, self
end
end
class Subweb < ActiveRecord::Base
def route_for_show
return :subweb_path, self
end
end
So the module knows the method to generate its path, but can't actually build the URL. So it passes the information back to the helper, which can generate the path.
I don't know if this is the best way to do it, but its working for me (and I've used this pattern for rendering links for another polymorphic relation).
Tuesday, February 20, 2007
The ever evolving test suite
I've been working on a rather http://www2.blogger.com/img/gl.link.giflarge rails project and have watched my testing style evolve. And this next step, inspired by Jay Fields has really got me excited. Take a peek.
What the above method does is allows for very "fast and loose" test definitions. For example, I can do the following:
Ultimately, I'm extremely satisfied with how this works. My functional tests can have test definitions with a prefix of the action.
I have updated my Textmate so I can Run Focused Unit Tests, as well as having test highlighted like other keywords (alias, class, require, etc.)
class Test::Unit::TestCase
class << self
def test(*names, &block)
test_name = :"test_#{names.join('_').gsub(/[^\w ]+/, '').gsub(/ +/, '_')}"
raise ArgumentError, "#{test_name} is already defined" if self.instance_methods.include?( test_name.to_s )
if block_given?
define_method( test_name, &block )
else
define_method( test_name) do
$stderr.puts "Incomplete test '#{names.join(', ')}' @ #{caller.last}"
end
end
end
end
end
What the above method does is allows for very "fast and loose" test definitions. For example, I can do the following:
class Foo < Test::Unit::TestCase
# This is a place holder test, and when ran will
# print a warning
test 'this will print a warning when the test runs'
# This is a genuine test
test 'this will assert true' do
assert true
end
# This is another genuine test, but I'm providing visually
# putting the test in another namespace
test :index => 'should render a list' do
assert_template 'list'
end
end
Ultimately, I'm extremely satisfied with how this works. My functional tests can have test definitions with a prefix of the action.
I have updated my Textmate so I can Run Focused Unit Tests, as well as having test highlighted like other keywords (alias, class, require, etc.)
Tuesday, January 30, 2007
Single Table Infuriation
Single Table Inheritance in Rails is an excellent pattern that works really well, except when it doesn't.
Each of the above classes are defined in their own files (app/models). There are times in development mode that I've noticed that Child.find(:all) will generate the following SQL:
In Rails 1.2 the fix for this is perhaps a little obfuscated.
Now, the other part of this solution is that you probably want to call require_dependency at the end of the class definition as there may be methods in the parent object that the child object needs. I suppose you could re-open the class at the end of the file.
class Parent < ActiveRecord::Base
end
class Child < Parent
end
class GrandChild < Child
end
Each of the above classes are defined in their own files (app/models). There are times in development mode that I've noticed that Child.find(:all) will generate the following SQL:
SELECT * FROM parents WHERE (type='Child')
In Rails 1.2 the fix for this is perhaps a little obfuscated.
class Parent < ActiveRecord::Base
require_dependency 'child'
end
class Child < Parent
require_dependency 'grand_child'
end
class GrandChild < Child
end
Now, the other part of this solution is that you probably want to call require_dependency at the end of the class definition as there may be methods in the parent object that the child object needs. I suppose you could re-open the class at the end of the file.
Wednesday, January 24, 2007
capture, binding and other diabolical machinations
For the South Bend Ruby Group I volunteered to talk about form builders for February. At the time of volunteering, I knew close to nothing about form builders. I decided to take the upcoming meeting as a reason for learning about the builders. And now I'm hooked.
But that is not the point of this particular post. What I'm talking about is the bizarre magic involved in the form builder. In a project that I've been working on we have a need for rendering lists both in tables and in lists. However we'd really like to re-use the view logic in generating the list. Its pretty much a matter of hot-swapping the tags appropriately.
We opted to create a builder class. You call :list_for, and can optionally pass a builder class (like the one below). Of particular note there is use of bindings and captures. I'm still a bit dazzled by what all is going on (and wrote the tests to make sure it all works). Below is my understanding of what is going on.
1) There is the :concat method (as defined in the ActionView::Helpers::TextHelper module). We are taking the results of first parameter and appending it to the proc's binding. The proc's binding is the result of the stuff in that proc. In the case of the above :list_for its all of the text generated :list_for's block.
2) There is the :capture method (as defined in ActionView::Helpers::CaptureHelper module). This little critter packages up the input block as text. I tried using block.call but this didn't work as intended (I got double the block text). If I didn't want the option of passing options to each of the elements, I could conceptually simplify the method by doing a manual opening of a tag, block.call, then manually closing the tag. This could be a performance piece that I will implement at a later point (though I'll need a Hash#to_markup_attributes method).
But that is not the point of this particular post. What I'm talking about is the bizarre magic involved in the form builder. In a project that I've been working on we have a need for rendering lists both in tables and in lists. However we'd really like to re-use the view logic in generating the list. Its pretty much a matter of hot-swapping the tags appropriately.
<% list_for(@list) do | l | %>
<% l.caption 'Hello World' %>
<% l.header_row(:id => 'list_head') do %>
<%= l.header_cell 'title' %>
<%= l.header_cell 'edit' %>
<%= l.header_cell 'delete' %>
<% end %>
<% l.row( :class => 'spam', :id => 'hot_dog') do | list_item | %>
<%= l.cell l.title %>
<%= l.cell 'edit_link' %>
<%= l.cell 'delete_link' %>
<% end %>
<% end %>
We opted to create a builder class. You call :list_for, and can optionally pass a builder class (like the one below). Of particular note there is use of bindings and captures. I'm still a bit dazzled by what all is going on (and wrote the tests to make sure it all works). Below is my understanding of what is going on.
1) There is the :concat method (as defined in the ActionView::Helpers::TextHelper module). We are taking the results of first parameter and appending it to the proc's binding. The proc's binding is the result of the stuff in that proc. In the case of the above :list_for its all of the text generated :list_for's block.
2) There is the :capture method (as defined in ActionView::Helpers::CaptureHelper module). This little critter packages up the input block as text. I tried using block.call but this didn't work as intended (I got double the block text). If I didn't want the option of passing options to each of the elements, I could conceptually simplify the method by doing a manual opening of a tag, block.call, then manually closing the tag. This could be a performance piece that I will implement at a later point (though I'll need a Hash#to_markup_attributes method).
def list_for(object, *args, &block)
raise ArgumentError, "Missing block" unless block_given?
options = args.last.is_a?(Hash) ? args.pop.symbolize_keys! : {}
builder = options.delete(:builder) || ListTableBuilder
builder.new(object, self, options, &block)
end
# <table>
# <caption>
# <tr>
# <th />
# </tr>
# <tr>
# <td />
# </tr>
# </table>
class ListTableBuilder
# Welcome to the insanely bizarre initialize function
def initialize(collection, template, options, &proc)
@collection, @template, @proc = collection, template, proc
@options = options.reverse_merge( :class => 'list', :id => 'list')
@template.concat( @template.content_tag('table', @template.capture(self, &proc) , @options ), @proc.binding)
end
# :options are applied to content_tag('td')
def cell(text, options = {})
@template.content_tag('td', text, options)
end
# :options are applied to content_tag('th')
def header_cell(title, options = {})
@template.content_tag('th', title, options)
end
# :options are applied to content_tag('caption')
def caption(text, options={})
@template.concat( @template.content_tag('caption', text, options), @proc.binding )
end
# :options are applied to content_tag('tr')
def header_row(options={}, &block)
raise ArgumentError, "Missing block" unless block_given?
@template.concat( @template.content_tag('tr', @template.capture(&block), options) , block.binding)
end
# Options
# The options will be added to each row.
# :class will append and not destroy the internal classes
# :id will be used as a prefix for the internal id generation
def row(options={}, &block)
raise ArgumentError, "Missing block" unless block_given?
options.symbolize_keys!
@collection.each do | c |
row_options = options.reverse_merge( :class => '' )
row_options[:class] += ' ' + @template.cycle('odd_row', 'even_row')
row_options[:id] += '_' + @template.send(:dom_id, c)
@template.concat( @template.content_tag('tr', @template.capture(c, &block ), row_options), block.binding )
end
end
end
Monday, January 22, 2007
Ruby Cookbook from O'Reilly
Ruby Cookbook: Recipes for Object-Oriented Scripting
by Lucas Carlson & Leonard Richardson
ISBN 0-596-52369-6
I haven't read this book cover-to-cover, but I have given it a very thorough inspection, skimming through sections to find points of interest and then stopping to read those recipes. So, actually, I've probably read a whole bunch of the book and didn't realize it.
The book is written in a very concise manner, with plenty of examples, lots of useful code, excellent cross-references and a very generous license that allows you to use the code from the book (albeit with some restrictions).
Each recipe is presented as a quartet of Problem, Solution, Discussion and See Also sections. Each problem is presented succinctly, and the solutions dive right into the required code (with code comments explaining some of the more complicated lines of code, but not explaining the self-documenting lines). The discussion section of each solution really digs into what is going on, code choices that were made, variations, and basically anything else that might be interesting. The see also section cross-references other recipes that may be pertinent to the given problem.
All told, this book does an amazing job of going through a wide range of problems, from Strings & Numbers to Meta-Programming & Rails and Graphics. This book is loaded with useful bits of information and I would highly recommend adding it to your collection.
by Lucas Carlson & Leonard Richardson
ISBN 0-596-52369-6
I haven't read this book cover-to-cover, but I have given it a very thorough inspection, skimming through sections to find points of interest and then stopping to read those recipes. So, actually, I've probably read a whole bunch of the book and didn't realize it.
The book is written in a very concise manner, with plenty of examples, lots of useful code, excellent cross-references and a very generous license that allows you to use the code from the book (albeit with some restrictions).
Each recipe is presented as a quartet of Problem, Solution, Discussion and See Also sections. Each problem is presented succinctly, and the solutions dive right into the required code (with code comments explaining some of the more complicated lines of code, but not explaining the self-documenting lines). The discussion section of each solution really digs into what is going on, code choices that were made, variations, and basically anything else that might be interesting. The see also section cross-references other recipes that may be pertinent to the given problem.
All told, this book does an amazing job of going through a wide range of problems, from Strings & Numbers to Meta-Programming & Rails and Graphics. This book is loaded with useful bits of information and I would highly recommend adding it to your collection.
Wednesday, January 17, 2007
Some Weird Weird Ruby (or nest Classes and Inheritence)
Thinking cap time. I am working on a CMS and the short of it is that a Page will have many PageBlock objects. The PageBlock is versioned and can have multiple things associated with it (i.e. Links or Files or Other Stuff, thankfully each page block is homogenous). I decided that the Links/Files will be serialized. So I wrote a serializing helper object. This serializing object
Now
DOH!!! Man that is definitely not the behavior I want. I guess my nested Class has some behavior that I'm not aware of. I'm certain the behavior is intentional, I just didn't know about it
So the solution?
Then I define PageLinkBlock and
Alas, the behavior that I want. Time to dig out the Pickaxe Book to see if there is a reference to this.
class PageBlock < ActiveRecord::Base
class << self
# I want to be able to configure the underlying DataSerializer
# in the containing class
delegate :set_valid_serialized_keys, :to => :data_serializing_class
def data_serializing_class
self::DataSerializer
end
end
class DataSerializer
class << self
def set_valid_serialized_keys( *attrs )
write_inheritable_attribute :valid_serialized_keys, attrs
class_inheritable_reader :valid_serialized_keys
end
end
def add_data(data = {})
data.assert_valid_keys(valid_serialized_keys)
... do the magic ...
end
end
end
Now
$ PageBlock.valid_serialized_keys
=> NoMethodError: undefined method `valid_serialized_keys' for PageBlock::DataSerializer:Class
class PageLinkBlock < PageBlock
set_valid_serialized_keys :path, :name
end
$ PageBlock.valid_serialized_keys
=> [:path, :name]
DOH!!! Man that is definitely not the behavior I want. I guess my nested Class has some behavior that I'm not aware of. I'm certain the behavior is intentional, I just didn't know about it
So the solution?
class PageBlock < ActiveRecord::Base
class << self
# I want to be able to configure the underlying DataSerializer
# in the containing class
delegate :set_valid_serialized_keys, :to => :data_serializing_class
def data_serializing_class
self::DataSerializer
end
def inherited(page_block_subclass)
page_block_subclass.module_eval <<-EOV
class DataSerializer < PageBlock::DataSerializer
end
EOV
super
end
end
class DataSerializer
class << self
def set_valid_serialized_keys( *attrs )
write_inheritable_attribute :valid_serialized_keys, attrs
class_inheritable_reader :valid_serialized_keys
end
end
def add_data(data = {})
data.assert_valid_keys(valid_serialized_keys)
... do the magic ...
end
end
end
Then I define PageLinkBlock and
$ PageBlock.valid_serialized_keys
=> NoMethodError: undefined method `valid_serialized_keys' for PageBlock::DataSerializer:Class
Alas, the behavior that I want. Time to dig out the Pickaxe Book to see if there is a reference to this.
Tuesday, January 16, 2007
Helper Module Testing
First and foremost
Then in your app/test/test_helper.rb add
Then make a test class
Of particular note, the name of the class that inherits from Test::Rails::HelperTestCase is very important. If the inheriting class name is MyHelperTest, make sure a module named MyHelper exists.
sudo gem install ZenTest
Then in your app/test/test_helper.rb add
require 'test/rails'
Then make a test class
class FooHelper
def print_hello_world
content_tag('p', 'Hello World!')
end
end
class FooHelperTest < Test::Rails::HelperTestCase
def test_method_goes_here
assert_equal '<p>Hello World</p>', print_hello_world
end
end
Of particular note, the name of the class that inherits from Test::Rails::HelperTestCase is very important. If the inheriting class name is MyHelperTest, make sure a module named MyHelper exists.
Wednesday, January 03, 2007
Dear RDoc,
One thing that I've quickly taken for granted is RDoc. Prior to RDoc, I lamented that the documentation that I wrote in code was impossible to discover, and thus the in-code documentation was useless for non-programmers. Thus, in order to "say" what the system did, additional documentation needed to be created. So now there were two sets of documents, both saying what the system did, but not communicating with each other.
Oh the humanity!
Enter RDoc. I can now rake that information and generate a useful document defining the system. It gets even better if I start using should-language, either in specify or test context (i.e. specify 'student should have a name' or def test_student_should_have_a_name). Then I can rake just the method names and get a document that says concisely what the system should do.
Of course this leads itself to writing test stubs earlier in the project process. Even better, it is possible for a project manager to write some of these specifications.
Suddenly, the tests, which everyone wants and needs, become useful not only for documentation but also for communication and um... testing. And better yet, the documentation is not discarded nor duplicated.
Oh the humanity!
Enter RDoc. I can now rake that information and generate a useful document defining the system. It gets even better if I start using should-language, either in specify or test context (i.e. specify 'student should have a name' or def test_student_should_have_a_name). Then I can rake just the method names and get a document that says concisely what the system should do.
Of course this leads itself to writing test stubs earlier in the project process. Even better, it is possible for a project manager to write some of these specifications.
Suddenly, the tests, which everyone wants and needs, become useful not only for documentation but also for communication and um... testing. And better yet, the documentation is not discarded nor duplicated.
Saturday, December 23, 2006
Contextual Decoration of a Ruby object
One of my current work problems is that I have an Account class that needs optional decoration if it has been initialized in the context of an Import. I was kicking around an Observer or AspectR but the latter library wreaks of Java, and the former just isn't as seamless as I'd like. Also, I need to be mindful of multiple threads. Below is the solution I came up with, without the problem specific code.
The situation is actually more complicated than what I am modeling below.
What is actually happening is that I have numerous objects that use the "is_a_import_model" interface. If a class "is_a_import_model" then its initialize method needs to be overwritten to account for the import_log. There is also a StreamParser that is reading an XML document to process the lots of imports. If the StreamParser is created in the context of an Import.run!, it needs to log and rescue all exceptions, otherwise, it re-raises those exceptions.
The situation is actually more complicated than what I am modeling below.
class Import
attr_reader :start_at, :stop_at
class << self
def run!(&block)
Thread.new do
Thread.current['import_log'] = import_log = self.new
import_log.start!
yield block if block
import_log.stop!
end
end
end
[:start, :stop].eacha do |m|
define_method("#{m}!") do
instance_variable_set("@#{m}_at", Time.now)
end
end
def log_change(message)
... magical stuff happens here
end
end
class Account < ActiveRecord::Base
attr_reader :import_log
def initialize
@import_log = Thread.current['import_log']
super
end
def after_save
import_log.log_change("Saved Account with ID=#{self.id}") if import_log
end
end
What is actually happening is that I have numerous objects that use the "is_a_import_model" interface. If a class "is_a_import_model" then its initialize method needs to be overwritten to account for the import_log. There is also a StreamParser that is reading an XML document to process the lots of imports. If the StreamParser is created in the context of an Import.run!, it needs to log and rescue all exceptions, otherwise, it re-raises those exceptions.
Tuesday, December 12, 2006
Protected Find
One of the greatest professional compliments I've ever received was regarding a system that I designed and developed. The purpose was to provide an interface for numerous reporting systems to funnel reporting data into a unified location. I designed the system so that all data was retrieved via task specific finders (i.e. :find_agreement_number, :find_current_document_type). As a result, if the database changed, the methods could still be used (albeit with a tweak to the internal logic). Since I was working in a compiled environment where changing a table meant recompiling the functions that directly accessed the table, the need for public finders was required.
The compliment I received was that 6 months after the design was done, the vendor's system that we were integrating with required a major upgrade and changes. And in order to make my system work, they just pointed the finders to a different table, and presto things were working again.
Now what does this have to do with Ruby on Rails? Today I was having a conversation with a co-worker regarding find. After some discussion, I came out and said I really wish :find were a protected method. Find is a very powerful method that allows you to fully access all data from anywhere. This power exposes your your view, controller, and other models to the risk that your model will change. To reduce this risk, I feel it is important to create specific finders in your model and test them. Also, if you use :find_by_name (a free method on any table that has a :name column), you should also write a test for this (at a minimum making sure that your model responds to that particular find).
The entire goal of creating specific finders is to make the maintenance of a project easier. In addition, it is much easier, in Rails, to test models than controllers or views.
The compliment I received was that 6 months after the design was done, the vendor's system that we were integrating with required a major upgrade and changes. And in order to make my system work, they just pointed the finders to a different table, and presto things were working again.
Now what does this have to do with Ruby on Rails? Today I was having a conversation with a co-worker regarding find. After some discussion, I came out and said I really wish :find were a protected method. Find is a very powerful method that allows you to fully access all data from anywhere. This power exposes your your view, controller, and other models to the risk that your model will change. To reduce this risk, I feel it is important to create specific finders in your model and test them. Also, if you use :find_by_name (a free method on any table that has a :name column), you should also write a test for this (at a minimum making sure that your model responds to that particular find).
The entire goal of creating specific finders is to make the maintenance of a project easier. In addition, it is much easier, in Rails, to test models than controllers or views.
Saturday, December 09, 2006
Pimping a Controller
I've recently started working on a REST based rails project. I've been thinking about the Rails model associations, and in particular has_many :through, which is used to help expose the join between two models. Given the following three models:
I want to be able to look at permissions in a few different ways (and roles and users for that matter). Now, I can modify my PermissionsController to set the object that will be used for all find calls. By default, the PermissionsController will use Permission.find, but if I swap the Permission object for @finder_object, I can set that @finder_object before_filter. See below:
Now, is this the best practice? I don't know, however, it feels like its something to explore. One noticeable caveat is that if you are using a REST-based controller, you will want to look at all of your Object.new calls and think about replacing them with @finder_object.build.
As an aside, the above controller would probably end up with several chunks of code that were:
What can I do to encapsulate this particular logic? I dug up the code from the :respond_to controller instance method, and created a construct for:
I'm not satisfied with it, but am wondering what I might be able to do.
class User < ActiveRecord::Base
has_many :permissions
has_many :roles, :through => :permissions
end
class Permission < ActiveRecord::Base
belongs_to :role
belongs_to :user
end
class Role < ActiveRecord::Base
has_many :permissions
has_many :users, :through => :permissions
end
I want to be able to look at permissions in a few different ways (and roles and users for that matter). Now, I can modify my PermissionsController to set the object that will be used for all find calls. By default, the PermissionsController will use Permission.find, but if I swap the Permission object for @finder_object, I can set that @finder_object before_filter. See below:
class PermissionsController < ActiveRecord::Base
before_filter :determine_finder_object
def index
@permissions = @finder_object.find(:all)
# respond to stuff, though you might want to use different templates
# based on the finder object
end
def determine_finder_object
if params[:user_id]
@finder_object = User.find(params[:user_id]).permissions
elsif params[:role_id]
@finder_object = Role.find(params[:role_id]).permissions
else
@finder_object = Permission
end
end
end
Now, is this the best practice? I don't know, however, it feels like its something to explore. One noticeable caveat is that if you are using a REST-based controller, you will want to look at all of your Object.new calls and think about replacing them with @finder_object.build.
As an aside, the above controller would probably end up with several chunks of code that were:
if params[:user_id]
#do something for users
elsif params[:role_id]
#do something for roles
else
#do something in the generic
end
What can I do to encapsulate this particular logic? I dug up the code from the :respond_to controller instance method, and created a construct for:
determine do |d|
d.user_id { #do something for users}
d.role_id { #do something for roles}
end
I'm not satisfied with it, but am wondering what I might be able to do.
Wednesday, November 29, 2006
Creatively Captivated
About 1 year ago, the developers of Lightsky went to the Snakes and Rubies seminar in Chicago (Dec 3, 2005). The event really got us talking and thinking. Corporately we decided to shift from PHP to Ruby on Rails. The decision has proven to be exhilarating. We started using Subversion. We started work on a core infrastructure that has been unit tested (and is being re-factored to REST). We are able to easily re-use code that is freely available (thank heaven for well tested plugins).
I owe a lot to that trip.
$ raving_ruby_fanboy += 1
I owe a lot to that trip.
$ raving_ruby_fanboy += 1
Tuesday, November 28, 2006
Peepcode's Restful Rails
I just finished watching Peepcode's Restful-Rails video. I highly recommend it. I've followed the RESTful push by the rails team, but I haven't seen the true impact. And now, having watched the video, I'm in the mood to re-factor the existing code for my project. I'm going to refrain from this refactoring because of the complications of the existing code-base. However, I'm curious to see what can come of this. The next project I work on will make use of REST.
http://peepcode.com/articles/2006/10/08/restful-rails
http://peepcode.com/articles/2006/10/08/restful-rails
Monday, November 27, 2006
Loving the test dots ..................F.....................
For the past while, I've been working on the largest project my company (www.lightsky.com) has ever undertaken. The scope is rather large and requires lots of data import. To make sure that we got off on the right foot, we spent a lot of time (perhaps too much) developing our framework for what is to come. One of the issues was that we were dealing with a very abstract tool in the beginning (an authorization system).
It has taken a lot of back and forth work, and a lot of diagramming and what-if scenarios. But in the end, I really think the system that we have is rather impressive. I also thank heaven that it is being developed in Ruby (forget Rails for the moment). Prior to Ruby, I had programmed in RPG, VBA, Cool: Plex (a CA case tool), PHP (only 3 months!) and Lotus Notes. One thing that was missing from all of these was a tightly integrated unit test suite. Ruby does this, and does it rather well. I'm certain there are test suites out there, but to have it built right into the language's core functions is shear brilliance.
Now, if I write good tests (which is a challenge as well), I can rest assured that my code works how I say it should work. This allows me to more easily pass the tool off and let others poke around with it. And, if a test breaks, its something they should look into. I have never felt so liberated by applying the constraint of testing as much as I can.
It has taken a lot of back and forth work, and a lot of diagramming and what-if scenarios. But in the end, I really think the system that we have is rather impressive. I also thank heaven that it is being developed in Ruby (forget Rails for the moment). Prior to Ruby, I had programmed in RPG, VBA, Cool: Plex (a CA case tool), PHP (only 3 months!) and Lotus Notes. One thing that was missing from all of these was a tightly integrated unit test suite. Ruby does this, and does it rather well. I'm certain there are test suites out there, but to have it built right into the language's core functions is shear brilliance.
Now, if I write good tests (which is a challenge as well), I can rest assured that my code works how I say it should work. This allows me to more easily pass the tool off and let others poke around with it. And, if a test breaks, its something they should look into. I have never felt so liberated by applying the constraint of testing as much as I can.
Sunday, November 26, 2006
Creating a unified UI
I've been working on a site that makes use of a view mixin structure. If the view is defined in the for the controller, use it, otherwise use the default view for that action. The views are very atomized [list.rhtml, _list.rhtml, _list_search_panel.rhtml, _list_search_form.rhtml, _list_table.rhtml, _list_table_head.rhtml, _list_table_row.rhtml]. It works great, but there are limitations. Now, the system was not created by me, but had input from me, so I'm not going to bad-mouth it. But what it has gotten me thinking about is my need for an ActionHelper object.
The ActionHelper object would be responsible for knowing what the breadcrumb trail should be as well as the page title, and "tabs" that should be on the page, actions that can be taken, the current location in the menu list. As of right now, this idea is sort of floating around gathering mental momentum. The purpose of this ActionHelper object is to create the interface for building a unified UI.
The ActionHelper object would be responsible for knowing what the breadcrumb trail should be as well as the page title, and "tabs" that should be on the page, actions that can be taken, the current location in the menu list. As of right now, this idea is sort of floating around gathering mental momentum. The purpose of this ActionHelper object is to create the interface for building a unified UI.
Subscribe to:
Posts (Atom)