Efficently querying multi language categories and category items - mysql

So I have a bit of a server response time issue - which I think is caused due to obsolete queries. One major query chain that I have takes up to 370ms, which is obviously causing an issue.
Here are the requirements:
5 Different languages
There are several Product Categories (i.e. Cat 1, Cat 2, Cat 3, etc.)
Categories displayed depend on language. For example whilst category 1 is displayed in all languages, category 2 is only displayed in Germany and France but not in the UK
Each category contains x number of items (has_many belongs_to relationship). Again some items are displayed in certain languages others are not. For example even category 2 is displayed in France and Germany, only in Germany you can buy Item 1 and hence Item 1 should not be displayed in France but Germany.
The categories and items do have boolean fields named after the locale. This way I can set via flag whether or not to display the category and item in a specific language.
My solution:
Building the solution is quiet easy. In controller I read out all the categories for the current locale:
application_controller.rb (since it is used on every single page)
#product_categories = ProductCategory.where("lang_" + I18n.locale.to_s + " = ?", true)
And in the view (the navigation) I do the following:
layouts/navs/productnav.html.haml
- #product_categories.each do |category|
...
- category.products.includes(:product_teasers).where("lang_" + I18n.locale.to_s + " = ? AND active = ?", true, true).in_groups_of(3).each do |group|
...
The issue with this solution is that each time I fire a lot of queries towards the database. Using "includes" does not solve it as I can not specify what items to pull. Furthermore I require the in_groups_of(3) in my loop to display the items correctly on the page.
I was also looking into memchached solutions to have the queries cached all together - i.e. Dalli however, this would require me to change a lot of code as I am guessing I would require to query all categories for each language and cache them. In addition to it I have to query each item for each langugage depending on language and store that somehow in an array ?!
My question:
How to approach this ? There must be a simpler and more efficient solution. How to efficiently query respectively cache this?
Thank you!
UPDATE:
As requested here are my two Models:
1.) ProductCategory
class ProductCategory < ActiveRecord::Base
translates :name, :description, :slug, :meta_keywords, :meta_description, :meta_title, :header, :teaser, :fallbacks_for_empty_translations => true
extend FriendlyId
friendly_id :name, :use => [:globalize, :slugged]
globalize_accessors :locales => [:at, :de, :ch_de, :ch_fr, :fr, :int_en, :int_fr], :attributes => [:slug]
has_paper_trail
has_many :products, :dependent => :destroy
validates :name, presence: true, uniqueness: { case_sensitive: false }
default_scope { includes(:translations) }
private
def slug_candidates
[
[:name]
]
end
end
2.) Product
And every product Category can have 0..n Products, and each Product must belongs to one category.
class Product < ActiveRecord::Base
translates :slug, :name, :meta_keywords, :meta_description, :meta_title, :teaser, :power_range, :product_page_teaser, :product_category_slider_teaser, :fallbacks_for_empty_translations => true
extend FriendlyId
friendly_id :name, :use => :globalize
before_save :change_file_name
searchable do
text :name, :teaser, :product_page_teaser, :product_category_slider_teaser
integer :product_category_id
end
belongs_to :product_category
has_many :product_teasers, :dependent => :destroy
has_many :product_videos, :dependent => :destroy
has_many :product_banners, :dependent => :destroy
has_many :product_documents, :dependent => :destroy
has_many :product_tabs, :dependent => :destroy
has_many :active_relationships, class_name: "Relationship",
foreign_key: "follower_id",
dependent: :destroy
has_many :passive_relationships, class_name: "Relationship",
foreign_key: "followed_id",
dependent: :destroy
has_many :following, through: :active_relationships, source: :followed
has_many :followers, through: :passive_relationships, source: :follower
has_many :references
has_and_belongs_to_many :contacts
accepts_nested_attributes_for :product_teasers, :reject_if => :all_blank, :allow_destroy => true
accepts_nested_attributes_for :product_tabs, :reject_if => :all_blank, :allow_destroy => true
accepts_nested_attributes_for :product_videos, :reject_if => :all_blank, :allow_destroy => true
accepts_nested_attributes_for :product_banners, :reject_if => :all_blank, :allow_destroy => true
accepts_nested_attributes_for :product_documents, :reject_if => :all_blank, :allow_destroy => true
has_paper_trail
validates :name, presence: true, uniqueness: { case_sensitive: false }
default_scope {includes(:translations)}
.... a lot more going on here ...
end
Please note: That category contains language flags (booleans), i.e lang_at, lang_de, lang_fr, etc. and if set then this category is displayed in that particualar language. SAME applies to products, as certain products are not displayed in all langauges altough the category might be.
Examples:
#product_categories = ProductCategory.where("product_categories.lang_" + I18n.locale.to_s + " = ?", true)
#products = Product.where("product_categories.lang_" + I18n.locale.to_s + " = ?", true)
I skipped any includes on purpose above - it is just to demonstrate the language logic.

UPDATE
The system have spent a lot of times to loop data in nested loop. Avoid to fetch data in nested loop. You have to use join or includes to catch your data more effective. For example:
Controller
#products = Product.includes(:product_category).where("product_categories.lang_" + I18n.locale.to_s + " = ? AND product_categories.active = ?", true, true).group_by(&:category)
View
- #products.each do |category, products|
<%= category.name %>
- products.each do |product|
<%= product.title %>
It needs to fix with your necessary code. I just help the main query. For example: active = ? is for products field or product_categories field. I hope It can help you.

Related

Adding current location to Ruby on Rails app with photo_geoloader

So I've sent about 3 days researching geoip, geocoder, cookies, sessions, html5 and so forth.
I am trying to add a current location (as of the location when the user posts the photo in form). The user will not change this data, I just want it to be automatically added to the post, and then added to the view of the post (currently i am using .time_ago for the timestamp of the post). I can currently show in a map, but am lost as to adding it to the post.
I prefer to user HTML5 or
rails #lat_lng = cookies[:lat_lng].split("|").
I just need it printed to the post of each user.
I have tried using Exifr gem to extract that data but it is not precise enough. I am interested also in photo_geoloader gem yet there is little documentation.
So far i have created a model using "photo" as my model name. Currently I am validating the uploaded image through my "post" model;
class Post < ActiveRecord::Base
acts_as_votable
belongs_to :model
has_many :comments, dependent: :destroy
has_many :notifications, dependent: :destroy
validates :model_id, presence: true
validates :image, presence: true
validates :caption, length: { minimum: 3, maximum: 300 }
has_attached_file :image,
styles: lambda { |a| a.instance.check_image_type}
def check_image_type
if is_image_type?
{:medium => "640"}
elsif is_video_type?
{
:medium => { :geometry => "640x480", :format => 'flv'},
:thumb => { :geometry => "100x100#", :format => 'jpg', :time => 10}, :processors => [:transcoder]
}
else
{}
end
end
def is_image_type?
image_content_type =~ %r(image)
end
def is_video_type?
image_content_type =~ %r(video)
end
validates_attachment_content_type :image, :content_type => [/\Aimage\/.*\Z/, /\Avideo\/.*\Z/, /\Aaudio\/.*\Z/]
scope :of_followed_models, -> (following_models) { where model_id: following_models }
end
I have researched everything so I'm looking for a full process in rails 5 :)

Rails searches the mistaken has_and_belongs_to_many table

I want to show all types which are related to a specific organisation in a select box of my document form. Types are part of the Ar engine. Organisations are part of another existing engine.
module Ar
module OrganisationPatch
extend ActiveSupport::Concern
included do
attr_accessible :ar_document_id
has_many :ar_documents, :class_name => 'Ar::Document'
has_and_belongs_to_many :ar_types, :class_name => 'Ar::Type'
end
end
end
module Ar
class Type < ActiveRecord::Base
attr_accessible :name
has_many :documents
has_and_belongs_to_many :organisations
end
end
class CreateTypeOrganisations < ActiveRecord::Migration
def change
create_table :ar_type_organisations, id: false do |t|
t.uuid :type_id, index: true
t.uuid :organisation_id, index: true
end
end
end
In my documents_controller I load types for forms about the before filter. The superior returns the organisation object:
def load_form_objects
unless current_user.admin?
#types = current_user.superior.ar_types
else
#types = Type.all
end
end
Calling the page I get this error and ask me why he is looking for a table called organisations_types:
ActiveRecord::StatementInvalid in Ar/documents#new
Mysql2::Error: Table 'portal.organisations_types' doesn't exist:
SELECT ar_types.* FROM ar_types INNER JOIN organisations_types
ON ar_types.id = organisations_types.type_id WHERE
organisations_types.organisation_id =
x'891c3986b33845d08d3951645a4f27d5'
Someone knows what I am doing wrong here?
Your table name isn’t map with lexical order what has_and_belongs_to_many expect. ( Expected order is organisations_types )
So you have to add :join_table option in both model's association. Like this,
has_and_belongs_to_many :ar_types, :class_name => 'Ar::Type', join_table: "ar_type_organisations"
has_and_belongs_to_many :organisations, join_table: "ar_type_organisations"
Reference

Join table in rails; many-to-many; trying to call join method on instance

I just did this a few hours ago and now I can't getting it working on a second attempt. There are many Users and many EventGoals. Users have many EventGoals and EventGoals have many Users, both through the join table called EventGoalClaims. The #event_goal is getting passed properly but I get a `
undefined method `event_goal_claims' for #<EventGoal:0x007fd11b0ce178>`
Here's the view (the first line is hitting an error):
<%= form_for([#event_goal, #event_goal.event_goal_claims.build]) do |f| %>
<%= f.hidden_field :user_id, :value => current_user.id %>
<%= f.hidden_field :user_last_name, :value => current_user.last_name %>
Controller
def create
#event_goal = EventGoal.find(params[:event_goal_id])
#event_goal_claim = #event_goal.event_goal_claims.build(eventgoalclaim_params)
#event_goal_claim.event_goal_id = #event_goal.id
#event_goal_claim.user_id = current_user.id
#event_goal_claim.user_last_name = current_user.last_name
...
private
def eventgoalclaim_params
params.require(:event_goal_claim).permit(:event_goal_id, :user_id, :user_last_name, :approved)
end
EDIT
class EventGoal < ActiveRecord::Base
belongs_to :event
has_many :users, through: :event_goal_claims
end
The answer likely lies in the associations of one of the three respective models. Make sure they look like this:
user.rb
Class User < ActiveRecord::Base
has_many :event_goal_claims
has_many :event_goals, through: :event_goal_claims
event_goal.rb
Class EventGoal < ActiveRecord::Base
has_many :event_goal_claims
has_many :users, through: :event_goal_claims
event_goal_claim.rb
Class EventGoalClaim < ActiveRecord::Base
belongs_to :user
belongs_to :event_goal
Also worth noting: This is all assuming your db has the foreign keys on the event_goal_claims table in your schema.rb labeled as user_id and event_goal_id. If they are different than that, you will need to explicitly state the name of the foreign key in the model's associations.

ActiveRecord Multi-association query

So I've got three models:
User
User Interest
Interest Tags
User Model
class User < ActiveRecord::Base
has_many :user_interests
end
InterestTag Model
class InterestTag < ActiveRecord::Base
has_many :user_interests, :dependent => :destroy
validates :name, :uniqueness => true
end
UserInterest Model
class UserInterest < ActiveRecord::Base
belongs_to :interest_tag
belongs_to :user
end
I'd like to use ActiveRecord to include the name of the user's interests when loading their profile using the following query:
#user = User.find(current_user.id, :include => [{:user_interests => :interest_tags}])
Migrations for interest_tags + user_interests
create_table :interest_tags do |t|
t.string :name, :null => false, :size => 30
t.timestamp :created_at
end
create_table :user_interests do |t|
t.integer :user_id
t.integer :interest_tag_id
end
What am I doing wrong?
You have to add an has_many :through association on User model.
class User < ActiveRecord::Base
has_many :user_interests
has_many :interest_tags, :through => :user_interests
end
class UserInterest < ActiveRecord::Base
belongs_to :interest_tag
belongs_to :user
end
class InterestTag < ActiveRecord::Base
has_many :user_interests, :dependent => :destroy
validates :name, :uniqueness => true
end
Now you can eager load the tags as follows:
User.find(current_user.id, :include => :interest_tags)
Note:
You might want to look at the acts_as_taggable_on gem for your requirement.
I assume you are building a tagging system, like in stackoverflow: every user kann have multiple tags that they are interested in. In that case the user_interests table is only a join table and does not need a model. Just use has_and_belong_to_many on the two real models.
See also this article on different ways to implement tagging with more or less normalized relational databases. You could also use a non-relational database like Mongodb, there you would need only one table to do tagging, see the cookbook.

Tricky MySQL Query for messaging system in Rails - Please Help

I'm writing a facebook style messaging system for a Rails App and I'm having trouble selecting the Messages for the inbox (with will_paginate).
The messages are organized in threads, in the inbox the most recent message of a thread will appear with a link to it's thread. The thread is organized via a parent_id 1-n relationship with itself.
So far I'm using something like this:
class Message < ActiveRecord::Base
belongs_to :sender, :class_name => 'User', :foreign_key => "sender_id"
belongs_to :recipient, :class_name => 'User', :foreign_key => "recipient_id"
has_many :children, :class_name => "Message", :foreign_key => "parent_id"
belongs_to :thread, :class_name => "Message", :foreign_key => "parent_id"
end
class MessagesController < ApplicationController
def inbox
#messages = current_user.received_messages.paginate :page => params[:page], :per_page => 10, :order => "created_at DESC"
end
end
That gives me all the messages, but for one thread the thread itself and the most recent message will appear (and not only the most recent message). I can also not use the GROUP BY clause, because for the thread itself (the parent so to say) the parent_id = nil of course.
Anyone got an idea on how to solve this in an elegant way? I already thought about adding the parent_id to the parent itself and then group by parent_id, but I'm not sure if that works.
Thanks
My solution would be to get a list of threads (which I'm assuming could be obtained by messages with no parent id). Then on the Message model, add a method that will find the latest message in the thread and return it. You can then use that method to obtain the latest method in each thread and put in a link to the head of the thread easily.
(Pseudo-)code:
class Message < ActiveRecord::Base
belongs_to :sender, :class_name => 'User', :foreign_key => "sender_id"
belongs_to :recipient, :class_name => 'User', :foreign_key => "recipient_id"
has_many :children, :class_name => "Message", :foreign_key => "parent_id"
belongs_to :thread, :class_name => "Message", :foreign_key => "parent_id"
def get_last_message_in_thread()
last_message = self
children.each do |c|
message = c.get_last_message_in_thread()
last_message = message if message.created_at > last_message.created_at
end
return last_message
end
end
class MessagesController < ApplicationController
def inbox
#messages = current_user.received_messages.find_by_parent_id(Null).paginate :page => params[:page], :per_page => 10, :order => "created_at DESC"
end
end
You could probably do a lot better than having a recursive function to find the last message in the thread, but it's the simplest solution I can think of to demonstrate the idea. I'm also not sure I have the correct syntax for finding unset parent id's in the inbox function, which is why I marked the code as pseudo code :)
giving the parent itself as parent makes it very easy to create queries that operate on the whole thread, because you can group (or anything similar) by parent_id.
if you handle the parents differently, all your queries have to take care of this too
The only efficient way would be to have a Thread model and use GROUP BY as you mentioned - Anything else would require iteration over the messages.
read update in comments
I figured the only good solution is using a second model to store the most recent messages for every thread (because of performance issues when using GROUP BY with a subselect, see my comments). It won't take a lot of space in the DB because we're only storing id's and no text or even blobs.
The RecentMessages Model would look something like this:
create_table :recent_messages do |t|
t.integer :sender_id
t.integer :recipient_id
t.integer :message_id
t.integer :message_thread_id
t.timestamps
end
class RecentMessage < ActiveRecord::Base
belongs_to :message
belongs_to :message_thread, :class_name => 'Message'
belongs_to :sender, :class_name => 'User', :foreign_key => "sender_id"
belongs_to :recipient, :class_name => 'User', :foreign_key => "recipient_id"
end
The main idea is: All the messages are stored in one model (Messages). Whenever a new message is added to a thread (or a thread is created), two things happen (e.g. with a after_save callback):
Store the new message in the RecentMessages model (that means sender_id, recipient_id, message_id, message_thread_id (= parent_id || id))
Get the most recent message (from this thread in messages), where sender_id == recipient_id and vice versa (note: This only works if the message model should only support messages between 2 users) and store it in the RecentMessages model as well (if found and if it's not already there)
Of course there should only be max. 2 recent_messages stored in the DB for every message_thread at any given time.
If one wants to show i.e. the inbox, the following has to happen:
#messages = current_user.recent_received_messages.paginate :page => params[:page], :per_page => 10, :order => "created_at DESC", :include => :message
That's the best I figured out so far. I still think it's ugly but it's fast and it works. If anyone comes up with a better solution, I'll be gratefull!
I don't know how to accomplish this in Rails, but this is how I did it directly in MySQL:
select * from messages where message_id in (
select max(message_id) from messages where to_uid = 51 group by thread_id
) order by timestamp desc
I used a subquery to grab the most recent message in a thread and then the main query to grab all of the fields for the messages found in the subquery.