Статьи

Neo4j на Heroku — Часть 2

Мы начинаем с того места, где остановились на Neo4j на Heroku — часть первая, поэтому убедитесь, что вы прочитали это, или вы немного растерялись. Пока что мы клонировали проект Neoflix , настроили наше приложение Heroku и добавили дополнение Neo4j к нашему приложению. Теперь мы готовы заполнить наш график.

Откройте два окна браузера. На одном вы перейдете к своему экземпляру Neo4j, работающему на Heroku,

$ heroku config
NEO4J_URL      => http://xxxxxxxx:yyyyyyyy@70825a524.hosted.neo4j.org:7014

а с другой стороны вы перейдете к маршруту create_graph вашего приложения. Так что если вы назвали свое приложение neoflix, вы бы выбрали neoflix dot herokuapp dot com / create_graph.

Это запустит метод create_graph, и вы увидите узлы и отношения, создаваемые на Neo4j Dashboard. Это чуть более миллиона отношений, так что это займет несколько минут. Существуют более быстрые способы загрузки данных в Neo4j (дождитесь третьей части этой серии), но в нашем случае это сработает.

 

Прекрасные ребята на themoviedb.org предоставляют API для всех разработчиков, которые хотят интегрировать данные фильмов и актеров вместе с постерами или фанатами фильмов. Вы можете запросить ключ API, и они ответят очень быстро. Итак, давайте добавим это к нашим конфигам Heroku.

heroku config:add TMDB_KEY=XXXXXXX
Adding config vars and restarting app... done, vXX
  TMDB    => XXXXXXX

Если вы хотите проверить локально, вы можете сделать это:

export TMDB_KEY=XXXXXXX

Теперь мы можем использовать эту переменную среды в нашем приложении вместе с гемом ruby-tmdb от Aaron Gough :

require 'ruby-tmdb'

Tmdb.api_key = ENV['TMDB_KEY']
Tmdb.default_language = "en"

  def get_poster(data)
    movie = TmdbMovie.find(:title => CGI::escape(data["title"] || ""), :limit => 1)
    if movie.empty?
     "No Movie Poster found"
    else
      "<a href="#{movie.url}" target='_blank'>
       <img src="#{movie.posters.first.url}">
       <h3>#{movie.tagline}</h3>
       <p>Rating: #{movie.rating} <br />
          Rated: #{movie.certification}</p><p>#{movie.overview}</p>"
    end
  end

Мы визуализируем график, как я показал вам ранее, используя Neovigator, но вместо того, чтобы извлекать свойства нашего узла (поскольку они довольно мягкие), мы запросим постер фильма.

Мы не будем визуализировать явные отношения, которые мы создали. Вместо этого мы будем визуализировать график неявных рекомендаций к фильму. Давайте посмотрим на этот метод сейчас:

def get_recommendations(neo, node_id)
  rec = neo.execute_script("m = [:];
                            x = [] as Set;
                            v = g.v(node_id);

                            v.
                            out('hasGenera').
                            aggregate(x).
                            back(2).
                            inE('rated').
                            filter{it.getProperty('stars') > 3}.
                            outV.
                            outE('rated').
                            filter{it.getProperty('stars') > 3}.
                            inV.
                            filter{it != v}.
                            filter{it.out('hasGenera').toSet().equals(x)}.
                            groupCount(m){\"${it.id}:${it.title.replaceAll(',',' ')}\"}.iterate();

                            m.sort{a,b -> b.value <=> a.value}[0..24];",
                            {:node_id => node_id.to_i})

  return [{"id" => node_id,
           "name" => "No Recommendations",
           "values" => [{"id" => "#{node_id}",
                         "name" => "No Recommendations"}]
          }] if rec == "{}"

  values = rec[1..rec.size-1].split(',').collect{ |v| {:id => v.split(':')[0].strip, 
                                                       :name => v.split(':')[1] } }

  [{"id" => node_id ,"name" => "Recommendations","values" => values }]
end

Давайте пройдемся по коду. В Groovy [:] есть карта (эквивалентная Ruby Hash) и, в конечном итоге, то, что мы хотим вернуть, поэтому мы создадим пустую и заполним ее позже. Затем мы создадим Set «x» (это неупорядоченная коллекция, см. Groovy List для упорядоченных коллекций). Мы также получаем нашу начальную вершину и присваиваем ей «v».

 

m = [:];
x = [] as Set;
v = g.v(node_id);

Мы наполним пустой набор, который мы создали, родами нашего фильма, а потом сравним с ним роды других фильмов.

v.
out('hasGenera').
aggregate(x).

Затем мы возвращаемся на 2 шага, что ставит нас в начало нашего фильма и переходят к пользователям, которые оценили фильм более чем на 3 звезды.

back(2).
inE('rated').
filter{it.getProperty('stars') > 3}.

От этих пользователей мы выходим, чтобы найти все фильмы, которые они также оценили более чем на 3 звезды.

outV.
outE('rated').
filter{it.getProperty('stars') > 3}.

Это не наш начальный фильм (помните, что мы установили его в переменную «v»).

inV.
filter{it != v}.

… И мы проверяем, что эти фильмы имеют те же роды, что и наш стартовый фильм (помните, что мы заполнили сет «х»).

filter{it.out('hasGenera').toSet().equals(x)}.

groupCount делает то, на что это похоже, и сохраняет значение в карте «m», которую мы создали ранее. Однако мы хотим получить id, title и count, поэтому мы немного разбираемся в строках, чтобы получить id и title (без запятых… я расскажу почему через минуту) и iterate () . Оболочка Gremlin выполняет итерации автоматически для вас, но поскольку мы отправляем этот скрипт Gremlin через REST API, это не так. Однажды ты выдернешь свои волосы, пытаясь понять, что не так, и будешь проклинать «повторяйся», как только это поймешь…

groupCount(m){\"${it.id}:${it.title.replaceAll(',',' ')}\"}.iterate();

Здесь мы сортируем нашу Карту (b имеет счетчик) и получаем 25 лучших записей.

m.sort{a,b -> b.value <=> a.value}[0..24];",

Поскольку Neo4j будет выполнять этот код много раз, вы хотите его параметризировать, поэтому он анализирует его только один раз.

{:node_id => node_id.to_i})

Если мы вернем пустой хеш, мы вернем неудачное сообщение «Нет рекомендаций»,

return [{"id" => node_id,
         "name" => "No Recommendations",
         "values" => [{"id" => "#{node_id}",
                       "name" => "No Recommendations"}]
        }] if rec == "{}"

Наконец, мы структурируем нашу Groovy Map в массив хэшей, которые мы используем в нашей визуализации, как я показал вам с Neovigator . Обратите внимание, что я разделяю запись запятыми (поэтому мы и подставили их раньше). Эта часть не понадобится очень скоро, так как финальная версия Neo4j 1.6 будет иметь поддержку JSON для Groovy Maps.

values = rec[1..rec.size-1].split(',').collect{ |v| {:id => v.split(':')[0].strip, 
                                                     :name => v.split(':')[1] } }
[{"id" => node_id ,"name" => "Recommendations","values" => values }]

 

Мы сохраняем результаты получения постера фильма и его рекомендации в течение 30 дней, пользуясь кэшем лака, предоставленным нам Heroku. Затем мы получаем наш начальный узел либо по id, либо по названию.

get '/resources/show' do
  response.headers['Cache-Control'] = 'public, max-age=2592000'
  content_type :json

  if params[:id].is_numeric?
    node = neo.get_node(params[:id])
  else
    node = neo.execute_script("g.idx(Tokens.T.v)[[title:'#{CGI::unescape(params[:id])}']].next();")
  end

  id = node_id(node)

  {:details_html => "<h2>#{get_name(node["data"])}</h2>" + get_poster(node["data"]),
   :data => {:attributes => get_recommendations(neo, id),
             :name => get_name(node["data"]),
             :id => id}
   }.to_json
end

По названию? Да, мы добавляем автозаполнение JQuery UI в наше приложение. Который передаст название фильма и найдет его в автоматическом индексе, который мы создали.

node = neo.execute_script("g.idx(Tokens.T.v)[[title:'#{CGI::unescape(params[:id])}']].next();")

… и вот оно у вас. Ваш собственный сайт с рекомендациями по фильмам на Heroku. Смотрите полный код на github.com/maxdemarzi/neoflix .

ОБНОВЛЕНИЕ: Похоже, что некоторые из вас, дорогие читатели, пытались запустить create_graph несколько раз, и это привело к путанице. Я постараюсь исправить это и скоро восстановить. Примечание для будущего себя: удалите маршрут create_graph на heroku перед публикацией поста.

Источник: http://maxdemarzi.com/2012/01/16/neo4j-on-heroku-part-two/