how to get scope with self reference in Rails? - ruby-on-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)') }

Related

Ruby - Query another table in a model definition

I want to query another table in a model definition. For instance, I have a table called miq_user_roles and I want to query and retrievesettings column value.
I tried adding the following
has_many :miq_user_roles
but when I try the where condition where(:settings => nil)
I get the error service_template doesn't have settings column. How can I query miq_user_roles for settings instead of service_template
service_template has a column called miq_group_id and its the id of miq_user_rolestable.
Following is the actual model definition where I want to include miq_user_roles table.
class ServiceTemplate < ApplicationRecord
include SupportsFeatureMixin
DEFAULT_PROCESS_DELAY_BETWEEN_GROUPS = 120
GENERIC_ITEM_SUBTYPES = {
"custom" => N_("Custom"),
"vm" => N_("Virtual Machine"),
"playbook" => N_("Playbook"),
"hosted_database" => N_("Hosted Database"),
"load_balancer" => N_("Load Balancer"),
"storage" => N_("Storage")
}.freeze
SERVICE_TYPE_ATOMIC = 'atomic'.freeze
SERVICE_TYPE_COMPOSITE = 'composite'.freeze
RESOURCE_ACTION_UPDATE_ATTRS = [:dialog,
:dialog_id,
:fqname,
:configuration_template,
:configuration_template_id,
:configuration_template_type].freeze
include CustomActionsMixin
include ServiceMixin
include OwnershipMixin
include NewWithTypeStiMixin
include TenancyMixin
include ArchivedMixin
include CiFeatureMixin
include_concern 'Filter'
include_concern 'Copy'
validates :name, :presence => true
belongs_to :tenant
has_many :service_templates, :through => :service_resources, :source => :resource, :source_type => 'ServiceTemplate'
has_many :services
has_many :service_template_tenants, :dependent => :destroy
has_many :additional_tenants, :through => :service_template_tenants, :source => :tenant, :dependent => :destroy
has_one :picture, :dependent => :destroy, :as => :resource, :autosave => true
belongs_to :service_template_catalog
belongs_to :zone
belongs_to :currency, :inverse_of => false
has_many :dialogs, -> { distinct }, :through => :resource_actions
has_many :miq_schedules, :as => :resource, :dependent => :destroy
has_many :miq_requests, :as => :source, :dependent => :nullify
has_many :active_requests, -> { where(:request_state => MiqRequest::ACTIVE_STATES) }, :as => :source, :class_name => "MiqRequest"
virtual_column :type_display, :type => :string
virtual_column :template_valid, :type => :boolean
virtual_column :template_valid_error_message, :type => :string
virtual_column :archived, :type => :boolean
virtual_column :active, :type => :boolean
default_value_for :internal, false
default_value_for :service_type, SERVICE_TYPE_ATOMIC
default_value_for(:generic_subtype) { |st| 'custom' if st.prov_type == 'generic' }
virtual_has_one :config_info, :class_name => "Hash"
scope :with_service_template_catalog_id, ->(cat_id) { where(:service_template_catalog_id => cat_id) }
scope :without_service_template_catalog_id, -> { where(:service_template_catalog_id => nil) }
scope :with_existent_service_template_catalog_id, -> { where.not(:service_template_catalog_id => nil) }
scope :displayed, -> { where(:display => true) }
scope :public_service_templates, -> { where(:display => true) }
supports :order do
unsupported_reason_add(:order, 'Service template does not belong to a service catalog') unless service_template_catalog
unsupported_reason_add(:order, 'Service template is not configured to be displayed') unless display
end
alias orderable? supports_order?
alias validate_order supports_order?
def self.with_tenant(tenant_id)
tenant = Tenant.find(tenant_id)
where(:tenant_id => tenant.ancestor_ids + [tenant_id])\
end
Add the table name:
where("miq_user_roles.settings" => nil)
Arel is nice, but you still have to use bits of SQL to get over issues like this.
You will have to use joins, to query on the associated table.
ServiceTemplate.joins(:miq_user_roles).where(miq_user_roles: { settings: nil })

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'

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.

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

Rails 3 error: Object doesn't support #inspect on includes (left outer join)

I'm using Rails 3.
I have 3 models:
class Deal < ActiveRecord::Base
has_many :wishes, :class_name => "Wish"
has_many :wishers, :through => :wishes, :source => :user
end
class User < ActiveRecord::Base
has_many :wishes, :class_name => "Wish", :conditions => { 'wishes.wished' => true }
has_many :wished_deals, :through => :wishes, :source => :deal
end
class Wish < ActiveRecord::Base
belongs_to :user
belongs_to :deal
end
And i'm trying to create the following scope in the Deal model:
scope :not_wished_by_user, lambda { |user| includes(:wishes).where('wishes.wished' != true, 'wishes.user_id' => user) }
What i want is all the Deals, except those that are marked as 'wished' by the given user in the block. But whenever i do that includes, i get the following error:
ruby-1.9.2-head > u = User.all.first
ruby-1.9.2-head > Deal.not_wished_by_user(u)
(Object doesn't support #inspect)
=>
Also, placing it in a function doesn't work. Any idea what this could be?
Thanks!
EDIT: These are Wishes table migration
class CreateWish < ActiveRecord::Migration
def self.up
create_table :wishes do |t|
t.integer :deal_id
t.integer :user_id
t.boolean :wished, :default => true
t.boolean :collected, :default => false
t.datetime :collected_date
t.timestamps
end
add_index :wishes, [:deal_id, :user_id], :uniq => true
end
end
See Update below vv
Old answer
You are not using any Deal attributes for selects so try to move code into Wish class:
class Wish < ActiveRecord::Base
belongs_to :user
belongs_to :deal
scope :'wished?', lambda{ |f| where('wished = ?', f) }
scope :not_wished_by_user, lambda{|user| wished?(false).where('user_id = ?', user)}
end
Usage exmple and output:
ruby-1.9.2-p180 :023 > Wish.not_wished_by_user(User.first).to_sql
=> "SELECT \"wishes\".* FROM \"wishes\" WHERE (wished = 't') AND (user_id = 1)"
Is this correct result for you?
PS:
In the Deal you can leave proxy-method like:
class Deal < ActiveRecord::Base
has_many :wishes, :class_name => "Wish"
has_many :wishers, :through => :wishes, :source => :user
def self.not_wished_by_user(user)
Wish.not_wished_by_user(user)
end
end
Update1 (subquery)
class Deal < ActiveRecord::Base
has_many :wishes, :class_name => "Wish"
has_many :wishers, :through => :wishes, :source => :user
scope :deal_ids_not_wished_by_user, lambda { |user|
joins(:wishes).where('wishes.user_id = ?', user).where('wishes.wished = ?', false).select('deals.id')
}
scope :wished_by_user, lambda { |user|
where("id not in (#{Deal.deal_ids_not_wished_by_user(user).to_sql})")
}
end
Usage example and output:
ruby-1.9.2-p180 :023 > Deal.wished_by_user(User.first).to_sql
=> "SELECT \"deals\".* FROM \"deals\" WHERE (id not in (SELECT deals.id FROM \"deals\" INNER JOIN \"wishes\" ON \"wishes\".\"deal_id\" = \"deals\".\"id\" WHERE (wishes.user_id = 1) AND (wishes.wished = 'f')))"
UPD2 (railish outer join)
Deal class:
class Deal < ActiveRecord::Base
has_many :wishes, :class_name => "Wish"
has_many :wishers, :through => :wishes, :source => :user
scope :not_wished_excluded, lambda { |user|
joins('LEFT OUTER JOIN wishes on wishes.deal_id = deals.id').
where('wishes.user_id = ? OR wishes.user_id is null', user).
where('wishes.wished = ? OR wishes.wished is null', true)
}
end
Usage:
ruby-1.9.2-p180 :096 > Deal.not_wished_excluded(User.first).to_sql
=> "SELECT \"deals\".* FROM \"deals\" LEFT OUTER JOIN wishes on wishes.deal_id = deals.id WHERE (wishes.user_id = 1 OR wishes.user_id is null) AND (wishes.wished = 't' OR wishes.wished is null)"

Resources