-
Notifications
You must be signed in to change notification settings - Fork 385
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Filterable Interface? #86
Comments
That is a really cool idea.. I think the concept of "stored procedures" in this manner would be very useful. It seems like adding a // providing the res, req as args.. though could just take the filter name as a string
// what if user wants to do some http/2 server push, or maybe the req has &count=x&offset=y, then
// they could pull just those results to do filtering on?
func (r *Review) Filter(res http.ResponseWriter, req *http.Request) ([][]byte, error) {
filter := req.URL.Query().Get("filter")
var result [][]byte
switch filter {
case "published":
// get all our data out in time-sorted order
jj := db.ContentAll("Review__sorted")
// decode to Go structs
var reviews []Review
var review Review
for i := range jj {
err := json.Unmarshal(jj[i], &review)
if err != nil {
return nil, err
}
reviews = append(reviews, review)
}
// keep reviews with timestamp <= now()
now := time.Now()
for i := range reviews {
rev := reviews[i]
ts := time.Unix(rev.Timestamp/int64(1000), 0)
if ts.Before(now) {
j, err := json.Marshal(rev)
if err != nil {
return nil, err
}
result = append(result, j)
}
}
}
return result, nil
} It feels a bit clunky at first pass, but I think if a Ponzu user is going to want this level of control, its not too much work.. what do you think? Also, just to clarify, this would only be run on a /api/contents request (multi-result, not single), right? |
I'll work through your code, but yes, only on '/api/contents', to filter rows from the dataset. I can see it's already possible suppress fields from the content item itself. |
On second thought.. we may be better off thinking about adding some hook into the db package, since the example above would need to buffer the entire data set for By adding a hook into the db package, I mean somehow passing the filtering func into something like db.ContentFilter(namespace string, func(data []byte) (bool, error)) ([][]byte, error) This way it could be called from inside a *Bolt.Tx transaction, and process each row the same way it's done here: It would change the above implementation, and simplify it a bit, to something more like: func (r *Review) Filter(name string) func(data []byte) (bool, error) {
var fn func(data []byte) (bool, error)
switch name {
case "published":
now := time.Now()
fn = func(data []byte) (bool, error) {
var rev Review
err := json.Unmarshal(data, &rev)
if err != nil {
return false, err
}
ts := time.Unix(rev.Timestamp/int64(1000), 0)
if ts.Before(now) {
return true, nil
}
}
}
return fn
} I'm not sure if that code above is even correct, but I hope it illustrates the point. I really like this idea though -- I appreciate you keeping log of new features/concepts... |
Hey @nilslice I'm keen to have a go at this also, but you'll have to forgive me, interfaces in practice are not my strong point yet. I've put together a strawman of how I think it could work (totally outside of Ponzu here), but I can't really assess the practicalities. I wonder, if you get chance could you let me know if I'm on an efficient or workable track. If you could advise in the context of this example that would be an enormous help too - there's a lot of code to follow in Ponzu :) |
Just to add, I envisaged checking if item implements filterable interface in the |
It's also occurred to that a map better, given the filters might be individually callable over API, a key to reference them would be needed |
@olliephillips - the code looks good, and is essentially the pattern I was thinking as well. I have a couple questions/considerations on what you think would best serve the project & our users.
If the Filter(s) shouldn't override all content API results, and be applied selectively, then we need some control over how they are applied from both HTTP API and DB package calls. We could add an option to the This is a pretty big feature, and I'd be glad to try and pair on it if you'd like, via screen share or something.. I'm in Los Angeles, CA though so the time difference may be tough. Happy to try and make it work if you're interested. /cc @kkeuning, @sirikon who have added some great features and I think would have helpful feedback if they'd like to offer it |
Thanks for the feedback on that piece. In terms of:
If the scope is bigger than I can see, it might be a feature that's too big for me at the moment, since I'm still getting familiar with the Ponzu codebase - but I'd give it a shot :) I'll have to work on this in my spare time, and since I'm in the UK, I'm not sure I can make a useful pair on this feature, but again happy to give it a shot. |
Cool - I think we are on the same page for all these points, except maybe the need for another API endpoint. After considering it a bit more, I think we can easily add this to the Since I am planning to move a couple things around, which would impact this Filterable concept, I think it would be best for me to work on it and check in with you to make sure things are still in line with your goals. If there are parts that you want to work on in parallel with me, that would be great and I'll open a new branch for this once it's ready. Do you have other considerations for creating a new API endpoint, other than colliding with existing code? I'm just not convinced that adding more than a type assertion and check for the |
Appreciate you looking for feedback on this. Not had lot of time on it, but one thing jumped out at me today.... without knowing the data/item type requested, my approach i.e with separate endpoint on which a view/filter is requested by name leaves me having to work out the data/item struct called from the view name requested. And I'm not sure reflection will get me that far. So I'd more or less modified my thinking to including the data struct the filter should work on, in the And this, I think, makes it little different than what you are suggesting - just another querystring parameter on the So I think your proposal has smaller footprint actually 👍 Please do include me in the development work, even if only for review. |
Ok, sounds good! To clarify:
Since the query params will still include the type name, like this: We'll be able to get the type from our Does that cover what you're unsure of? |
Sorry, I wasn't clear. I think we are on the same page. In my comment I was referring to my proposed approach being weak - the separate endpoint. Not yours. When passed only a view name like this:
My initial idea can't work - I need the object type name or the filter interface implementation is useless? But by including type name, it's effectively identical to your suggestion: You had:
or
I'd have had to do this:
Which is, bar, semantics, absolutely the same? I'm sorry for any confusion - I was in total agreement with your proposal to extend the |
@olliephillips ok, got it! yes, those would be the same aside from semantics. thank you for clarifying |
I also found myself reaching for something like this in thinking about how I would implement soft-deletions, by having a |
@ivanov - that is interesting.. how would you anticipate it to be configured from the user's perspective? as in, without a func (r *Review) Filter(name string) func(data []byte) (bool, error) {
var fn func(data []byte) (bool, error)
switch name {
case "published":
now := time.Now()
fn = func(data []byte) (bool, error) {
var rev Review
err := json.Unmarshal(data, &rev)
if err != nil {
return false, err
}
ts := time.Unix(rev.Timestamp/int64(1000), 0)
if ts.Before(now) {
return true, nil
}
}
default:
// call some function or process data directly here
// which would run on every request where filter was
// not explicitly set
}
return fn
} I think we'd also need to add a http Hook, since the Filter method is in the // in system/item/item.go
type Hookable interface {
// ...
BeforeFilter(res http.ResponseWriter, req *http.Request) error
AfterFilter(res http.ResponseWriter, req *http.Request) error
} Whereby returning an error from Your point also highlights what would have been a bug... the Filterable interface was initially only going to be called on Thanks for your feedback on this.. if you have any more thoughts or suggestions please keep them coming! |
Closing this issue. It is on the project roadmap. Please reopen if need to. |
Again, just thinking out loud. I know we discussed queries/reports early on and I'll quite happily do this in the client app, but it keeps striking me that a filterable interface could be useful.
A single method
item.filter(data) (data) {}
This would allow some basic data manipulation of item type resultset, by ranging through it and making comparisons.
One example, removing items with a timestamp in the future - unpublished as yet if you like. This could be simply achieved if this behaviour was preferred.
Taking it further, if multiple filters could be specified and the API could also accept filter in the querystring to select which filter to use, items could have their own stored procedures in effect.
This cuts down on the handballing of data client side and is maintainable and extendable.
I don't think I could put this together, but wanted to log it. Hope that's ok.
The text was updated successfully, but these errors were encountered: