I´m trying to make a User - Infos association where my User has_many Infos.
I´m trying to call my User Infos on the index form from Users.
By calling <%= user.infos %> on my index.html.erb (Users) it returns me all datas from the Infos table like this:
[#<Info id: 2, name: "Thales Miguel", date: "1989-07-14", area: "An\xC3\xA1lise de Sistemas", comment: "Analista j\xC3\xBAnior de sistemas.", user_id: 1, created_at: "2012-01-16 15:54:29", updated_at: "2012-01-16 15:54:29">]
I assumed that by doing <%= user.infos.comment %> it would return me the "comment" from that user, but all I get is this error:
undefined method `comment' for #<ActiveRecord::Relation:0x3b8ebe8>
What am I doing wrong?
infos_controller:
class InfosController < ApplicationController
def create
#user = User.find(params[:user_id])
#info = #user.infos.create(params[:info])
redirect_to user_path(#user)
end
def destroy
#user = User.find(params[:user_id])
#info = #user.infos.find(params[:id])
#info.destroy
redirect_to user_path(#user)
end
def new
#user = User.new
#user.build_info
end
end
info model:
class Info < ActiveRecord::Base
belongs_to :user
end
user model:
class User < ActiveRecord::Base
validates :login, :presence => true
validates :password, :presence => true,
:length => {:minimum => 5}
has_many :infos, :dependent => :destroy
end
This:
[#<Info id: 2, name: "Thales Miguel", date: "1989-07-14", area: "An\xC3\xA1lise de Sistemas", comment: "Analista j\xC3\xBAnior de sistemas.", user_id: 1, created_at: "2012-01-16 15:54:29", updated_at: "2012-01-16 15:54:29">]
is an array. You have to choose one of the objects. You can't call an attribute value without selecting one of theses objects. Okay, in this case there is only one object but that doesn't matter.
So, collecting all info what written above:
You cannot call user.infos.comment, because the return value of user.infos is an ActiveRecord::Relation in Rails 3.2 and an array in Rails 3.1 and below. Neither ActiveRecord::Relation nor array do not know what it should do with comment method because they don't know anything of the contained objects.
You have multiple solutions for solve this problem.
If user is limited to have only one info:
class User < ActiveRecord::Base
has_one :info, :dependent => :destroy
end
<%= user.info.comment %>
If user can have multiple info objects:
<%- user.infos.each do |info| %>
<%= info.comment %>
<% end -%>
Or
<ul><li>
<%= user.infos.collect(&:comment).join('</li><li>').html_safe %>
</li></ul>
Or if you want to display only the first info:
<%= user.infos.any? ? user.infos.first.comment : "N/A" %>
Be careful the latest solution: user.infos never be nil, but user.infos.first can be nil, so you must check available infos with user.infos.any?
You should do this way:
<% user.infos.each do |info| %>
<%= info.comment %>
<% end %>
As davidb said, <% user.infos %> returns an array. The object of .comment should be an instance.
The problem is
info is a relationship link
that returns an array of info records, if you want to show the first one for example
user.infos[0].comment
or
user.infos.first.comment
if that user don't have infos then return nil
Related
I am having an issue understanding how to use Rails' delegated types when it comes to validations failing on the delegatee.
Having the following code
inbox.rb
class Inbox < ApplicationRecord
delegate :name, to: :inboxable
delegated_type :inboxable, types: %w[ Mailbox Messagebox ], dependent: :destroy
end
class Mailbox < ApplicationRecord
include Inboxable
belongs_to :inbox_domain
validates :alias, presence: true, uniqueness: true
def name
"#{self.alias}##{self.domain.name}"
end
end
messagees_controller.rb
def create
#mailbox = Inbox.create inboxable: Mailbox.new(mailbox_params)
if #mailbox.save
redirect_to #mailbox.inboxable, notice: "<b>#{#mailbox.name}</b> was created."
else
render :new
end
end
private
def mailbox_params
params.require(:mailbox).permit(:alias, :inbox_domain_id)
end
When i want to create a mailbox where the alias is already taken, the following error is thrown because Mailbox.new fails the validation
ActiveRecord::NotNullViolation (PG::NotNullViolation: ERROR: null value in column "inboxable_id" violates not-null constraint
DETAIL: Failing row contains (13, 2021-09-26 20:48:53.970799, 2021-09-26 20:48:53.970799, Mailbox, null, f).
):
Tried solution
What is the correct way to handle this scenario? I have tried to check explicitly Mailbox.new first, like this:
mailbox = Mailbox.new(mailbox_params)
if mailbox.valid?
#inbox = Inbox.create inboxable: mailbox
......
While it technically works, it's a mess once you also have to validate attributes on Inbox itself
Use validates_associated to trigger the validations on the associated record:
class Inbox < ApplicationRecord
delegate :name, to: :inboxable
delegated_type :inboxable, types: %w[ Mailbox Messagebox ], dependent: :destroy
validates_associated :inboxable
end
This will add an error ("Inboxable is invalid") to the errors object on this model and prevent saving if the associated mailbox is not valid.
What you want in your controller is:
def create
# .create both instanciates and saves the record - not what you want here
#mailbox = Inbox.new(inboxable: Mailbox.new(mailbox_params))
if #mailbox.save
redirect_to #mailbox.inboxable, notice: "<b>#{#mailbox.name}</b> was created."
else
render :new
end
end
If you want to display the errors for the associated item you need to access and loop through the errors object on it:
# app/views/shared/_errors.html.erb
<ul>
<% object.errors.each do |attribute, message| %>
<li><%= message %>
<% end %>
</ul>
<%= form_with(model: #inbox) do |form| %>
<% if form.object.invalid? %>
<%= render(partial: 'shared/errors', object: form.object) %>
<% if form.object.inboxable.invalid? %>
<%= render(partial: 'shared/errors', object: form.object.inboxable) %>
<% end %>
<% end %>
# ...
<% end %>
I have a bit of a problem with a "has one" association on my app.
What I want to achieve is to be able to attach an optional quote to the topic. The quote can only be used once (in other words, if it's used for topic 1, it can't be used for any other topics).
I have a Topic model and a Quote model.
Topic has one quote.
Quote belongs to topic.
I also want to be able to attach a quote to other models (ex. Profile Model).
I'm really confused on what to do on my "edit topic" view as well as in the controller. I thought it would work like a "one to many" association, which I had no problem configuring. Somehow the "has one" is more complicated (for me!)
What I'd like is to have in the "edit topic" view a radio list of the available quotes which I can freely update. (Same for the "new topic" view).
My current controller:
def edit
#topic = Topic.find(params[:id])
#quote = #topic.quote
#packages = #topic.packages
#books = #topic.books
#tasklists = #topic.tasklists
#links = #topic.links
#terms = #topic.terms
end
def update
#topic = Topic.find(params[:id])
if #topic.update_attributes(topic_params)
flash[:success] = t('helpers.success-update', model: "topic")
redirect_to backend_topics_url
else
render partial: 'edit'
end
end
def topic_params
params.require(:topic).permit(:topic_id, :theme_id, :cover, :topic_status, :topic_access, :slug, *Topic.globalize_attribute_names, :quote_attributes => [:id, :topic_id], :package_ids => [], :book_ids => [], :link_ids => [], :tasklist_ids => [], :term_ids => [])
end
My current Topic model:
has_one :quote
accepts_nested_attributes_for :quote
My current Quote model:
belongs_to :topic
And my "Edit Topic" view:
<h4>Quote</h4>
<% if #quote %>
<h5>Current quote</h5>
<%= #quote.quote %> <%= link_to('[change]', '#') %>
<% end %>
<%= f.input :quote, :collection => Quote.all, :label_method => :quote, :label_value => :id, :checked => #quote.id, as: :radio_buttons %>
I'm sure there is something obvious that I'm missing but I can't figure out what.
Any ideas?
Thanks!
- Vincent
First off, if you want to have quote belong to multiple models, you will need a polymorphic association. Otherwise, you would need to add multiple foreign ids to quote like this: topic_id, profile_id etc and that will get messy fast. You can view a screencast on polymorphism here: http://railscasts.com/episodes/154-polymorphic-association-revised
has_one and belongs_to is basically the exact same as has_many and belongs_to except you are only dealing with 1 record instead of a collection of records.
For your current setup - in your edit action you need to fetch all the quotes that are not associated to any Topics. You can do that like this:
#available_quotes = Quote.where(topic_id: nil)
and then:
<%= f.input :quote, :collection => #available_quotes, :label_method => :quote, :label_value => :id, :checked => #quote.id, as: :radio_buttons %>
instead of
Quote.all in your form which is returning all quotes.
If you move to a polymorphic model, watch the screencast, and you would replace "commentable_id" in the screencast with something like "quotable_id" and then in your edit action to find the unassigned quotes you would do this:
#quotes = Quote.where(quotable_id: nil)
I have a form that lets users add a new blocked tv show to their list of blocked shows. The form is not taking the param values (:user_id, :title, :image) that I tried to set in the controller. I'm a beginner, so I'm guessing the syntax is the problem.
Also I am getting a Couldn't find Tvshow without Id error when trying to use the #tvshow instance variable to set the param values of :title and :image. Each Blocked show should have the same title and image as the tvshow that the user selects in the collection_select. Is there an easier way to do this?
View
<%= form_for #blockedshow do |b| %>
<%= b.label :tvshow_id, "Add a Blocked TV Show " %><br/>
<%= collection_select(:blockedshow, :tvshow_id, Tvshow.all, :id, :title, prompt: true) %>
<%= submit_tag 'Add' %>
<% end %>
Controller
class BlockedshowsController < ApplicationController
def new
#blockedshow = Blockedshow.new
end
def create
#tvshow = Tvshow.find params[:blockedshow][:id]
#blockedshow = Blockedshow.new(safe_blockedshow)
params[:user_id] = current_user.id
params[:title] = #tvshow.title
params[:image] = #tvshow.image
if #blockedshow.save
flash[:notice] = "New Blocked TV Show added successfully"
redirect_to tv_show_index_path
else
render 'new'
end
end
private
def safe_blockedshow
params.require(:blockedshow).permit(:title, :user_id, :tvshow_id, :image)
end
end
Blockedshow model
class Blockedshow < ActiveRecord::Base
has_many :phrases
has_many :tvshows
belongs_to :user
end
Tvshow model
class Tvshow < ActiveRecord::Base
has_many :phrases
belongs_to :blockedshow
def self.search_for (query)
where('title LIKE :query', query: "%#{query}%")
end
end
Routes
resources :blockedshows
post 'blockedshows', to:'blockedshows#create#[:id]'
you are getting the issue because params[:blockedshow][:id] is not passed, if your trying to access the Tvshow id selected by from the drop-list you can do the following
#tvshow = Tvshow.find params[:blockedshow][:tvshow_id]
Just fixed by changing the controller to this:
def create
#tvshow = Tvshow.find params[:blockedshow][:tvshow_id]
#blockedshow = Blockedshow.new(
:user_id =>current_user.id,
:title=> #tvshow.title,
:image=> #tvshow.image,
:tvshow_id=>#tvshow.id
)
In my application I have a very simple association. A User has_many Emails, this association works as expected with create/update/delete.
How ever when I attempt to display the information this is where things become some what difficult.
In my controller I have the following bit of code:
def prospective_user
#users = Account::User.all_by_user_status(0)
#users.each do |u|
u.email = u.email.get_primary_email
end
end
What this should do is create an instance of the Account::Email model with a single record.
The method get_primary_email looks like this:
def self.get_primary_email
first :conditions => ["is_primary = 1"]
end
The issue I am seeing is with in my view, I am getting the exception below. This is a bit confusing as I am not looping over the email object.
undefined method each' for #<Account::Email:0x7fcc3a5c49d8>
The code I am using to test with is this:
<% #users.each do |u|%>
<p>
<%=debug(u.email)%>
</p>
<% end %>
Do something like that instead:
class User < ActiveRecord::Base
has_many :emails
has_one :primary_email, :class_name => 'Email',
:conditions => { is_primary: 1 }
end
Then you can call user.primary_email directly
# your controller
def prospective_user
#users = Account::User.includes(:primary_email).where(:status => 0)
end
# your view
<% for user in #users %>
<%= user.primary_email %>
<% end %>
Background:
I followed the tutorial here to setup a polymorphic User favorites data model in my application. This allows me to let a User make pretty much any Entity in the system which I add 'has_many :favorites, :as => :favorable' line to its model a favorite. I plan on using this to implement a Facebook style 'Like' system as well as several other similar systems.
To start off I added the favoritability to a Post model (each user can create status updates like on Facebook). I have it all done and unit tested so I know the data model is sound and functioning from either side of the relationship (User and Post).
Details:
I have a Home controller with a single index method and view.
on the index view I render out the posts for the user and the user's friends
I want the user to be able to like posts from their friends
The Posts controller has only a create and a destroy method with associated routes (not a full fledged resource) and through the Post method via AJAX posts are created and deleted without issue
Where I am stuck
How do I add the link or button to add the post to the user's Favorites?
According to the tutorial the way to create a new Favorite through the polymorphic association is to do it from the Post.favorites.build(:user_id => current_user.id). From this direction the build handles pulling out the Post's ID and TYPE and all I have to do is pass in the user's id
Do I use an AJAX form post to a Favorites controller with a Create and Destroy method similar to the Post controller?
I am still struggling to uncross the wires in my brain from ASP.Net N-Tier web application development over to Rails MVC. Hasn't been too bad until now ;)
I bet there are Gems out there that might do this but I need to learn and the best way is to suffer through it. Maybe a tutorial or sample code from someone who has implemented liking functionality within their application would be helpful.
Thanks in advance for the assistance!
Jaap, I appreciate your comment on my question. After writing the question I pretty much didn't want to wait because the real learning takes place through trial and error, so I errored it up ;)
It turns out that what you suggested was pretty much in line with exactly what I ended up doing myself (it's always nice to find out that what you decide to do is what others would do as well, I love the sanity check value of it all).
So here is what I did and it is all working through post-backs. Now I just need to implement AJAX and style it:
My favorite model because my Polymorphic Favorites model requires that an Entity can only be favorited once by a user I added to the validations 'Scopes' which indicate that for each attribute it has to be unique in the scope of the other 2 required attributes. This solves the issue of multiple favorites by the same user.
class Favorite < ActiveRecord::Base
before_save :associate_user
belongs_to :favorable
belongs_to :user
# Validations
validates :user_id, :presence => true,
:uniqueness => {:scope => [:favorable_id, :favorable_type], :message => "item is already in favorites list."}
validates :favorable_id, :presence => true,
:uniqueness => {:scope => [:user_id, :favorable_type], :message => "item is already in favorites list."}
validates :favorable_type, :presence => true,
:uniqueness => {:scope => [:favorable_id, :user_id], :message => "item is already in favorites list."}
# Callbacks
protected
def associate_user
unless self.user_id
return self.user_id = session[:user_id] if session[:user_id]
return false
end
end
end
My User Model (that which is relevant): I added 2 methods, the get_favorites which is the same as favorable one from the tutorial and a Favorite? method which checks to see if the Entity in question has already been added to the user's favorites.
class User < ActiveRecord::Base
# Relationships
has_many :microposts, :dependent => :destroy
has_many :favorites
# Methods
def favorite?(id, type)
if get_favorites({:id => id, :type => type}).length > 0
return true
end
return false
end
def get_favorites(opts={})
# Polymorphic Favoritability: allows any model in the
# application to be favorited by the user.
# favorable_type
type = opts[:type] ? opts[:type] : :topic
type = type.to_s.capitalize
# add favorable_id to condition if id is provided
con = ["user_id = ? AND favorable_type = ?", self.id, type]
# append favorable id to the query if an :id is passed as an option into the
# function, and then append that id as a string to the "con" Array
if opts[:id]
con[0] += " AND favorable_id = ?"
con << opts[:id].to_s
end
# Return all Favorite objects matching the above conditions
favs = Favorite.all(:conditions => con)
case opts[:delve]
when nil, false, :false
return favs
when true, :true
# get a list of all favorited object ids
fav_ids = favs.collect{|f| f.favorable_id.to_s}
if fav_ids.size > 0
# turn the Capitalized favorable_type into an actual class Constant
type_class = type.constantize
# build a query that only selects
query = []
fav_ids.size.times do
query << "id = ?"
end
type_conditions = [query.join(" AND ")] + fav_ids
return type_class.all(:conditions => type_conditions)
else
return []
end
end
end
end
My Micropost Model (that which is relevant): note the Polymorphic association in the has_many relationship titled :favorites.
class Micropost < ActiveRecord::Base
attr_accessible :content
# Scopes
default_scope :order => 'microposts.created_at DESC'
# Relationships
belongs_to :user
has_many :favorites, :as => :favorable # Polymorphic Association
# Validations
validates :content, :presence => true, :length => { :minimum => 1, :maximum => 140 }
validates :user_id, :presence => true
end
My Micropost Form: as you can see I am passing in the entity that will be mapped to the Favorite model as a local variable to the 2 Favorite forms as 'local_entity'. This way I can pull out the ID and the TYPE of the Entity for the Polymorphic association.
<div class="post">
<span class="value">
<%= micropost.content %>
</span>
<span>
<% if current_user.favorite?(micropost.id, micropost.class.to_s) %>
<%= render :partial => 'favorites/remove_favorite', :locals => {:local_entity => micropost} %>
<% else %>
<%= render :partial => 'favorites/make_favorite', :locals => {:local_entity => micropost} %>
<% end %>
</span>
<span class="timestamp">
Posted <%= time_ago_in_words(micropost.created_at) %> ago.
</span>
<div class="clear"></div>
</div>
My Make Favorite Form:
<%= form_for current_user.favorites.build do |f| %>
<div><%= f.hidden_field :favorable_id, :value => local_entity.id %></div>
<div><%= f.hidden_field :favorable_type, :value => local_entity.class.to_s %></div>
<div class="actions"><%= f.submit "make favorite" %></div>
<% end %>
My Remove Favorite Form:
<%= form_for current_user.get_favorites(
{:id => local_entity.id,
:type => local_entity.class.to_s}),
:html => { :method => :delete } do |f| %>
<div class="actions"><%= f.submit "remove favorite" %></div>
<% end %>
If you don't want to call this on the current_user, you would have to have these routes in your config/routes.rb to make nested routes for favorites on a user. I assume you have a Favorite model which belongs_to :user:
resources :users do
resources :favorites
end
Then make sure your favorites controller loads the user in some kind of before_filter:
def load_user
#user = User.load params[:user_id]
end
And then you can render a remote form to create a new favorite for any kind of object (it will only show a button):
<%= remote_form_for [#user, Favorite.new] do |f| -%>
<%= f.hidden_field :favorable_type, object.class.to_s %>
<%= f.hidden_field :favorable_id, object.id %>
<%= f.submit 'Like' %>
<%- end -%>
You would have to render that form as a partial sending along an object (e.g. a Post) and then it will create an AJAX POST call to /users/:id/favorites/ which will create the favorite object and render some kind of javascript response in a create.rjs file.
I hope this helps. The code itself is untested, but it might get you moving.