Querying polymorphic associations causing validity error - ruby-on-rails

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'

Related

Displaying data from a not connected table IF it matches a piece of information in Rails

I am trying to limit displays of data on my regional page for those events that only occur within that region (by city). Events are not connected with the region. So in my region view I have:
<h3> <%=#region.name%> </h3>
<% #region.locations.each do |location| %>
<%= location.name %>
<% end %>
<h3> Event </h3>
<% Event.all.order('time_start ASC').limit(5).each do |item| %>
<p><%= link_to item.name, item_event_path(item.url_name)%></p>
<p><%=item.city%>, <%=item.state%></p>
<%end%>
How do I do a comparison between Region's city (through Location table's name) and Event's city? This way I can restrict the view to only be those that match?
Ultimately I want to limit the Event section to only display those results where the city matches the location's name.
UPDATE:
Here are the models.
Location:
class Location < ApplicationRecord
scope :ordered, -> { order(name: :desc) }
scope :search, ->(term) { where('name LIKE ?', "%#{term}%") }
belongs_to :region, optional: true
belongs_to :spud_user, optional: true
has_many :careers, dependent: :destroy
validates :name, :address_1, :city, :state, :postal_code, :hours_operation, :phone, presence: true
end
Region:
class Region < ApplicationRecord
scope :ordered, -> { order(name: :desc) }
scope :search, ->(term) { where('name LIKE ?', "%#{term}%") }
has_many :locations, dependent: :destroy
has_many :careers, through: :locations
validates :name, presence: true
end
Event:
class Event < ApplicationRecord
scope :ordered, -> { order(time_start: :desc) }
scope :ascending, -> { order(time_start: :asc) }
scope :search, ->(term) { where('name LIKE ?', "%#{term}%") }
scope :by_month, ->(month) { where('extract(month from time_start) = ?', month) }
scope :by_state, ->(state) { where(state: state) }
scope :by_city, ->(city) { where(city: city) }
validates :name, presence: true
validates :street_address, :city, :state, :zip, :time_start, :time_end, presence: true, unless: :online?
validate :valid_time_span?
validate :valid_url?
def valid_url?
return true if web_url.blank?
uri = URI.parse(web_url)
errors.add(:web_url, 'invalid url') unless uri.is_a?(URI::HTTP) && !uri.host.nil?
end
def valid_time_span?
return true if time_start <= time_end
errors.add(:time_start, ' must be before Time End')
end
end
Just setup indirect associations:
class Region
has_many :locations
has_many :events, through: :locations
end
class Location
belongs_to :region
has_many :events
end
class Event
belongs_to :location
has_one :region, through: :location
end
This will let you query from event to region and vice versa.

undefined method `last_comment_at=' for #<BlogPost:0x0000000adb9110>

I create new post:
def create
params[:blog_post][:draft] = params[:draft].present?
#post = current_user.blog_posts.new(blog_post_params)
#post.save!
redirect_to #post, notice: (#post.draft? ? 'Черновик сохранен' : 'Пост опубликован')
rescue ActiveRecord::RecordInvalid
flash[:error] = ":("
render :new
end
When i create new post, displaying error:
undefined method `last_comment_at=' for #<BlogPost:0x0000000adb9110>
app/models/blog_post.rb:86:in `set_last_comment_at'
app/controllers/blog_posts_controller.rb:25:in `create'
config/initializers/flash_session_cookie_middleware.rb:19:in `call'
Model BlogPost:
class BlogPost < ActiveRecord::Base
# attr_accessible :subject, :body, :tag_list, :commentable_by, :visible_by, :attachments_attributes
include ActiveModel::ForbiddenAttributesProtection
belongs_to :user
has_many :comments, as: :owner, dependent: :destroy
has_many :attachments, as: :owner, dependent: :destroy
has_many :photos, through: :attachments, source: :asset, source_type: 'Photo'
has_many :videos, through: :attachments, source: :asset, source_type: 'Video'
belongs_to :article
has_many :blog_post_subscriptions, dependent: :destroy
has_many :subscribers, through: :blog_post_subscriptions, class_name: 'User', source: :user
has_one :poll, as: :owner, dependent: :destroy
has_many :poll_items, dependent: :destroy
accepts_nested_attributes_for :attachments, :allow_destroy => true
accepts_nested_attributes_for :poll, allow_destroy: true,
reject_if: proc { |attributes| attributes['question'].blank? }
validates :user, :subject, :presence => true
validates :body, presence: true, if: :body_required?
validates :body, length: { maximum: 65000 }
validate :validate_duplicate, on: :create
def body_required?
!article.present?
end
# after_create :bonus_for_blog_post
# after_create :notify_subscribers
before_create :check_paid_attributes
after_create :set_last_comment_at
def notify_subscribers!
unless draft?
Resque.enqueue BlogPostNotifier, self.id
end
end
after_save :set_published_at
def set_published_at
if published_at.nil? and !draft?
update_column :published_at, Time.now
bonus_for_blog_post!
notify_subscribers!
end
end
define_index do
indexes subject
indexes body
indexes tags_line
indexes user.username, as: :blog_post_author
has created_at
has published_at
where "draft=0"
group_by 'subject, body, tags_line, blog_posts.published_at, users.username'
set_property delta: ThinkingSphinx::Deltas::ResqueDelta
end
def to_s
subject || "[без заголовка]"
end
scope :drafts, where(draft: true)
scope :public, lambda { where(draft: false).where(:published_at.lte => Time.now) }
scope :with_privacy, lambda { |u|
unless u.moderator?
friend_ids = u.friend_ids + [u.id]
public.where(' blog_posts.visible_by IS NULL OR visible_by = "all" OR ' +
'(blog_posts.visible_by = "me" AND user_id = ?) OR' +
'(blog_posts.visible_by = "friends" AND user_id IN (?)) OR ' +
'(blog_posts.visible_by = "fof" AND EXISTS ' +
'(SELECT id FROM friendships WHERE friendships.user_id = blog_posts.user_id AND ' +
'friendships.friend_id IN (?) LIMIT 1)) OR ' +
'(blog_posts.visible_by = "bl" AND NOT EXISTS ' +
'(SELECT id FROM black_list_items WHERE black_list_items.owner_type="User" AND black_list_items.owner_id=blog_posts.user_id AND black_list_items.blocked_user_id=?))', u.id, friend_ids, friend_ids, u.id)
end
}
def set_last_comment_at
self.last_comment_at = created_at
save
end
acts_as_taggable
scope :tagged, lambda {|tag| tagged_with(tag) if tag }
before_save :set_tags_line
def set_tags_line
self.tags_line = tag_list.join(', ')
end
def user_can_edit?(user)
self.user.id == user.id or user.moderator?
end
def user_can_comment?(u)
u.can_comment_blog_post? self
end
protected
def bonus_for_blog_post!
unless draft?
user.bonus(:blog_post_bonus)
end
end
def check_paid_attributes
unless user.paid?
self.commentable_by = self.visible_by = 'all'
end
end
def validate_duplicate
errors.add(:base, :duplicate) unless user.blog_posts.where(body: body, article_id: article_id, :created_at.gt => 1.minute.ago).empty?
end
private
after_update :expire_cache
def expire_cache
expire_fragment "#{dom_id}_body"
expire_fragment "#{dom_id}_body_short"
expire_fragment "#{dom_id}_attachments"
Rails.cache.delete "#{dom_id}_tags"
end
before_save :emoji
def emoji
self.body = Rumoji.encode self.body
end
end
I think, problem is here:
def set_last_comment_at
self.last_comment_at = created_at
save
end
But why can't it find the method last_comment_at?
I fixed issue, i run migrate and added new column last_comment_at to table BlogPost

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.

Rails: Create dynamic amount of relationships upon creating a new model

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.

API: Update model through an other related model

I'm trying make it possible to update a a LineItem trough a CreditNote. It's for an API, so I'm trying to update that trough a JSON.
My relational model is:
class TestCreditNote < ActiveRecord::Base
self.table_name = :credit_notes
has_many :line_items, :class_name => TestLineItem, :foreign_key => :artef_id
accepts_nested_attributes_for :line_items
end
class TestLineItem < ActiveRecord::Base
self.table_name = :line_items
attr_accessible :description
belongs_to :credit_note, :class_name => TestCreditNote, :foreign_key => :artef_id
end
When executing this test:
it "should update the sales line item record" do
put "api/v1/credit_notes/#{#credit_note.id}", { :test_credit_note => { :line_items => [{ :description => 'PEPITO'}] }}, http_headers
data = JSON.parse(response.body, :symbolize_names => true)
TestCreditNote.find(#sales_credit_note.id).line_item.description.should == 'PEPITO'
end
It fails because of:
ActiveModel::MassAssignmentSecurity::Error:
Can't mass-assign protected attributes: line_items
I've add the attr_accesible :line_items_attributes
class TestCreditNote < ActiveRecord::Base
self.table_name = :credit_notes
has_many :line_items, :class_name => TestLineItem, :foreign_key => :artef_id
accepts_nested_attributes_for :line_items
attr_accessible :line_items_attributes
end
And the same in the test
it "should update the sales line item record" do
put "api/v1/credit_notes/#{#credit_note.id}", { :test_credit_note => { :line_items_attributes => [{:id => 1, :description => 'PEPITO'}] }}, http_headers
data = JSON.parse(response.body, :symbolize_names => true)
TestCreditNote.find(#sales_credit_note.id).line_item.description.should == 'PEPITO'
end

Resources