For some reason I'm getting an NoMethodError for some query, which actually works in the rails console.
Code in index.html.erb
#requests.first.acceptance
This is my Error
undefined method `acceptance' for #<ActiveRecord::Relation::ActiveRecord_Relation_Arrangement:0x000000042ceeb8>
These are my Modules.
class Arrangement < ActiveRecord::Base
belongs_to :acceptance
belongs_to :request
belongs_to :offer, inverse_of: :arrangements
end
class Acceptance < ActiveRecord::Base
belongs_to :user, inverse_of: :acceptances
has_one :arrangement
end
class Request < ActiveRecord::Base
belongs_to :user, inverse_of: :requests
has_one :arrangement
end
This is my Controller
def index
#requests = Array.new
for request in current_user.requests do
#requests << Arrangement.where("request_id = ?", request.id)
end
#acceptances = Array.new
for acceptance in current_user.acceptances do
#acceptances << Arrangement.where("acceptance_id = ?", acceptance.id)
end
end
I can't figure out what I've done wrong here. Everything works in the console, though not in the browser.
Arrangement.where("request_id = ?", request.id)
Returns an array-like relation object which may contain multiple records, not just a single record.
However, on this line in your controller
#requests << Arrangement.where("request_id = ?", request.id)
You're adding the relation to your array, so that
#requests.first.acceptance
Returns the relation, instead of the first record.
One way to fix this is to do this in your controller:
#requests = Array.new
for request in current_user.requests do
#requests << Arrangement.where("request_id = ?", request.id).first
end
Solved
I was passing an array in the #requests array in my controller.
Related
I'm trying to make a category system for my blog but I've hit this error. Each blog_category can have multiple sub_categories by having parent_id pointing to the id of the main category. Some sub_categories and main blog_categories don't have anything in them. How would I prevent this NoMethodError from hitting?
BlogCategoriesController:
class BlogCategoriesController < ApplicationController
def index
#category = BlogCategory.find_by_id(params[:id])
#sub_category = #category.sub_categories.first
#posts = #subcategory.posts
end
private
def cat_params
params.require(:blog_category).permit(:name, :parent_id, :sub_category)
end
end
BlogCategory Model:
class BlogCategory < ApplicationRecord
has_many :posts
# This is called a self referential relation. This is where records in a table may point to other records in the same table.
has_many :sub_categories, class_name: "BlogCategory", foreign_key: :parent_id
# This is a scope to load the top level categories and eager-load their posts, subcategories, and the subcategories' posts too.
scope :top_level, -> { where(parent_id: nil).includes :posts, sub_categories: :posts }
end
The Posts point to the blog category via t.integer "blog_category_id" in the post table and has a belongs_to :blog_category in the Post model.
You can add a validation
def index
#category = BlogCategory.find_by_id(params[:id])
unless #category.nil?
#sub_category = #category.sub_categories.first
#posts = #subcategory.posts
end
end
When searching for students and showing results, I want to be able to update a field in the student_metric table to show how many times a student has shown up in search results.
However, every time i run the show routine i get a undefined method `id' for nil:NilClass. Why wont it find the link between the search_student and student_metric table?
Error
NoMethodError at /users/1/searches/4
undefined method `searchinclusions' for nil:NilClass
searches controller
def show
#search = current_user.searches.find_by(id: params[:id])
#search.search_students.each do |c|
c.student_metric.searchinclusions += 1
end
end
search model
class Search < ActiveRecord::Base
belongs_to :user
def search_students
students = StudentProfile.all
students = students.where(["name LIKE ?", "%#{name}%"]) if name.present?
return students
end
end
student_profile model
class StudentProfile < ActiveRecord::Base
has_one :student_metric, dependent: :destroy
belongs_to :user
end
student_metric model
class StudentMetric < ActiveRecord::Base
belongs_to :user
belongs_to :student_profile
end
I can't seem to get the logic for my conditions to work for this query. I can get #showemail = PreferenceSetting.find(1) to work...but when I try to add conditions, it keeps throwing a "NoMethodError". I am fairly new at rails and am really stuck on this.
def show
#showemail = PreferenceSetting.where('user_id = ?', params[:u]).where('user_preference_id = ?', 1)
end
This is my code for the view
<%= #showemail.prefers %>
Every time I try to access the 'show' view it says "undefined method `prefers'.
My Models
class PreferenceSetting < ActiveRecord::Base
belongs_to :users_preference, inverse_of: :preference_settings
belongs_to :user, inverse_of: :preference_settings
end
class UserPreference < ActiveRecord::Base
has_many :preference_settings, inverse_of: :user_preference
end
find returns an instance whereas your where methods return an ActiveRecord::Relation object.
Adjust the code of your show action like this:
def show
conditions = {user_id: params[:u], user_preference_id: 1}
#showemail = PreferenceSetting.where(conditions).last
# you could also use: PreferenceSetting.find_by(conditions)
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.
Using Rails 3.2. Let's say I want 2 options:
Get all trip photos.
Get the first trip photo.
I have the following code:
# trip.rb
class Trip < ActiveRecord::Base
has_many :trip_days
def trip_photos
if (photos = trip_days.map(&:spots).flatten.map(&:photos).flatten.map)
photos.each do |photo|
photo.url(:picture_preview)
end
end
end
def trip_photo
trip_photos.first
end
end
# trip_day.rb
class TripDay < ActiveRecord::Base
belongs_to :trip
has_many :trip_day_spots
has_many :spots, :through => :trip_day_spots
end
# trip_day_spot.rb
class TripDaySpot < ActiveRecord::Base
belongs_to :trip_day
belongs_to :spot
end
#spot.rb
class Spot < ActiveRecord::Base
end
# trips_controller.rb
class TripsController < ApplicationController
def index
#trips = Trip.public.paginate(:page => params[:page], :per_page => 25)
end
end
As expected, the trip_photos method generates lots of SQL query. I wonder if there is any better way to do it?
It is because of N+1 queries. In this cases, we need to eager load all the associations of base object, so that when ever you call its associated object, it wont fire any queries for fetching them, simply it will get them from its cached object.
Hope this will work, but not tested. I assumed and wrote the following query.
def trip_photos
user_trip_days = trip_days.includes(:spots => :photos)
photos = user_trip_days.collect {|trip_day| trip_day.spots.map(&:photos).flatten}.flatten
photos.each do |photo|
photo.url(:picture_preview)
end if photos
end
Let me know if you get any errors.
For more info on eager loading associated objects in ActiveRecord, go through
Guides for Rails and Rails cast and Rails Tips
This might not be the most rails-y way, but if you truly wanted to get all the spots in one hit you could do something like:
def spots
Spot.joins("join trip_days_spots on spots.id = trip_days_spots.spot_id join trip_days on trip_days.id = trip_days_spots.trip_day_id join trips on trips.id = trip_days.trip_id").where("trips.id = ?", self.id)
end
then change your loop to:
def trip_photos
spots.map(&:photos).flatten.each do |photo|
photo.url(:picture_preview)
end
end
The code works fine, but to eager load, just add :include:
# trips_controller.rb
class TripsController < ApplicationController
def index
#trips = Trip.public.paginate(:include => [:trip_days => [:spots => :photos]], :page => params[:page], :per_page => 25)
end
end