Rails: Create dynamic amount of relationships upon creating a new model - ruby-on-rails

In our app, a User can join a Bet through memberships. Each Bet can have many rounds, and each Round has many players (users) based upon who is a member of the bet at the time the round is created.
A round has many players through contracts (relationship between Round and User). Basically, I want to be able to create a new Round and automatically create a Contract for each user who is a member of the bet. Members can join and leave bets, any only be part of the rounds that were created while they had a Membership to the Bet.
I'm new to Rails, and can't think of a way to automatically make a contract relationship for each user who has a membership to the bet. Any ideas would really be appreciated!!
class Bet < ActiveRecord::Base
has_many :memberships, dependent: :destroy
#
has_many :agree_members, -> { where(memberships: { accepted: true }).where(memberships: { against: false }) }, through: :memberships, source: :user
has_many :against_members, -> { where(memberships: { accepted: true }).where(memberships: { against: true }) }, through: :memberships, source: :user
#
has_many :agree_requesters, -> { where(memberships: { accepted: false }).where(memberships: { against: false }) }, through: :memberships, source: :user
has_many :against_requesters, -> { where(memberships: { accepted: false }).where(memberships: { against: true }) }, through: :memberships, source: :user
def members
agree_members | against_members
end
#
def requests
agree_requesters | against_requesters
end
has_many :rounds
end
============
class Round < ActiveRecord::Base
belongs_to :bet
has_many :contracts, dependent: :destroy
has_many :potential_winners, -> { where(contracts: { agrees: true, agree_wins: true, signed: false }) }, through: :contracts, source: :user
has_many :potential_losers, -> { where(contracts: { agrees: true, agree_wins: false, signed: false }) }, through: :contracts, source: :user
has_many :winners, -> { where(contracts: { agrees: true, agree_wins: true, signed: true }) }, through: :contracts, source: :user
has_many :losers, -> { where(contracts: { agrees: true, agree_wins: false, signed: true }) }, through: :contracts, source: :user
end
==============
class User < ActiveRecord::Base
# BETS (and bet memberships)
has_many :memberships
has_many :agree_bets, -> { where(memberships: { accepted: true }).where(memberships: { against: false }) }, through: :memberships, source: :bet
has_many :against_bets, -> { where(memberships: { accepted: true }).where(memberships: { against: true }) }, through: :memberships, source: :bet
has_many :pending_bets, -> { where(memberships: { accepted: false }) }, through: :memberships, source: :bet
def active_bets
agree_bets | against_bets
end
def joined_bets
active_bets | pending_bets
end
# ROUNDS (and contracts)
has_many :contracts
#IGNORE THIS LOGIC, NOT SET UP YET AND NOT RELEVANT
has_many :agree_maybe_wins, -> { where(contracts: { agree: true }).where(memberships: { against: false }) }, through: :contracts, source: :round
has_many :against_maybe_wins, -> { where(contracts: { agree: true }).where(memberships: { against: false }) }, through: :contracts, source: :round
has_many :agree_maybe_loses, -> { where(contracts: { agree: true }).where(memberships: { against: false }) }, through: :contracts, source: :round
has_many :against_maybe_loses, -> { where(contracts: { agree: true }).where(memberships: { against: false }) }, through: :contracts, source: :round
has_many :agree_wins, -> { where(contracts: { agree: true }).where(memberships: { against: false }) }, through: :contracts, source: :round
has_many :against_wins, -> { where(contracts: { agree: true }).where(memberships: { against: false }) }, through: :contracts, source: :round
has_many :agree_losses, -> { where(contracts: { agree: true }).where(memberships: { against: false }) }, through: :contracts, source: :round
has_many :against_losses, -> { where(contracts: { agree: true }).where(memberships: { against: false }) }, through: :contracts, source: :round
def potential_wins
agree_maybe_wins | against_maybe_wins
end
def wins
agree_wins | against_wins
end
def potential_losses
agree_maybe_wins | against_maybe_wins
end
def losses
agree_wins | against_wins
end
end

One approach here would be a has_many :through relationship. If I understand your application correctly, your model relations could be roughly summarized as:
User -> Contract -> Round -> Bet
Conceived of this way, you can then deal with :memberships as an alias in the Round model.
Class Round
has_many :memberships, class_name: "User", through: :contracts
end
With the Contract model of the format looking containing user_id and round_id, each time a user is involved in a Round, you would create a Contract model to represent that involvement as a "member". In other words, creating a contract wouldn't be an additional step, but rather the fundamental action of entering a user into a round. Specifically, you could write the following:
class User
def self.add_to_round(round_id)
Contract.create(user_id: self.id, round_id: round_id)
end
end
and then query things like Round.first.members or User.first.rounds.
For convenience, you may also wish to create another has_many :through relationship that allows you to query directly from Users to Bets (or the reverse). This would look like:
class User
has_many :rounds, through: :contracts
has_many :bets, through: :rounds
end
With both of these relationships in place, you should then be able to call User.first.bets to get all bets that a user has been involved in.

Related

Querying polymorphic associations causing validity error

I have a polymorphic association in my app to track orders in my app. I'd like to be able to query Meal, a model that is :orderable but for some reason after adding the association, calls to :valid? throw an error.
class Order < ApplicationRecord
belongs_to :order_status
belongs_to :client
has_many :order_items
has_many :orderables, through: :order_items, source: :orderable, source_type: 'Meal'
end
class OrderItem < ApplicationRecord
belongs_to :order
belongs_to :meal, -> { where( order_items: { orderable_type: 'Meal' } ).includes(:meal) }, foreign_key: 'orderable_id'
belongs_to :orderable, polymorphic: true
validates :quantity, presence: true, numericality: { only_integer: true, greater_than: 0 }
validate :order_present
validate :item_present
scope :are_veggies, -> { includes( :meal ).where( :meals => { type_of: 'veggie' }) }
scope :are_meals, -> { includes( :meal ).where.not( :meals => { type_of: 'veggie' }) }
private
def item_present
if orderable.present? and !orderable.active
errors.add(:orderable, "Selected item is not available.")
end
end
def order_present
if order.nil?
errors.add(:order, "is not a valid order.")
end
end
end
class Meal < ApplicationRecord
extend FriendlyId
friendly_id :name, use: :slugged
has_many :tags, as: :taggable
has_many :order_items, as: :orderable
validates_presence_of :name, :type_of
default_scope { where(active: true) }
end
All the queries work properly, I can call Order.find(x).meals. I can call Order.find(x).order_items.are_meals.
But when I attempt to call OrderItem.find(x).valid? I see an error: ActiveRecord::ConfigurationError: Can't join 'Meal' to association named 'meal'; perhaps you misspelled it?. Any idea what is causing that?
I was including the wrong model:
# This
belongs_to :meal, -> { where( order_items: { orderable_type: 'Meal' } ).includes(:meal) }, foreign_key: 'orderable_id'
# Should have been
belongs_to :meal, -> { where( order_items: { orderable_type: 'Meal' } ).includes(:order_items) }, foreign_key: 'orderable_id'

how to get scope with self reference in Rails?

i have a model
class Category < ActiveRecord::Base
belongs_to :parent, :class_name => 'Category'
has_many :children, :class_name => 'Category', :foreign_key => 'parent_id'
end
I want to a Scope with select all Category in tables categories with category.children == nil or (category.children.count == 0).
Pls help me.
scope :no_children, -> { includes(:children).where(children: { id: nil }) }
or
scope :without_children, { where('not exists (select null from children where children.parent_id = category.id)') }

Is there a cleaner or shorter way to display these has_many relationships?

Is there a way to clean up these has_many relationships, they're brutal to look at. Could I put them in a block or DRY them up in any way?
# mymodel.rb
has_many :friendships, -> { includes :friend }
has_many :friends, -> { where(friendships: { status: 'accepted'}) },
through: :friendships, :source => :friend
has_many :requests, -> { where(friendships: { status: 'requested'}) },
through: :friendships, :source => :friend
has_many :requested_friendships, -> { where(friendships: { status: 'requestor'}) },
through: :friendships, :source => :friend
One handy method is with_options, which lets you apply the same options to a series of method calls. You might use it like this:
has_many :friendships, -> { includes :friend }
with_options through: :friendships, source: :friend do |model|
model.has_many :friends, -> { where(friendships: { status: 'accepted' }) }
model.has_many :requests, -> { where(friendships: { status: 'requested' }) }
model.has_many :requested_friendships, -> { where(friendships: { status: 'requestor' }) }
end
I think that's pretty good. If you want you could enhance it with a scope:
has_many :friendships, -> { includes :friend }
with_options through: :friendships, source: :friend do |model|
model.has_many :friends, -> { with_friendship_status 'accepted' }
model.has_many :requests, -> { with_friendship_status 'requested' }
model.has_many :requested_friendships, -> { with_friendship_status 'requestor' }
end
scope :with_friendship_status, ->(status) { where(friendships: { status: status }) }
Alternatively, you could do something like this:
has_many :friendships, -> { includes :friend }
{ friends: "accepted",
requests: "requested",
requested_friendships: "requestor"
}.each do |assoc, status|
has_many assoc, -> { where(friendships: { status: status }) },
through: :friendships, source: :friend
end
...but I think you lose a lot of readability that way without gaining much.

Filtering polymorphic association by type for a view

I have a polymorphic association that looks like this:
class Event < ActiveRecord::Base
belongs_to :eventable, :polymorphic => true
end
With a bunch of types:
class Nap < ActiveRecord::Base
include Eventable
end
class Meal < ActiveRecord::Base
include Eventable
end
module Eventable
def self.included(base)
base.class_eval do
has_one :event, :as => :eventable, :dependent => :destroy
accepts_nested_attributes_for :event, :allow_destroy => true
scope :happened_at, -> (date) {
where("events.happened_at >= ? AND events.happened_at <= ?",
date.beginning_of_day, date.end_of_day).order("events.happened_at ASC")
}
base.extend(ClassMethods)
end
end
module ClassMethods
define_method(:today) do
self.happened_at(Date.today)
end
end
end
And so on.
Here's the other end of the relationship:
class Person < ActiveRecord::Base
has_many :events
has_many :meals, {
:through => :events,
:source => :eventable,
:source_type => "Meal"
}
has_many :naps, {
:through => :events,
:source => :eventable,
:source_type => "Nap"
}
has_many :moods, {
:through => :events,
:source => :eventable,
:source_type => "Mood"
}
has_many :notes, {
:through => :events,
:source => :eventable,
:source_type => "Note"
}
...
end
I want to grab all the events of all types that belong to a person for display in a single view. Here's what I'm doing:
def show
#events = Event.by_person(#person).happened_at(date)
#meals, #naps, #moods, #notes = [], [], [], [], []
#events.each do |e|
#meals << e.eventable if e.eventable_type == 'Meal'
#naps << e.eventable if e.eventable_type == 'Nap'
#moods << e.eventable if e.eventable_type == 'Mood'
#notes << e.eventable if e.eventable_type == 'Note'
end
end
I need to filter by type because the view is going to be displaying type-specific attributes in each section of the view.
Question: Should this logic of filtering out the collection of events by type into their own type-specific arrays exist in the controller? Or elsewhere? Perhaps the model?
I was reluctant to just pass #events to the view and have the type test happen in the view itself. That seemed wrong.
You can use the #events query to create a subquery without having to iterate (I'm assuming you have the inverse has_many :events, as: :eventable in each of your other models):
#events = Event.by_person(#person).happened_at(date)
#meals = Meal.joins(:event).where events: { id: #events }
#naps = Nap.joins(:event).where events: { id: #events }
# etc.

Can I use accepts_nested_attributes_for hash in user_info_mapping or it isn't possible?

My user model has the following code:
has_one :settings,
class_name: 'Setting', dependent: :destroy
accepts_nested_attributes_for :settings, allow_destroy: true
And in the sorcery config I wrote:
config.vk.user_info_mapping = { login: "domain",
settings_attributes: { name: "full_name", about: "about" } }
Is that even possible? Thanks for your help!

Resources