Sep 27, 2011

Enabling Comments on New Models in LovdByLess

Today a project I've been working on called for enabling comments on a new model that I'd introduced into a lovdbyless application. Took me a couple hours to figure it all out, so I thought I'd write down the steps. Hopefully I won't miss any
  1. Link the new model with the existing comments model. This is easy to do, just add the association with :as => :commentable line to the new model. My final model looked like this:
      
    class Rating < ActiveRecord::Base
      has_many :comments, :as => :commentable, :order => "created_at asc"
      belongs_to :profile
      belongs_to :title
      validates_presence_of :rating, :message => "You didn't supply a rating"
    
      validates_uniqueness_of :title_id, :scope => :profile_id, :message => "You've already told us about that title"
    end
    
  2. Create a route for the comments that belong to the new model. I added this to my routes.rb:
      
      map.resources :ratings do |rating|
        rating.resources :comments
      end
    
  3. I had an existing view/partial. I had to add this code to it to make a link to the existing Thickbox comment form. It's lifted straight from the _blog.html.erb and adapted for use with my model
  4. <%= "Comments (#{rating.comments.size})" %> | <%= inline_tb_link('Add a Comment', "#{dom_id(rating)}_new_comment", :title => "Leaving A Comment") if @p %>
    
  5. Same partial. Here too, code is borrowed from the _blog.html.erb partial to get things moving along. This code provides the summary for the comments related to the model element. It also provides the hidden comment form div.
    <% rating.comments.each do |c| %> <%= render :partial => 'comments/comment', :locals => { :comment => c } %> <% end %>
  6. The special sauce is the last step that I couldn't troubleshoot easily. If you get this far and try to execute, you'll get HTTP status 500 errors from Mongrel when you try to submit the ThickBox form. It's all done in AJAX, so it's not too easy to diagnose. I found the missing step was to add a line or two in comments_controller.rb for my new model The first bit of the protected section had to change. Ultimately, this sets the @parent which is needed in the create method
        def parent; @rating || @blog || @profile || nil; end
        
        def setup
          @profile = Profile[params[:profile_id]] if params[:profile_id]
          @user = @profile.user if @profile
          @blog = Blog.find(params[:blog_id]) unless params[:blog_id].blank?
          @rating = Rating.find(params[:rating_id]) unless params[:rating_id].blank?
          @parent = parent
        end
    
I've modified the css quite a bit, but this is the result I got:

Aug 14, 2011

Beware the yield return

Stumbled on a weird performance issue the other day at work. We were seeing abnormal calls from the middleware to the database, and there were a lot of them.

I fumbled around in the code and found a bunch of yield returns. I was under the impression that the yield return was like a lazy initialize and that returning using yield meant that the IEnumerable would be evaluated once and then cached.

Boy, was I mistaken.

Turns out that the yield return and IEnumerable does not have any built in caching. To cache the results you have to call ToList on the IEnumerable and use the resulting List. If not, the block with yield return will be evaluated every time you traverse the IEnumerable variable. More like a delegate than a lazy initialized variable. For me, the delegate was accessing the database and it was used in more than one place.

Try this code as an example:

using System; 
using System.Collections.Generic; 
using System.Threading; 

namespace ConsoleApplication2 
{ 
    class Program 
    { 
        static void Main(string[] args) 
        { 
            var list = IterateRand.GetRandomNumbers(); 

            foreach (var v in list) 
            { 
                Console.WriteLine(v); 
            } 

            // Sleep so the Random is seeded with a new value
            Thread.Sleep(100); 

            Console.WriteLine(); 
            foreach (var v in list) 
            { 
                Console.WriteLine(v); 
            } 
        } 
    } 
    static class IterateRand 
    { 
        public static IEnumerable GetRandomNumbers() 
        { 
            var rand = new Random(); 
            for (int i = 0; i < 10; i++) 
            { 
                yield return rand.NextDouble(); 
            } 
        } 
    } 
} 
You should get two completely different sets of random numbers. It's fine as long as the consuming code is written in such a way that the IEnumerable is only used once. I'll have to be more careful how I use yield return in the future.


May 25, 2011

CakePHP Auth Conventions

CakePHP is something new that I'm picking up. My first exposure to web frameworks was Ruby on Rails. Like Rails, the Cake mantra is convention over configuration. It's an awesome way to program. You can save hours of work if you know what the conventions are and follow them. Even bypassing the conventions is not a superhuman effort, usually a few lines of configuration.

One issue I had with Cake was implementing controller authorization. As you might expect it's because I didn't know and follow convention.

I set up my AppController class to use the Auth module and set the mode to 'controller' in beforeFilter. That meant that each controller had to have isAuthorized function defined.

I knew that most of my controllers were going to be uniform in the rights granted to anonymous users, so I coded the isAuthorized method in the AppController too.

class AppController extends Controller {
    var $components = array('Auth', 'Session');
    var $uses = array('Role');

    function beforeFilter() {
        $this->Auth->authorize = 'controller';
    }

    function isAuthorized() {
        if (strcmp($this->action, "index") == 0 || strcmp($this->action, "view") == 0) {
            return true;
        }
        $user = $this->Auth->user('role_id');
        $admin = $this->Role->findByName('Admin');
        $admin = $admin['Role']['id'];
        if ($user == $admin) {
            return true;
        }
        else {
            return false;
        }
    }
}

I thought I'd allowed access to any "index" and "view" action program wide by always returning true from isAuthorized if those actions were requested. Admins had all CRUD rights and regular users had no additional rights over the anonymous users. Sub classes would further refine these rights if necessary.

The scheme didn't work as I expected though because the anonymous users seemed to be getting blocked from all actions, while regular users had the expected rights. Admins were also behaving as expected.

I had to modify my controller to this

class AppController extends Controller {
    var $components = array('Auth', 'Session');
    var $uses = array('Role');

    function beforeFilter() {
        $this->Auth->authorize = 'controller';
        $this->Auth->allow('view', 'index');
    }

    # defaulting all controllers to allow user access to view and index
    # anonymous access to be allowed by subclasses
    function isAuthorized() {
        $user = $this->Auth->user('role_id');
        $admin = $this->Role->findByName('Admin');
        $admin = $admin['Role']['id'];
        if ($user == $admin) {
            return true;
        }
        else {
            return false;
        }
    }
}

In the beforeFilter I had to specifically enable the actions I wanted open to anonymous use. I guess the Auth component will default to not authorize before checking the isAuthorized function if the session does not contain a user.

Spent a few hours tracking that down using pr($this->Auth->user()) calls and die to trace program flow.

May 18, 2011

WPF Binding not working? Check the Output Window

Took me a long time and hours of frustration troubleshooting WPF binding failures to finally find a silver bullet. Actually, this stuff is all over the place on the net, but for whatever reason, not mentioned in many WPF books.

If you are expecting to see a binding when you start your WPF application, but the value never changes, or is default, or the bound listview is empty when it should have data, check the output window in Visual Studio while you debug.

Bindings in WPF are best-efforts only by the framework. Not really sure why. I sometimes wish a giant unhandled exception error would show up instead of silent failure. Nevertheless...

Here's a sample error message you might find there.

Let's say you have the following XAML layed out and you're trying to bind a listview to a 'Parameters' ObservableCollection member of the control data context. But when you filled out the XAML, it was misspelled.

<DockPanel>
  <Label DockPanel.Dock="Top" Style="{StaticResource LayoutLabel}">Parameter Selection</Label>
  <Border  Style="{StaticResource LayoutBorder}">
    <ScrollViewer DockPanel.Dock="Bottom">
      <ListView View="{DynamicResource ParameterView}" ItemsSource="{Binding Path=Parametrs}"/>
    </ScrollViewer>
  </Border>
</DockPanel>

Now when you debug, the members of the 'Parameters' ObservableCollection don't appear in the listview. Go and check the output window and you might see this in the output window
System.Windows.Data Error: 39 : BindingExpression path error: 'Parametrs' property not found on 'object' ''OrderedEnumerable`2' (HashCode=57007955)'. BindingExpression:Path=Parametrs; DataItem='OrderedEnumerable`2' (HashCode=57007955); target element is 'ListView' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')
Lots of gobbledygook, but clearly in this message WPF is complaining that binding cannot be made with a Parametr property. It shouldn't be too hard to find the trouble with this information.

More often than not, it's not a spelling mistake, but a plain mismatch between the object being searched by WPF and the binding path given.

Example being if the aforementioned control had a datacontext set to 'MyObject' but the class of 'MyObject' doesn't have a 'Parameters' property at all.

May 14, 2011

Cool Places to Build Stuff

A survey of Software developers finds very narrow focus in what motivates. Even the most hardened veteran still gets tingles when building something new and learning.

If you haven't read 'Drive' by Daniel Pink yet, you should get on that. His insights are very true for us software people. Just a fantastic book. If you are feeling unmotivated by your decent paying job, get this book for some possible explanations.

Bottom line, it's very cool to build stuff. Along that vein, I've been visiting this blog a lot in the past few days. It seems to be written directly for me. I'm a shutterbug, a product review addict, and software professional (which they seem to have a requirement for).

I found a job listing on the site and it sounds like a wicked cool place to work. Lots of building stuff, very few meetings.

But the most interesting part isn't the description of themselves in the job listing, but their use of coding challenge to evaluate new candidates. It doesn't appear to be one of those high pressure 'code while we watch' type deals. Rather, you download the challenge data and produce a solution. Email them a link and your resume when you're done.

I might just do that.

Apr 29, 2011

Seth Godin Quote Post

A refreshing outlook on "My boss won't let me". You have to overcome this attitude to do something useful.
When we say, "my boss won't let me," what we're often saying is, "my boss wants great results, but she's not willing to let me take initiative without responsibility."
I'd be shocked if any smart boss took a different approach. Who's going to give you authority without responsibility
Thanks for the daily inspiration Seth.

Apr 22, 2011

Better Than Any Software Developer Compensation Package

I saw this promise in a Software Developer Wanted ad today
We promise you’ll never spend more than an hour a week in meetings.
Heady promise. Still, I know lots of developers who would go for this. At least, they will take a second look at the posting when they read that they aren't really expected to attend too many meetings.


Very clever ad. They definitely know their demographic.