TOPIC: A Small Design Issue
#1139
A Small Design Issue 2 Years, 6 Months ago
Ruby on Rails has dynamic "finder" methods for querying the database. For example:

User.find_by_login_and_password(login, password)

In Suneido you would do:

Query1("users where login=" $ Display(login) $ " and password=" $ Display(password))

This is longer and harder to read and you can forget to use Display.

One thing I don't like about the Rails dynamic finders is that the field names (in the method name) are separated from the values (in the arguments).

I like this better:

QueryFind1('users', login: login, password: password)

It is easy to implement:

Code:

QueryFind1

function (@args)
    {
    query = args[0]
    args.Delete(0)
    where = ""
    args.Each
        {|value, member|
        where $= 'ntwhere ' $ member $ '=' $ Display(value)
        }
    return Query1(query $ where)
    }


This could be added to Query1 but it is built-in so I made this a new function for now.

But then I wanted the same for QueryFirst, QueryLast, and QueryAll. But, of course, I did not want to duplicate the code.

I took advantage of Suneido's ability to pass functions around and wrote:

Code:

QueryFind_

function (fn, args)
    {
    query = args[0]
    args.Delete(0)
    where = ""
    args.Each
        {|value, member|
        where $= 'ntwhere ' $ member $ '=' $ Display(value)
        }
    return fn(QueryAddWhere(query, where))
    }


Note: I had to use QueryAddWhere in case the query had a sort (which has to be at the end).

Then the individual functions were like:

Code:

QueryFind1

function (@args)
    {
    return QueryFind_(Query1, args)
    }


It worked fine, but I was feeling uncomfortable because I had not written any tests. All I really needed to test was QueryFind_ and preferably without actually querying the database (slow and unnecessary).

Then I realized that it was simpler (and more easily tested) if QueryFind_ just returned the query.

Code:

QueryFind_

function (args)
    {
    query = args[0]
    args.Delete(0)
    where = ""
    args.Each
        {|value, member|
        where $= 'ntwhere ' $ member $ '=' $ Display(value)
        }
    return QueryAddWhere(query, where)
    }

QueryFind1

function (@args)
    {
    return Query1(QueryFind_(args))
    }


Then I could easily write a test:

Code:

QueryFind__Test

Test
    {
    Test_main()
        {
        AssertEqw(QueryFind_(['test']), 'test')
        AssertEqw(QueryFind_(['test sort one']), 'test sort one')
        AssertEqw(QueryFind_(['test', one: 'two']), 'test where one="two"')
        AssertEqw(QueryFind_(['test', one: 'two', three: 44]),
            'test where one="two" where three=44')
        AssertEqw(QueryFind_(['test sort abc,def', one: 'two', three: 44]),
            'test where one="two" where three=44 sort abc,def')
        }
    }


Later I realized that I could have tested the original version by passing a dummy function like:

Code:

        fn = function (s) { return s }
        AssertEqw(QueryFind_(fn, ['test']), 'test')


But I think the second version is better because it is simpler. Just because it is possible to pass functions does not mean you should!

NOTE: Using object.Each like this is a new feature that will be in the next release.
 
 
andrew