Если Вы интересуетесь ORM или просто читаете официальный форум Kohana, то наверняка успели обратить внимание на данную библиотеку. Ее разработчик, Jonathan Geiger, решил взять все самое лучшее из ORM и Sprig, дабы создать модуль с блэкджеком под собственные нужды. Поскольку ORMы — весьма популярная тема (в том числе и на данном сайте), решено ознакомиться с данной реализацией.
Итак, скачиваем модуль и начинаем исследование содержимого. Для начала отмечу, что в составе модуля поставляется документация в формате «родного» Userguide. То есть если в проекте подключены модули Jelly и Userguide, то в секции Modules документации появится соответствующий раздел Jelly. В документации присутствует и русский перевод (спасибо тебе, Сергей avis Гладковский). Документации вполне достаточно для быстрого старта, особенно если есть опыт работы с ORM или Sprig.
Модели
С чего обычно начинается описание ORM-подобных библиотек? Правильно, с моделей! Вот пример из документации:
class Model_Post extends Jelly_Model { public static function initialize(Jelly_Meta $meta) { $meta->table('posts') ->fields(array( 'id' => new Field_Primary, 'name' => new Field_String, 'body' => new Field_Text, 'status' => new Field_Enum(array( 'choices' => array('draft', 'review', 'published'))), 'author' => new Field_BelongsTo, 'tags' => new Field_ManyToMany, )); } } |
Очень похоже на Sprig — все необходимые поля объявляются при инициализации (напомню, что ORM по умолчанию запрашивает описания полей у базы данных), для каждого вида поля создается свой класс (потомок Jelly_Field
). У полей есть свойства $in_db
(существует ли данное поле в таблице БД), $primary
(булевый флаг, является ли поле первичным ключом), $default
(значение по умолчанию) и т.д. — большинство свойств знакомы нам по Sprig. Методом table()
устанавливается имя таблицы БД (на самом деле это необязательно, т.к. имя для таблицы соответствует рекомендациям ORM и было бы вычислено автоматически).
Параметры модели
Все, что описывает модель в части связи с БД (поля, имя таблицы, имя первичного ключа и т.д.), хранится в объекте класса Jelly_Meta
. В вышеприведенном примере метод initialize()
модели как раз заполнял переменную $meta
, которая будет прикреплена к модели Model_Post. Эти данные выделены в отдельный объект не просто так — класс Jelly хранит (регистрирует) метаданные всех созданных ранее моделей в свойстве $_models
, и в дальнейшем ознакомиться с ними можно будет без необходимости инициализировать саму модель. Достаточно вызвать Jelly::meta('post')
, и на выходе получим Meta-объект для модели Model_Post.
CRUD
Для загрузки, создания, изменения и удаления объектов в Jelly используются различные методы (как и в Sprig).
Доступ к записям в БД
// загрузка записи с идентификатором = 1 $post = Jelly::select('post', 1); // то же самое $post = Jelly::select('post')->load(1); // загрузка всех записей $posts = Jelly::select('post')->execute(); // загрузка с условием $approved_posts = Jelly::select('post')->where('is_approved', '=', 1)->execute(); |
В первом случае будет извлечена только одна запись, поэтому $post
содержит объект Model_Post. В случае же вызова execute()
на выходе будет объект класса Jelly_Collection
, это что-то вроде ORM_Iterator
из ORM второй ветки Kohana.
На самом деле, если вызвать метод
limit()
с параметром 1, то результат будет содержать одну модель, как и в случае сload()
. По сути вызовload($id)
просто краткая запись отwhere('id', '=', $id)->limit(1)->execute()
.
С объектом Jelly_Collection
можно работать также, как и с ORM_Iterator
или Database_Result
: перебирать элементы в цикле, подсчитать общее число записей в нем, преобразовать в массив с помощью метода as_array($key, $value)
.
Новая запись
// создание нового объекта $post = Jelly::factory('post'); // заполняем поля по одиночке $post->title = 'new post'; // или разом $post->set(array('text' => 'new post text', 'created' => time())); // сохраняем $post->save(); |
Можно использовать и
Jelly::insert('post')->set(...)->execute()
, но это не так удобно, по сравнению с созданием модели и ее заполнением. Зато быстрее!
Редактирование записи
$post = Jelly::factory('post'); // устанавливаем нужные значения $post->title = 'edited post'; // сохраняем. Не забудьте передать идентификатор записи! $post->save(1); |
Похоже на создание новой записи, но тут мы заранее знаем идентификатор записи и меняем значения отдельных ее полей (в данном случае поле `title`) без загрузки самой записи. На самом деле можно было сделать и «по старинке»:
$post = Jelly::select('post', 1); $post->title = 'edited post'; // модель загружена из БД, передавать идентификатор в метод save() не надо $post->save(); |
Усложнение запросов
Как и в случае с ORM, в Jelly для непосредственной работы с базой данных используется Query_Builder
, точнее его потомок, Jelly_Builder
. Поэтому синтаксис запросов выглядит знакомым, доступны методы where(
), join()
и т.д.
А теперь представьте себе, что вы часто выбираете записи по определенному условию, например утвержденные администратором/модератором статьи:
$posts = Jelly::select('post')->where('is_approved', '=', 1)->execute(); |
В ORM такие вещи можно было спрятать в методе модели, примерно так:
public function approved($value = 1) { $this->where('is_approved', '=', $value); return $this; } $posts = ORM::factory('post')->approved()->find_all(); |
Однако в Jelly методы select()
, insert()
, update()
и delete()
возвращают Jelly_Builder
, а не текущую модель, поэтому в модели подобные методы логики приложения создавать бессмысленно. Зато можно создавать свои классы Jelly_Builder
, например для модели Model_Post
создаем Model_Builder_Post
и в нем прописываем нужные методы:
class Model_Builder_Post extends Jelly_Builder { public function approved($value = 1) { return $this->where('is_approved', '=', $value); } } |
Jelly очень гибка и имеет множество настроек. В том числе есть и возможность поменять префикс для моделей и билдеров. Берем заглушку classes/jelly.php (Jelly использует ту же тактику избыточного именования моделей, что и Ko3) и меняем свойство
$_model_prefix
под свои нужды, например ‘myjelly_‘. В результате все модели и билдеры должны будут храниться в директории classes/myjelly, а имена этих классов начинаться не с ‘Model_‘, а с ‘Myjelly_‘. Но, конечно, мало кто будет с этим заморачиваться.
Псевдонимы
Случается, что первичные ключи называются не по «фэн-шую», например post_id
или IdPost
, а хотелось бы обращаться по привычному имени id
. Выход есть — используйте псевдонимы. Существует два варианта, привычный и инновационный
- В методе
fields()
модели объявляете имя поля как Вам удобно, и указываете в свойстве ‘column‘ реальное имя поля. Например, так:
'id' => new Field_Primary(array('column' => 'post_id')),
В данном способе нет ничего нового, такие псевдонимы доступны как в ORM, так и в Sprig. - Сперва по всем правилам объявляете поле, а ниже создаете новые поля, «переадресовывая» их на ранее созданное. Поясню на примере:
'post_id' => new Field_Primary, 'id' => 'post_id',
Теперь можно использовать
$post->id
, в результате будет отрабатывать поле ‘post_id‘. Можно создать и второй псевдоним, тоже будет работать. А вот по цепочке (id1 -> id -> post_id
) не работает, ну и ладно.
Конечно, рекомендуется использовать метод
id()
для получения значения первичного ключа модели (в ORM его аналогом был методpk()
). Обращаться к именам полей напрямую лучше пореже, дабы не привязываться к потенциально изменчивой структуре БД.
Мета-псевдонимы
Очень интересное и вкусное нововведение. Обычно при объявлении тех же связей мы пишем реальные имена полей (первичных ключей, внешних ключей и т.д.), аналогичные неудобства при заполнении условий Query_Builder‘а (что-то типа where('id', 'IN', $values)
). А вот в Jelly есть специальные слова, позволяющие указать на нужные поля без их конкретного имени! Вот их перечень:
- :primary_key — тут все понятно, будет заменено на имя первичного ключа.
- :foreign_key — имя внешнего ключа для других моделей (обычно явно не указывается, поэтому формируется как
имя_модели + '_id'
). - :name_key — имя «титульного» поля, например ‘username‘ для модели
User
или ‘title‘ дляPost
(если конечно Вы соответствующее свойство$name_key
указывали в модели). Полезно для получения спискаid=>value
из набора записей, например$posts->as_array(':primary_key', ':name_key')
. - :unique_key. Данный мета-алиас предназначен для работы с уникальными ключами, в основном через
Jelly_Builder
(аналог стандартногоQuery_Builder
‘а). По умолчанию в моделях Jelly есть только один уникальный ключ — это конечно первичный ключ. Даже если Вы добавите в свойствах какого-либо поля параметр$unique = TRUE
, автоматически оно определяться не будет. Надо создавать свой билдер для модели и в нем прописывать методunique_key($value)
. Вот как он изначально написан вJelly_Builder
:
public function unique_key($value) { return $this->_meta->primary_key(); }
Если мы для модели
Model_User
захотим сделать уникальными поляemail
иusername
, то в классаModel_Builder_User
создадим свой метод:public function unique_key($id) { if ( ! empty($id) AND is_string($id) AND ! ctype_digit($id)) { return validate::email($id) ? 'email' : 'username'; } return parent::unique_key($id); }
В общем, все как и в старом-добром Auth. А дальше уже можно использовать:
$user = Jelly::select('user')->where(':unique_key', '=', $_POST['login'])->load(); // или более короткая запись $user = Jelly::select('user')->load($_POST['login']); // еще более короткая запись! $user = Jelly::select('user', $_POST['login']);
Так как мы определили метод для уникальных полей, вместо
id
можно передавать имя пользователя илиemail
— нам по сути неважно. В данном случае мы позволим пользователю входить как по логину, так и адресу эл.почты.
Связи
Естественно, есть поддержка привычных нам типов связей (используем классы Field_HasOne
, Field_HasMany
, Field_BelongsTo
и Field_ManyToMany
). Для настройки связи используются три параметра, которые передаются в конструктор:
- ‘column‘ — имя поля в таблице модели, которое является внешним ключом. Естественно, это доступно только для связей
BelongsTo
, т.к. в остальных случаях внешний ключ выносится в другую модель (и свойство$in_db
, упомянутое выше, для этих видов связей равно FALSE). - ‘foreign‘ — строка с описанием внешнего ключа. Обычно указывается в виде ‘model.field‘, например ‘article.id‘. Если данный параметр не указан, то будет автоматически сгенерировано значение исходя из имени связи, с использованием первичного ключа связанной модели. Также можно указать только имя модели, в этом случае к нему будет добавлен первичный ключ.
- ‘through‘ — этот параметр нужен для связей
ManyToMany
, чтобы указать промежуточную таблицу. Его можно указать как строку (тогда это должно быть имя промежуточной модели), как массив (поле ‘model‘ определяет имя промежуточной модели, а ‘columns‘ — массив полей с первичными ключами связанных моделей). Если не указывать вообще, сработает автозаполнение, принцип именования промежуточной модели аналогичен ORM‘у (Role_User
для моделейUser
иRole
).
Много текста, мало смысла, поэтому вот несколько примеров описания связей для модели Model_User
:
// все по умолчанию, связь с моделью Model_Post по ключу user_id 'posts' => new Jelly_Field_HasMany; // добавляем отсебятины, пусть внешний ключ называется author_id 'posts' => new Jelly_Field_HasMany(array('foreign' => 'post.author_id')); // + меняем имя связи 'articles' => new Jelly_Field_HasMany(array('foreign' => 'post.author_id')); // добавляем связь для получения отмодерированных статей 'moderated' => new Jelly_Field_HasMany(array('foreign' => 'post.moderator_id')); // по умолчанию промежуточная модель Role_User должна содержить ключи role_id и user_id 'roles' => new Jelly_Field_ManyToMany; // указываем явно, что в модели Role внешним ключом будет первичный ключ 'roles' => new Jelly_Field_ManyToMany(array('foreign' => 'role.role:primary_key')); // используем нестандартное имя промежуточной модели 'roles' => new Jelly_Field_ManyToMany(array('through' => 'user_role')); // в данном примере имя промежуточной модели и содержащиеся в ней ключи нестандартные 'roles' => new Jelly_Field_ManyToMany(array('through' => array('model' => 'user_role', 'columns' => array('userId', 'roleId')))); |
Обратите внимание, что при объявлении имен полей в промежуточной модели (параметр
['through']['columns']
) важен порядок их следования. Сперва указывается внешний ключ от текущей модели (в данном случае этоuserId
), а потом — от связанной.
Валидация
При объявлении полей модели есть возможность указать и условия валидации (rules/filters/callbacks
, а также label
). Тут все в принципе аналогично Sprig. Для уникальных полей (свойство $unique == TRUE
) автоматически добавляется callback-метод _is_unique()
для проверки на наличие проверяемого значения в БД. В случае ошибок при проверке выбрасывается Validate_Exception
, поэтому не забывайте оборачивать вызовы validate()
и save()
в конструкции try ... catch()
. Метод validate()
возвращает массив проверенных данных в случае успеха.
Работа с формами
Каждое поле модели реализует метод input($prefix, $data)
, который должен выводить поле ввода/выбора значения (через представление View). Первый аргумент ($prefix
) позволяет указать свой путь для представления, по умолчанию он ‘jelly/field‘ (да, в views/jelly/field/ можно подсмотреть уже имеющиеся заготовки для стандартных типов данных Jelly). Параметр $data
позволяет передать в шаблон дополнительные переменные, которые будут добавлены к публичным свойствам поля. Еще в шаблоне будет доступна переменная $field
, которая содержит собственно сам выводимый объект Jelly_Field
.
Заключение
Это был беглый обзор возможностей Jelly, основная цель которого — познакомиться с основами этой замечательной библиотеки. На данный момент могу выделить следующие плюсы и минусы:
+ неплохая документация, причем сразу в комплекте с модулем.
+ высокая гибкость и настраиваемость, даже выше, чем у Sprig.
+ введение объекта Meta
позволяет уменьшить количество запросов к БД, когда нужны только метаданные о моделях.
- относительная сложность по сравнению со Sprig и тем более ORM.
Модуль очень интересный, и в целом сообществу очень понравился (достаточно посмотреть на количество топиков о нем). Разработкой заняты солидные люди, добавляются вспомогательные модули (типа Jelly-Auth), в общем обратите на него внимание
Спасибо, шикарная статья. В закладки на будущее. Вопрос есть, можно ли без особого опыта работы со стандартным ORM использовать в дальнейшем Jelly, стоит ли пытаться изучать сразу Jelly или необходимо сначала попрактиковаться с ORM?
В принципе документации хватает на то, чтобы сделать первые шаги. Дальше методом тыка + изучение исходников (комментарии качественные, читать легко). Да и на форуме уже хватает топиков по Jelly
Great article. I would really like though to hear about advantages over using the official ORM..
Hi Rafi!
I’m waiting for ORM v3.1.0 release
Hey.. I tried asking jheatco about the 3.1 release.. Do you have a clue when will it be released?
All I know is 3.1.0 roadmap and his feature request…
Привет! У тебя в коде теги похерены
$post->save(1);</pre.
Это в разделе Редактирование записи.
Статья отличная. Я вот все никак не решаюсь на ОРМ перейти, но сейчас для меня самое то!
Спасибо.
ЗЫ ты надеюсь сделал в куках запоминание данных для каммента??
Ага, спасибо, исправил. А куки — хз, вроде как были проблемы у отдельных пользователей. Я сейчас в положении буриданова осла — этот блог обновлять давно пора, но WP не хочу, а на Коханке свой написать времени особо нет
Да, я сам недавно нашел эту ОРМ-ку. Хочу ее отдельно от Коханы заюзать (в своем фреймворке), не знаю: получится ли.
Ну-ну, не сбивайте читателя с толку:
public static function factory($model, $values = NULL)
{
$class = Jelly::class_name($model);
return new $class($values);
}
На factory возвращается модель.
Эй, ребята .. Если вы обнаружили Jelly гораздо удобнее, можете ли вы объяснить, почему? Я пытаюсь исследования различия, но есть немного времени.
Спасибо
да, кстати, про преимущества Jelly — я на гитхабе видел форк от русского чувака, который переводит доку на русский
Я бы не сказал, что Jelli — новое слово в ORM.
Скорее всего это напоминает игру в доганялки…
С некоторым запаздыванием любимых ЗВЕЗД.
Что-то подобные появилось почти год тому назад в DooPHP!
Иван, спасибо тебе за обзор! Как всегда очень понятно и по делу…
Как плюс (по сравнению со Sprig) отметил бы нативное использование query builder’а при построении запросов. В Sprig это сделано не особо удобно, что повышало порог вхождения и вносило элемент отторжения у людей, кто активно пользовал Kohana ORM. Jelly благодаря этому для них стал бы как раз легче в понимании, мне кажется…
Xobb прав — factory() метод возвращает модель. CUD же методы — Jelly_Builder…
Так же недавно ввели фабричный метод для определения типа полей Jelly::field(‘FieldType’). Так что можно определять типы по-старинке названием класса, а можно и красивым новым способом (работая в Netbeans, пользование фабричных методов у меня приводит к тренировке памяти, что не может не радовать…:))
@Xobb, @avis
Конечно же, это не factory(), а select(). Исправил.
спасибо за статью
сейчас пытаюсь портировать sprig-mptt в jelly-mptt — не так просто это сделать, Jelly действительно посложнее будет
Шикарная статья ! в прочем как всегда =)
После беглого осмотра Sprig вызвал отторжение, до сих пор юзаю ORM, а после вашей статьи как то начал задумыватся о переходе на Jelly.
Надеюсь следующая версия ORM догонит по функциональности других собратьев =)
Добавили with хорошо, уже отрыв от Sprig. Но все же нехватает оптимизации загрузки данных для hasMany/ManyToMany.
Пример.
Есть таблица languages(языки) и зависимая tests(тесты). Отношение language has many tests.
Пробегая языки в цикле и дергая зависимые тесты мы генерируем запрос на каждый язык. А надо вторым запросом получать все тесты, внешние ключи которых равны ID выбранных языков.
Даже топик открыли на гитхабе
http://github.com/jonathangeiger/kohana-jelly/issues#issue/19
Здравствуйте Иван. Я уже задал вопрос на форуме сообщества Коханы, но решил еще спросить здесь. Так как с ORM познакомился недавно возникает много вопросов, которые ранее с легкостью решал с помощью обычных запросов. Например как получить данные из 2-х таблиц с связкой через LEFT JOIN.
Имею 2 таблицы:
// Таблица, в которой хранится общая информация
CREATE TABLE `posts` (
`id` int(5) NOT NULL auto_increment,
`author` varchar(32) default NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8
// Таблица, в которой храним мультиязычные данные
CREATE TABLE `postsdescs` (
`id` int(11) NOT NULL default ’0′,
`lang` varchar(2) default ‘ru’,
`name` varchar(32)
) ENGINE=MyISAM DEFAULT CHARSET=utf8
Например получим все новости на русском языке:
SELECT posts.*, postsdescs.name FROM posts LEFT JOIN postsdescs ON (posts.id = postsdescs.id) WHERE lang = ‘ru’;
Или же все варианты конкретной новости с ID 1:
SELECT posts.*, postsdescs.name FROM posts LEFT JOIN postsdescs ON (posts.id = postsdescs.id) WHERE posts.id = 1;
Подскажите пожалуйста как получить подобные данные с помощью библиотеки Jelly?
Нужно ли создавать отдельную модель на каждую таблицу или же только на основную?
В процессе «экспериментов» я создал 2 модели:
post.php
class Model_Post extends Jelly_Model {
public static function initialize(Jelly_Meta $meta) {
$meta->name_key(‘id’)->fields(array(
‘id’ => new Field_Primary,
‘author’ => new Field_String,
‘postdesc’ => new Field_HasMany(array(‘model’=>’postsdesc’, ‘foreign’=>’id’))
));
}
}
и postdesc.php
class Model_PostsDesc extends Jelly_Model {
public static function initialize(Jelly_Meta $meta) {
$meta->fields(array(
‘id’ => new Field_BelongsTo,
‘name’ => new Field_String,
));
}
}
Данные пытаюсь получить так:
Jelly::select(‘post’)->where(‘lang’,'=’,'ru’)->execute();
Но получаю ошибку:
Database_Exception [ 1054 ]: Unknown column ‘lang’ in ‘where clause’ [ SELECT `posts`.`id` AS `id`, `posts`.`author` AS `author` FROM `posts` WHERE `lang` = 'ru' ]
Я осознаю то, что наворотил и скорее всего неправильно, но прошу строго не судить а указать на путь истинный )). Где я ошибся и как правильно построить модель(и)? Заранее спасибо.
Добрый день, можно сделать нечто вроде UPDATE table SET a = a + 1 одним запросом используя Jelly ?
Как можно подать запрос на старт транзакции «TRANSACTION START» ?
@taggi
Так как класс Jelly_Builder является потомком класса Database_Query_Builder, то можно все делать аналогично обычной работе с модулем Database. Т.е. что-то вроде Jelly::update(‘model’)->set(array(‘a’ => (new Database_Expression(‘a+1′))))->execute().
2biakaveron
Спасибо, а есть ли возможность написать такой запрос с помощью конструктора
«SELECT * FROM table LOCK IN SHARE MODE» ?
Любые нестандартные запросы (блокировка таблиц к примеру) лучше оформить в виде отдельного метода модели (или сделать наследование соответствующего драйвера БД). Не забывайте, что Query Builder предназначен для построения запросов без привязки к конкретной СУБД.
2biakaveron
Да, Вы безусловно правы, лучше попытаться расширить драйвер mysql для таких вещей. Если готовых решений нет, то придется поэкспериментировать :~)
Кстати нашел один глюк в Jelly при навешивание правил валидации на поле типа Integer получается не совсем корректная вещь, Jelly автоматом приводит значение к целому типу, а потом уже применяет валидацию.
Например мы хотим повесить правила:
не пустое
целочисленное
при любом текстовом значение поля например ‘qwer’ мы будем получать ошибку ‘поле должно содержать значание’, т.е. срабатывает правило 1, хотя это неверно.
Выход либо объявлять поле как String, либо приводить любой текст в форме к целому типу. И то, и то не совсем верно, но работает :~)
Боюсь показаться тупицей, но нигде не могу найти следующую информацию.
Вроде как модуль коханы для работы с базой не поддерживает mssql. Вопросов, собсвенно два — так ли это и решается ли этот вопрос использованием jelly?
Просто я веду разработку сайта под с использованием sqlite или mysql, но на «боевой» машине СОВЕРШЕННО НЕОЖИДАННО может оказаться только mssql…
Поддерживает — через драйвер PDO. Вроде как есть и модуль на github’е, но он еще сырой.
По поводу Jelly — не путайте, ORM всего лишь библиотека для взаимодействия с БД, никаких собственных драйверов она не содержит.
Спасибо, попробую этот kohana-mssql.
Здравствуйте, у меня возник вопрос в связи с валидацией.
Собственно я пробую реализовать UnitTest’ы и поэтому проверяю, валидируется ли моя модель с неправильными данными, к моему удивлению она валидируется!
Если при вызове validate(), поле не инициализировано, то _все_ правила игнорируются, включая not_empty, что собственно противоречит здравому смыслу! Если же инициализировать поле с пустым значением то все правила срабатывают.
Я заметил что при вызове метода save() правила срабатывают и неинициализированное поле выбрасывает ошибку.
Но дело в том что для тестирования мне не нужно сохранять что-то в базу, мне нужно всего-лишь проверить валидацию!
Если честно, то я не знаю, баг ли это или фича, или же я что-то не понял. Пожалуйста, подскажите.
@Макушкин
На самом деле в методе validate() существует проверка на наличие измененных данных. Так как Вы ничего не меняли (случай первый), то валидация на этой проверке и заканчивается. Во втором случае уже есть измененное поле (`name`), оно участвует в проверке, поэтому и выбрасывается Exception.
Как вариант, можно использовать тот факт, что метод validate() возвращает массив полей, участвовавших в проверке. Так что можно добавить примерно следующие строки:
В целом, это проблема не конкретного поля, т.е. это не игнор правила not_empty, а механизм обработки изменений модели.
Спасибо за ответ. Теперь я понимаю почему валидация себя так ведет. Это очень логично если мы имеем дело с загруженной моделью, ведь не имеет смысла проверять поля которые были уже проверены раньше.
Но в моем случае модель создается заново, т.е. все поля просто необходимо проверить.
Хорошо что все опенсорс Мне хотелось бы добавить к методу validate() возможность проверки всех данных, как это делает метод save(), например вызывая его вот так: jelly_model->validate_all(), где этот метод всего лишь обертка для $this->validate($this->_changed + $this->_original).
Пока я Вам писал, даже решение придумал
Скорее всего сделаю я свой класс-обертку для Jelly_Model и засуну эту ф-ю в него.