How to get many sum of child columns? - ruby-on-rails

I have two tables, Member and MemberRecord.
This are their relationship:
# Member Model
class Member < ActiveRecord::Base
has_many :member_records, :dependent => :destroy
end
# MemberRecord Model
class MemberRecord < ActiveRecord::Base
belongs_to :member
end
In MemberRecord There are many columns: two_pointer_attempt, two_pointer_made, three_pointer_attempt, three_pointer_made, free_throws_attempt, free_throws_made, offensive_rebound, defensive_rebound, assist, block, steal, turnover, foul, score
Can I get those columns sum in more efficient way?
This is what I did so far:
class Member < ActiveRecord::Base
belongs_to :team
has_many :member_records, :dependent => :destroy
validates :name, :number, presence: true
validates_uniqueness_of :name, scope: :team_id
validates_inclusion_of :number, in: 0..99
def sum_two_pointer_made
self.member_records.sum(:two_pointer_made)
end
def sum_two_pointer_attempt
self.member_records.sum(:two_pointer_attempt)
end
def sum_two_pointer_total
sum_two_pointer_made + sum_two_pointer_attempt
end
def sum_three_pointer_made
self.member_records.sum(:three_pointer_made)
end
def sum_three_pointer_attempt
self.member_records.sum(:three_pointer_attempt)
end
def sum_three_pointer_total
sum_three_pointer_made + sum_three_pointer_attempt
end
def sum_free_throws_made
self.member_records.sum(:free_throws_made)
end
def sum_free_throws_attempt
self.member_records.sum(:free_throws_attempt)
end
def sum_free_throws_total
sum_free_throws_made + sum_free_throws_attempt
end
def sum_offensive_rebound
self.member_records.sum(:offensive_rebound)
end
def sum_defensive_rebound
self.member_records.sum(:defensive_rebound)
end
def sum_assist
self.member_records.sum(:assist)
end
def sum_block
self.member_records.sum(:block)
end
def sum_steal
self.member_records.sum(:steal)
end
def sum_turnover
self.member_records.sum(:turnover)
end
def sum_foul
self.member_records.sum(:foul)
end
def sum_score
self.member_records.sum(:score)
end
end

I will give you an example with two columns and you can extend it for your number of columns.
class Member < ActiveRecord::Base
# add associations here as already present
MR_SUM_COLUMNS = %w{
assist
block
} # add more member record columns here
MR_SUM_COLUMNS.each do |column|
define_method "member_record_#{column}_sum" do
member_record_sums.send(column)
end
end
private
def member_record_sums
#_member_record_sums ||=
begin
tn = MemberRecord.table_name
sums_str =
MR_SUM_COLUMNS.map do |c|
"SUM(#{tn}.#{c}) AS #{c}"
end.join(', ')
self.member_records.select(sums_str).first
end
end
end
m = Member.first
s1 = m.member_record_assist_sum
s2 = m.member_record_block_sum
Explanation:
In ActiveRecord's select method, you can store the sum of a column as a particular value. For example:
# you have only two members with ids 1 and 2
m = Member.select("SUM(id) AS id_sum").first
m.id_sum #=> 3
So we're storing all sums of member_records in one go: in the member_record_sums method. We are also using an instance variable to store the results so that subsequent calls to the method do not query the database.
From there, all we have to do is define our sum-lookup methods dynamically.

Related

Adding an additional model relationships using build() on Rails

Currently I have the following four models:
users <-- agency_memberships --> agencies
|
|
agency_roles
agency_memberships is a join table, and agency_roles is a small lookup table with the roles :
ID NAME
-----------
1 admin
2 editor
...
Before adding the AgencyRole model, when a user was created, if a param create_agency = true, then this was enough to create a new Agency (along the join table AgencyMembership).
# user_controller.rb
def create
#user = User.new(user_params)
#user.agencies.build(name: "Demo Agency") if params[:user][:create_agency]
if #user.save!
...
end
However, now I need to add a valid AgencyRole before saving.
Is it possible to do so with .build() or what is the Rails best practice to do so?
Right now, I'm creating all relationships manually before saving, which works but isn't as compact:
def create
#user = User.new(user_params)
if (params[:user][:create_agency])
agency_name = params[:user][:agency_name]
agency_role = AgencyRole.find_by(name: 'admin')
#agency_membership = AgencyMembership.new(agency_role: #agency_role)
#agency = Agency.new(name: agency_name)
#agency_membership.agency = #agency
#agency_membership.user = #user
#agency_membership.agency_role = agency_role
#user.agency_memberships << #agency_membership
end
if #user.save!
...
end
EdIT: My model relationships are as follows:
class AgencyMembership < ApplicationRecord
belongs_to :agency
belongs_to :user
belongs_to :agency_role
end
class AgencyRole < ApplicationRecord
validates :name, uniqueness: { case_sensitive: false }, presence: true
end
class Agency < ApplicationRecord
has_many :agency_memberships
has_many :projects
has_many :users, through: :agency_memberships
end
class User < ApplicationRecord
has_many :agency_memberships, inverse_of: :user
has_many :agencies, through: :agency_memberships
end
You can encapsulate and separate it from the controller, keep thin controller fat model, beside that you can use autosave in order to auto save association.
class AgencyMembership < ApplicationRecord
belongs_to :agency_role, autosave: true
end
class Agency < ApplicationRecord
has_one :agency_membership, autosave: true
end
module BuildAgency
def build_with_role(attributes = {}, &block)
agency_role_name = attributes.delete(:role)
agency = build(attributes, &block) # association: user - membership - agency
# add role
agency_role = AgencyRole.find_by(name: agency_role_name)
agency.agency_membership.agency_role = agency_role # autosave
agency
end
end
class User < ApplicationRecord
has_many :agencies, autosave: true, :extend => BuildAgency
def build_agency(attributes)
new_agency = agencies.build_with_role(attributes)
# ...
new_agency
end
end
# controller
def create
if (params[:user][:create_agency])
#user.build_agency(name: params[:user][:agency_name], role: params[:user][:agency_role])
end
if #user.save! # it'll save agencies since we set `autosave`
end

rails validate dependent model

There have 2 tables: Orders and Arrivals. There can be many arrivals on an order. I want to validate the creation of arrivals for a specific order.
Orders has fields book_id and quantity:integer
Arrivals has fields order:belongs_to and quantity:integer
Order.rb:
class Order < ActiveRecord::Base
has_many :arrivals
def total_arrival_quantity
arrivals.map(&:quantity).sum
end
def order_quantity_minus_arrival_quantity
quantity - total_arrival_quantity
end
end
Arrival.rb:
class Arrival < ActiveRecord::Base
belongs_to :order
validates :total_arrival_quantity_less_or_equal_to_order_quantity, on: create
validates :current_arrival_quantity_less_or_equal_to_order_quantity, on: create
def current_arrival_quantity_less_or_equal_to_order_quantity
self.quantity <= order.quantity
end
end
How can I make the two validations work?
Something like this should work,
validate :order_quantity, on: :create
private
def order_quantity
if quantity > order.order_quantity_minus_arrival_quantity
errors.add(:quantity, 'cannot be greater than ordered quantity.')
end
end

Expose a has_many relationship through a single text field

I have a model A that "has many" B.
class A < ActiveRecord::Base
has_many :B
attr_accessible :title
end
class B < ActiveRecord::Base
belongs_to :A
attr_accessible :name
end
I want to add a field to my "edit A" form : a textarea in which I will enter my B's :name for each line, and on submit, parse the field, and process each line.
The question is, how should I do that ?
Edit
Following Rails - Add attributes not in model and update model attribute I have come to this :
class A < ActiveRecord::Base
has_many :B
attr_accessible :title
def my_b
list = ""
self.B.each do |b|
list += "#{b.name}\n"
end
logger.debug("Displayed Bs : " + list)
list
end
def my_b=(value)
logger.debug("Saved Bs : " + value)
# do my things with the value
end
end
But def bees=(value) never seems to be fired.
What am I doing wrong ?
Edit 2
My actual code is visible here : https://github.com/cosmo0/TeachMTG/blob/edit-deck/app/models/deck.rb
You can put an :attr_accessor, like:
class A < ActiveRecord::Base
has_many :B
attr_accessible :title
attr_accessor :field_of_happiness
def field_of_happiness=(value)
# override the setter method if you want
end
def field_of_happiness(value)
# override the getter method if you want
end
end
Reference: attr_accessor api doc
It helps you on some way?
Oh my. Turns out the problem was not lying in the model, but in the controller... I forgot to add a simple line in the update method to assign my POST value to my class field...
Anyway, final solution is this :
In my controller :
def update
#a.whatever = params[:a][:whatever]
#a.my_b = params[:a][:my_b]
#a.save
end
In my model :
class A < ActiveRecord::Base
has_many :B
attr_accessible :whatever
def my_b
list = ""
self.B.each do |b|
list += "#{b.name}\n"
end
list
end
def my_b=(value)
# parse the value and save the elements
end
end

Set rooms for users

I'm trying to resolve a problem but I don't find the solution. This is my code:
class User < AR::Base
belongs_to :room
end
class Room < AR::Base
has_many :users
end
class SetupRooms
def initialize
#users = User.all
#rooms = Room.all
#room_max_users = #users.size / #rooms.size
end
def setup
groups = #users.in_groups_of(#room_max_users)
# Now, How Can I fill rooms with users?
end
end
Thanks
def setup
groups = #users.in_groups_of(#room_max_users)
#rooms.zip(groups).each do |room, group| # now "room" is a Room and "group" is an Array of Users
group.delete_if { |user| user.nil? } # in_groups_of pads with nil if there are leftover spaces
room.users = group
end
end
You do not need the initialize method. Your setup can be written like this
def setup
Room.all.each_with_index do |room, i|
room.users = User.find(:all, :limit => room_max_users + i + 1)
room.save
end
end
So this fills your rooms with users, based on their id in the database. Only the needed users are loaded at once so it should not be performance critical.
btw this method should be defined as a class method, perhaps on Room so you could invoke it like
class Room < AR::Base
has_many :users
def self.fill_with_users
Room.all.each_with_index do |room, i|
room.users = User.find(:all, :limit => room_max_users + i + 1)
room.save
end
end
end
Room.fill_with_users
In this way you won't need your setup class as well.

How do I save a model with this dynamically generated field?

I have a rails model that looks something like this:
class Recipe < ActiveRecord::Base
has_many :ingredients
attr_accessor :ingredients_string
attr_accessible :title, :directions, :ingredients, :ingredients_string
before_save :set_ingredients
def ingredients_string
ingredients.join("\n")
end
private
def set_ingredients
self.ingredients.each { |x| x.destroy }
self.ingredients_string ||= false
if self.ingredients_string
self.ingredients_string.split("\n").each do |x|
ingredient = Ingredient.create(:ingredient_string => x)
self.ingredients << ingredient
end
end
end
end
The idea is that when I create the ingredient from the webpage, I pass in the ingredients_string and let the model sort it all out. Of course, if I am editing an ingredient I need to re-create that string. The bug is basically this: how do I inform the view of the ingredient_string (elegantly) and still check to see if the ingredient_string is defined in the set_ingredients method?
Using these two together are probably causing your issues. Both are trying to define an ingredients_string method that do different things
attr_accessor :ingredients_string
def ingredients_string
ingredients.join("\n")
end
Get rid of the attr_accessor, the before_save, set_ingredients method and define your own ingredients_string= method, something like this:
def ingredients_string=(ingredients)
ingredients.each { |x| x.destroy }
ingredients_string ||= false
if ingredients_string
ingredients_string.split("\n").each do |x|
ingredient = Ingredient.create(:ingredient_string => x)
self.ingredients << ingredient
end
end
end
Note I just borrowed your implementation of set_ingredients. There's probably a more elegant way to break up that string and create/delete Ingredient model associations as needed, but it's late and I can't think of it right now. :)
The previous answer is very good but it could do with a few changes.
def ingredients_string=(text)
ingredients.each { |x| x.destroy }
unless text.blank?
text.split("\n").each do |x|
ingredient = Ingredient.find_or_create_by_ingredient_string(:ingredient_string => x)
self.ingredients
I basically just modified Otto's answer:
class Recipe < ActiveRecord::Base
has_many :ingredients
attr_accessible :title, :directions, :ingredients, :ingredients_string
def ingredients_string=(ingredient_string)
ingredient_string ||= false
if ingredient_string
self.ingredients.each { |x| x.destroy }
unless ingredient_string.blank?
ingredient_string.split("\n").each do |x|
ingredient = Ingredient.create(:ingredient_string => x)
self.ingredients << ingredient
end
end
end
end
def ingredients_string
ingredients.join("\n")
end
end

Resources