rails polymorphic association doesn't working - ruby-on-rails

I have model with polymorphic association.
class Tag < ActiveRecord::Base
#attributes target_id, target_type
belongs_to :target, polymorphic: true
end
And Target model, which is user.
class User < ActiveRecord::Base
has_many :tags, as: :target
end
But method #user = User.find params[:id]; #user.tags returns #<ActiveRecord::Associations::CollectionProxy []> while Tag.where(target_id: #user.id, target_type: 'User') returns some objects I except.
What's wrong?

It's a wrong way because it gives error like NameError: undefined local variable or method user for main:Object, first you have to find user and it's tags like:
#user=User.find(1)
#user.tags

From an instance of the User model, you can retrieve a collection of tags like this:
#user = User.find(params[:id])
#user.tags
Similarly, if you have an instance of the Tag model, you can get to its parent:
#tag.target

Problem was solved!
I just add to model foreign_key.
class User < ActiveRecord::Base
has_many :tags, as: :target,
foreign_key: target_id
end
Thanks everyone, who try to help.

Related

How to check whether an object is a result of has_one or has_many?

I build a query dynamically, based on either a has_one or has_many relation. So, I can end up with either an object, or CollectionProxy. How can I test, based on this result, whether the query used the has_one or the has_many relation?
I thought of checking the type, but the CollectionProxy's type subclasses the related model's type.
This dynamic query involves calling an attribute on an object, which can be either a has_one or a has_many relation. Something like:
class User < ActiveRecord::Base
has_one :profile
has_many :names
user = User.new
attr = 'profile' # or 'names'
user.send(attr) # I want to check whether this is a result of which of the two relations
You can use Active Record's reflection:
User.reflect_on_association(:profile)
#=> #<ActiveRecord::Reflection::HasOneReflection:0x007fd2b76705c0 ...>
User.reflect_on_association(:names)
#=> #<ActiveRecord::Reflection::HasManyReflection:0x007fd2b767de78 ...>
Within a case statement:
klass = User
attr = :profile
case klass.reflect_on_association(attr)
when ActiveRecord::Reflection::HasOneReflection
# ...
when ActiveRecord::Reflection::HasManyReflection
# ...
end
### OR by macro
case klass.reflect_on_association(attr).macro
when :belongs_to
# ...
when :has_many
# ...
when :has_one
# ...
end
This works based on the association declaration in your model (user.rb), i.e. without accessing the database.
You can actually check the type of the result. You just have to check if it's an ActiveRecord::Base or an ActiveRecord::Associations::CollectionProxy.
Following your example:
class User < ActiveRecord::Base
has_one :profile
has_many :names
user = User.new
attr = 'profile'
user.send(attr).is_a? ActiveRecord::Base # true
user.send(attr).is_a? ActiveRecord::Associations::CollectionProxy # false
attr = 'names'
user.send(attr).is_a? ActiveRecord::Base # false
user.send(attr).is_a? ActiveRecord::Associations::CollectionProxy # true
This was tested on a Rails 4.1.4 but the classes are the same since Rails 3, apparently.
Consider using try like this:
post.try(:owner)
this way a has_one relation will return the owner and has_many won't.
It some situations it may be inconclusive, but should suffice for most of them.
owner is just an example:
class Post
has_one :owner
class Owner
belongs_to :post
post = Post.create ...
post.try(:owner)
returns owner if class Post has_one :owner, and nil if class Post has_many :owners
For your example: user.try(:profile)

Rails association works when it doesnt use an instance variable

I'm getting the strangest error I've seen in Rails so far. In my view, I can print out the email associated with a painting if I find the record directly (e.g. Painting.find(15). But if I try to use an instance variable it errors (e.g #painting).
views/paintings/show.html.erb
<%= Painting.find(15).artist.user.email %> # works
<%= #painting.artist.user.email %> # Error: "undefined method 'user' for nil:NilClass"
controllers/paintings_controller.rb
def show
#painting = Painting.find(15)
end
Models: "users", "artists", "paintings".
A user can be an artist. So a user has_one artist.
An artist has_many paintings.
I think you should add associations. This how they should look like:
class User < ActiveRecord::Base
has_one :artist # it's ok
end
class Artist < ActiveRecord::Base
belongs_to :user # add this
has_many :paintings
end
class Painting < ActiveRecord::Base
belongs_to :artist
end
For me both cases works with this associations.
Use
def show
#painting = Painting.find(15).last
end
Currently the second one is returning a 1 element array, but in order to call a dependent method, you must specify 1 item.

Add record to a model upon create used in many models

I have a survey and I would like to add participants to a Participant model whenever a user answers to a question for the first time. The survey is a bit special because it has many functions to answer questions such as Tag words, Multiple choices and Open Question and each function is actually a model that has its own records. Also I only want the Participant to be saved once.
The Participant model is fairly simple:
class Participant < ActiveRecord::Base
belongs_to :survey
attr_accessible :survey_id, :user_id
end
The Survey model is also straightforward:
class Survey < ActiveRecord::Base
...
has_many :participants, :through => :users
has_many :rating_questions, :dependent => :destroy
has_many :open_questions, :dependent => :destroy
has_many :tag_questions, :dependent => :destroy
belongs_to :account
belongs_to :user
accepts_nested_attributes_for :open_questions
accepts_nested_attributes_for :rating_questions
accepts_nested_attributes_for :tag_questions
...
end
Then you have models such as rating_answers that belong to a rating_question, open_answers that belong to open_questions and so on.
So initially I thought for within my model rating_answers I could add after_create callback to add_participant
like this:
class RatingAnswer < ActiveRecord::Base
belongs_to :rating_question
after_create :add_participant
...
protected
def add_participant
#participant = Participant.where(:user_id => current_user.id, :survey_id => Survey.find(params[:survey_id]))
if #participant.nil?
Participant.create!(:user_id => current_user.id, :survey_id => Survey.find(params[:survey_id]))
end
end
end
In this case, I didn't know how to find the survey_id, so I tried using the params but I don't think that is the right way to do it. regardles it returned this error
NameError (undefined local variable or method `current_user' for #<RatingAnswer:0x0000010325ef00>):
app/models/rating_answer.rb:25:in `add_participant'
app/controllers/rating_answers_controller.rb:12:in `create'
Another idea I had was to create instead a module Participants.rb that I could use in each controllers
module Participants
def add_participant
#participant = Participant.where(:user_id => current_user.id, :survey_id => Survey.find(params[:survey_id]))
if #participant.nil?
Participant.create!(:user_id => current_user.id, :survey_id => Survey.find(params[:survey_id]))
end
end
end
and in the controller
class RatingAnswersController < ApplicationController
include Participants
def create
#rating_question = RatingQuestion.find_by_id(params[:rating_question_id])
#rating_answer = RatingAnswer.new(params[:rating_answer])
#survey = Survey.find(params[:survey_id])
if #rating_answer.save
add_participant
respond_to do |format|
format.js
end
end
end
end
And I got a routing error
ActionController::RoutingError (uninitialized constant RatingAnswersController::Participants):
I can understand this error, because I don't have a controller for participants with a create method and its routes resources
I am not sure what is the proper way to add a record to a model from a nested model and what is the cleaner approach.
Ideas are most welcome!
current_user is a helper that's accessible in views/controller alone. You need to pass it as a parameter into the model. Else, it ain't accessible in the models. May be, this should help.
In the end I ended up using the after_create callback but instead of fetching the data from the params, I used the associations. Also if #participant.nil? didn't work for some reason.
class RatingAnswer < ActiveRecord::Base
belongs_to :rating_question
after_create :add_participant
...
protected
def add_participant
#participant = Participant.where(:user_id => self.user.id, :survey_id => self.rating_question.survey.id)
unless #participant.any?
#new_participant = Participant.create(:user_id => self.user.id, :survey_id => self.survey.rating_question.id)
end
end
end
The cool thing with associations is if you have deeply nested associations for instead
Survey has_many questions
Question has_many answers
Answer has_many responses
in order to fetch the survey id from within the responses model you can do
self.answer.question.survey.id
very nifty!

Retrieving model attribute from table+column name

Let's say you have the following models:
class User < ActiveRecord::Base
has_many :comments, :as => :author
end
class Comment < ActiveRecord::Base
belongs_to :user
end
Let's say User has an attribute name, is there any way in Ruby/Rails to access it using the table name and column, similar to what you enter in a select or where query?
Something like:
Comment.includes(:author).first.send("users.name")
# or
Comment.first.send("comments.id")
Edit: What I'm trying to achieve is accessing a model object's attribute using a string. For simple cases I can just use object.send attribute_name but this does not work when accessing "nested" attributes such as Comment.author.name.
Basically I want to retrieve model attributes using the sql-like syntax used by ActiveRecord in the where() and select() methods, so for example:
c = Comment.first
c.select("users.name") # should return the same as c.author.name
Edit 2: Even more precisely, I want to solve the following problem:
obj = ANY_MODEL_OBJECT_HERE
# Extract the given columns from the object
columns = ["comments.id", "users.name"]
I don't really understand what you are trying to achieve. I see that you are using polymorphic associations, do you need to access comment.user.name while having has_many :comments, :as => :author in your User model?
For you polymorphic association, you should have
class Comment < ActiveRecord::Base
belongs_to :author, :polymorphic => true
end
And if you want to access comment.user.name, you can also have
class Comment < ActiveRecord::Base
belongs_to :author, :polymorphic => true
belongs_to :user
end
class User < ActiveRecord::Base
has_many :comments, :as => :author
has_many :comments
end
Please be more specific about your goal.
I think you're looking for a way to access the user from a comment.
Let #comment be the first comment:
#comment = Comment.first
To access the author, you just have to type #comment.user and If you need the name of that user you would do #comment.user.name. It's just OOP.
If you need the id of that comment, you would do #comment.id
Because user and id are just methods, you can call them like that:
comments.send('user').send('id')
Or, you can build your query anyway you like:
Comment.includes(:users).where("#{User::columns[1]} = ?", #some_name)
But it seems like you're not doing thinks really Rails Way. I guess you have your reasons.

Active Relation: Retrieving records through an association?

I have the following models:
class User < ActiveRecord::Base
has_many :survey_takings
end
class SurveyTaking < ActiveRecord::Base
belongs_to :survey
def self.surveys_taken # must return surveys, not survey_takings
where(:state => 'completed').map(&:survey)
end
def self.last_survey_taken
surveys_taken.maximum(:position) # that's Survey#position
end
end
The goal is to be able to call #user.survey_takings.last_survey_taken from a controller. (That's contrived, but go with it; the general goal is to be able to call class methods on #user.survey_takings that can use relations on the associated surveys.)
In its current form, this code won't work; surveys_taken collapses the ActiveRelation into an array when I call .map(&:survey). Is there some way to instead return a relation for all the joined surveys? I can't just do this:
def self.surveys_taken
Survey.join(:survey_takings).where("survey_takings.state = 'completed'")
end
because #user.survey_takings.surveys_taken would join all the completed survey_takings, not just the completed survey_takings for #user.
I guess what I want is the equivalent of
class User < ActiveRecord::Base
has_many :survey_takings
has_many :surveys_taken, :through => :survey_takings, :source => :surveys
end
but I can't access that surveys_taken association from SurveyTaking.last_survey_taken.
If I'm understanding correctly you want to find completed surveys by a certain user? If so you can do:
Survey.join(:survey_takings).where("survey_takings.state = 'completed'", :user => #user)
Also it looks like instead of:
def self.surveys_taken
where(:state => 'completed').map(&:survey)
end
You may want to use scopes:
scope :surveys_taken, where(:state => 'completed')
I think what I'm looking for is this:
class SurveyTaking < ActiveRecord::Base
def self.surveys_taken
Survey.joins(:survey_takings).where("survey_takings.state = 'completed'").merge(self.scoped)
end
end
This way, SurveyTaking.surveys_taken returns surveys taken by anyone, but #user.survey_takings.surveys_taken returns surveys taken by #user. The key is merge(self.scoped).
Waiting for further comments before I accept..

Resources