4

I am creating a system of newsfeed, and as you can easily guess, it is beyond my skills. Please be kind to put me on the right track or provide something I can go on with.

I have several hundred events (model name is Event1, table 'events') I also have a pivot table in which users can assign any event's importance (values 0,1,2,3)

The relevant columns of the pivot table user_attitudes (Model Userattitude) are id, item_type, item_id, importance, attitude, creator_id An example three record are:

456 - event - 678 - 2 - 4

457 - event - 690 - 3 - 15

458 - event - 690 - 1 - 4

459 - participant - 45 - 1 - 4

Plain English: Total aggregated importance of the event #690 is '4', while the event #678 is '2'. Therefore in my ranking the event #690 should be listed as first.

Just to see the bigger pic: the user #4 also rated participant # 45 as importance = 1.

The table services many models - the above example include two - just to give a better image of what I have.

WHAT I NEED:

  1. I wish to print a ranking of top 5 events (and later other models). I wish to be able to use two methods of calculating the total score:

    • by counting the actual value (0,1,2,3)
    • by counting any value above 0 as 1 point.
  2. I want to generate views which filter events by this criteria:

    • at least one user set the importance to '0' (I need it to flag an event as untrustworthy)
    • events which has not been rated yet
    • reverse of the above - events which are rated by at least one user
    • events listed by number of users who assigned any importance to it
  3. This is easy, but still I have no idea how to make it happen. The same filters as the above #2, but related to a particular user decisions:

    • list 5 or 10 events (random or newest) which has not yet been rated by the user maybe something like this would be an answer:

    $q->where('creator_id', '=', Auth::user()->id);

Relevant code: As I don't really grasp the merged relations, I might fail to show everything needed to provide help - ask for more code in comments.

Models:

Event1 (table 'events'):

public function importances()
{
    return $this->morphMany('Userattitude', 'item');
}

public function user_importance($user)
{
    return $this->morphMany('Userattitude', 'item')->where('creator_id', ($user ? $user->id : NULL))->first();
}

User: (table 'users' - standard user table)

public function importances()
{
    return $this->hasMany('Userattitude', 'creator_id');
}

In model Userattitude (different from User, table name 'user_attitudes')

public function events()
{
    return $this->morphTo('item')->where('item_type', 'event');
}



public function event()

    {
        return $this->belongsTo ('Event1', 'item_id');
    }

PROBLEMS IN REPLY TO @lucas answer:

PROBLEM 1.

table name 'items' keeps me confused as in my project 'items' are events (model Event1), the participants (model Entity) and other objects. Can we stick to my naming until I get hold of the knowledge you are providing? it also contains column named attitudes, which is used for blacklisting particular items. For instance, an item of type 'entity' (possible participant of multiple events) can be voted by user two-wise: - by importance set by an user (we are doing this now, values available to use are 0,1,2,3) - by attitude of an user toward (possible value (-1, 0, 1) Such solution allows me to compute karma of each item. For instance -1 x 3 = -3 (worst possible karma value), while 1 x 2 = 2 (medium positive karma).

In consequence I am unable to use queries with the users method. It is still too confusing to me, sorry. We diverted too far from my original mental image. Consider this query:

$events = Event1::has('users', '<', 1)->get();

If in Event1 I declare

    public function users()
{
    return $this->morphToMany('User', 'item', null, null, 'creator_id');
}

Note: User is the standard users table, where username, password and email are stored

I get this error:

[2014-12-28 05:02:48] production.ERROR: FATAL DATABASE ERROR: 500 = SQLSTATE[42S02]: Base table or view not found: 1146 Table 'niepoz_niepozwalam.items' doesn't exist (SQL: select * from `Events` where (select count(*) from `users` inner join `items` on `users`.`id` = `items`.`creator_id` where `items`.`item_id` = `Events`.`id` and `items`.`item_type` = Event1) >= 1) [] []

if I change the method definition to

    public function users()
{
    return $this->morphToMany('Userattitude', 'item', null, null, 'creator_id');
}

Note: Userattitude is model (table name is 'user_attitudes') where i store user judgments. This table contains columns 'importance' and 'attitude'.

I get the same error.

If I change the method to

    public function users()
{
    return $this->morphToMany('User', 'Userattitudes', null, null, 'creator_id');
}

I get this:

[2014-12-28 05:08:28] production.ERROR: FATAL DATABASE ERROR: 500 = SQLSTATE[42S22]: Column not found: 1054 Unknown column 'user_attitudes.Userattitudes_id' in 'where clause' (SQL: select * from Events where (select count(*) from users inner join user_attitudes on users.id = user_attitudes.creator_id where user_attitudes.Userattitudes_id = Events.id and user_attitudes.Userattitudes_type = Event1) >= 1) [] []

Possible solution: the 'user_attitudes' table alias with name 'items'. I could create a view with the required name. I did it, but now the query produces no results.

PROBLEM 2

should I rename creator_id into user_id - or keep both columns and keep duplicated information in them? The creator_id follows conventions and I use it to create records... how to resolve this dillema?

PROBLEM 3.

As far as I understand, if I want to get a USER-RELATED list of top-5 events, I need to ad another line to the code, which narrows search scope to records created by a particular logged in user:

Auth::user()->id)

The code would look like this: All with importance 0

$events = Event1::whereHas('users', function($q){
$q->where('importance', 0);
$q->where('creator_id', '=', Auth::user()->id);

})->get();

right?

PROBLEM 5:

Ok, I am now able to output a query like these:

$rank_entities = Entity::leftJoin('user_attitudes', function($q){
                $q->on('entity_id', '=', 'entities.id');
                $q->where('item_type', '=', 'entity');
            })
            ->selectRaw('entities.*, SUM(user_attitudes.importance) AS importance')
            ->groupBy('entities.id')
            ->orderBy('importance', 'desc')
            ->take(6)
            ->get(); 

and in the foreach loop I can display the total importance count with this code:

{{$e->importance or '-'}}

But How I could display count of an alternative query: SUM of values from another column, named attitude, which can be computed in this SEPARATE query:

In other words, in my @foreach loop I need to display both $e->importance and a computed SUM(user_attitudes.attitude) AS karma, which for now can be received with this query:

$rank_entities = Entity::leftJoin('userattitudes', function($q){
                $q->on('entity_id', '=', 'entities.id');
                $q->where('item_type', '=', 'entity');
            })
            ->selectRaw('entities.*, SUM(userattitudes.karma) AS karma')
            ->groupBy('entities.id')
            ->orderBy('karma', 'desc')
            ->take(5)
            ->get(); 

My solution would be to create some extra columns in the 'entities' table: - karma_negative - karma_positive to store/update total amount of votes each time someone is voting.

Peter
  • 2,634
  • 7
  • 32
  • 46

2 Answers2

1

First, let's talk about the setup. I wasn't entirely sure how and if your's works but I created this on my testing instance and it worked, so I recommend you change yours accordingly:

Database

events

That's a simple one (and you probably already have it like this

  • id (primary key)
  • name (or something like that)
  • etc

users

I'm not sure if in your example that is Userattitude but I don't think so...

  • id (primary key)
  • email (?)
  • etc

items

This is the important one. The pivot table. The name can be different but to keep it simple and follow conventions it should be the plural of the polymorphic relation (in your case item => items)

  • id (actually not even necessary, but I left it in there)
  • item_type
  • item_id
  • importance
  • creator_id (consider changing that to user_id. This would simplify the relationship declaration)


Models

I think you have to read the docs again. You had several weird relations declared. Here's how I did it:

Event1

By default Laravel uses the classname (get_class($object)) as value for the ..._type column in the database. To change that you need to define $morphClass in your models.

class Event1 extends Eloquent {

    protected $table = 'events';
    protected $morphClass = 'event';

    public function users()
    {
        return $this->morphToMany('User', 'item', null, null, 'creator_id');
    }
}

User

class User extends Eloquent implements UserInterface, RemindableInterface {

    // ... default laravel stuff ...

    public function events(){
        return $this->morphedByMany('Event1', 'item', null, null, 'creator_id');
    }
}


Queries

Alright now we can get started. First one additional information. I used Eloquent relations whenever possible. In all the queries a join() is made it would be slower to use relations because certain things (like counting or calculating the maximum) would have to be done in PHP after the query. And MySQL does a pretty good job (also performance wise) at those things.

Top 5 by total value

$events = Event1::leftJoin('items', function($q){
                    $q->on('item_id', '=', 'events.id');
                    $q->where('item_type', '=', 'event');
                })
                ->selectRaw('events.*, SUM(items.importance) AS importance')
                ->groupBy('events.id')
                ->orderBy('importance', 'desc')
                ->take(5)
                ->get();

Top 5 by number of votes over 0

$events = Event1::leftJoin('items', function($q){
                    $q->on('item_id', '=', 'events.id');
                    $q->where('item_type', '=', 'event');
                    $q->where('importance', '>', 0);
                })
                ->selectRaw('events.*, COUNT(items.id) AS importance')
                ->groupBy('events.id')
                ->orderBy('importance', 'desc')
                ->take(5)
                ->get();

All with importance 0

$events = Event1::whereHas('users', function($q){
    $q->where('importance', 0);
})->get();

All without any votes

$events = Event1::has('users', '<', 1)->get();

All with 1+ votes

$events = Event1::has('users')->get();

All ordered by number of votes

$events = Event1::leftJoin('items', function($q){
                    $q->on('item_id', '=', 'events.id');
                    $q->where('item_type', '=', 'event');
                })
                ->selectRaw('events.*, COUNT(items.id) AS count')
                ->groupBy('events.id')
                ->orderBy('count', 'desc')
                ->get();

Newest 5 without votes

If you are using Eloquents timestamps created_at:

$events = Event1::has('users', '<', 1)->latest()->take(5)->get();

If you're not (order by greatest id):

$events = Event1::has('users', '<', 1)->latest('id')->take(5)->get();

Random 5 without votes

$events = Event1::has('users', '<', 1)->orderByRaw('RAND()')->take(5)->get();

I did not add any explanations to the queries on purpose. If you want to know more about something specific or need help, please write a comment

lukasgeiter
  • 147,337
  • 26
  • 332
  • 270
  • If `Userattitutes` is the table/model connecting `users` and `events` then you don't need a model for that at all. The value of `item_type` should be `event` (or whatever you specify as `morphClass`) – lukasgeiter Dec 27 '14 at 23:00
  • that't amazing! Thank you! I need to digest this and experiment to produce better questions. But now... I am not really sure how to implement some of your code. To receive help I need to expose my project to public. I a reluctant to do so. Can provide more data in a private conversation with you? Later I will refine my original question to make it more intelligible to others. – Peter Dec 28 '14 at 00:44
  • No, I wont write you an email. Also you probably shouldn't expose your address publicly. I'm open to a stackoverflow chat though. They are not private but I think I can help you anyways. Are you online in about 5 hours? (1 PM UTC) – lukasgeiter Dec 28 '14 at 08:13
  • yes, I am online. I am trying to make the users relation working (PROBLEM 1) - I just cloned the table 'user_attitudes' into 'items' – Peter Dec 28 '14 at 08:21
  • [I created a chat](http://chat.stackoverflow.com/rooms/67788/room-for-piotr-plebaniak-and-lukasgeiter) – lukasgeiter Dec 28 '14 at 12:35
  • I just approached the problem yet another time today. Still I cannot figure out what is wrong with the 'users' relation. It doesn't work in my app. Can you please respond to my feedback - the problem #1. As far as I can guess the relation is influenced by L4 conjectures on table and column names. A mystery to me. Thx – Peter Dec 31 '14 at 00:39
  • Try this: `return $this->morphToMany('User', 'item', 'user_attitudes', null, 'creator_id');` `item` is the name of the relation and `user_attitudes` the tablename – lukasgeiter Dec 31 '14 at 00:46
  • is there a way to erase, hide the chat? it can be seen as 2nd result if i search google for my name! disconcerting. – Peter Dec 31 '14 at 13:48
  • Only moderators can delete chats. However I removed your name from the title and you can change your username on `chat.stackoverflow.com` to "Peter" as you did here. – lukasgeiter Dec 31 '14 at 15:23
0

PROBLEM 4: SOLVED! (credit to @lukasgeiter)

If you wish to display a ranking of items and limit the results to a particular tag defined in a pivot table, this is the solution:

$events = Event1 (table name = 'events')

For example, the tag would be war: defined in table eventtags

Event nature are defined as

id = '1' is name = 'wars' id = '2' is name = 'conflicts' pivot table, which assigns multiple tags: event_eventtags they are defined as id = '4'

Example records for event_eventtags:

id - event_id - eventtag_id

1 - 45 - 1

2 - 45 - 2

Plain English: the Event1 #45 is tagged as war(#1) and conflict(#2)

Now in order to print a list of 10 wars you should define your query in this way:

$events= Entity::join('event_eventtags', function($q){
         $q->on('entity_id', '=', 'entities.id');
         $q->where('entitycapacitytypes_id', '=', 1);
     })->leftJoin('user_attitudes', function($q){
         $q->on('item_id', '=', 'entities.id');
         $q->where('item_type', '=', 'entity');
    })
    ->selectRaw('entities.*, SUM(user_attitudes.importance) AS importance')
    ->groupBy('entities.id')
    ->orderBy('importance', 'desc')
    ->take(10)
    ->get();

The user_attitudes is part of voting system described in the original question. You can remove it and sort events by another method.

lukasgeiter
  • 147,337
  • 26
  • 332
  • 270
Peter
  • 2,634
  • 7
  • 32
  • 46