Nested model in json in has_many through association - ruby-on-rails

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

Related

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

JSON include syntax

My setup: Rails 2.3.10, Ruby 1.8.7
I have a rather complicated set of relationships between several models.
class A
has_many :classB
has_many :classD
end
class B
belongs_to :classA
has_many :classC
end
class C
belongs_to :classB
belongs_to :classE
end
class D
belongs_to :classA
belongs_to :classE
end
class E
has_many :classD
has_many :classC
end
I'm having an issue with the JSON syntax to get all the related information starting with classA. Here's what I have working so far.
classA.to_json(:include => {:classB => {:include => [:classC, :classE]}})
I can't get the syntax working to also include classD and related classE records.
UPDATE Actually something like this might work except that I can't mix hashes and arrays
classA.to_json(:include => [ :classB => { :include => { :classC => { :include => :classE } } },
:classD, :classE ])
Note that I didn't use singular/plural in my example code above but in my real code, I am. Any insights will be much appreciated.
Thanks,
Bob
This should work:
classA.to_json(:include => {
:classB => {:include => {:classC => {:include => :classE}}},
:classD => {},
:classE => {},
})
Try this, you should only need one :include =>:
classA.to_json(:include => {:classB => [:classC, { :classE => :classD }] })
I don't know if you want classE included through both class C and classD but this should work:
classA.to_json(:include => { :classB => { :include => { :classC => { :include => :classE } } },
:classD => { :include => :classE } })
EDIT:
class A
has_many :bs
has_many :ds
end
class B
belongs_to :a
has_many :cs
end
class C
belongs_to :b
belongs_to :e
end
class D
belongs_to :a
belongs_to :e
end
class E
has_many :ds
has_many :cs
end
#class_a = A.first
#class_a.to_json(:include => { :bs => { :include => { :cs => { :include => :e } } },
:ds => { :include => :e } })

How to access ':has_many :though' join table data when using to_json?

I have three models (simplified here):
class Child < ActiveRecord::Base
has_many :childviews, :dependent => :nullify
has_many :observations, :through => :childviews
end
class Childview < ActiveRecord::Base
belongs_to :observation
belongs_to :child
end
class Observation < ActiveRecord::Base
has_many :childviews, :dependent => :nullify
has_many :children, :through => :childviews
end
I'm sending this to some JavaScript using Rails' to_json method like this:
render :layout => false , :json => #child.to_json(
:include => {
:observations => {
:include => :photos,
:methods => [:key, :title, :subtitle]
}
},
:except => [:password]
)
This works perfectly. Observations are retrieved fine 'through' the join table (childviews).
However, I also want to get at data that sits in the childviews join table; specifically the value for 'needs_edit'.
I can't figure out how to get at this data in a to_json call.
Can anyone help me? Many thanks in advance.
qryss
Not sure, but shouldn't this work?
#child.to_json(
:include => {
:observations => {
:include => :photos,
:methods => [:key, :title, :subtitle]
},
:childviews => { :only => :needs_edit }
},
:except => [:password]
)
EDIT:
This might work too, since childviews belongs_to the overvation:
#child.to_json(
:include => {
:observations => {
:include => { :photos, :childviews => { :only => :needs_edit } }
:methods => [:key, :title, :subtitle]
}
},
:except => [:password]
)
Thanks to Rock for the pointers - I now have it working!
This code:
#child.to_json(:include =>
{
:observations => {
:include => {
:photos => {},
:childviews => {:only => :needs_edit}
},
:methods => [:S3_key, :title, :subtitle]
}
},
:except => [:password]
)
gives me this output (abbreviated for clarity):
{
"child":
{
"foo":"bar",
"observations":
[
{
"foo2":"bar2",
"photos":
[
{
"foo3":"bar3",
}
],
"childviews":
[
{
"needs_edit":true
}
]
}
]
}
}
Thank you, Rock! That was doing my head in.
:)
qryss

Resources