API: Update model through an other related model - ruby-on-rails

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

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 })

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.

Nested model in json in has_many through association

Using Rails 3.2. I have the following code:
# month.rb
class Month < ActiveRecord::Base
has_many :days
def map_markers
days.as_json(
:only => :position,
:include => {
:day_shops => {
:only => :position,
:include => {
:shops => {
:only => [ :name ]
}
}
}
}
)
end
end
# day.rb
class Day < ActiveRecord::Base
belongs_to :month
has_many :day_shops
has_many :shops, :through => :day_shops
end
# day_shop.rb
class DayShop < ActiveRecord::Base
belongs_to :day
belongs_to :shop
end
# shop.rb
class Shop < ActiveRecord::Base
end
What I am trying to achieve is to wrap the shop model in the day_shop model (which is a through table), but when I wrapped it up in the JSON like above, I get:
undefined method 'shops' for #<DayShop id: 87, day_id: 26, shop_id: 1, position: 1>
My expected JSON would be:
- position: 1
:day_shops:
- position: 1
:shops:
- name: Company A
- position: 2
:shops:
- name: Company B
- position: 2
:day_shops:
- position: 1
:shops:
- name: Company A
- position: 2
:shops:
- name: Company C
How can I change my method? Thanks.
DayShop belongs to a Shop while you are including shops to a day_shop in your map_marker method. Modify map_marker to:
def map_markers
days.as_json(
:only => :position,
:include => {
:day_shops => {
:only => :position,
:include => {
:shop => {
:only => [ :name ]
}
}
}
}
)
end

rails 3 custom validation error messages in a join table, how?

How can I return errors messages from a cross reference table with multiple records when I trying to create those? I'm trying this:
## activity_set.rb
class ActivitySet < ActiveRecord::Base
has_many :activity_set_lessons
has_many :lessons, :through => :activity_set_lessons
validates :name, :presence => true
def activity_set_lessons=(data)
data.each_with_index do |v, i|
activity_set_lessons.build(
:lesson_id => v[:lesson_id],
:sort_order => i,
:weight_percentage => v[:weight_percentage]
)
end
end
end
## activity_set_lesson.rb
class ActivitySetLesson < ActiveRecord::Base
belongs_to :activity_set
belongs_to :lesson
validates :lesson_id, :presence => true
validates_each :weight_percentage do |record, attr, value|
record.errors.add :base, "woot" if value.blank?
end
end
This is the request data:
## params[:activity_set]
"activity_set" => {
"name" => "hshshshs",
"keywords" => "",
"activity_set_lessons" => [
{"weight_percentage" => "", "lesson_id"=>"4"},
{"weight_percentage" => "", "lesson_id"=>"5"}
]
}
Error messages from #activity_set when I do #save:
{
"errors":{
"activity_set_lessons":["is invalid","is invalid"]
},
"full_messages":[
"Activity set lessons is invalid","Activity set lessons is invalid"
]
}
I always got the same error message even if I'm adding a custom one in the join table. How can I return a message like: "woot 1 is wrong" or something like that, per validation?.
Thanks.
make use of accepts_nested_attributes_for
## activity_set.rb
class ActivitySet < ActiveRecord::Base
has_many :activity_set_lessons
has_many :lessons, :through => :activity_set_lessons
validates :name, :presence => true
accepts_nested_attributes_for :activity_set_lessons
end
view will look like
= form_for #activity_set do |f|
[activity_set form fields ]
= f.fields_for :activity_set_lessons do |p|
= p.select :lession_id
= p.select :weight_percentage

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