Using GORM efficiently to retrieve join data - Grails 2.3 - performance

I have User, and Follow Domain class that construct the Follower, Following relationship like Twitter. When my User visits another User's page, and click on their Follower list. I pop-up the list of the visited person's follower list, and then show a button in front of it that is labeled "Follow" or "Unfollow" depending on if you already following that person. So I do it the following way, but I'm not sure if this is efficient or maybe there is a better way of doing it. In order to make it efficient, I only retrieve 20 follower at a time and allow pagination.
> example of pop-up:
> ----------------------------------------------
> alex [follow]
> dave [unfollow]
> sarah[follow]
> paul [follow]
> ----------------------------------------------
Follow domain class has:
// The person to follow
User follow
// The person who is following the follow
User follower
// The status of the relationship if removed or still following
boolean status
User domain class is the typical Spring Security User class with a bunch of extra fields like locations and etc.
In my controller when I receive a request for follower list I do the following.
def visitedUser = User.get(visitedUserId)
// get the list of top 20 followers of this user
def followerList = Follow.findAllByFollow(visitedUser, [max:20])
// get the list of all people current user follow who is in the 20 follower list
def mutualFollow = Follow.findAllByFollowerAndFollowInList(currentUser, followerList)
Now I have the list of all the followers in profile of the person I'm visiting, and also the list of those who are mutual. Then I pass both to my GSP and while loop through the followerList in GSP, I compare the id of that follower, if the follower exist in my mutualFollow list I tweak the button to "unfollow" otherwise I keep the button as follow.
One step further to optimize this is to user projection to only retrieve the id of the mutualFollow instead of the whole USER domain object, but since USERs coming back are proxied and they have wrapper around them, I'm not sure that makes a big difference. Because All I retrieve is the id.
I appreciate any suggestion to improve this approach or an alternative. Thanks

I decided to use HQL and only retrieve the necessary info of those USERs within the follow list, and then I did a join to get the mutual people they both follow. And used that in the GSP page. Here is the simplified solution in SQL:
SELECT Ur.follow_id, Urs.follow_id as mutual FROM (SELECT * FROM FOLLOW WHERE FOLLOWER_ID=1)as Urs
RIGHT JOIN (SELECT * FROM FOLLOW WHERE FOLLOWER_ID=3) as Ur
ON Urs.follow_id=Ur.follow_id order by Ur.follow_id;

Related

How to make an API endpoint in Laravel, that can be used to search any table we want?

Background
Let us consider a hypothetical scenario. Suppose we have five different tables in our database,
Customers
Categories
Products
Orders
OderDetails
Our client wants us to add a search bar to the frontend, where a user can search for a specific product and upon tapping the search button, matching products has to be displayed on the frontend.
My Approach for Tackling This Problem
In order to add the aforementioned functionality, I came across the following strategy.
👉 I would add an input box to ender the product name and a submit button.
👉 Upon pressing the submit button, a GET request will be sent to the backend. On the query parameters, the product name that has been entered by the user will be included
👉 Once the GET request is received to the backend, I will pass it down to the ProductsController and from within a method defined inside the ProductController, I will use the Product model to query the products table to see if there are any matching results.
👉 If there are any matching results, I will send them to the frontend inside a JSON object and if there aren't any matching results, I will set a success flag to false inside the JSON object and send it to the frontend
👉 In the frontend, if there are any matching results, I will display them on the screen. Else, I will display "No Results Found!"
Problem with My Approach
Everything works fine if we only want to search the products table. But what if, later our client tells us something like "Remember that search functionality you added for the products? I thought that functionality should be added to orders as well. I think as the list of orders by a user grows and grows, they should be able to search for their previous orders too."
👉 Now, since in our previous approach to search products was implemented in the ProductController using the Product model, when we are adding the same functionality to Orders, WE WOULD HAVE TO DO THE SAME THINGS WE DID IN THE ProductsController AGAIN INSIDE THE OrdersController USING THE Order model. It does not take seconds to understand that this leads to duplication of code.
Problem Summary
❓ How do we add an API endpoint in laravel for a search functionality that can be used to search any table in our database and get results, instead of the search functionality being scoped to a specific controller and a corresponding model?
A good start would be to create a trait called something like searchable and add it to all the models you want to search and put any shared logic between the different models in there. Possibly you'd want to manually allow different columns so in each model you have an array of searchable columns which you'd refer to in your trait.
Your controller would have to point to the related model and call a method in the trait that searches.
As others have pointed out this is a high level question so I won't go too much in detail. Of course there are a million ways to implement this, just try and keep shared logic in place.

Laravel many-to-many

I've Googled for a while and found multiple threads discussing problems like this, but I just can't get my head around how to do exactly what I want to do, hope someone can point me in the correct direction.
I'm making a learning platform in Laravel. What I want right now is that when lesson A is completed by someone belonging to workplace B a notification should be sent to user C. So I have made a table notification_receivers containing lesson_id, workplace_id, and user_id, pointing to the respective tables.
Of course, I also have the corresponding models (User, Lesson, Workplace) set up, but what I can't understand is exactly how to set up the model relations. I'm currently making the Blade template used for editing the notification receivers belonging to a particular lesson, and I need to make the following: Get all notification receiver users for this lesson and then for every one of those users, get the related workplaces.
My first try was this in the Lesson model:
public function notification_receivers(): BelongsToMany
{
return $this->belongsToMany('App\User', 'notification_receivers')->withPivot(["workplace_id"]);
}
Which of course doesn't work straight off, since some users will be returned multiple times (once for each workplace). How do I do to get every user just once?
And when I have my users, how do I get the workplaces for each user? If I get it to work, the withPivot above will give me the IDs, but how do I get a collection of the Workplaces?

Handle model dependencies in Laravel Repository Pattern

I'm discovering the Repository Pattern for my Laravel project but I have to say that I'm a bit lost once a model has several dependencies and the examples on the web are always basic and don't answer the question for more complex use cases.
Let's imagine a user on my app. He can have badges, he has different things on the app that will be slightly modified when he first performs the action (when he first sees the comments, I tell him once the different things he can do, etc), he has several "counters" to record the number of comments he made, the number of friends he invited, without having to count each entry each time.
My database looks like this:
users table:
id
pseudo
name
password
...
badges table:
user_id
badge1_xxxxxx
badge2_xxxxxx
...
I have a very limited number of badges so I decided to create a column for each of them and as soon as a user wins a badge, I get his entry (in OneToOne relationship) and I indicate that the badge in question has been won.
onboarding table:
user_id
seen_comments (boolean)
seen_results (boolean)
...
As you can see, I store each action I'd like the user to do in different columns and as soon as he has done one and I've been able to modify my app accordingly (by showing him an arrow, etc), I put the column in question to true.
user_counters table:
user_id
count_comments
count_invited_friends
...
I don't consider a user to be a user if he doesn't have an entry in each of the tables (I could have done everything in one table but the users table seemed to me to become huge). The only relationship used is OneToOne between the user and the table in question.
Should I do this ?
class UserRepository {
public function register($data) {
// Create the user
$user = User::create($data);
// Create all its dependencies which are required if I want to consider the user as fully registered in my DB
$user->badges()->create();
$user->onboarding()->create();
$user->counter()->create();
// Return the user
return $user;
}
}
Or should I create a Repository for each of these elements and create the entire user in a UserService ?
How far should I separate things and when does it become overkill?
Is there something that I don't understand in concept of Repository ? If so, could you give me some links that you found useful because I feel like I ran out of ideas for search keywords.
Thanks

Get all rows user is authorised to see

I have Book and Page models.
Users may be allowed to see individual pages from the book or the whole book.
For example, I have the following pages table:
id | page_number | book_id
1 1 4
2 2 4
3 3 4
User A has permissions to see only pages 1 and 2, how can I fetch from the database only the pages he has permissions to view (i.e., 1,2)
Also, User B has permission to see the whole book (the permission assigned to the book record, not for all pages in the book), so he should get pages 1-3 (and maybe future pages that may be added into the book).
Is it possible to implement this in Laravel? Maybe with bouncer or Laravel-permissions?
Probably easiest to keep this simple: I wouldn't look to a package to implement, I think perhaps just a couple of relationship tables on the User where the pages or books are the models that user is authorized to see:
book_user, where you have a user_id and a book_id
And
page_user where you have a page_id and user_id plus a book_id on the page model itself so that you can pull the pages for a specific book that the user is authorized to see.
Make your many to many relationships on the User along with a pivot for book_id on the pages relationship, and then pull the appropriate authorization after you have the current user. For example:
// $user set above - might be $user = \Auth::user() or from an id from a form
if(isset($user->books->find($someBookID))
$book = Book::find($someBookID);
// pages below might be related to a specific book: pages->where('book_id', $something)
elseif(count($user->pages->pluck('page_id'))
$pages = Page::whereIn('page_number', $user->pages->pluck('page_id')->toArray())
And then send these to your view based on a bit more logic within the if-checks above.
This is really just pseudo code, as I don't know much about the rest of your codebase, but hopefully the idea behind this will give you an easy solution to what you've asked.

Multiple levels of authorization with cancancan

I am facing a challenge. A multi level authorization system and a friend told me cancancan could help me out.
I took a read of documentation but couldn't figure it out. So I'll explain here.
My goal is to create a sys were:
User "John" from South department creates Jane
User Jane creates June an Paul
Users June and Paul create Bob, Nat and Emily
Also
User Peter from North department creates Rita
User Rita creates Becca and Lisa
Users Becca and Lisa create Clara, Lory and Alexa
Now, each user can see only records made by themselves, or from users that are below than, but not above or from other departments.
How should I start with the models and everything?
First of all, you will need to keep track of this tree at database level. In order to do that, you can add a parent_id reference to the users table that will reference the user that created that particular user. This column will be empty for those users that were not created by anyone, and would hold the id of the parent user otherwise. I would highly recommend using a foreign key too, in order to guarantee referential integrity.
Then you will need to define your CanCanCan abilities in a way that this tree structure is traversed properly. You can take as a reference this example derived from the docs:
class Ability
def initialize(user)
can :read, Photo, Book do |resource|
resource.creator.descendant_of? user
end
end
end
I am assuming the User model has a descendant_of? method that traverses the tree from the current user towards the root checking whether the given user is a parent of the creator of the resource. A possible implementation could look like this:
# app/models/user.rb
def descendant_of?(target_user)
parent_id == target_user.id || parent&.descendant_of?(target_user)
end
Depending on the characteristics of your users tree, this proposal can quickly become poor in terms of efficiency, as all users from the owner to the root may be loaded from database in order to perform these checks. Here a couple of ideas that could be worth to explore in case you start facing these issues:
Use an array-like type for the parent_ids column to store the whole list of parent ids. The main drawbacks I see with this proposal is that it adds some overhead if there are changes in the tree, and referential integrity constraints gets lost (AFAIK it is not possible to add a foreign key in this scenario).
In addition to the proposed above, modify the CanCanCan ability to run the query directly against the database instead of doing that check in memory (executing ruby code).

Resources