Ruby array validate based on join table foreign key [duplicate] - ruby-on-rails

This question already has an answer here:
Need help on join table, limiting results to only the resource ID
(1 answer)
Closed 8 years ago.
I have a data model that looks like so;
class Tran < ActiveRecord::Base
belongs_to :buyer
validates :buyer_id, presence: true
belongs_to :seller
validates :seller_id, presence: true
end
class Seller < ActiveRecord::Base
has_many :trans
has_many :buyers, through: :trans
validates :seller_id, presence: true
end
class Buyer < ActiveRecord::Base
has_many :trans, :foreign_key => "buyer_id"
has_many :sellers, through: :trans
validates :buyer_id, presence: true
end
And then on each Seller page I have the following code that I can successfully give a list of the seller's top Buyers descending based on their total spend.
<ol>
<% #seller.buyers.uniq{|t| t.buyer_id }.sort_by {|su| su.trans.sum(:sum)}.reverse.each do |su| %>
<li><%= su.name %> <%= su.trans.sum(:sum) %></li>
<% end %>
</ol>
This code works in that it validates only Buyers that that have bought a product from the Seller, however it does not currently validate only the Trans made with the Seller (e.g. pulls in data between ALL the Sellers).
I have tried putting in a second validation around seller.id too, but unsure how to it.
Does anyone have a solution?
So essentially the list needs to validate that on the Tran, both the seller_id is owned by the Seller, plus present in the Trans join table, and that it also pulls in the buyer_id, but only the transactions that both supplier_id and buyer_id are present.

Rather than getting the Seller and the Seller's Buyers, and then the Buyer's Trans, how about getting the Seller and the Seller's Trans(es), and then the Buyer's information from each Seller's Trans?

Remove the unwanted code..its just pure has many through association...it should be
class Buyer< ActiveRecord::Base
has_many :trans
has_many :sellers, through: :trans
validates_presence_of :sellers
##or you can use blocks also such as below for validation(not null)
has_many :sellers,through :trans,:reject_if => proc { |a| a['name'].blank? }
end
class Tran< ActiveRecord::Base
belongs_to :buyers
belongs_to :sellers
end
class Seller< ActiveRecord::Base
has_many :trans
has_many :buyers, through: :trans
validates_presence_of :buyers
end
###to get all sellers of a buyer
#buyer.sellers
##to get all buyers from a seller
#seller.buyers
To make this work..you need to have three migration files including trans table with just two column-buyer_id and seller_id which will prevent any fallback in joins between buyer/seller and hence you dont need to validate the buyer_id/seller_id
class CreateBuyersSellersTrans < ActiveRecord::Migration
def change
create_table :buyers do |t|
##ensures uniqueness
t.string :name, unique: true
t.timestamps
end
create_table :sellers do |t|
##ensures uniqueness
t.string :name, unique: true
t.timestamps
end
create_table :trans do |t|
t.belongs_to :buyer
t.belongs_to :seller t.timestamps
end
end
end
in your controller
#get all uniq buyers of a seller in array format removing empty even if exits
#sellers=#seller.buyers.flatten.compact.uniq.delete_if(&:blank?)
in your view
<ol>
<% #sellers.sort_by {|su| su.trans.sum(:sum)}.reverse.each do |su| %>
<li><%= su.name %> <%= su.trans.sum(:sum) %></li>
<% end %>
</ol>

Related

Parse data from many-to-many relationship in Ruby on Rails

I want to create a function where user be able to report the post if the post content contains something that doesn't follow the guidelines.
What I have so far:
model/post.rb
class Post < ApplicationRecord
belongs_to :author, class_name: 'User', foreign_key: 'author_id'
has_many :reports, dependent: :destroy
scope :reported, -> { distinct.joins(:reports) }
validates :title, :content, :author_id, presence: true
end
model/user.rb
class User < ApplicationRecord
has_many :posts, dependent: :destroy, foreign_key: "author_id"
has_many :reports, dependent: :destroy
end
model/report.rb
class Report < ApplicationRecord
belongs_to :user
belongs_to :author, class_name: 'User', foreign_key: "author_id"
belongs_to :post
validates :content, :user_id, :post_id, :author_id, presence: true
end
When the user trying to report the post, user will need to input content of the report and the data will be save on the Report Model.
This is my Report structure:
class CreateReports < ActiveRecord::Migration[5.2]
def change
create_table :reports do |t|
t.text :content
t.references :user
t.references :author
t.references :post, foreign_key: true
t.timestamps
end
end
end
I managed to store the data to the Report model/table.
So, in my dashboard_controller, I would like the author of the post noticed that they has some posts that being reported by the user that needs some action/attention from their side (edit or anything)
controller/dashboard_controller.rb
class DashboardController < ApplicationController
def report
#reports = current_user.posts.reported
#render json: #reports
end
end
The result will display the list of posts that user reported.
My questions:
How do I display content of the report from reports table?
I added #reports = current_user.posts.joins(:reports).select("posts.*, reports.*") in the dashboard_controller, it display the results that I wanted in json, but when I try to iterate like below, it doesn't display anything on views, no errors.
<% #reports.each do |report| %>
<h1><% report.content %></h1>
<% end %>
As far as I know, calling .joins will do SQL queries (N+1) instead of .includes. I didn't managed to get it working using .includes. Is it fine from what I've done?
Or is there any better solutions to what I've done now?
Thanks!

Habtm join tables for a model with a self join

This follows on from my previous question
I have a user model with two self joins, seller and buyer.
I have an categories model, a seller has_and_belongs_to_many categories, as does a buyer.
How do I create the migration so I can do seller.categories and categories.buyers etc...
I thought it would be something like I have below but it doesn't work...
def change
create_table :categories_sellers do |t|
t.references :category
t.references :user
end
add_foreign_key :categories_sellers, :users, column: :trainer_id
add_index :categories_users, [:category_id, :seller_id]
add_index :categories_users, :seller_id
end
end
To answer your question, it looks like you just need to change t.references :user to t.references :seller.
That said, I would highly suggest modeling your project as such:
module User
extend ActiveSupport::Concern
has_many :category_users, as: :user
has_many :categories, through: :category_users
# include any common methods for buyers and sellers,
# basically your current User model
end
class Buyer < ActiveRecord::Base
include User
end
class Seller < ActiveRecord::Base
include User
end
class CategoryUser < ActiveRecord::Base
belongs_to :category
belongs_to :user, polymorphic: true
end
class Category < ActiveRecord::Base
has_many :category_users
has_many :buyers, through: :category_users, source: :user, source_type: 'Buyer'
has_many :sellers, through: :category_users, source: :user, source_type: 'Seller'
end
I know that may require some changes that you didn't anticipate, but in doing that, you get more natural methods such as:
category.buyers
category.sellers
buyer.categories
seller.categories
Under the hood, your join table will have columns like:
id -- the row id
category_id -- the category id, of course
user_id -- the id of the Buyer or Seller
user_type -- one of "Buyer" or "Seller" (or any other type you deem as "user")
To run the migrations:
User doesn't need one, it's not a model.
Buyer and Seller are pretty straightforward, old User model + Buyer/Seller model.
Category doesn't need one as it already exists.
CategoryUser:
def change
create_table :category_users do |t|
t.references :category
t.references :user, polymorphic: true
end
add_index :category_users, :category_id
add_index :category_users, [:category_id, :user_id]
add_index :category_users, [:category_id, :user_id, :user_type]
end
I haven't checked this personally but should be right, or close. The overall principle, though, is to make use of polymorphic associations to make a more natural association between "some kind of user" (whether it be a Buyer, Seller, or any other type of user you come up with) and a category. Then, you don't need to replicate the same sort of associations over and over again because the models slightly vary.
Here's more details on this approach:
http://guides.rubyonrails.org/association_basics.html#polymorphic-associations

Adding data in an has_many association

My project consists of People, Events and Comments.
Users can post comments ( accounts are not necessary ) and can attend events.
I'm using a has_many: through association between the people and events but whatever I try, I can't add people to events.
I don't have a clue how I can select someone and add this person to the event_people table.
The models
class EventPeople < ActiveRecord::Base
belongs_to :event
belongs_to :people
end
class Event < ActiveRecord::Base
validates :title, presence:true, length: {minimum: 3}
validates :date_from, presence:true
validates :date_to, presence:true
validates :time_from, presence:true
validates :time_to, presence:true
has_many :comments, dependent: :destroy
has_many :people, :through => :event_people
has_many :event_people
end
class People < ActiveRecord::Base
validates :name, presence: true
validates :email, presence: true, length: { minimum: 5}
validates :birthdate, presence: true
has_many :comments
has_many :events, :through => :event_people
has_many :event_people
end
show method from the eventcontroller
def show
#event = Event.find(params[:id])
end
the table
create_table "event_people", force: true do |t|
t.integer "event_id"
t.integer "people_id"
t.datetime "created_at"
t.datetime "updated_at"
end
Delete the
has_many :event_people
in your People and Event Class
and your migration should look like:
create_table "event_people" do |t|
t.belongs_to :event
t.belongs_to :people
t.timestamps
end
You can either add them individually as klausinho is suggesting, have a from for EventPeople where you'd add one person at a time.
Or the has_many association will give you the people_ids= method on events. So you can then use collection check boxes
<%= form_for #event do |f| %>
<div class="field">
<%= f.collection_check_boxes(:people_ids, People.order(:name), :id, :name)
</div>
<% end %>
Or you could use a multiple select instead.

rails_admin plugin issue with nested form on a has_many :through relationship

I have what seems to be a cookie cutter problem that even has an associated wiki page here: https://github.com/sferik/rails_admin/wiki/Has-many-%3Athrough-association
I'll try to be brief. I have a has_many :through relationship in an application I'm building. The models involved are the following:
Athlete, AthleteRole, SportRole.
The sport_roles table has a list of generic roles that an athlete can have such as first baseman, second baseman, etc.
The athlete_roles table is the many to many join table that contains an athlete_id and a sport_id.
My models are defined below with code examples. I simply want to be able to create an Athlete and associate them with 1+ sport roles (which will ultimately create 1+ new records in the athlete_roles table). It shouldn't ask me for an athlete_id as the athlete won't have an id until save is called on the backend and validation passes. I don't need to be able to create new sport_roles here. We'll assume all roles that the new athlete being created can take on have already been predefined.
** EDIT **
To clarify, my question is, how do I get to pick one or multiple existing sport roles for an athlete using the rails_admin plugin, NOT in a stand alone form? I do not wish to create new sport roles, but I want to be able to pick an existing one or two when creating an athlete and have that data reflected in the athlete_roles table.
Code below.
class Athlete < ActiveRecord::Base
has_many :athlete_roles, :dependent => :delete_all, :autosave => true, :include => :sport_role
has_many :sport_roles, :through => :athlete_roles
attr_accessible :first_name
attr_accessible :middle_name
attr_accessible :last_name
attr_accessible :suffix_name
attr_accessible :birthdate
# FOR RAILS_ADMIN
# for a multiselect widget:
attr_accessible :sport_role_ids
accepts_nested_attributes_for :athlete_roles, :allow_destroy => true
attr_accessible :athlete_roles_attributes
end
class AthleteRole < ActiveRecord::Base
attr_accessible :athlete_id
attr_accessible :sport_role_id
# Associations
belongs_to :athlete
belongs_to :sport_role
# validations
validates :athlete_id,:presence=>true,:uniqueness=>{:scope => [:sport_role_id], :message => "is already associated with this Sport Role"}
validates :sport_role_id,:presence=> true
end
class SportRole < ActiveRecord::Base
has_many :athlete_roles, :dependent => :delete_all
has_many :athletes, :through => :athlete_roles
attr_accessible :name
attr_accessible :short_name
attr_accessible :description
attr_accessible :created_at
attr_accessible :updated_at
attr_accessible :athlete_ids
attr_accessible :athlete_role_ids
validates :name, :presence => true
validates :short_name, :presence => true
validates :description,:length=>{:maximum => 500, :allow_nil => true}
end
I think the problem here resides in your model.
A model like you are describing, is a has and belongs to many relation and should look like so:
athlete:
class Athlete < ActiveRecord::Base
has_and_belongs_to_many :sport_roles
end
sports role
class SportRole < ActiveRecord::Base
has_and_belongs_to_many :athletes
end
The migrations should look like:
athlete
class CreateAthletes < ActiveRecord::Migration
def change
create_table :athletes do |t|
t.timestamps
end
end
end
sports role
class CreateSportRoles < ActiveRecord::Migration
def change
create_table :sport_roles do |t|
t.timestamps
end
end
end
relation between athletes and sports roles
class SportRolesAthletes < ActiveRecord::Migration
def change
create_table :sport_roles_athletes , :id => false do |t|
t.integer :sport_role_id
t.integer :athlete_id
end
end
end

Rails 3 has_many through checkbox form doesn't (does not) work

I have been stuck on this for a day now. I've heard all of this talk of Rails being able to handle easy complexities like this (although this isn't/shouldn't be complex).
Story: User can have many advanced degrees. I want to be able to create this association using a has_many through relationship and use checkboxes in my view.
Models:
class User < ActiveRecord::Base
has_many :user_degree_lists
has_many :degrees, :through => :user_degree_lists, :source => :advanced_degree, :dependent => :destroy
end
class AdvancedDegree < ActiveRecord::Base
attr_accessible :value, :description
has_many :user_degree_lists
end
class UserDegreeList < ActiveRecord::Base
belongs_to :user
belongs_to :advanced_degree
end
ActiveRecord:
class CreateUserDegreeLists < ActiveRecord::Migration
def self.up
create_table :user_degree_lists do |t|
t.integer :user_id
t.integer :advanced_degree_id
t.timestamps
end
add_index :user_degree_lists, :user_id
add_index :user_degree_lists, :advanced_degree_id
add_index :user_degree_lists, [:user_id, :advanced_degree_id], :unique => true
end
def self.down
drop_table :user_degree_lists
end
end
View:
<%= form_for(#user, :html => {:autocomplete => 'off', :id => "sign_up_user" }) do |f| %>
...
<% for advanced_degree in AdvancedDegree.find(:all)%>
<%= check_box_tag "user[advanced_degree_ids][]", advanced_degree.id, #user.degrees.include? (advanced_degree.id) %>
<%= f.label :advanced_degrees, advanced_degree.description %>
...
<% end %>
Once the form is submitted, all user fields are updated, but the :user_degree_lists relationship is not created.
What am I doing wrong here?
Not sure if you solved this already, but one thing I spotted: shouldn't the class User have 'has_many :advanced_degrees' versus 'has_many :degrees'? Might want to try that without the source on it (unless you're trying for something polymorphic), that's how I did something similar.
1) I would rename "UserDegreeList" to "UserDegree" since this is a join table.
2) "AdvancedDegree.find(:all)" can be "AdvancedDegree.all".
3) I agree with the previous comment and it should be renamed to "has_many :advanced_degrees"
4) To solve the issue, try adding this to User:
accepts_nested_attributes_for :advanced_degrees, :allow_destroy => true, :reject_if => :all_blank
You need to make sure that attr_accessible has the attr you are setting in the check boxes.
class Zone < ActiveRecord::Base
attr_accessible :name, :active, :user_ids
has_many :user_zones
has_many :users, :through => :user_zones
end
class User < ActiveRecord::Base
attr_accessible :name, :zone_ids
has_many :user_zones
has_many :zones, :through => :user_zones
end

Resources