Похоже, вас интересуют только значения вашего вложенного хэша. В этом случае вы можете просто сделать
titles = hash.values.map(&:keys).flatten.uniq
rows = hash.values.map { |data| data.values_at(*titles) }
s = CSV.generate do |csv|
csv << titles
rows.each do |row|
csv << row
end
end
(обновить)
Заголовки должны быть сведены в простой массив.
Ответ Джастина правильный ... Я рекомендую вам проголосовать за него, так как он был первым. Его подход особенно полезен, если у вас есть несколько представлений, которые нуждаются в этой настройке A / B.
Тем не менее, обратите внимание, что вам не нужен декоратор или изменения в urls.py, если у вас всего несколько представлений. Если вы оставили свой файл urls.py как есть ...
(r'^foo/', my.view.here),
... вы можете использовать request.GET для определения запрошенного варианта представления:
def here(request):
variant = request.GET.get('ui', some_default)
Если вы хотите избежать жесткого кодирования имен шаблонов для отдельных A / B / Представления C / etc, просто сделайте их условными обозначениями в вашей схеме именования шаблонов (как рекомендует подход Джастина):
def here(request):
variant = request.GET.get('ui', some_default)
template_name = 'heretemplates/page%s.html' % variant
try:
return render_to_response(template_name)
except TemplateDoesNotExist:
return render_to_response('oops.html')
Если вы используете параметры GET как вы предположили (? ui = 2
), то вам не нужно вообще трогать urls.py. Ваш декоратор может проверить request.GET ['ui']
и найти то, что ему нужно.
Чтобы избежать жесткого кодирования имен шаблонов, возможно, вы могли бы обернуть возвращаемое значение из функции view? Вместо того, чтобы возвращать выходные данные render_to_response, вы можете вернуть кортеж (template_name, context)
и позволить декоратору манипулировать именем шаблона. Как насчет этого? ВНИМАНИЕ: Я не тестировал этот код
def ab_test(view):
def wrapped_view(request, *args, **kwargs):
template_name, context = view(request, *args, **kwargs)
if 'ui' in request.GET:
template_name = '%s_%s' % (template_name, request.GET['ui'])
# ie, 'folder/template.html' becomes 'folder/template.html_2'
return render_to_response(template_name, context)
return wrapped_view
Это действительно простой пример, но я надеюсь, что он поможет понять идею. Вы можете изменить несколько других вещей об ответе, таких как добавление информации в контекст шаблона. Вы можете использовать эти контекстные переменные для интеграции с аналитикой вашего сайта, например, с Google Analytics.
В качестве бонуса, вы могли бы реорганизовать этот декоратор в будущем, если вы решите прекратить использовать параметры GET и перейти к чему-либо на основе файлов cookie и т. д.
Обновить Если у вас уже написано много просмотров, и вы не хотите измените их все, вы можете написать свою собственную версию render_to_response
.
def render_to_response(template_list, dictionary, context_instance, mimetype):
return (template_list, dictionary, context_instance, mimetype)
def ab_test(view):
from django.shortcuts import render_to_response as old_render_to_response
def wrapped_view(request, *args, **kwargs):
template_name, context, context_instance, mimetype = view(request, *args, **kwargs)
if 'ui' in request.GET:
template_name = '%s_%s' % (template_name, request.GET['ui'])
# ie, 'folder/template.html' becomes 'folder/template.html_2'
return old_render_to_response(template_name, context, context_instance=context_instance, mimetype=mimetype)
return wrapped_view
@ab_test
def my_legacy_view(request, param):
return render_to_response('mytemplate.html', {'param': param})
Перед тем, как погрузиться в код, полезно сделать шаг назад и абстрагироваться от того, что пытается сделать A / B-тестирование. Что именно нам нужно для проведения теста?
Имея это в виду, давайте подумаем о реализации.
Цель
Когда мы думаем о цели в Интернете, обычно мы имеем в виду, что пользователь достигает определенной страницы или что они завершают конкретное действие, например, успешная регистрация в качестве пользователя или переход на страницу оформления заказа.
В Django мы могли моделировать это несколькими способами - возможно, наивно внутри представления, вызывая функцию всякий раз, когда была достигнута цель:
def checkout(request):
a_b_goal_complete(request)
...
Но это не помогает, потому что нам придется добавлять этот код везде, где он нам понадобится - плюс, если мы используем какие-либо подключаемые приложения, мы предпочли бы не редактировать их код, чтобы добавить наш A / B-тест.
Как мы можем ввести цели A / B без прямого редактирования кода представления? А как насчет промежуточного программного обеспечения?
class ABMiddleware:
def process_request(self, request):
if a_b_goal_conditions_met(request):
a_b_goal_complete(request)
Это позволит нам отслеживать цели A / B в любом месте сайта.
Как мы узнаем, что условия цели были выполнены? Для простоты реализации я предлагаю, чтобы мы знали, что цель удовлетворяет условиям, когда пользователь достигает определенного пути URL. В качестве бонуса мы можем измерить это, не пачкая руки внутри вида. Возвращаясь к нашему примеру регистрации пользователя, мы могли бы сказать, что эта цель была достигнута, когда пользователь достигает URL-пути:
/ registration / complete
Итак, мы определяем a_b_goal_conditions_met
:
a_b_goal_conditions_met(request):
return request.path == "/registration/complete":
Paths
Когда вы думаете о Paths в Django, естественно, что вы подумали об использовании разных шаблонов. Остается выяснить, есть ли другой способ. При A / B-тестировании вы делаете небольшие различия между двумя страницами и измеряете результаты. Следовательно, рекомендуется определить единый базовый шаблон пути, от которого должны исходить все пути к цели.
Как следует отображать эти шаблоны? Декоратор, вероятно, будет хорошим началом - в Django лучше всего включать параметр имя_шаблона
в ваши представления, чтобы декоратор мог изменить этот параметр во время выполнения.
@a_b
def registration(request, extra_context=None, template_name="reg/reg.html"):
...
Вы могли видеть, что этот декоратор либо исследует обернутую функцию и изменение аргумента имя_шаблона
или поиск правильных шаблонов откуда-то (например, модели). Если бы мы не сделали Я хочу добавить декоратор к каждой функции, которую мы могли бы реализовать как часть нашего ABMiddleware:
class ABMiddleware:
...
def process_view(self, request, view_func, view_args, view_kwargs):
if should_do_a_b_test(...) and "template_name" in view_kwargs:
# Modify the template name to one of our Path templates
view_kwargs["template_name"] = get_a_b_path_for_view(view_func)
response = view_func(view_args, view_kwargs)
return response
Нам также нужно будет добавить способ отслеживать, в каких представлениях выполняются A / B-тесты и т. д.
A система отправки зрителей по Пути
Теоретически это просто, но существует множество различных реализаций, поэтому неясно, какая из них лучше. Мы знаем, что хорошая система должна равномерно разделять пользователей по пути - Должен использоваться какой-то метод хеширования - Возможно, вы могли бы использовать модуль счетчика кэша памяти, разделенный на количество путей - может быть, есть лучший способ.
Система для записи Результаты теста
Нам нужно записать, сколько пользователей пошли по тому пути - мы » http://github.com/paulmars/seven_minute_abs/tree/master
Обновление
При дальнейшем размышлении и исследовании Оптимизатора веб-сайтов Google становится очевидным, что в приведенной выше логике есть зияющие дыры. Используя разные шаблоны для представления путей, вы нарушаете кеширование представления (или, если представление кэшировано, оно всегда будет обслуживать один и тот же путь!). Вместо использования Paths я бы украл терминологию GWO и использовал идею Combinations
- это одна конкретная часть изменения шаблона - например, изменение тега
сайта.
Решение могло бы включать теги шаблонов, которые отображались бы до JavaScript. Когда страница загружается в браузере, JavaScript делает запрос к вашему серверу, который выбирает одну из возможных комбинаций.
Таким образом, вы можете протестировать несколько комбинаций на странице с сохранением кеширования!
Обновление
Еще есть место для переключения шаблонов - скажем, вы вводите совершенно новую домашнюю страницу и хотите проверить ее производительность по сравнению со старой. - вы все равно захотите использовать технику переключения шаблонов. Следует иметь в виду, что вам придется придумать способ переключения между X количеством кэшированных версий страницы. Для этого вам нужно переопределить стандартное кэшированное промежуточное ПО, чтобы увидеть, выполняется ли это A / B-тест по запрошенному URL-адресу. Затем он мог выбрать правильную кешированную версию для отображения !!!
Обновление
Используя идеи, описанные выше, я реализовал подключаемое приложение для базового A / B-тестирования Django. Вы можете получить его с Github:
http: // github. com / johnboxall / django-ab / tree / master
Django Lean - хороший вариант для A / B-тестирования
Код, основанный на коде Джастина Восса:
def ab_test(force = None):
def _ab_test(view):
def wrapped_view(request, *args, **kwargs):
request, template_name, cont = view(request, *args, **kwargs)
if 'ui' in request.GET:
request.session['ui'] = request.GET['ui']
if 'ui' in request.session:
cont['ui'] = request.session['ui']
else:
if force is None:
cont['ui'] = '0'
else:
return redirect_to(request, force)
return direct_to_template(request, template_name, extra_context = cont)
return wrapped_view
return _ab_test
пример функции с использованием кода:
@ab_test()
def index1(request):
return (request,'website/index.html', locals())
@ab_test('?ui=33')
def index2(request):
return (request,'website/index.html', locals())
Что здесь происходит: 1. Переданный параметр UI сохраняется в переменной сессии 2. Каждый раз загружается один и тот же шаблон, но контекстная переменная {{ui}} хранит идентификатор UI (вы можете использовать его для изменения шаблона). 3. Если пользователь заходит на страницу без ?ui=xx, то в случае index2 он перенаправляется на '?ui=33', в случае index1 переменная UI устанавливается в 0.
Я использую 3 для перенаправления с главной страницы на Google Website Optimizer, который в свою очередь перенаправляет обратно на главную страницу с правильным параметром ?ui.