Rails: Method returns object instead of attribute - ruby-on-rails

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

Related

Prevent from raising ActiveRecord::RecordInvalid or adding twice on has_many association

I want to change has_many association behaviour
considering this basic data model
class Skill < ActiveRecord::Base
has_many :users, through: :skills_users
has_many :skills_users
end
class User < ActiveRecord::Base
has_many :skills, through: :skills_users, validate: true
has_many :skills_users
end
class SkillsUser < ActiveRecord::Base
belongs_to :user
belongs_to :skill
validates :user, :skill, presence: true
end
For adding a new skill we can easily do that :
john = User.create(name: 'John Doe')
tidy = Skill.create(name: 'Tidy')
john.skills << tidy
but if you do this twice we obtain a duplicate skill for this user
An possibility to prevent that is to check before adding
john.skills << tidy unless john.skills.include?(tidy)
But this is quite mean...
We can as well change ActiveRecord::Associations::CollectionProxy#<< behaviour like
module InvalidModelIgnoredSilently
def <<(*records)
super(records.to_a.keep_if { |r| !!include?(r) })
end
end
ActiveRecord::Associations::CollectionProxy.send :prepend, InvalidModelIgnoredSilently
to force CollectionProxy to ignore transparently adding duplicate records.
But I'm not happy with that.
We can add a validation on extra validation on SkillsUser
class SkillsUser < ActiveRecord::Base
belongs_to :user
belongs_to :skill
validates :user, :skill, presence: true
validates :user, uniqueness: { scope: :skill }
end
but in this case adding twice will raise up ActiveRecord::RecordInvalid and again we have to check before adding
or make a uglier hack on CollectionProxy
module InvalidModelIgnoredSilently
def <<(*records)
super(valid_records(records))
end
private
def valid_records(records)
records.with_object([]).each do |record, _valid_records|
begin
proxy_association.dup.concat(record)
_valid_records << record
rescue ActiveRecord::RecordInvalid
end
end
end
end
ActiveRecord::Associations::CollectionProxy.send :prepend, InvalidModelIgnoredSilently
But I'm still not happy with that.
To me the ideal and maybe missing methods on CollectionProxy are :
john.skills.push(tidy)
=> false
and
john.skills.push!(tidy)
=> ActiveRecord::RecordInvalid
Any idea how I can do that nicely?
-- EDIT --
A way I found to avoid throwing Exception is throwing an Exception!
class User < ActiveRecord::Base
has_many :skills, through: :skills_users, before_add: :check_presence
has_many :skills_users
private
def check_presence(skill)
raise ActiveRecord::Rollback if skills.include?(skill)
end
end
Isn't based on validations, neither a generic solution, but can help...
Perhaps i'm not understanding the problem but here is what I'd do:
Add a constraint on the DB level to make sure the data is clean, no matter how things are implemented
Make sure that skill is not added multiple times (on the client)
Can you show me the migration that created your SkillsUser table.
the better if you show me the indexes of SkillsUser table that you have.
i usually use has_and_belongs_to_many instead of has_many - through.
try to add this migration
$ rails g migration add_id_to_skills_users id:primary_key
# change the has_many - through TO has_and_belongs_to_many
no need for validations if you have double index "skills_users".
hope it helps you.

rails ams, Nested models undefined method `' for nil:NilClass

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

Saving Rails associations after creating User

I'm new to Rails and ActiveRecord and need some help. Basically, I have 4 models: User, Property, PropertyAccount, and AccountInvitation. Users and Properties have a many to many relationship via PropertyAccounts. AccountInvitations have a user's email and a property_id.
What I want to happen is that after a user registers on my app, his user account is automatically associated with some pre-created Properties. What I don't know how to do is write the query to get the Property objects from the AccountInvitations and save them to the User object. Please see def assign_properties for my pseudo code. Any help is welcome, thanks so much!
class User < ActiveRecord::Base
has_many :property_accounts
has_many :properties, through: :property_accounts
after_create :assign_properties
# Check to see if user has any pre-assigned properties, and if so assign them
def assign_properties
account_invitations = AccountInvitations.where(email: self.email)
if account_invitations.any?
account_invitations.each do |i|
properties += Property.find(i.property_id)
end
self.properties = properties
self.save
end
end
end
class AccountInvitation < ActiveRecord::Base
belongs_to :property
validates :property_id, presence: true
validates :email, uniqueness: {scope: :property_id}
end
class Property < ActiveRecord::Base
has_many :account_invitations
has_many :property_accounts
has_many :users, through: :property_accounts
end
class PropertyAccount < ActiveRecord::Base
belongs_to :property
belongs_to :user
end
Thanks to #wangthony , I looked at the includes method on http://apidock.com/rails/ActiveRecord/QueryMethods/includes and tweaked one of their examples in order to get this to work. Here's the solution:
def assign_property
self.properties = Property.includes(:account_invitations).where('account_invitations.email = ?', self.email).references(:account_invitations)
self.save
end
I believe you can do this:
user.properties = Property.includes(:account_invitations).where(email: user.email)
user.save

Rails: includes with polymorphic association

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.

How to model has_many with polymorphism?

I've run into a situation that I am not quite sure how to model.
EDIT: The code below now represent a working solution. I am still interested in nicer looking solutions, though.
Suppose I have a User class, and a user has many services. However, these services are quite different, for example a MailService and a BackupService, so single table inheritance won't do. Instead, I am thinking of using polymorphic associations together with an abstract base class:
class User < ActiveRecord::Base
has_many :services
end
class Service < ActiveRecord::Base
validates_presence_of :user_id, :implementation_id, :implementation_type
validates_uniqueness_of :user_id, :scope => :implementation_type
belongs_to :user
belongs_to :implementation, :polymorphic => true, :dependent => :destroy
delegate :common_service_method, :name, :to => :implementation
end
#Base class for service implementations
class ServiceImplementation < ActiveRecord::Base
validates_presence_of :user_id, :on => :create
#Virtual attribute, allows us to create service implementations in one step
attr_accessor :user_id
has_one :service, :as => :implementation
after_create :create_service_record
#Tell Rails this class does not use a table.
def self.abstract_class?
true
end
#Name of the service.
def name
self.class.name
end
#Returns the user this service
#implementation belongs to.
def user
unless service.nil?
service.user
else #Service not yet created
#my_user ||= User.find(user_id) rescue nil
end
end
#Sets the user this
#implementation belongs to.
def user=(usr)
#my_user = usr
user_id = usr.id
end
protected
#Sets up a service object after object creation.
def create_service_record
service = Service.new(:user_id => user_id)
service.implementation = self
service.save!
end
end
class MailService < ServiceImplementation
#validations, etc...
def common_service_method
puts "MailService implementation of common service method"
end
end
#Example usage
MailService.create(..., :user => user)
BackupService.create(...., :user => user)
user.services.each do |s|
puts "#{user.name} is using #{s.name}"
end #Daniel is using MailService, Daniel is using BackupService
Notice that I want the Service instance to be implictly created when I create a new service.
So, is this the best solution? Or even a good one? How have you solved this kind of problem?
I don't think your current solution will work. If ServiceImplementation is abstract, what will the associated classes point to? How does the other end of the has_one work, if ServiceImplementation doesn't have a pk persisted to the database? Maybe I'm missing something.
EDIT: Whoops, my original didn't work either. But the idea is still there. Instead of a module, go ahead and use Service with STI instead of polymorphism, and extend it with individual implementations. I think you're stuck with STI and a bunch of unused columns across different implementations, or rethinking the services relationship in general. The delegation solution you have might work as a separate ActiveRecord, but I don't see how it works as abstract if it has to have a has_one relationship.
EDIT: So instead of your original abstract solution, why not persist the delgates? You'd have to have separate tables for MailServiceDelegate and BackupServiceDelegate -- not sure how to get around that if you want to avoid all the null columns with pure STI. You can use a module with the delgate classes to capture the common relationships and validations, etc. Sorry it took me a couple of passes to catch up with your problem:
class User < ActiveRecord::Base
has_many :services
end
class Service < ActiveRecord::Base
validates_presence_of :user_id
belongs_to :user
belongs_to :service_delegate, :polymorphic => true
delegate :common_service_method, :name, :to => :service_delegate
end
class MailServiceDelegate < ActiveRecord::Base
include ServiceDelegate
def name
# implement
end
def common_service_method
# implement
end
end
class BackupServiceDelegate < ActiveRecord::Base
include ServiceDelegate
def name
# implement
end
def common_service_method
# implement
end
end
module ServiceDelegate
def self.included(base)
base.has_one :service, :as => service_delegate
end
def name
raise "Not Implemented"
end
def common_service_method
raise "Not Implemented"
end
end
I think following will work
in user.rb
has_many :mail_service, :class_name => 'Service'
has_many :backup_service, :class_name => 'Service'
in service.rb
belongs_to :mail_user, :class_name => 'User', :foreign_key => 'user_id', :conditions=> is_mail=true
belongs_to :backup_user, :class_name => 'User', :foreign_key => 'user_id', :conditions=> is_mail=false

Resources