Showing posts with label rails. Show all posts
Showing posts with label rails. Show all posts

Sunday, April 10, 2011

Data, Context, and Interaction (DCI) Architecture

I started reading about DCI last year, and it sounded interesting, but the more I've thought about it, the more enthusiastic I've become.

DCI is the creation of Trygve Reenskaug, the inventor of MVC.  James Coplien is also very involved in advancing DCI, and it is one of the main topics of his latest book, "Lean Architecture".

DCI could have as much impact as MVC on the way we build software.

I did a presentation on DCI last November. In preparation, I wrote up an introduction and posted it on the DCI (object composition) mailing list, asking for comments. Here's the revised version (with great thanks to James Coplien and the others who provided feedback):



Data-Context-Interaction (DCI)

Although the concept of roles is not new in software development, DCI promotes roles to a critical level of importance. Domain objects are not reduced in importance, but are set free from all of the baggage typically dumped on them to support all of the roles they are expected to fill. The objects are distilled to their basic essence, still encapsulating their data, but only providing the methods essential to what the object “is”, not everything it “does”.

Roles are the home for all of the things an object "does". ("Is" and "does" are the authors' terms for distinguishing between an object's core responsibilities and its roles.) Obviously each object can fill many roles, with each role cleanly separating what would otherwise be mashed into one of the many personalities of the object. In addition, the role / object separation opens up the possibility of having roles that support many types of objects – as long as the underlying object supports the interface required by the role, everything’s good.

In DCI, roles are not standalone objects; they don't even have to be implemented as classes. A role is a set of methods to be dynamically added to an object. In Ruby, roles can be implemented as modules, simply extending the appropriate domain objects. (It’s a bit more complicated in crustier languages ;-)

The linking of roles to domain objects is the responsibility of the Contexts. Each user / system interaction scenario is represented by a context, typically initiated by a controller. The context not only assembles the roles and objects, but also orchestrates any high-level interaction that does not cleanly fit within the object roles nor the controller (for example, a sequence of steps specific to the implementation of a story).  Contexts can be nested and reused, but should be stacked, so that only one is active at any time.

Friday, June 26, 2009

Tracking value changes in ActiveRecord objects

Info about getting to old / new values on activerecord objects:

http://ryandaigle.com/articles/2008/3/31/what-s-new-in-edge-rails-dirty-objects

This can be useful if validation varies based upon the prior state of the the object, etc.

Rails Time Zone Support

A good summary of how to support users in different time zones:

http://mad.ly/2008/04/09/rails-21-time-zone-support-an-overview/

Thursday, June 11, 2009

Ext JS with Rails

Trying to get ExtJS working with Rails...

One of the first steps described in the Ext tutorials is just getting a page set up referencing the javascript libraries: http://extjs.com/learn/Tutorial:HTML_Page_Setup

Unfortunately, that example didn't work in rails. However, taking some suggestions from here, putting this in the head of a rails layout seems to work:

<%= stylesheet_link_tag '../javascripts/ext/resources/css/ext-all' %>
<%= javascript_include_tag 'ext/adapter/prototype/prototype.js' %>
<%= javascript_include_tag 'ext/adapter/prototype/effects.js' %>
<%= javascript_include_tag 'ext/adapter/prototype/ext-prototype-adapter.js' %>
<%= javascript_include_tag 'ext/adapter/ext/ext-base.js' %>
<%= javascript_include_tag 'ext/ext-core.js' %>
<%= javascript_include_tag 'ext/ext-all.js' %>

script type="text/javascript" (took off the <>'s from this line for posting)
Ext.BLANK_IMAGE_URL = "../javascripts/ext/resources/images/default/s.gif";
Ext.onReady(function() {
console.info('finally!');
});
/script (took off the <>'s from this line too)

This is basically what's in their tutorial html page, but with extra include tags... It assumes that, as they suggest, you unzip extjs under the rails public/javascripts directory. For the example above, the directory was just named 'ext'.

With that in place (the onReady can be removed), a page using that layout can do something like this:

<%= link_to_function 'Ext Test',
"Ext.MessageBox.show({title: 'Ext Box',
msg: 'Ok?',
buttons: Ext.MessageBox.OK})" %>


(Note: this was Rails 2.2.2 with ExtJS 2.2.1)

Monday, May 4, 2009

More on Saving Dates to Oracle

I did some digging into rails after my last post (but before I saw the comment from Raimonds, so I still need to check that out -- thanks Raimonds!), and found that the setter method generated by rails is doing the timezone adjustment, turning the Date into a TimeWithZone (see the define_write_method_for_time_zone_conversion method in active_record/attribute_methods.rb).

However, in the process of determining which setter method to use, it looks at the skip_time_zone_conversion_for_attributes list, which controls whether to do the timezone conversion or just generate the standard setter method for the attribute. You can just add a line to your active record class, like this:

skip_time_zone_conversion_for_attributes << :my_date_attribute_name

With that line added, the date remains a date, and everything in the persistence layer is happy.

As I mentioned, I haven't tried it yet, but the new oracle enhanced adapter looks like the way to go. I thought I'd post this anyway, in case it's of use to anyone else -- there doesn't seem to be much written about "skip time zone conversion", and it might be useful in other situations...

Friday, May 1, 2009

Saving Dates to Oracle with JRuby / Rails

When you create date or datetime attributes in rails, both types get mapped to date columns in Oracle. The problem I was running into was trying to save date values (without time values).

Date attributes that were set in a view worked fine, but those that were set programmatically were getting saved with the time -- the time being offset from midnight based upon my timezone. I messed with the timezone setting ("config.timezone =" in environment.rb), but everything seemed to be working fine except this. It works if you set the attribute to a string value (i.e., '2009-05-01'), like the view / controller code does, but that's a pain.

So, digging through the activerecord extensions in jdbc_oracle.rb, I found that it tries to guess the type based upon whether there are values for hours, minutes, or seconds in the ActiveSupport::TimeWithZone objects that get sent through. The ones coming from the view had no hours, minutes, nor seconds, but the ones that had been set in the code (originally set to Date.today, for example) were getting converted into TimeWithZone objects that were midnight on the specified date, then adjusted for the timezone, turning the date part into the prior date. (I didn't dig through the rails code to find out where/how/why that conversion is taking place, but that might yield a better solution -- if any body knows, please share!) Since rails uses TimeWithZone objects, that seemed like a reasonable place to look.

Looking at the doc for TimeWithZone, it says that you shouldn't ever create instances directly, you should use TimeZone methods instead, via the Time.zone instance. The TimeZone methods include "today", "local", "parse" etc -- nice! So I tried each of those:
  • a = Time.zone.today
  • b = Time.zone.local(2009, 5, 1)
  • c = Time.zone.parse('2009-05-01')
Well, "b" and "c" worked -- they got saved to the database as dates -- but "a" acted the same as Date.today. It turns out that's exactly what Time.zone.today returns: a Date instance!?! I'm not sure why it works that way, but...

So, until I have time to dig through rails to see if there's a way to avoid the time zone conversion (besides setting the attribute to a string), I'll start using the TimeZone methods, with this little patch stuck in the config/initializers/ directory:

class ActiveSupport::TimeZone
def today
today = Date.today
self.local(today.year, today.month, today.day)
end
end

Again, if anyone has another suggestion, please share. Also, I'm not sure if this is JRuby-specific, but I don't have time to test that right now either...

(Note: running rails 2.2.2 w/ jruby 1.1.5 and activerecord-jdbc-adapter 0.9)

Tuesday, April 21, 2009

Rails 2 Functional Tests with HTTP Basic Authentication


I set up http basic authentication, as a quick way to show user-specific data for a demo I'm going to be doing.  Of course this broke my functional tests.

I found a solution here (thanks Flip!) -- manually adding an entry to session.env.  However, before I found that, I was digging through some of the rails code, and found a way to create the encoded credentials, which eliminates the string concatenation of Flip's solution (and may be a bit more robust going forward, in case the encoding changes in the future):

    @request.env['HTTP_AUTHORIZATION'] = 
         ActionController::HttpAuthentication::
             Basic.encode_credentials('username', 'password')

     (sorry about the line breaks -- tried to make it readable 
      without wrapping)

If anyone comes up with a more elegant solution, please let me know!

Wednesday, March 25, 2009

Warbler deployment to development environment


Warbler supports passing the environment on the command line, so if you do:

   warble war RAILS_ENV=development

and deploy the .war, it will use the development database connection, etc.

Wednesday, March 11, 2009

Multiple Gem Repositories

Found an easier way:  the -i option for gem install creates a new repository.  Just install a gem into an empty directory, and it creates a new repository (for example:  gem install rake -i new_repository) .  Just point GEM_HOME and/or GEM_PATH to the new repository, add the bin subdirectory to your PATH, and you're good to go.