Commit b56289bd by Trong Huu Nguyen

Merge branch 'dhp_solr' into 'development'

[Review] Solr feature

See merge request !5
parents efca5cdb acbd7d6e
...@@ -23,3 +23,6 @@ ...@@ -23,3 +23,6 @@
# Ignore database config file # Ignore database config file
/config/database.yml /config/database.yml
config/settings.local.yml
config/settings/*.local.yml
config/environments/*.local.yml
...@@ -36,6 +36,8 @@ gem 'carrierwave', '~> 1.0' ...@@ -36,6 +36,8 @@ gem 'carrierwave', '~> 1.0'
gem 'mini_magick' gem 'mini_magick'
# bootstrap-sass is a Sass-powered version of Bootstrap 3 # bootstrap-sass is a Sass-powered version of Bootstrap 3
gem 'bootstrap-sass', '~> 3.3.6' gem 'bootstrap-sass', '~> 3.3.6'
# Config helps you easily manage environment specific settings in an easy and usable manner.
gem 'config'
# Faker, a port of Data::Faker from Perl, is used to easily generate fake data: names, addresses, phone numbers, etc. # Faker, a port of Data::Faker from Perl, is used to easily generate fake data: names, addresses, phone numbers, etc.
gem 'faker', '~> 1.6', '>= 1.6.3' gem 'faker', '~> 1.6', '>= 1.6.3'
# Kaminari is a Scope & Engine based, clean, powerful, agnostic, customizable and sophisticated paginator for Rails 4+ # Kaminari is a Scope & Engine based, clean, powerful, agnostic, customizable and sophisticated paginator for Rails 4+
...@@ -58,6 +60,8 @@ gem 'toastr-rails', '~> 1.0', '>= 1.0.3' ...@@ -58,6 +60,8 @@ gem 'toastr-rails', '~> 1.0', '>= 1.0.3'
# (or more often navigation item) # (or more often navigation item)
# is selected based on the current page or other arbitrary condition # is selected based on the current page or other arbitrary condition
gem 'active_link_to', '~> 1.0', '>= 1.0.4' gem 'active_link_to', '~> 1.0', '>= 1.0.4'
# RSolr aims to provide a simple and extensible library for working with Solr
gem 'rsolr', '~> 2.0', '>= 2.0.2'
# Use Capistrano for deployment # Use Capistrano for deployment
# gem 'capistrano-rails', group: :development # gem 'capistrano-rails', group: :development
......
...@@ -76,7 +76,11 @@ GEM ...@@ -76,7 +76,11 @@ GEM
execjs execjs
coffee-script-source (1.12.2) coffee-script-source (1.12.2)
concurrent-ruby (1.0.5) concurrent-ruby (1.0.5)
config (1.4.0)
activesupport (>= 3.0)
deep_merge (~> 1.1.1)
connection_pool (2.2.1) connection_pool (2.2.1)
deep_merge (1.1.1)
devise (4.3.0) devise (4.3.0)
bcrypt (~> 3.0) bcrypt (~> 3.0)
orm_adapter (~> 0.1) orm_adapter (~> 0.1)
...@@ -87,6 +91,8 @@ GEM ...@@ -87,6 +91,8 @@ GEM
execjs (2.7.0) execjs (2.7.0)
faker (1.6.6) faker (1.6.6)
i18n (~> 0.5) i18n (~> 0.5)
faraday (0.12.1)
multipart-post (>= 1.2, < 3)
ffi (1.9.18) ffi (1.9.18)
font-awesome-rails (4.7.0.2) font-awesome-rails (4.7.0.2)
railties (>= 3.2, < 5.2) railties (>= 3.2, < 5.2)
...@@ -132,6 +138,7 @@ GEM ...@@ -132,6 +138,7 @@ GEM
mini_portile2 (2.2.0) mini_portile2 (2.2.0)
minitest (5.10.2) minitest (5.10.2)
multi_json (1.12.1) multi_json (1.12.1)
multipart-post (2.0.0)
mysql2 (0.4.6) mysql2 (0.4.6)
nio4r (2.1.0) nio4r (2.1.0)
nokogiri (1.8.0) nokogiri (1.8.0)
...@@ -187,6 +194,9 @@ GEM ...@@ -187,6 +194,9 @@ GEM
responders (2.4.0) responders (2.4.0)
actionpack (>= 4.2.0, < 5.3) actionpack (>= 4.2.0, < 5.3)
railties (>= 4.2.0, < 5.3) railties (>= 4.2.0, < 5.3)
rsolr (2.0.2)
builder (>= 2.1.2)
faraday
rubocop (0.49.1) rubocop (0.49.1)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 2.3.3.1, < 3.0) parser (>= 2.3.3.1, < 3.0)
...@@ -262,6 +272,7 @@ DEPENDENCIES ...@@ -262,6 +272,7 @@ DEPENDENCIES
capybara (~> 2.13) capybara (~> 2.13)
carrierwave (~> 1.0) carrierwave (~> 1.0)
coffee-rails (~> 4.2) coffee-rails (~> 4.2)
config
devise (~> 4.3) devise (~> 4.3)
faker (~> 1.6, >= 1.6.3) faker (~> 1.6, >= 1.6.3)
font-awesome-rails (~> 4.7, >= 4.7.0.2) font-awesome-rails (~> 4.7, >= 4.7.0.2)
...@@ -275,6 +286,7 @@ DEPENDENCIES ...@@ -275,6 +286,7 @@ DEPENDENCIES
puma (~> 3.7) puma (~> 3.7)
rails (~> 5.1.1) rails (~> 5.1.1)
rails-assets-bootstrap-touchspin! rails-assets-bootstrap-touchspin!
rsolr (~> 2.0, >= 2.0.2)
rubocop (~> 0.49.1) rubocop (~> 0.49.1)
sass-rails (~> 5.0) sass-rails (~> 5.0)
selenium-webdriver selenium-webdriver
......
(function($) { (function($) {
// Vertical Spinner - Touchspin - Product Details Quantity input // Vertical Spinner - Touchspin - Product Details Quantity input
if ( $.fn.TouchSpin ) { if ( $.fn.TouchSpin ) {
$('#product_quantity').TouchSpin({ $('.vertical-spinner').TouchSpin({
verticalbuttons: true verticalbuttons: true
}); });
$('.qty-input').TouchSpin(); $('.qty-input').TouchSpin();
......
(function($) { (function($) {
toastr.options.closeButton = true; toastr.options.closeButton = true;
// Search Dropdown Toggle
$('.search-toggle').on('click', function (e) {
$('.header-search-wrapper').toggleClass('open');
e.preventDefault();
});
}).apply(this, [jQuery]); }).apply(this, [jQuery]);
\ No newline at end of file
...@@ -92,15 +92,6 @@ html .btn-primary:active:focus { ...@@ -92,15 +92,6 @@ html .btn-primary:active:focus {
#header .header-logo img { #header .header-logo img {
margin: 0 24px 0 0; margin: 0 24px 0 0;
} }
#header .header-container {
padding-top: 28px;
padding-bottom: 28px;
position: relative;
display: table;
}
#header .header-container.header-nav {
padding: 0;
}
#header .cart-area { #header .cart-area {
float: right; float: right;
vertical-align: middle; vertical-align: middle;
...@@ -171,7 +162,6 @@ header .header-search { ...@@ -171,7 +162,6 @@ header .header-search {
position: relative; position: relative;
width: 100%; width: 100%;
min-width: 250px; min-width: 250px;
padding-right: 170px;
background-color: #fff; background-color: #fff;
} }
#header .header-search .header-search-wrapper.open { #header .header-search .header-search-wrapper.open {
...@@ -278,6 +268,9 @@ header .header-search { ...@@ -278,6 +268,9 @@ header .header-search {
color: #000; color: #000;
background-color: transparent; background-color: transparent;
} }
#header .header-nav-main nav > ul > li > a {
border-radius: 0;
}
.panel-default>.panel-heading { .panel-default>.panel-heading {
color: #333; color: #333;
background-color: #f5f5f5; background-color: #f5f5f5;
......
class SearchController < ApplicationController
before_action :set_search_result, only: :show
# GET /search
def show
@products = if @search_result.is_a?(Array)
Kaminari.paginate_array(@search_result).page(params[:page]).per(5)
else
@search_result.page(params[:page]).per(5)
end
end
private
def set_search_result
@search_result = if params[:q].present?
Search.new(params[:q]).products
else
Product.all
end
end
end
\ No newline at end of file
class StaticPagesController < ApplicationController class StaticPagesController < ApplicationController
def index def index
@latest_products = Product.page(params[:page]).per(10) @latest_products = Product.page(params[:page]).per(5)
# @TODO: Get recommended products # @TODO: Get recommended products
@recommended_products = Product.last(6) @recommended_products = Product.last(6)
# session.clear # session.clear
......
module SearchHelper
def render_search_result(search_result)
if search_result.blank?
render html: 'Sorry! There are no products matched your query'
else
render(partial: 'products/product_list',
locals: { products: search_result })
end
end
end
\ No newline at end of file
...@@ -16,7 +16,7 @@ class Cart ...@@ -16,7 +16,7 @@ class Cart
real_quantity = product_quantity(product_id, quantity) real_quantity = product_quantity(product_id, quantity)
return false unless product_is_in_stock?(product_id, real_quantity) return false unless product_is_in_stock?(product_id, real_quantity)
if product_item_exist?(product_id) if product_item_exist?(product_id)
@cart[product_id.to_s][:quantity.to_s] += real_quantity.to_i @cart[product_id.to_s][:quantity.to_s] = real_quantity.to_i
else else
@cart[product_id.to_s] = { quantity: real_quantity.to_i } @cart[product_id.to_s] = { quantity: real_quantity.to_i }
end end
......
class Search
def initialize(search_query)
@solr = SolrSearch.new
@search_query = search_query
end
def products
@solr.search_for(@search_query)
filter_products(@solr.docs)
end
private
def filter_products(products)
product_ids = products.map { |product| product[:id.to_s] }
Product.find(product_ids)
end
end
\ No newline at end of file
require 'rsolr'
class SolrSearch
attr_accessor :rsolr
def initialize
@rsolr = RSolr.connect url: Settings.rsolr.address
@response = nil
end
def search_for(search_query)
search_query ||= ''
@response = rsolr.get 'select', params: { q: sanitize_query(search_query) }
end
def docs
@response['response']['docs']
end
def sanitize_query(query)
query.gsub(/[^0-9A-Za-z]/, '')
end
end
\ No newline at end of file
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
<td class="product-image-td"></td> <td class="product-image-td"></td>
<td class="product-name-td"> <td class="product-name-td">
<h2 class="product-name"> <h2 class="product-name">
<%= link_to product_item[:product].title, product_item_url(product_item[:product]) %> <%= link_to product_item[:product].title, product_url(product_item[:product]) %>
</h2> </h2>
</td> </td>
<td><%= number_to_currency(product_item[:product].price) %></td> <td><%= number_to_currency(product_item[:product].price) %></td>
......
...@@ -36,15 +36,11 @@ ...@@ -36,15 +36,11 @@
</a> </a>
</div> </div>
</div> </div>
<div class="header-search"> <%= render 'layouts/header/search_form' %>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> <%= render 'layouts/header/main_nav' %>
<div class="header-container header-nav">
</div>
</div> </div>
</header> </header>
\ No newline at end of file
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
<%= csrf_meta_tags %> <%= csrf_meta_tags %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= stylesheet_link_tag 'https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700,800%7CShadows+Into+Light' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
</head> </head>
......
<div class="header-container header-nav">
<div class="container">
<div class="header-nav-main">
<nav>
<ul id="mainNav" class="nav nav-pills">
<%= active_link_to 'Home', root_url, wrap_tag: :li, active: :exclusive %>
</ul>
</nav>
</div>
</div>
</div>
\ No newline at end of file
<div class="header-search">
<a href="#" class="search-toggle"><i class="fa fa-search"></i></a>
<%= form_tag search_result_path, method: :get do %>
<div class="header-search-wrapper">
<%= text_field_tag :q, params[:q], class: 'form-control', placeholder: 'Search...' %>
<%= button_tag raw('<i class="fa fa-search"></i>'), class: 'btn btn-default' %>
</div>
<% end %>
</div>
\ No newline at end of file
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
<div class="col-md-12"> <div class="col-md-12">
<div class="form-group"> <div class="form-group">
<%= f.label :quantity %> <%= f.label :quantity %>
<%= f.number_field :quantity, class: "form-control" %> <%= f.number_field :quantity, class: "form-control vertical-spinner" %>
</div> </div>
</div> </div>
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
<div class="product-detail-qty"> <div class="product-detail-qty">
<%= number_field_tag :product_quantity, 1, min: 0, class: 'vertical-spinner' %> <%= number_field_tag :product_quantity, 1, min: 0, class: 'vertical-spinner' %>
</div> </div>
<%= submit_tag 'Add to cart', class: 'addtocart' %> <%= button_tag raw(fa_icon('shopping-cart', text: 'Add to cart')), class: 'addtocart' %>
<% end %> <% end %>
</div> </div>
</div> </div>
......
<div class="product-actions">
<%= form_tag cart_add_product_item_path(product.id), remote: true do %>
<%= hidden_field_tag :product_quantity, 1 %>
<%= button_tag fa_icon('shopping-cart', text: 'Add to cart'), class: 'addtocart' %>
<% end %>
</div>
\ No newline at end of file
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
<div class="product-price-box"> <div class="product-price-box">
<span class="product-price"><%= number_to_currency(product.price) %></span> <span class="product-price"><%= number_to_currency(product.price) %></span>
</div> </div>
<%= render partial: 'products/product_action', locals: { product: product } %>
</div> </div>
</div> </div>
</li> </li>
......
...@@ -11,11 +11,12 @@ ...@@ -11,11 +11,12 @@
<%= link_to fa_icon('pencil'), edit_product_path(product) if product.belongs_to_user?(current_user) %> <%= link_to fa_icon('pencil'), edit_product_path(product) if product.belongs_to_user?(current_user) %>
</h2> </h2>
<div class="product-short-desc"> <div class="product-short-desc">
<%= product.description %> <%= truncate(product.description, length: 300) %>
</div> </div>
<div class="product-price-box"> <div class="product-price-box">
<span class="product-price"><%= number_to_currency(product.price) %></span> <span class="product-price"><%= number_to_currency(product.price) %></span>
</div> </div>
<%= render partial: 'products/product_action', locals: { product: product } %>
</div> </div>
</div> </div>
</li> </li>
......
<div class="container">
<div class="row">
<div class="col-md-9 col-md-push-3">
<h2 class="h2 heading-primary mt-lg clearfix">
<span>Search result</span>
</h2>
<%= render_search_result(@products) %>
<div class="toolbar-bottom">
<div class="toolbar">
<div class="sorter">
<%= paginate @products, theme: 'bootstrap' %>
</div>
</div>
</div>
</div>
<div class="col-md-3 col-md-pull-9 sidebar shop-sidebar">
<%= render 'shared/sidebar' %>
</div>
</div>
</div>
<tr>
<td><%= link_to "##{product.id}", edit_product_path(product) %></td>
<td class="product-name-td">
<h2 class="product-name">
<%= link_to product.title, product_url(product) %>
</h2>
</td>
<td><%= number_to_currency(product.price) %></td>
<td><%= product.quantity %></td>
<td><%= link_to fa_icon('pencil'), edit_product_path(product) %></td>
</tr>
\ No newline at end of file
<table class="cart-table">
<thead>
<tr>
<th>ID</th>
<th>Product Name</th>
<th>Unit Price</th>
<th>Qty</th>
<th></th>
</tr>
</thead>
<tbody>
<% @products.each do |product| %>
<%= render partial: 'product', locals: { product: product } %>
<% end %>
</tbody>
</table>
\ No newline at end of file
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<h2 class="h2 heading-primary font-weight-normal"> <h2 class="h2 heading-primary font-weight-normal">
My Products My Products
</h2> </h2>
<%= render partial: 'products/product_list', locals: { products: @products } %> <%= render 'product_table' %>
<div class="toolbar-bottom"> <div class="toolbar-bottom">
<div class="toolbar"> <div class="toolbar">
<div class="sorter"> <div class="sorter">
......
Config.setup do |config|
# Name of the constant exposing loaded settings
config.const_name = 'Settings'
# Ability to remove elements of the array set in earlier loaded settings file. For example value: '--'.
#
# config.knockout_prefix = nil
# Overwrite arrays found in previously loaded settings file. When set to `false`, arrays will be merged.
#
# config.overwrite_arrays = true
# Load environment variables from the `ENV` object and override any settings defined in files.
#
# config.use_env = false
# Define ENV variable prefix deciding which variables to load into config.
#
# config.env_prefix = 'Settings'
# What string to use as level separator for settings loaded from ENV variables. Default value of '.' works well
# with Heroku, but you might want to change it for example for '__' to easy override settings from command line, where
# using dots in variable names might not be allowed (eg. Bash).
#
# config.env_separator = '.'
# Ability to process variables names:
# * nil - no change
# * :downcase - convert to lower case
#
# config.env_converter = :downcase
# Parse numeric values as integers instead of strings.
#
# config.env_parse_values = true
end
...@@ -20,4 +20,5 @@ Rails.application.routes.draw do ...@@ -20,4 +20,5 @@ Rails.application.routes.draw do
get '/', to: 'users#show', as: 'user_profile' get '/', to: 'users#show', as: 'user_profile'
get '/products', to: 'users#products', as: 'user_products' get '/products', to: 'users#products', as: 'user_products'
end end
get '/search', to: 'search#show', as: 'search_result'
end end
# RSolr
rsolr:
address: http://localhost:8983/solr/dhp_venshop
\ No newline at end of file
namespace :import_solr_data do
desc "Import product data from database"
task product: :environment do
documents = []
Product.all.each do |product|
documents << { id: product.id, title: product.title, price: product.price }
end
solr_search = SolrSearch.new
solr_search.rsolr.add documents
end
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment