I have the following models:
class Appeal < ActiveRecord::Base
belongs_to :applicant, :autosave => true
belongs_to :appealer, :autosave => true
end
class Appealer < ActiveRecord::Base
has_many :appeals, :autosave => true
end
class Applicant < ActiveRecord::Base
has_many :appeals
end
What I want is for every appealer to hold a reference to the applicant of his last appeal
so I modified Appealer model to:
class Appealer < ActiveRecord::Base
has_many :appeals, :autosave => true
def last_applicant
return self.appeals.last.applicant
end
end
but I get the error:
undefined method `applicant' for nil:NilClass
what is strange that if I debug this (via RubyMine - Evaluate Expression) I can get the applicant.
If I try to get the last appeal:
class Appealer < ActiveRecord::Base
has_many :appeals, :autosave => true
def last_appeal
return self.appeals.last
end
end
everything works.
Im working with active-model-serializer, tried to do it also in the serializer (I actually need this value on a specific call - not allover the model) but it also didnt worked with the same errors.
the AMS code:
class AppealerTableSerializer < ActiveModel::Serializer
attributes :id, :appealer_id, :first_name, :last_name, :city
has_many :appeals, serializer: AppealMiniSerializer
def city
object.appeals.last.appealer.city
end
end
MY QUESTION:
How can I get the nested object attributes in my JSON?
What am I doing wrong?
EDIT:
My controller call:
class AppealersController < ApplicationController
def index
appealers = Appealer.all
render json: appealers, each_serializer: AppealerTableSerializer, include: 'appeal,applicant'
end
end
I've tried with and without the include, still not works
Maybe I am missing something, because this looks like you have Appealer record that doesn't have any appeals yet.
In such case, this code
def last_appeal
return self.appeals.last
end
Will return nil, which won't raise any errors. But if you call this
def last_applicant
return self.appeals.last.applicant
end
return self.appeals.last is nil and you try to call applicant method on nil object instead of Appeal object.
To fix it just add check for nil
class Appealer < ActiveRecord::Base
has_many :appeals, :autosave => true
def last_applicant
last = self.appeals.last
if last.nil?
return nil
else
return last.applicant
end
end
end
Related
In my Rails 6 app I have these models:
class User < ApplicationRecord
has_many :read_news_items
has_many :news_items, :through => :read_news_items
end
class NewsItem < ApplicationRecord
has_many :read_news_items
has_many :users, :through => :read_news_items
def read?(user)
read_news_items.where(:user_id => user.id).any?
end
end
class ReadNewsItem < ApplicationRecord
belongs_to :user
belongs_to :news_item
end
In my controller action I want to list all news items and highlight the ones that have not yet been read by the user:
class NewsItemsController < ApplicationController
def index
#news_items = NewsItem.all
end
end
The problem is that this generates N+1 queries for each record because the read?(current_user) gets called for each user record.
How can this problem be overcome?
I tried appending includes(:read_news_items) and joins(:read_news_items) to the database query in my controller but to no avail.
You could try:
class NewsItem < ApplicationRecord
has_many :read_news_items
def read?(user)
if read_news_items.loaded?
read_news_items.any? {|rni| rni.user_id == user.id }
else
read_news_items.where(:user_id => user.id).any?
end
end
end
class NewsItemsController < ApplicationController
def index
#news_items = NewsItem.includes(:read_news_items).all
end
end
OK, I learned something from every answer that was given here. So thanks for that.
I changed my read? method to the following which seems to have eliminated the N+1 queries:
class NewsItem < ApplicationRecord
def read?(user)
user.read_news_items.pluck(:news_item_id).include?(id)
end
end
I read this interesting article about Using Polymorphism to Make a Better Activity Feed in Rails.
We end up with something like
class Activity < ActiveRecord::Base
belongs_to :subject, polymorphic: true
end
Now, if two of those subjects are for example:
class Event < ActiveRecord::Base
has_many :guests
after_create :create_activities
has_one :activity, as: :subject, dependent: :destroy
end
class Image < ActiveRecord::Base
has_many :tags
after_create :create_activities
has_one :activity, as: :subject, dependent: :destroy
end
With create_activities defined as
def create_activities
Activity.create(subject: self)
end
And with guests and tags defined as:
class Guest < ActiveRecord::Base
belongs_to :event
end
class Tag < ActiveRecord::Base
belongs_to :image
end
If we query the last 20 activities logged, we can do:
Activity.order(created_at: :desc).limit(20)
We have a first N+1 query issue that we can solve with:
Activity.includes(:subject).order(created_at: :desc).limit(20)
But then, when we call guests or tags, we have another N+1 query problem.
What's the proper way to solve that in order to be able to use pagination ?
Edit 2: I'm now using rails 4.2 and eager loading polymorphism is now a feature :)
Edit: This seemed to work in the console, but for some reason, my suggestion of use with the partials below still generates N+1 Query Stack warnings with the bullet gem. I need to investigate...
Ok, I found the solution ([edit] or did I ?), but it assumes that you know all subjects types.
class Activity < ActiveRecord::Base
belongs_to :subject, polymorphic: true
belongs_to :event, -> { includes(:activities).where(activities: { subject_type: 'Event' }) }, foreign_key: :subject_id
belongs_to :image, -> { includes(:activities).where(activities: { subject_type: 'Image' }) }, foreign_key: :subject_id
end
And now you can do
Activity.includes(:part, event: :guests, image: :tags).order(created_at: :desc).limit(10)
But for eager loading to work, you must use for example
activity.event.guests.first
and not
activity.part.guests.first
So you can probably define a method to use instead of subject
def eager_loaded_subject
public_send(subject.class.to_s.underscore)
end
So now you can have a view with
render partial: :subject, collection: activity
A partial with
# _activity.html.erb
render :partial => 'activities/' + activity.subject_type.underscore, object: activity.eager_loaded_subject
And two (dummy) partials
# _event.html.erb
<p><%= event.guests.map(&:name).join(', ') %></p>
# _image.html.erb
<p><%= image.tags.first.map(&:name).join(', ') %></p>
This will hopefully be fixed in rails 5.0. There is already an issue and a pull request for it.
https://github.com/rails/rails/pull/17479
https://github.com/rails/rails/issues/8005
I have forked rails and applied the patch to 4.2-stable and it works for me. Feel free to use my fork, even though I cannot guarantee to sync with upstream on a regular basis.
https://github.com/ttosch/rails/tree/4-2-stable
You can use ActiveRecord::Associations::Preloader to preload guests and tags linked, respectively, to each of the event and image objects that are associated as a subject with the collection of activities.
class ActivitiesController < ApplicationController
def index
activities = current_user.activities.page(:page)
#activities = Activities::PreloadForIndex.new(activities).run
end
end
class Activities::PreloadForIndex
def initialize(activities)
#activities = activities
end
def run
preload_for event(activities), subject: :guests
preload_for image(activities), subject: :tags
activities
end
private
def preload_for(activities, associations)
ActiveRecord::Associations::Preloader.new.preload(activities, associations)
end
def event(activities)
activities.select &:event?
end
def image(activities)
activities.select &:image?
end
end
image_activities = Activity.where(:subject_type => 'Image').includes(:subject => :tags).order(created_at: :desc).limit(20)
event_activities = Activity.where(:subject_type => 'Event').includes(:subject => :guests).order(created_at: :desc).limit(20)
activities = (image_activities + event_activities).sort_by(&:created_at).reverse.first(20)
I would suggest adding the polymorphic association to your Event and Guest models.
polymorphic doc
class Event < ActiveRecord::Base
has_many :guests
has_many :subjects
after_create :create_activities
end
class Image < ActiveRecord::Base
has_many :tags
has_many :subjects
after_create :create_activities
end
and then try doing
Activity.includes(:subject => [:event, :guest]).order(created_at: :desc).limit(20)
Does this generate a valid SQL query or does it fail because events can't be JOINed with tags and images can't be JOINed with guests?
class Activity < ActiveRecord::Base
self.per_page = 10
def self.feed
includes(subject: [:guests, :tags]).order(created_at: :desc)
end
end
# in the controller
Activity.feed.paginate(page: params[:page])
This would use will_paginate.
I'm in the process of building my first Rails application and am going crazy knowing this is probably a simple fix. I'm having trouble returning the proper object and attribute from a method. I have three models: User, Feed and Subscription.
User:
class User < ActiveRecord::Base
.
.
has_many :subscriptions, dependent: :destroy
has_many :feeds, through: :subscriptions
def subscribe!(feed_id)
subscriptions.create!(feed_id: feed.id)
end
end
Subscription:
class Subscription < ActiveRecord::Base
.
.
belongs_to :user, class_name: "User"
belongs_to :feed, class_name: "Feed"
validates :user_id, presence: true
validates :feed_id, presence: true
end
Feed:
class Feed < ActiveRecord::Base
.
.
belongs_to :subscriptions
def self.create_feed(feed_url)
feed = Feedzirra::Feed.fetch_and_parse(feed_url)
unless exists? :feed_url => feed.url
create!(
:title => feed.title,
:feed_url => feed.feed_url,
:url => feed.url,
:etag => feed.etag,
:last_modified=> feed.last_modified
)
end
end
end
Whenever I call user.subscriptions.create!(feed) explicitly from the console it works just fine, but the test using it don't pass. The test in question:
describe "subscribing" do
let(:feed) { Feed.create_feed("http://www.somevalidfeed.com/feed/") }
before { #user.subscribe!(feed) }
end
This returns:
NameError: undefined local variable or method `feed' for #<User:0x007f9c951b3b98>
I don't understand why this is returning an object or why it's trying to find the id of the User instead of the Feed object that's being passed to it. After searching for a few hours I just can't seem to get pointed in the right direction, so any help would be very much appreciated!
In your User class, I think your subscribe method is wrong, it should be
def subscribe!(feed)
subscriptions.create!(feed_id: feed.id)
end
probably here's the problem:
def subscribe!(feed_id)
subscriptions.create!(feed_id: feed.id)
end
probably you meant
def subscribe!(feed_id)
subscriptions.create!(feed_id: feed_id)
end
or even
def subscribe!(feed)
subscriptions.create!(feed_id: feed.id)
end
For example there are some models
class Model_1 < ActiveRecord::Base
has_many :images, :as => :imageable
end
class Model_2 < ActiveRecord::Base
# doesn't have has_many association
end
...
class Image < ActiveRecord::Base
belongs_to :imageable, :polymorphic => true
end
How can I check that model has has_many association? Something like this
class ActiveRecord::Base
def self.has_many_association_exists?(:association)
...
end
end
And it can be used so
Model_1.has_many_association_exists?(:images) # true
Model_2.has_many_association_exists?(:images) # false
Thanks in advance
What about reflect_on_association?
Model_1.reflect_on_association(:images)
Or reflect_on_all_associations:
associations = Model_1.reflect_on_all_associations(:has_many)
associations.any? { |a| a.name == :images }
I found the following to be the simple way to achieve the desired result:
ModelName.method_defined?(:method_name)
Example:
Model_1.method_defined?(:images) # true
Model_2.method_defined?(:images) # false
Reference: https://stackoverflow.com/a/18066069/936494
You could probably use respond_to?
class ActiveRecord::Base
def self.has_many_association_exists?(related)
self.class.associations.respond_to?(related)
end
end
You could just have a method that tries to access a Model_1 object images inside an exception block like (roughly) :
begin
model1_obj.images
rescue
puts 'No association between model_1 and images'
end
Inside rescue, you can just return false if you like.
What's wrong in my model, using rails 3.0.5 and ruby 1.9.2?
class Milestone < ActiveRecord::Base
has_many :capstone_milestones
has_many :capstones, :through => :capstone_milestones
belongs_to :department
attr_accessible :id, :name, :description, :department_id, :project
accepts_nested_attributes_for :capstone_milestones, :allow_destroy => true
def before_create # or after_initialize
self.project ||= 'Default'
end
def xpos
(Milestone.department.id - 100000)*100
end
end
When i do milestone.xpos in the view, i get "undefined method `department'" error message.
Thx!
You cant access department by class name because you will get it as an instance method.
You can access like
#milestone = Milestone.find(id)
#milestone.department_id
In your case just replace Milestone with self.
(self.department.id - 100000)*100