I was hoping this would be simple, but in essence I would like to count the number of Users who have certain attributes through a join table in Rails 4.
I have a table called Views which holds three columns:
t.references :viewer
t.references :viewed
t.boolean :sentmessage, default: false
I then have the fields references as:
belongs_to :viewer, :class_name => "User"
belongs_to :viewed, :class_name => "User"
Each user record is then associated with a number of other records like Stats, Questions and a number of others. I'm interested in effectively counting how many viewers of a viewed record are Male or Female (and other search fields) which is data all held in User.stat.gender.name etc.
I'm trying to use a group statement but have no idea how to drill down and count the number of Males etc. I've tried:
#results = View.where(viewed: 63).group("viewer.stat.gender")
But this is so wrong it's frightening.
Any help to do this would be appreciated.
I worked it out finally. For anyone else who is interested:
View.where(viewed_id: 63).joins(viewer: {stat: :gender}).group("name").count
Didn't realise what an INNER JOIN was but some research and some trial and error means I can now show information about the users who have visited.
Related
I am trying to store some leads in my Rails CRM Application. Here's the sample Lead model.
class Lead < ActiveRecord::Base
belongs_to :campaign
validates_presence_of :name
end
Here's a sample migration that I'm using to store my leads in the database.
class CreateLeads < ActiveRecord::Migration
def change
create_table :leads do |t|
t.string :name, null: false, default: ""
t.string :contacts, null: false, array: true, default: []
t.string :emails, null: false, array: true, default: []
t.string :budget, null: false, default: "Not Specified"
t.string :requirements, null: false, array: true, default: []
t.timestamps null: false
end
end
end
There are possibilities that my leads have multiple email addresses, contact numbers and requirements. Therefore, I decided to implement the forementioned colums as arrays.
I would like to ensure that none of a lead's email addresses or contacts are used to create a new lead's row in the database.
Should I implement this using the model or via the migration? Please direct me on how to implement this the rails way.
Arrays within Row Implementation - Problematic
Arrays within tables? Relational databases are built to hold records in tables. Both humans and databases understand this. Unless there are cogent reasons for doing so, don't do it.
I haven't looked at the underlying implementation, but even if the implementation is the SAME, it's an approach I haven't seen out there in the world.
Solution: Add a new table
Use another table for lead email addresses. i.e. emails. Add the following columns: id, lead_id and email. and link via lead_id foreign key. then when creating a new lead you will have to validate that lead by by checking that emails table whether that email address already exists.
You can add a DB constraint checking whether the email already exists, or you can validate via models, or perhaps a combination of both?
Caveat
but googlers are coming here looking for a solution for how to add a unique constraint to an array column in Rails/AR. Would be nice if there was still a solution for that other than saying don't do this
I answered OP's very specific question - i.e. "is this a good idea? is it the rails way?". If Googlers want an answer to an entirely different question - they are more than welcome to continue googling, or to ask a new question on StackOverflow.
I currently have the following models: MinorCategory > Product > Review
On a view, I show the 12 MinorCategories that have the most reviews. This view is very slow to respond, and I think it is a problem with how I do the query.
Here is my current code:
class MinorCategory < ActiveRecord::Base
has_many :products
has_many :reviews, through: :products
...
def count_reviews
self.reviews.count
end
...
end
class Review < ActiveRecord::Base
belongs_to :product, touch: true
...
end
class HomeController < ApplicationController
#categories = MinorCategory.all.sort_by(&:count_reviews).reverse.take(12)
end
So that is basically it. In the view itself I go through each #categories and display a few things, but the query in the controller is what seems to be slow. From SkyLight:
SELECT COUNT(*) FROM "reviews" INNER JOIN "products" ON "reviews"."product_id" = "products"."id" WHERE "products"."minor_category_id" = ? ... avg 472ms
I am not good with sql or active record, and still pretty new to Ruby on Rails. I've spent a couple hours trying other methods, but I can not get them to work so I thought I would check here.
Thank you in advance to anybody that has a moment.
You need some basic SQL knowledge to better understand how database queries work, and how to take advantage of a DBMS. Using ActiveRecord is not an excuse to not learn some SQL.
That said, your query is very inefficient because you don't use the power of the database at all. It's a waste of resources both on the Ruby environment and on the database environment.
The only database query is
MinorCategory.all
which extracts all the records. This is insanely expensive, especially if you have a large number of categories.
Moreover, self.reviews.count is largely inefficient because it is affected by the N+1 query issue.
Last but not least, the sorting and limiting is made in the Ruby environment, whereas you should really do it in the database.
You can easily obtain a more efficient query by taking advantage of the database computation capabilities. You will need to join the two tables together. The query should look like:
SELECT
minor_categories.*, COUNT(reviews.id) AS reviews_count
FROM
"minor_categories" INNER JOIN "reviews" ON "reviews"."minor_category_id" = "minor_categories"."id"
GROUP BY
minor_categories.id
ORDER BY
reviews_count DESC
LIMIT 10
which in ActiveRecord translates as
categories = MinorCategory.select('minor_categories.*, COUNT(reviews.id) AS reviews_count').joins(:reviews).order('reviews_count DESC').group('minor_categories.id').limit(10)
You can access a single category count by using reviews_count
# take a category
category = categories[0]
category.reviews_count
Another approach that doesn't require a JOIN would be to cache the counter in the category table.
tldr;
In Rails, how do you set the target column of a foreign key to be a column other than its parent’s id?
Efforts so far
This feels like it should be a simple operation, but I’m having little success. I have a parent model, Order, which has many OrderItems, but I want the foreign key of OrderItems to reference a composite of Order’s reference1 and reference2 fields.
I’ve looked at a few paths:
First try
class Order < ActiveRecord::Base
has_many :order_items, foreign_key: :order_reference,
primary_key: :unique_reference
inverse_of: :order
validates :reference1, uniqueness: { scope: :reference2 }
end
class OrderItem < ActiveRecord::Base
belongs_to :order, foreign_key: unique_reference,
primary_key: order_reference
inverse_of: :order_item
end
(Where I created a redundant-feeling unique_reference column, that we populated before creation with reference1+reference2, and gave OrderItem a corresponding order_reference column)
I tried a few variants of the above, but couldn’t persuade the OrderItem to accept unique_reference as the key. It managed to link records, but then when I called OrderItem#order_reference, instead of contents matching the corresponding Order#unique_reference field it would return a stringified version of its parent’s id.
Second try
I removed the unique_reference column from the Order class, replacing it with a method of the same name and a has_many block:
class Order
has_many :order_items, -> (order) { where("order_items.order_reference = :unique_reference", unique_reference: order.unique_reference) }
def unique_reference
"#{reference1}#{reference2}"
end
end
class OrderItem < ActiveRecord::Base
belongs_to :order, ->(item){ where("CONCAT(surfdome_archived_order.source, surfdome_archived_order.order_number) = surfdome_archived_order_items.archived_order_reference") }
end
This time, calling Order#order_items raises a SQL error:
Unknown column 'order_items.order_id' in 'where clause': SELECT order_items.* FROM order_items WHERE order_items.order_id = 1 AND (order_items.order_reference = 'ref1ref2')
Every SQL query I’ve thought to try has the same underlying problem - somewhere, Rails decides we’re still implicitly trying to key by order_id, and I can’t find a way to persuade it otherwise.
Other options
At the moment my options seem to be to use the Composite Primary Keys gem or just ignoring Rails' built-in associations and hacking our own with db queries, but neither seems ideal - and it seems like Rails would surely have an option to switch foreign keys. But if so, where is it?
Thanks,
Sasha
I am wondering, what would be the best option to implement a virtual model.
Let's say we have users, we would like them to have a gender attribute.
The easiest option would be to have a Gender model and then do a belongs_to :gender
Then, each time you ask for a user, it will also perform a SELECT on the genders table
Therefore, does anyone have an idea on how to implement a virtual model that would not be persisted in the database and just be a regular model with the ability through the belongs_to association to perform a select on Male or female ?
I know I could just use Serialization but I feel like it's a bit of a waste of memory/space/ressources to store a serialized object each time I create a user and would definitely love a solution where I can use the traditional gender_id:integer ...
Thank you for your help guys.
I suggest you use enum field (Rails 4.1+), which allows its value to be within a predefined set of values (you can enumerate them, hence the name). It is persisted as a plain integer in database, but convenience methods are generated for easily manipulating them:
class User < ActiveRecord::Base
enum gender: [ :male, :female ]
end
user.male!
user.female? # => false
user.gender # => "male"
User.male # => a collection of users with "male" specified in gender
Conversation.genders # => { "male" => 0, "female" => 1 }
There is a good question here explaining how to correctly use ActiveAdmin with associations.
In my situation though I have a customer model has_many associated to a sales model and the sales model is pretty big. So when I try to view my customer page in ActiveAdmin the server is running a call for all sales so that (I am guessing) it can return those associated columns.
This is timing out my server (504 Gateway Time-out ngx_openresty/1.4.3.6).
Is there any way to say to ActiveAdmin to ignore an association for that view? Ie the index view. Once I get to the 'show' view and have isolated a customer it is ok to run the query on that customers sales but running all customers with all sales is not required on the index page.
Hope I have been clear.
Ok I have just realised that without specifying which columns I want in the index on the customer.rb file it will try and grab all including the associated columns (correct me if I am wrong on that).
Either way, before I only had he config.per_page line. By adding index do and my columns it is working properly. That was easy!
ActiveAdmin.register Customer do
config.per_page = 25
index do
selectable_column
id_column
column :customer_code
column :customer_name
column :customer_rep_name
column :created_at
actions
end
filter :customer_rep_name
filter :market_segment_name
end