Recent

Author Topic: What path to take when applying filters  (Read 4031 times)

Gustavo 'Gus' Carreno

  • Hero Member
  • *****
  • Posts: 1115
  • Professional amateur ;-P
What path to take when applying filters
« on: February 23, 2017, 09:29:59 am »
Hi there,

I decided to make a component that can monitor/control a qBittorrent client via it's WebUI API.

I'm at the point where I have to decide how to implement the filter parameters you can pass to the /query/gettorrent API call [WebUI Wiki].

My mind has been pondering various implementations, various historic standards learned by using components, new standards from Request/Response Objects and the PSR-7 of PHP.

Here's what I've been mulling over:
1. The lazy one:
  • Have a Filtered: Boolean property: this comes to mind from the DB aware components.
  • Have a Filter: String property: just have the user use 'param1=value1&param2=value' and blindly trust him to not f-up. Maybe point to the WebUI wiki for filter and value definitions
Pros:
  • Really easy to implement
Cons:
  • It's really against all the standards we've been trying so hardly to acquire.
2. The less lazy one
  • Have a Filtered: Boolean property.
  • Have a Filter: TStringList property: the user adds param=value lines. Still hoping the user does not f-up.
Pros:
  • Easy to implement.
  • It's a bit more standard compliant.
  • Slightly more secure.
Cons:
  • Still irks me a lot.
  • The fact that it's only slightly more secure is not of any comfort.
3. Steal it from PHP
  • Forget about the Filtered: Boolean property.
  • Have functions like withParam1(const aValue: String) that returns Self. One for each param.
Pros:
  • Chainable, which is all the rush at the moment.
  • Filters can be in a private FParams: record, so it would be very easy to contain.
Cons:
  • Not very compliant with my view of the "Component Way(c)".
  • Not having the Filtered: Boolean property will be a PITA to suss out what filters to include/exclude.
4. One more, for good measure
  • Have a Filtered: Boolean property.
  • Have a Filters: record property exposing a sub-level on the object for the params/values.
Pros:
  • It's contained.
  • User friendly.
  • Having Filtered: Boolean avoids using filters if not needed.
Cons:
  • Exposing stuff and not really being able to sanitize with getters/setters.
  • Since we only use filters upon calling GetTorrents(), why should we have those filters on the main object?.
5. Sinning by error of one
  • Forget about the Filtered: Boolean property.
  • Have a type TFilters = record.
  • Have a GetTorrents() and a GetTorrentsFiltered(const aFilters: TFilters).
Pros:
  • It's contained.
  • User friendly.
  • A lot less functions and objects being passed around.
  • Separation of concerns box is ticked since the record is not part of the main object.
Cons:
  • You have to initialize another variable to pass to the filtered function.
  • After writing all this I kinda have answered my question.
  • I do suffer from insecurity, so I still need some input  ::)

So... Please give me your honest opinion.

Thanks very much to anyone that has the patience to read it all!!

Cheers,
Gus
Lazarus 3.99(main) FPC 3.3.1(main) Ubuntu 23.10 64b Dark Theme
Lazarus 3.0.0(stable) FPC 3.2.2(stable) Ubuntu 23.10 64b Dark Theme
http://github.com/gcarreno

Thaddy

  • Hero Member
  • *****
  • Posts: 14223
  • Probably until I exterminate Putin.
Re: What path to take when applying filters
« Reply #1 on: February 23, 2017, 10:33:54 am »
I would go with the functional approach like in you noticed in PHP (not always) or KOL (Always)  (Yes the KOL Object Pascal framework works like that too, so it's not stealing from PHP  8-) )
But there are many that don't agree with me.

OTOH that approach makes filtering easy and readable. Functions returning self can be very powerful and have my preference over a procedural approach.
Things like LINQ (KOL predates LINQ by a good many years) are based on the same principle and I like that because of its readability.  The order in which a filter is applied is immediately obvious.

What I like is that you obviously have an interest in your architecture and given it already a lot of thought. I am sure you will find the right one for the job at hand.
« Last Edit: February 23, 2017, 01:46:01 pm by Thaddy »
Specialize a type, not a var.

Gustavo 'Gus' Carreno

  • Hero Member
  • *****
  • Posts: 1115
  • Professional amateur ;-P
Re: What path to take when applying filters
« Reply #2 on: February 24, 2017, 06:33:19 am »
I would go with the functional approach like in you noticed in PHP (not always) or KOL (Always)  (Yes the KOL Object Pascal framework works like that too, so it's not stealing from PHP  8-) )

Well, if I'm honest, I think that jQuery was, historically, one of the first ones to get that into the wild :)

But there are many that don't agree with me.

Yeah, ya know, hatters will be hatters :)

OTOH that approach makes filtering easy and readable. Functions returning self can be very powerful and have my preference over a procedural approach.
Things like LINQ (KOL predates LINQ by a good many years) are based on the same principle and I like that because of its readability.  The order in which a filter is applied is immediately obvious.

Yeah, I was expecting someone to advocate this. jQuery, PHP and LINQ are all into that. And for all the reasons you're mentioning.

I'm still a little more inclined towards option 5. Probably because I like the PHP way of creating a $options=array(...) and then just calling the function with that...

I'll wait for some more input and then probably toss a coin, LOL!!

What I like is that you obviously have an interest in your architecture and given it already a lot of thought. I am sure you will find the right one for the job at hand.

Unfortunately, for the crawling pace I'm at now, thinking has been probably most of I've been doing.

I like to get it right. That's obviously not good, since we all know that perfection is the enemy of release. And also, I'm too old and have WAY too many standards floating in my head. And not only from Object Pascal.

All the new stuff that is coming out in terms of TDD and best practices in the fields of OOP, Functional programming and all these new architectures/frameworks etc...

All this floats around in my head and just gets in the way... Hence trying to ask for help instead of completely over-thinking it!

And for your input, MANY, many thanks!!

Cheers,
Gus
Lazarus 3.99(main) FPC 3.3.1(main) Ubuntu 23.10 64b Dark Theme
Lazarus 3.0.0(stable) FPC 3.2.2(stable) Ubuntu 23.10 64b Dark Theme
http://github.com/gcarreno

Thaddy

  • Hero Member
  • *****
  • Posts: 14223
  • Probably until I exterminate Putin.
Re: What path to take when applying filters
« Reply #3 on: February 24, 2017, 07:30:17 am »
jQuery dates from 2006? KOL dates from 2000.... But anyway functional programming is much older....

But other than that: you are proficient. I would hire you. Make the right decision - not necessarily the functional approach. make a decision on merits - You probably will. Future is bright... 8-)
« Last Edit: February 24, 2017, 07:47:19 am by Thaddy »
Specialize a type, not a var.

Gustavo 'Gus' Carreno

  • Hero Member
  • *****
  • Posts: 1115
  • Professional amateur ;-P
Re: What path to take when applying filters
« Reply #4 on: February 24, 2017, 07:42:43 am »
jQuery dates from 2006? KOL dates from 2000.... But anyway functional programming is much older....

I stand corrected. In my, very feeble, defence, I don't know the KOL framework and was lazy to investigate it :-\

But other than that: you are proficient. I would hire you. Make the right decision.

Well, thanks man!!

Unfortunately, due to over thinking and other stuff, I suffer from depression and I'm pretty much unemployable :-\
That's why, now I just play with my favourite language and have fun, not meltdowns :D

If not, I would hold you to your word :D

Cheers,
Gus
Lazarus 3.99(main) FPC 3.3.1(main) Ubuntu 23.10 64b Dark Theme
Lazarus 3.0.0(stable) FPC 3.2.2(stable) Ubuntu 23.10 64b Dark Theme
http://github.com/gcarreno

Thaddy

  • Hero Member
  • *****
  • Posts: 14223
  • Probably until I exterminate Putin.
Re: What path to take when applying filters
« Reply #5 on: February 24, 2017, 07:49:11 am »
See private
Specialize a type, not a var.

Gustavo 'Gus' Carreno

  • Hero Member
  • *****
  • Posts: 1115
  • Professional amateur ;-P
Re: What path to take when applying filters
« Reply #6 on: February 24, 2017, 08:00:47 am »
See private

When you say this, do you mean the in board messages?

Cuz I went there, and my email client and I have no new message.

Am I missing another private area?

Cheers,
Gus
Lazarus 3.99(main) FPC 3.3.1(main) Ubuntu 23.10 64b Dark Theme
Lazarus 3.0.0(stable) FPC 3.2.2(stable) Ubuntu 23.10 64b Dark Theme
http://github.com/gcarreno

Gustavo 'Gus' Carreno

  • Hero Member
  • *****
  • Posts: 1115
  • Professional amateur ;-P
Re: What path to take when applying filters
« Reply #7 on: February 24, 2017, 03:43:35 pm »
Hey there,

In the end I opted for Thaddy's suggestion on having the possibility of chained functions, and got the whole thing out of the context of the main component:

Code: Pascal  [Select][+][-]
  1. // From FPDoc output:
  2. type TqBTorrentsFilter = class(TObject) end;
  3. public
  4.   constructor Create;
  5.   destructor Destroy; override;
  6.   function Clear;                     // Clears the filters.
  7.   function withFilter();              // Adds the filter named 'filter'.
  8.   function withOutFilter;             // Removes the filter named 'filter'.
  9.   function withCategory();            // Adds the filter named 'category'.
  10.   function withOutCategory;           // Removes the filter named 'category'.
  11.   function withSort();                // Adds the filter named 'sort'.
  12.   function withOutSort;               // Removes the filter named 'sort'.
  13.   function withReverse();             // Adds the filter named 'reverse'.
  14.   function withOutReverse;            // Removes the filter named 'reverse'.
  15.   function withLimit();               // Adds the filter named 'limit'.
  16.   function withOutLimit;              // Removes the filter named 'limit'.
  17.   function withOffset();              // Adds the filter named 'offset'.
  18.   function withOutOffset;             // Removes the filter named 'offset'.
  19.   property Filters: string; [r]       // A String with the filters already delimited by '&'.
  20.   property Count: Integer; [r]        // The amount of filters contained in the object.
  21.  

And use it on:
Code: Pascal  [Select][+][-]
  1. // From FPDocs output:
  2. type TqBitTorrentWebUI = class(TComponent) end;
  3. public
  4.   {...}
  5.   function GetTorrents;                          // Queries the API for torrents.
  6.   function GetTorrentsFiltered({<-- HERE -- });  // Queries the API for torrents applying a filter.
  7.   {...}
  8.  

Internally I have a:
Code: Pascal  [Select][+][-]
  1. private
  2.   {...}
  3.   function DoGetTorrents(const aFilter: TqBTorrentsFilter): Boolean;
  4.   {...}
  5.  

Both GetTorrents() and GetTorrentsFiltered() call that private function, having the first one just use nil when it calls said private function.

The class is, non other than, a very specialized wrapper around TStringList where in the creator I make sure:
Code: Pascal  [Select][+][-]
  1. constructor TqBTorrentsFilter.Create;
  2. begin
  3.   FFilters := TStringList.Create;
  4.   FFilters.NameValueSeparator := '=';
  5.   FFilters.AlwaysQuote := False;
  6.   FFilters.Delimiter := '&';
  7. end;
  8.  

I also make sure that:
  • if any with<FilterName> is called twice I edit the value set on the first call.
  • when I call any withOut<FilterName> the entry is deleted.

The code for adding and deleting is a bit ugly and really makes me long for the ease of PHP array(), isset() and unset(). :D

Cheers,
Gus
Lazarus 3.99(main) FPC 3.3.1(main) Ubuntu 23.10 64b Dark Theme
Lazarus 3.0.0(stable) FPC 3.2.2(stable) Ubuntu 23.10 64b Dark Theme
http://github.com/gcarreno

 

TinyPortal © 2005-2018