The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
use strict;
use warnings;
use Test::More skip_all => 'it requires mysql database';

use FindBin;
use lib "$FindBin::Bin/../lib";

BEGIN {
    use_ok('DBIx::ActiveRecord');
    use_ok('DBIx::ActiveRecord::Model');
};

=pod
create database ar_test;
use ar_test;
CREATE TABLE users (
  id serial NOT NULL,
  name varchar(50) NOT NULL,
  profile text,
  blood_type varchar(2),
  deleted bool,
  created_at datetime NOT NULL,
  updated_at datetime NOT NULL
) ENGINE=InnoDB;
CREATE TABLE posts (
  id serial NOT NULL,
  user_id bigint NOT NULL,
  title varchar(255) NOT NULL,
  content text,
  created_at datetime NOT NULL,
  updated_at datetime NOT NULL
) ENGINE=InnoDB;
CREATE TABLE comments (
  id serial NOT NULL,
  post_id bigint NOT NULL,
  user_id bigint NOT NULL,
  content text,
  created_at datetime NOT NULL,
  updated_at datetime NOT NULL
) ENGINE=InnoDB;
=cut

package User;
use base 'DBIx::ActiveRecord::Model';
__PACKAGE__->table('users');
__PACKAGE__->columns(qw/id name profile blood_type deleted created_at updated_at/);
__PACKAGE__->primary_keys(qw/id/);
__PACKAGE__->has_many(posts => 'Post');
__PACKAGE__->has_one(post => 'Post');
# end User Model

package Post;
use base 'DBIx::ActiveRecord::Model';
__PACKAGE__->table('posts');
__PACKAGE__->columns(qw/id user_id title content created_at updated_at/);
__PACKAGE__->primary_keys(qw/id/);
__PACKAGE__->belongs_to(user => 'User');
__PACKAGE__->has_many(comments => 'Comment');
# end Post Model

package Comment;
use base 'DBIx::ActiveRecord::Model';
__PACKAGE__->table('comments');
__PACKAGE__->columns(qw/id post_id user_id content created_at updated_at/);
__PACKAGE__->primary_keys(qw/id/);
# end Comment Model

package main;


DBIx::ActiveRecord->connect("dbi:mysql:ar_test", 'root', 'root', {});
{
    # set up
    User->unscoped->delete_all;
    ok 1;
}

{
    # basic CRUD
    my $u = User->new({name => 'hoge', profile => 'hogehoge'});
    is $u->name, 'hoge';
    is $u->profile, 'hogehoge';

    $u->name('hoge2');
    is $u->name, 'hoge2';
    $u->name();
    is $u->name, 'hoge2';
    $u->name(undef);
    is $u->name, undef;

    $u->name('hoge');
    ok !$u->id;
    $u->save; # insert!
    ok $u->id;
    $u->save; # update!
    ok $u->id;

    my $us = User->all;

    is @$us, 1;
    is $us->[0]->name, 'hoge';
    is $us->[0]->profile, 'hogehoge';

    $u = $us->[0];

    $u->name('hoge2');
    $u->save; # update!

    $us = User->all;
    is @$us, 1;
    is $us->[0]->name, 'hoge2';
    is $us->[0]->profile, 'hogehoge';

    $us->[0]->delete;

    $us = User->all;
    is @$us, 0;
}

{
    # created_at, updated_at
    my $u = User->new({name => 'test'});
    ok !$u->created_at;
    ok !$u->updated_at;
    $u->save;

    ok $u->created_at;
    ok $u->updated_at;
    is $u->created_at, $u->updated_at;

    sleep(1);

    $u->name("test2");
    $u->save;
    ok $u->created_at;
    ok $u->updated_at;
    ok $u->created_at ne $u->updated_at;
}

{
    # scoped searches
    User->create({name => 'hoge'});
    User->create({name => 'fuga'});
    User->create({name => 'hoge2', profile => 'a'});

    my $s = User->eq(name => 'hoge');
    my $us = $s->all;
    is @$us, 1;
    is $us->[0]->name, 'hoge';
    is $s->to_sql, "SELECT * FROM users WHERE name = ?";

    $s = User->eq(name => 'hoge2')->eq(profile => 'a');
    $us = $s->all;
    is @$us, 1;
    is $us->[0]->name, 'hoge2';
    is $s->to_sql, "SELECT * FROM users WHERE name = ? AND profile = ?";

    $s = User->in(id => [1,2,3])->not_null('profile')->contains(profile => 'a');
    $s->all;
    is $s->to_sql, "SELECT * FROM users WHERE id IN (?, ?, ?) AND profile IS NOT NULL AND profile LIKE ?";
}

{
     # scope
     User->default_scope(sub{ shift->ne(deleted => 1) });
     User->scope(type_a => sub{ shift->eq(blood_type => 'A') });
     User->scope(type_a_or_b => sub{ shift->in(blood_type => ['A', 'B']) });

     is(User->scoped->to_sql, "SELECT * FROM users WHERE deleted != ?");

     User->delete_all;
     User->new({deleted => 1, name => 'deleted user'})->save;

     my $us = User->all;
     is @$us, 0;
     ok 1;

     User->type_a->type_a_or_b->all;

     is(User->type_a->to_sql, "SELECT * FROM users WHERE deleted != ? AND blood_type = ?");
     is(User->type_a_or_b->to_sql, "SELECT * FROM users WHERE deleted != ? AND blood_type IN (?, ?)");
     is(User->type_a_or_b->type_a->to_sql, "SELECT * FROM users WHERE deleted != ? AND blood_type IN (?, ?) AND blood_type = ?");
}

{
    # association - belongs_to

    my $u = User->new({name => 'aaa'});
    $u->save;

    my $p = Post->new({user_id => $u->id, title => 'aaa title'});
    $p->save;
    my $s = $p->user;

    ok 1;
}

{
    # association - has_many

    my $u = User->new({name => 'aaa'});
    $u->save;
    my $s = $u->posts;

    is $s->to_sql, "SELECT * FROM posts WHERE user_id = ?";
    is_deeply [$s->_binds], [$u->id];
    ok 1;
}

{
    # association - has_one

    my $u = User->new({name => 'aaa'});
    $u->save;
    my $post = $u->post;

    ok 1;
}

{
    # joins
    my $s = User->joins('posts')->merge(Post->eq(title => 'aaa'));
    $s->all;
    is $s->to_sql, "SELECT me.* FROM users me LEFT JOIN posts posts ON posts.user_id = me.id WHERE me.deleted != ? AND posts.title = ?";
}

{
    # select
    my $s = User->select("id", "name")->in(id => [1,2,3]);
    $s->all;
    is $s->to_sql, "SELECT id, name FROM users WHERE deleted != ? AND id IN (?, ?, ?)";

    # join and select
    $s = User->joins('posts')->merge(Post->eq(title => 'aaa'))->select("id", "name")->in(id => [1,2,3]);
    $s->all;
    is $s->to_sql, "SELECT me.id, me.name FROM users me LEFT JOIN posts posts ON posts.user_id = me.id WHERE me.deleted != ? AND posts.title = ? AND me.id IN (?, ?, ?)";
}

{
    # order, group, limit, offset
    my $s = User->desc("created_at")->asc("id");
    is $s->to_sql, "SELECT * FROM users WHERE deleted != ? ORDER BY created_at DESC, id";
    $s->all;

    $s = User->group("blood_type");
    is $s->to_sql, "SELECT * FROM users WHERE deleted != ? GROUP BY blood_type";
    $s->all;

    $s = User->limit(5)->offset(2);
    is $s->to_sql, "SELECT * FROM users WHERE deleted != ? LIMIT ? OFFSET ?";
    $s->all;

    $s = User->eq(id => 1)->lock;
    is $s->to_sql, "SELECT * FROM users WHERE deleted != ? AND id = ? FOR UPDATE";
    $s->all;
}

{
    User->first;
    User->last;
    ok 1;
}

{
    # transaction
    User->transaction(sub {
    });
    User->transaction(sub {
      die;
    });
}

{
    # scope cache
#    print STDERR "*** cache test ***\n";
    User->all;
    User->all;

#    print STDERR "*** all only ***\n";
    my $s = User->scoped;
    $s->all;
    $s->first;
    $s->last;

#    print STDERR "*** first, last, all ***\n";
    $s = User->scoped;
    $s->first;
    $s->last;
    $s->first;
    $s->last;
    $s->all;
    $s->all;

#    print STDERR "*** new scope! ***\n";
    $s->eq(id => 1)->all;

    ok 1;
}
# includes
{

#    print STDERR "*** includes user => posts ***\n";
    User->unscoped->delete_all;
    Post->unscoped->delete_all;
    my $u1 = User->new({name => 'hoge', deleted => 0});
    my $u2 = User->new({name => 'fuga', deleted => 0});
    $u1->save;
    $u2->save;

    Post->new({title => 'hoge 01', user_id => $u1->id})->save;
    Post->new({title => 'hoge 02', user_id => $u1->id})->save;
    Post->new({title => 'hoge 03', user_id => $u1->id})->save;
    Post->new({title => 'hoge 04', user_id => $u1->id})->save;
    Post->new({title => 'fuga 01', user_id => $u2->id})->save;
    Post->new({title => 'fuga 02', user_id => $u2->id})->save;

    my $us = User->includes('posts')->all;
    is @{$us->[0]->posts->all}, 4;
    is @{$us->[1]->posts->all}, 2;

    ok 1;
}

{
    # array operator
    ok @{User->scoped};

    my $users = User->includes('posts');
    foreach my $u (@$users) {
        foreach my $p (@{$u->posts}) {
            ok $u;
            ok $p;
        }
    }
    ok 1;
}

{
    # nested joins

    my $users = User->joins('posts', 'comments');
    ok @{$users};

    is $users->to_sql, "SELECT me.* FROM users me LEFT JOIN posts posts ON posts.user_id = me.id LEFT JOIN comments comments ON comments.post_id = posts.id WHERE me.deleted != ?";
}

{
    # nested includes
    User->unscoped->delete_all;
    Post->unscoped->delete_all;
    Comment->delete_all;

    my $u1 = User->create({name => 'hoge', deleted => 0});
    my $u2 = User->create({name => 'fuga', deleted => 0});

    my $p1 = Post->create({user_id => $u1->id, title => "hoge 01"});
    Comment->create({content => 'hoge 01 com1', post_id => $p1->id, user_id => $u2->id});
    Comment->create({content => 'hoge 01 com2', post_id => $p1->id, user_id => $u2->id});
    Comment->create({content => 'hoge 01 com3', post_id => $p1->id, user_id => $u2->id});
    Comment->create({content => 'hoge 01 com4', post_id => $p1->id, user_id => $u2->id});

    my $p2 = Post->create({user_id => $u1->id, title => "hoge 02"});
    Comment->create({content => 'hoge 02 com1', post_id => $p2->id, user_id => $u2->id});
    Comment->create({content => 'hoge 02 com2', post_id => $p2->id, user_id => $u2->id});

    my $p3 = Post->create({user_id => $u2->id, title => "fuga 01"});
    Comment->create({content => 'fuga 01 com1', post_id => $p3->id, user_id => $u1->id});
    Comment->create({content => 'fuga 01 com2', post_id => $p3->id, user_id => $u1->id});

    my $users = User->includes('posts', 'comments');
    ok @{$users};
}

{
    # count
    Post->count;
    Post->eq(title => 5)->count;
    Post->joins('user')->merge(User->eq(id => 3))->count;

}

done_testing;