DataMapper enum field doesn't persist when saved or updated - mysql

I am trying to map a legacy MySQL database with a fresh DataMapper model.
MySQL schema:
CREATE TABLE IF NOT EXISTS `backer` (
`backer_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`secret` varchar(16) NOT NULL,
`email` varchar(255) DEFAULT NULL,
`status` enum('pending','ready') NOT NULL DEFAULT 'pending', # relevant bit
PRIMARY KEY (`backer_id`),
UNIQUE KEY `backer_id` (`secret`),
KEY `email` (`email`,`status`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=8166 ;
DataMapper model:
class Backer
include DataMapper::Resource
storage_names[:default] = "backer"
property :id, Serial, :field => "backer_id"
property :secret, String, :field => "secret"
property :email, String, :field => "email"
property :status, Enum[ :pending, :ready ], :field => "status", :default => "pending"
has n, :items, :child_key => "backer_id"
end
DataMapper.finalize
With most attributes, I can persist:
b = Backer.first
b.first_name = "Daniel"
b.save!
# Backer.first.first_name == "Daniel"
It persists fine.
But when I do it with an enum:
b = Backer.first
b.status = :pending
b.save!
# b.status == :pending
# Backer.first.status != :pending
# Backer.first.update!(:status => :pending) ...
Updates/saves to an enum field doesn't seem to persist it.
SQL looks typically like:
1.9.3p327 :019 > Backer.last.update(:status => :ready)
~ (0.000349) SELECT `backer_id`, `secret`, `email`, `status` FROM `backer` ORDER BY `backer_id` DESC LIMIT 1
~ (0.000190) UPDATE `backer` SET `status` = 2 WHERE `backer_id` = 8166
=> true
But when I view the object (e.g. Backer.last) then status is not changed.
UPDATE
I've verified that the enumerated status field in the MySQL database is being persisted. However, the enumerated status property in the DataMapper class doesn't reflect this at all (is always nil).
What's happening here?

Thanks #morbusg: that's one solution.
The other one was to change the Enum at my end to a String and that's worked fine for my purposes.

Related

how can i get mysql to return an array that is inside JSON

im trying to return a product that can have three images per product.
something like this:
{
product_id: 1,
product_name:"test",
category_id_fk: 1,
product_price: 12,
product_desc:"lorem-ipsum",
product_images:[path1, path2, path3]
}
Here is what i have got so far,
create table product(
product_id serial not null primary key,
category_id_fk int not null references category(category_id) ,
product_image_fk int not null references product_images(product_images_id),
product_name varchar(50) not null,
product_price int not null,
product_desc varchar(200) not null);
create table product_images(
product_images_id serial not null primary key,
product_image_one varchar(150) not null,
product_image_two varchar(150) not null,
product_image_three varchar(150) not null);
this is the query im using for the product details page
SELECT * FROM product WHERE category_id_fk = ? AND product_id = ?;
Im new to SQL and have been really having a tough time with this specifically, would
appreciate it a lot if anyone could explain to me what im doing wrong or if you have any tips for me.
I don't know what programming language you are using, but in this case I assume you are using a programming language like PHP as a REST API provider because you are using prepared statements
Here is the query:
SELECT * FROM `product_images` `pi` LEFT JOIN `product` `p`
ON `pi`.`product_images_id` = `p`.`product_image_fk` WHERE
`p`.`product_id` = ? AND `p`.`category_id_fk` = ?
Then on your PHP scripts you can make it like this:
$sql = "SELECT * FROM `product_images` `pi` LEFT JOIN `product` `p`
ON `pi`.`product_images_id` = `p`.`product_image_fk` WHERE
`p`.`product_id` = ? AND `p`.`category_id_fk` = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("ii", $productId, $categoryId);
$stmt->execute();
$row = $stmt->get_result()->fetch_assoc();
$jsonArray = [
"product_id" => $row["product_id"],
"product_name" => $row["product_name"],
"category_id_fk" => $row["category_id_fk"],
"product_price" => $row["product_price"],
"product_desc" => $row["product_desc"],
"product_images" => [$row["product_image_one"], $row["product_image_two"], $row["product_image_three"]]
];
$expectedJson = json_encode($jsonArray);
And you can do whatever you want to the json maybe by storing it on the json file or to print it as http response
SELECT JSON_OBJECT( 'product_id', p.product_id,
'product_name', p.product_name,
'category_id_fk', p.category_id_fk,
'product_price', p.product_price,
'product_desc', p.product_desc,
'product_images', JSON_ARRAY(pi.product_image_one,
pi.product_image_two,
pi.product_image_three) ) output
FROM product p
JOIN product_images pi ON p.product_image_fk = pi.product_images_id;
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=16f3a1fe3619c6c3601983d4c00de07d

How to set data type as longtext in Yii2 migration

I use following code in order to get desired values of attribute for bodytext. Data type should be longtext() or varchar(). DBMS is MySQL.
With following code bodytext will be created as varchar(255). Why?
$this->createTable('mail_eingang', [
'id' => $this->primaryKey(),
'mail_adresse_absender' => $this->string(255)->notNull(),
'betreff' => $this->string(255),
'bodytext' => $this->string(), //this will actually set bodytext to varchar(255). It should be longtext() or varchar() !!
'gelesen' => $this->smallInteger(1)->notNull()->defaultValue(0),
'angelegt_am' => $this->datetime(),
'angelegt_von' => $this->integer(11),
'aktualisiert_am' => $this->datetime(),
'aktualisiert_von' => $this->integer(11),
'FOREIGN KEY ([[angelegt_von]]) REFERENCES person ([[id]]) ON DELETE SET NULL ON UPDATE RESTRICT',
'FOREIGN KEY ([[aktualisiert_von]]) REFERENCES person ([[id]]) ON DELETE SET NULL ON UPDATE RESTRICT',
], $tableOptions);
If 64 KB is enough for you, you may use text():
'bodytext' => $this->text()
If you really need a longtext column, you should use something like:
'bodytext' => $this->getDb()->getSchema()->createColumnSchemaBuilder('longtext');
Or specify type as raw string:
'bodytext' => 'LONGTEXT',

Storing 2 same id of post table with different language into database

I'm building a multi language website. In this case just 2 languages, it's Indonesian and English using Laravel.
I have posts table, it will store id of each post and post_translations table is to store local, title, and description of post.
I got the problem when storing data into database. I don't have any idea how to store post without inc the id except I've added 2 same post with Indonesian and English.
This is the result (wrong)
posts table
id
1
2
post_translations table
id post_id locale title
1 1 en fisrt post
2 2 id post yang pertama
Expexted result
posts table
id
1
post_translations table
id post_id locale title
1 1 en fisrt post
2 1 id post yang pertama
PostController
public function store(Request $request) {
$this->validate($request, [
'title' => 'required',
'slug' => 'required',
'content' => 'required'
]);
$post = new Post;
$post->title = $request->title;
$post->slug = $request->slug;
$post->content = $request->content;
$post->save();
return redirect()->route('post.index');
}
Ok, so here we go (please note that this is not the only way):
install spatie/laravel-translatable with
composer require spatie/laravel-translatable
**Note: for native spatie/laravel-translatable go to version 2 **
create a table with this structure:
CREATE TABLE articles (
id int(10) UNSIGNED NOT NULL,
title text COLLATE utf8_unicode_ci,
slug text COLLATE utf8_unicode_ci,
content text COLLATE utf8_unicode_ci,
created_at timestamp NULL DEFAULT NULL,
updated_at timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Note: beter use a migration. I just exported a table I did earlier to test
Insert the data in the datatabase in json format like this:
INSERT INTO `pages` (`id`, `title`, `slug`, `content`, `created_at`, `updated_at`) VALUES
(1, '{"ro":"Acas\\u0103","en":"Home"}', NULL, '{"ro":"<p><strong>Test title<\\/strong><\\/p>\\r\\n\\r\\n<p>Test content romanian<\\/p>\\r\\n","en":"<p><strong>test title english<\\/strong><\\/p>\\r\\n\\r\\n<p>test content english.<\\/p>\\r\\n"}', '2017-04-03 11:45:56', '2017-04-03 12:15:16');
Now create the blade to edit, update, create show etc. To get the language do something like this in the blade:
{{ $data->title }}
{!! nl2br($data->content) !!}
And in the controller:
add something like this:
/**
* Generate the field by language
*
* #param Model $entry the item selected from the database
*
* #return array
*/
public function getTranslatableFields($fields)
{
$inputs = [];
$languages = $this->getLanguages();
foreach ($languages as $language) {
foreach ($fields as $field) {
$inputs[] = [
'name' => "{$field['name']}[{$language->abbr}]",
'label' => $field['label'] . " ($language->abbr)",
'lang' => $language->abbr,
'type' => array_key_exists('type', $field) ? $field['type'] : 'text'
];
}
}
return $inputs;
}
I added this function in a LangTrait. Since I also use backpack for laravel I did some more things.
For edit I added this method in the trait:
/**
* Show the form for editing the specified resource.
*
* #param int $id the item's identifier
*
* #return Response
*/
public function edit($id)
{
$data['entry'] = Model::find($id);
$data['title'] = trans('lang_file.edit').' '.$this->entity_name; // name of the page
$data['fields'] = $this->getMultiLangFields($data['entry']);
$data['id'] = $id;
return view('crud::edit', $data);
}
/**
* Generate the field by language
*
* #param Model $entry the item selected from the database
*
* #return array
*/
protected function getMultiLangFields($entry)
{
$fields['id'] = ['name' => 'id', 'type' => 'hidden', 'value' => $entry->id];
foreach ($this->crud->update_fields as $key => $field) {
$value = null;
if (array_key_exists('lang', $field)) {
$name = preg_replace('/(\[\w{2}\])$/i', '', $field['name']);
$value = $entry->getTranslation($name, $field['lang']);
}
$fields[$key] = array_merge($field, ['value' => $value]);
}
return $fields;
}
/**
* Get the application active languages
*
* #return \Backpack\LangFileManager\app\Models\Language
*/
protected function getLanguages()
{
return Language::whereActive(1)->orderBy('default', 'desc')->get();
}
Finally in my main controller I did:
use LangTrait; (contains everything above)
In construct I added this:
$this->getTranslatableFields($fields)
where $fields it's the list of fields I need
All methods should be adapted to you html format. As I said I use backpack for laravel and fields are formatted accordingly
And finally for the getLanguage file to work I created a new table and a model in the DB:
Model:
class Language extends Model
{
protected $table = 'languages';
protected $fillable = ['name', 'flag', 'abbr', 'native', 'active', 'default'];
public $timestamps = false;
public static function getActiveLanguagesArray()
{
$active_languages = self::where('active', 1)->get()->toArray();
$localizable_languages_array = [];
if (count($active_languages)) {
foreach ($active_languages as $key => $lang) {
$localizable_languages_array[$lang['abbr']] = $lang;
}
return $localizable_languages_array;
}
return config('laravellocalization.supportedLocales');
}
public static function findByAbbr($abbr = false)
{
return self::where('abbr', $abbr)->first();
}
}
Table:
CREATE TABLE `languages` (
`id` int(10) UNSIGNED NOT NULL,
`name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`app_name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`flag` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
`abbr` varchar(3) COLLATE utf8_unicode_ci NOT NULL,
`script` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL,
`native` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL,
`active` tinyint(3) UNSIGNED NOT NULL DEFAULT '1',
`default` tinyint(3) UNSIGNED NOT NULL DEFAULT '0',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
`deleted_at` timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Data in the table:
INSERT INTO `languages` (`id`, `name`, `app_name`, `flag`, `abbr`, `script`, `native`, `active`, `default`, `created_at`, `updated_at`, `deleted_at`) VALUES
(1, 'English', 'english', '', 'en', 'Latn', 'English', 1, 0, NULL, NULL, NULL),
(2, 'Romanian', 'romanian', '', 'ro', 'Latn', 'română', 1, 1, NULL, NULL, NULL);
Since I did this through a package I kind of messed around a little bit with the code.
Now, for the spatie/laravel-translatable package version:
set up the service provider in config/app.php add this in the providers array:
Spatie\Translatable\TranslatableServiceProvider::class,
In the model Articles add use HasTranslations; like this:
use Illuminate\Database\Eloquent\Model;
use Spatie\Translatable\HasTranslations;
class NewsItem extends Model
{
use HasTranslations;
public $translatable = ['name']; // list the columns you want to be translatable (will have json format)
}
save a new entry and use it:
$article = new Article;
$article->setTranslation('name', 'en', 'Updated name in English')
->setTranslation('name', 'nl', 'Naam in het Nederlands');
$article->save();
$article->name; // Returns 'Name in English' given that the current app locale is 'en'
$article->getTranslation('name', 'nl'); // returns 'Naam in het Nederlands'
app()->setLocale('nl');
$article->name; // Returns 'Naam in het Nederlands'
examples from: https://github.com/spatie/laravel-translatable
Database table format is as stated above in the first version
If it does not work out let me know and I'll look at your code.
I think you should change your tables strucuture:
posts : id, slug.
post_translations : id, post_id, locale, title, content
Also add relation to your Post model:
public function translations()
{
return $this->hasMany(PostTranslation::class, 'post_id');
}
And update your controller:
$post = new Post;
$post->slug = $request->slug;
$post->save();
$post->translations()->create([
'locale' => 'en', //or grab it from $request
'title' => $request->title,
'content' => $request->content
])
It will create your post and add translation to it

CAKEPHP 3 after save success close date in database is not null, it has todays value

I have the input field
echo $this->Form->input('close_date',['empty' => true, 'default' => '']);
with validation rules ..
$validator->add('close_date', 'valid', ['rule' => 'date'])->allowEmpty('close_date');
the output of $this->request->data before :
$ticket = $this->Tickets->patchEntity($ticket, $this->request->data);
is
['close_date' => [
'day' => '',
'month' => '',
'year' => ''
]]
but after save success close date in database is not null, it has todays value ....
in MySql table field close_date is type DATE default NULL
what am i missing here ... i want this field to continue NULL why is cake saving with todays date?
AFAICT the datetime type is not able to marshall empty date array structures into null values yet. You may want to report this over at github, it might be worth adding support for this to the core.
As a (temporary) workaround you could for example use the Model.beforeMarshal event/callback and manually set the property to null in case the date array is empty, something like:
public function beforeMarshal(Event $event, \ArrayObject $data, \ArrayObject $options)
{
if (
isset($data['close_date']) &&
is_array($data['close_date']) &&
!array_filter($data['close_date'])
) {
$data['close_date'] = null;
}
}

Active Record (Object doesn't support #inspect) in includes method

Here is my MySQL database schema:
create table url (
id int(11) primary key,
url text,
url_md5 char(32),
urlpath text,
cleanurl text,
title text
);
create table record (
id int(11) primary key,
uid varchar(25),
url text,
url_md5 char(32)
);
create table user (
uid varchar(25) primary key
);
And this is a legacy project, so the schema created before i use active recorad. My ruby code is just like this:
class User < ActiveRecord::Base
self.table_name = 'user'
has_many :records, class_name: 'Record',
foreign_key: 'uid', primary_key: 'uid'
def self.cached_count
#cached_count ||= User.count
end
end
class Record < ActiveRecord::Base
self.table_name = 'record'
has_one :urldetail, class_name: 'Url',
foreign_key: 'url_md5', primary_key: 'url_md5',
select: [:urlpath, :cleanurl]
end
class Url < ActiveRecord::Base
self.table_name = 'url'
belongs_to :record, class_name: 'Record',
primary_key: 'url_md5', foreign_key: 'url_md5'
end
It looks a little complicated. The url and record table create association by url_md5 and the record table and user table create association by uid. They are not the primary keys. And when I use includes it shows error like this:
2.0.0-p247 :003 > u.records.includes(:urldetail)
D, [2013-10-30T20:08:33.207225 #47431] DEBUG -- : Record Load (9.3ms) SELECT `record`.* FROM `record` WHERE `record`.`uid` = '5669408714090543819'
D, [2013-10-30T20:08:33.303839 #47431] DEBUG -- : Url Load (45.7ms) SELECT urlpath, cleanurl FROM `url` WHERE `url`.`url_md5` IN ('2029708d8c99a3f781b6d695ddedf80c', '977434e3d3b080971d67b6a5c3850d57', '784820ccc528cc3a3cf2df3b677f8761', '14eea8c73d7dab59dbdd6f2d5c19eaed', 'dfb5ea7bbca8018d5bf7aedf4f03df1f', 'aad852910d0f6d1c96fa82e99399800d', '873a263e4bcd28f9e95e150a746c1188')
(Object doesn't support #inspect)
=>
I googled the problem but get nothing.
Update
If I write u.records.includes(:urldetail).to_a then the result will be:
SELECT urlpath, cleanurl FROM `url` WHERE `url`.`url_md5` IN ('2029708d8c99a3f781b6d695ddedf80c', '977434e3d3b080971d67b6a5c3850d57', '784820ccc528cc3a3cf2df3b677f8761', '14eea8c73d7dab59dbdd6f2d5c19eaed', 'dfb5ea7bbca8018d5bf7aedf4f03df1f', 'aad852910d0f6d1c96fa82e99399800d', '873a263e4bcd28f9e95e150a746c1188')
ActiveModel::MissingAttributeError: missing attribute: url_md5
But I do have column url_md5.