Looking for a review of my ActiveRecord relations - ruby-on-rails

I recently just started learning RoR and I am creating a hobby project.
So some quick background: Each customer is identified by an account number. Each product sale has an account number attributed with it and the products table contains all the specific product data.
My question is - with the format I have now, is this the proper way I should be linking these tables? One of the issues I am having is filtering a group of sales based on product major. So say I have a customer and I only want to view product sales where the major is "commercial", how do I go about filtering this data? See the scope I created - but I am not sure how to use it.
class Product < ActiveRecord::Base
has_many :product_sales, :primary_key => :prodnum, :foreign_key => :prodnum
has_many :customers, through: :sales
scope :commercial_products, -> { where(major: 'Commercial') }
end
class ProductSale < ActiveRecord::Base
belongs_to :customer, :foreign_key => :account
belongs_to :product, :foreign_key => :prodnum, :primary_key => :prodnum
end
class Customer < ActiveRecord::Base
has_many :product_sales, :primary_key => :account, :foreign_key => :account
has_many :products, through: :product_sales
end
and my schema
create_table "customers", force: :cascade do |t|
t.integer "account"
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "product_sales", force: :cascade do |t|
t.integer "account"
t.string "month"
t.string "prodnum"
t.integer "sales"
t.integer "qtyshipped"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "products", force: :cascade do |t|
t.string "pcatcode"
t.string "pcatname"
t.string "major"
t.string "prodline"
t.string "brand"
t.string "tier"
t.string "prodnum"
t.string "proddesc"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end

... I have a customer and I only want to view product sales where the major is "commercial", how do I go about filtering this data?
This should do it:
#customer.product_sales.where(product: {major: 'Commercial'})
PS. Your models look correct, but this bit
has_many :customers, through: :sales
# Probably you meant: through: product_sales

Related

Creating Custom Friendship Associations Based Around an "Event" Model

I've been researching friendship models using roles, custom associations, etc. But I haven't been able to connect my project to the concepts in a clear way.
I want a "User" to be able to create an event I'm calling a "Gather". A User can also attend a Gather created by other Users. By attending a Gather, the "User" can also be a "Gatherer".
The list of Gatherers will technically be considered friends of the "creator". This is how far I've gotten:
Models:
User
Gather
Gatherer (?)
User
class User < ApplicationRecord
has_many :gathers_as_creator,
foreign_key: :creator_id,
class_name: :Gather
has_many :gathers_as_gatherer,
foreign_key: :gatherer_id,
class_name: :Gather
end
Gather
class Gather < ApplicationRecord
belongs_to :creator, class_name: :User
belongs_to :gatherer, class_name: :User
end
My question is, do I need to a join table, such as Gatherer, to allow multiple attendees and then later pull a friend list for the user/creator ?
Gatherer
belongs_to :gather_attendee, class_name: "User"
belongs_to :attended_gather, class_name: "Gather"
Here's what I think that schema would look like:
create_table "gatherers", force: :cascade do |t|
t.bigint "attended_gather_id"
t.bigint "gather_attendee_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["attended_gather_id"], name: "index_gatherers_on_attended_gather_id"
t.index ["gather_attendee_id"], name: "index_gatherers_on_gather_attendee_id"
end
Help, my head is spinning trying to understand the connections and how to proceed.
Previous planning:
Schema:
create_table "activities", force: :cascade do |t|
t.string "a_type"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "gatherers", force: :cascade do |t|
t.bigint "attended_gather_id"
t.bigint "gather_attendee_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["attended_gather_id"], name: "index_gatherers_on_attended_gather_id"
t.index ["gather_attendee_id"], name: "index_gatherers_on_gather_attendee_id"
end
create_table "gathers", force: :cascade do |t|
t.integer "creator_id"
t.integer "activity_id"
t.text "gather_point"
t.boolean "active"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "interest_gathers", force: :cascade do |t|
t.string "gather_id"
t.string "interest_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "interests", force: :cascade do |t|
t.string "i_type"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "users", force: :cascade do |t|
t.string "username"
t.string "img"
t.string "first_name"
t.string "last_name"
t.string "state"
t.string "city"
t.string "bio"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
add_foreign_key "gatherers", "gathers", column: "attended_gather_id"
add_foreign_key "gatherers", "users", column: "gather_attendee_id"
end
class User < ActiveRecord::Base
has_many :gatherers, foreign_key: gather_attendee_id
has_many :attended_gathers, through: :gatherers
has_many :created_gathers, foreign_key: :creator_id, class_name: "Gather"
end
class Gather < ActiveRecord::Base
has_many :gatherers, foreign_key: :attended_gather_id
has_many :attendees, through: :gatherers, source: :gather_attendee
belongs_to :creator, class_name: "User"
end
class Gatherer < ActiveRecord::Base
belongs_to :gather_attendee, class_name: "User"
belongs_to :attended_gather, class_name: "Gather"
end
The naming here is not great. When naming your models choose nouns as models represent the actual things in your buisness logic - choosing verbs/adverbs makes the names of your assocations very confusing.
class User < ApplicationRecord
has_many :gatherings_as_creator,
class_name: 'Gathering',
foreign_key: :creator_id
has_many :attendences
has_many :gatherings,
through: :attendences
end
# think of this kind of like a ticket to an event
# rails g model Attendence user:references gathering:references
class Attendence < ApplicationRecord
belongs_to :user
belongs_to :gathering
end
# this is the proper noun form of gather
class Gathering < ApplicationRecord
belongs_to :creator,
class_name: 'User'
has_many :attendences
has_many :attendees,
though: :attendences,
class_name: 'User'
end
My question is, do I need to a join table, such as Gatherer, to allow multiple attendees and then later pull a friend list for the user/creator ?
Yes. You always need a join table to create many to many assocations. Gatherer is a pretty confusing name for it though as that's a person who gathers things.
If you want to get users attending Gatherings created by a given user you can do it through:
User.joins(attendences: :groups)
.where(groups: { creator_id: user.id })
You're on the right track.
If I understand what you're looking for correctly, you want a Gather to have many Users and a User to have many Gathers (for the attending piece). So you need a join table like this (this is similar to your gatherers table, but is in a more conventional Rails style):
create_join_table :gathers, :users do |t|
t.index [:gather_id, :user_id]
t.index [:user_id, :gather_id]
end
And then you'd want your User model to be like this:
class User < ApplicationRecord
has_many :gathers_as_creator, foreign_key: :creator_id, class_name: "Gather"
has_and_belongs_to_many :gathers
end
class Gather < ApplicationRecord
belongs_to :creator, class_name: "User"
has_and_belongs_to_many :users
end
(You can change the name of that :users association if you really want, by specifying extra options -- I just like to keep to the Rails defaults as much as I can.)
That should be the bulk of what you need. If you want to pull all the friends of a creator for a specific gather, you would just do gather.users. If you want to pull all of the friends of a creator across all their gathers, that will be:
creator = User.find(1)
friends = User.joins(:gathers).where(gathers: { creator: creator }).all

How to get fields from has_many :throught association

I have a many-to-many association throught RoomsUsers model and in this model i have a role field, association works well but i can't access this field.
My schema looks like:
create_table "messages", force: :cascade do |t|
t.text "body"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
t.integer "room_id"
end
create_table "rooms", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "rooms_user_id"
end
create_table "rooms_users", force: :cascade do |t|
t.integer "user_id"
t.integer "room_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "role"
t.integer "last_checked"
end
create_table "users", force: :cascade do |t|
t.string "name"
t.string "password_digest"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "mail"
t.integer "rooms_user_id"
end
User model:
class User < ApplicationRecord
has_secure_password(validations: false)
has_many :messages
has_many :rooms_users
has_many :rooms, through: :rooms_users
accepts_nested_attributes_for :rooms_users
attr_accessor :register, :mail_confirmation, :login
end
Room model:
class Room < ApplicationRecord
has_many :rooms_users
has_many :users, through: :rooms_users
accepts_nested_attributes_for :rooms_users
has_many :message
end
RoomsUsers model:
class RoomsUsers < ApplicationRecord
belongs_to :user
belongs_to :room
end
And i am trying to get role field from first user's room.
User.first.rooms.first.role
It give's me NoMethodError (undefined method `role' for #). What's wrong?
You're trying to access role field in the rooms table, but it is in rooms_users table. Should be:
User.first.rooms_users.first.role
And remove rooms_user_id from rooms and users table, you don't need it
If you want to access "role" field through Rooms model, you will need to change the place of your "role" field from rooms_users table to rooms table. Doing it you can access "role" using User.first.rooms.first.role.
However if you want to keep "role" field in rooms_users table, so you will need to use User.first.rooms_users.first.role as Vasilisa has already mentioned.
t.integer "rooms_user_id" are not necessary in rooms and users tables. The has_many used in rooms and users are already linking rooms_users with them.

Rails ActiveRecord Query: Sum total article view count per category

I have articles that are able to be tagged in categories. I am struggling to create a query which will extract the sum of view count (tracked using impressionist gem) of articles within each category.
Schema:
create_table "article_categories", force: :cascade do |t|
t.integer "article_id"
t.integer "category_id"
end
create_table "articles", force: :cascade do |t|
t.string "title"
t.text "description"
t.bigint "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "impressions_count", default: 0
end
create_table "categories", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Article model
class Article < ApplicationRecord
is_impressionable :counter_cache => true, :unique => false
belongs_to :user
has_many :impressions, as: :impressionable
has_many :article_categories
has_many :categories, through: :article_categories
end
Category model
class Category < ApplicationRecord
has_many :article_categories
has_many :articles, through: :article_categories
end
ArticleCategory model
class ArticleCategory < ActiveRecord::Base
belongs_to :article
belongs_to :category
end
Here is what i have tried so far and the errors when i test in rails console:
cat_group = Article.joins(:article_categories).group_by(&:category_ids)
// this query results in an array of articles within each category
1) cat_group.sum("impressions_count")
//TypeError: no implicit conversion of Array into String
2) cat_group.select("sum(impressions_count) AS total_count")
//ArgumentError: wrong number of arguments (given 1, expected 0)
3) Article.joins(:article_categories).group(:category_id)
//ActiveRecord::StatementInvalid: PG::GroupingError: ERROR: column "articles.id" must appear in the GROUP BY clause or be used in an aggregate function
If I understand you correctly and view count is impressions_count, here is query that you can use:
Article.joins(:categories).group('categories.name').sum(:impressions_count)

Rails 5 inverse of not working

I'm trying to return JSON API where a show action will
render json: user, include [:books, :friends, :comments]
Problem is, if I try to use the inverse_of in my User and Book model classes like this:
User Serializer
class UserSerializer < ActiveModel::Serializer
...
has_many :friends
has_many :books, inverse_of: :author
...
end
Book Serializer
class BookSerializer < ActiveModel::Serializer
...
belongs_to :author, class_name: "User", inverse_of: :books
...
end
I get an error:
ActiveRecord::StatementInvalid (SQLite3::SQLException: no such column: books.user_id: SELECT "books".* FROM "books" WHERE "books"."user_id" = ?):
If I remove the inverse_of and has_many from my User serializer, then I don't get any errors, but then the JSON being returned does not contain the included association.
Likewise, the same happens between Comment and User models.
Am I doing something wrong ?
My DB Schema for my two models are:
User Schema
create_table "users", force: :cascade do |t|
t.string "first_name"
t.string "last_name"
t.string "username"
t.string "email"
t.string "password_digest"
t.boolean "banned"
t.integer "role_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "photo"
t.boolean "email_confirmed", default: false
t.string "confirm_token"
t.string "password_reset_token"
t.boolean "show_private_info", default: false
t.boolean "show_contact_info", default: false
t.index ["role_id"], name: "index_users_on_role_id"
end
Book Schema
create_table "books", force: :cascade do |t|
t.string "title"
t.boolean "adult_content"
t.integer "author_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "published"
t.string "cover"
t.text "blurb"
t.index ["author_id"], name: "index_books_on_author_id"
end
When I went to generate my Book model with:
rails generate model books ... author:references
It created this migration file:
class CreateBooks < ActiveRecord::Migration[5.0]
def change
create_table :books do |t|
t.string :title
t.boolean :adult_content
t.references :author, foreign_key: true
t.timestamps
end
end
end
I assume that includes the necessary foreign key setup...
Try to change this line in your User model(user.rb):
has_many :books, inverse_of: :author
to
has_many :books, inverse_of: :author, foreign_key: :author_id
You need to tell rails what foreign_key you used if it's not the default one.And the association should be declared in your models, not serializers. In serializer you are adding keys by "has_many", inverse_of does't works here.

Validating the presence of a column on a join model

I think this is a fairly common issue but I couldn't find a solution. My domain has two models, Company and ExternalLink (a representation of a social media provider like Facebook or Twitter). The join model, CompaniesExternalLink, has the direct URL to a social media account (ie http://www.facebook.com/goldmansachs). I need to validate the presence of that URL, but the join is created on the association, which breaks and returns ActiveRecord::RecordInvalid
Any advice would be great. Sample code below:
class Company < ActiveRecord::Base
has_many :companies_external_links
has_many :external_links, through: :companies_external_links, dependent: :destroy
end
class ExternalLink < ActiveRecord::Base
has_many :companies_external_links
has_many :companies, through: :companies_external_links, dependent: :destroy
end
class CompaniesExternalLink < ActiveRecord::Base
validates :url, presence: true
belongs_to :company
belongs_to :external_link
validates :external_link, uniqueness: {scope: :company_id}
end
The relevant parts of the schema.rb
create_table "companies", force: true do |t|
t.datetime "created_at"
t.datetime "updated_at"
t.string "name"
t.string "state"
t.string "logo"
t.string "slug"
t.text "description"
end
create_table "companies_external_links", force: true do |t|
t.integer "company_id"
t.integer "external_link_id"
t.string "url"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "external_links", force: true do |t|
t.string "source"
t.datetime "created_at"
t.datetime "updated_at"
t.string "icon"
end

Resources