Cannot favorite users using polymorphic association - ruby-on-rails

Using the system below users are able to favorite styles in the format of:
=> #<Favorite id: 59, user_id: 2, favoritable_id: 15, favoritable_type: "Style", created_at: "2013-04-06 08:47:08", updated_at: "2013-04-06 08:47:08">
I have built the system to enable users to favorite users as well, but for some reason the line Favorite.create!(:user => user) is not creating the expected favorite model object when called on User, as above when called on Style. Instead I am getting nils as below:
=> #<Favorite id: 65, user_id: 2, favoritable_id: nil, favoritable_type: nil, created_at: "2013-04-06 09:35:07", updated_at: "2013-04-06 09:35:07">
Problem is above. Details are below
Favorite Class:
class Favorite < ActiveRecord::Base
belongs_to :favoritable, :polymorphic => true
belongs_to :user
attr_accessible :user
validates_presence_of :user
validates_uniqueness_of :user_id, :scope => [:favoritable_type, :favoritable_id]
end
Model definitions including favoritable module (see below):
class User < ActiveRecord::Base
include Favoritable
class Style < ActiveRecord::Base
include Favoritable
Favoritable Module:
module Favoritable
extend ActiveSupport::Concern
included do
attr_accessible :favorites
has_many :favorites, :as => :favoritable, :autosave => true
accepts_nested_attributes_for :favorites
def add_favorite(user)
self.favorites << Favorite.create!(:user => user)
save
end
def delete_favorite(user)
self.favorites.where(:user_id => user.id).each do |favorite|
favorite.destroy
end
end
end
end
Favorites Controller:
class FavoritesController < ApplicationController
def fav_user
#user = User.find(params[:id])
#user.add_favorite(current_user)
respond_to do |format|
format.js { render :action => "update_favorite_disp", :layout => false }
end
end
def delete_fav_user
#user = User.find(params[:id])
respond_to do |format|
if #user.delete_favorite(current_user)
format.js { render :action => "update_favorite_disp", :layout => false }
end
end
end
def fav_style
#style = Style.find(params[:id])
#style.add_favorite(current_user)
respond_to do |format|
format.js { render :action => "update_favorite", :layout => false }
end
end
def delete_fav_style
#style = Style.find(params[:id])
respond_to do |format|
if #style.delete_favorite(current_user)
format.js { render :action => "update_favorite", :layout => false }
end
end
end
end

You need to pass in the :favoritable model in add_favorite:
def add_favorite(user)
Favorite.create!(:user =>user, :favoritable => self)
save
delete_favorite also needs to delete based on the :favoritable_id, instead of :user_id:
def delete_favorite(user)
user.favorites.where(:favoritable_id => self.id).each do |favorite|
favorite.destroy
end
end

Related

795: unexpected token at in POST + Ruby on Rails

I'm trying to put together the postman a POST call to yell my class these are my model and controller
#yell.rb
class Yell < ActiveRecord::Base
include ActionView::Helpers::DateHelper
belongs_to :user, inverse_of: :yells
has_many :negotiations, inverse_of: :yell, :dependent => :delete_all
has_one :trade_offer, inverse_of: :yell, :dependent => :destroy
has_and_belongs_to_many :categories, inverse_of: :yell, :dependent => :delete_all
def as_json(options={})
super(:only => [:id, :yell_type, :status, :payment_type],
:include => {
:trade_offer => {:only => [:id, :title, :description, :price],
:include => [:photos => {:only => [:id],
:methods => :url}]
},
:categories => {:only => [:id, :name]},
:user => {:only => [:id, :name, :avatar]}
},
:methods => [ :times_ago]
)
end
def times_ago
time_ago_in_words(self.created_at)
end
def url
self.trade_offer.photos.url.url
end
end
#yells_controller.rb
# POST /yells.json
def create
if (YELLTYPES[0][YELLTYPE_OFFER].include? params[:yell_type]) || (params[:yell_type].equal? YELLTYPE_REQUEST_BUY)
#yell = Yell.new(yell_params)
#user = User.find_by_id(params[:user_id]) #just have it because it has not current_user
#yell.user = #user
if #yell.save
#creating an offer on a yell
#trade_offer = TradeOffer.new(trade_offer_params)
#trade_offer.yell = #yell
if #trade_offer.save
#yell.trade_offer = #trade_offer
else
#yell.destroy
render json: {status: 3, message:"offer not create", data: nil}, status: :unprocessable_entity
return
end
#categories relating to yell if the category does not exist it will be created
Array(params[:yell][:categories]).each do |rel|
#category = Category.find_by_name(rel[:name])
if #category
#categories_yells = CategoriesYell.new(category_id: #category.id, yell_id: #yell.id)
if #categories_yells.save
#yell.categories.build(id: #category.id, name: rel[:name])#only creates the relationship
else
#yell.destroy
render json: {status: 4, message:"relationship category not create", data: nil}, status: :unprocessable_entity
end
else
#yell.categories.create(name: rel[:name]) #creates the relationship and category
end
end
#creating photos related to an offer
Array(params[:yell][:trade_offer][:photos]).each do |rel|
#photo = Photo.new(url: rel, trade_offer_id: #trade_offer.id)
if !#photo.save
#yell.destroy
render json: {status: 5, message:"photo not upload", data: nil}, status: :unprocessable_entity
return
end
end
render json: {status: 0, message:"succes", data: #yell}, status: :created
else
render json: {status: 6, message:"yell not create", data: nil}, status: :unprocessable_entity
end
else
render json: {status: 7, message:"type not permitted", data: {types_permitted: YELLTYPES}}, status: :unprocessable_entity
end
end
the idea is to go to a scream a category of series and a series of images. This call worked perfectly when the body one step aplication / json, but with this I can not upload images.
I was going to call the header content-type = aplication/json, just had to take it, and added to my route defaults: {format: 'json'}

Limiting a select_tag list to only objects that belong_to another object in rails

I have a list of classrooms that belong_to a school and a school has_many classrooms. I have users who belong to a school and fill out a form of information. On this form I have the following selector:
Pins/_form.html.erb
<div class="form-group">
<%= label_tag(:classroom, "Select your classroom:") %>
<%= select_tag "pin[code]", options_from_collection_for_select(Classroom.all, "code", "code", ) %>
</div>
Right now this form shows all the classrooms listed in the data base. I need it to show just the classrooms that belong to the school. Classrooms have a name and a code, the code is what ties the students form submission to that classroom.
I'm still learning RoR and have struggled to figure this type of problem one out on a couple of occasions.
Here's the Classroom Controller
class ClassroomsController < ApplicationController
before_action :set_classroom, only: [:show, :edit, :update, :destroy]
after_action :verify_authorized
respond_to :html
def home
#classrooms = Classroom.all
respond_with(#classrooms)
authorize #classrooms
end
def index
#classrooms = Classroom.all
respond_with(#classrooms)
authorize #classrooms
end
def show
respond_with(#classroom)
end
def new
#school = School.find(params[:school_id])
#classroom = #school.classrooms.new
#teachers = #school.teachers
respond_with(#classroom)
flash[:notice] = "Classroom created."
authorize #classroom
end
def edit
end
def create
#school = School.find(params[:school_id])
#classroom = #school.classrooms.new(classroom_params)
#classroom.save
respond_with #school
authorize #classroom
end
def update
#classroom.update(classroom_params)
respond_with([#school,#classroom])
end
def destroy
#classroom.destroy
redirect_to #school
flash[:notice] = "You have succesfully deleted the classroom."
end
private
def set_classroom
#classroom = Classroom.find(params[:id])
#school = School.find(params[:school_id])
authorize #classroom
end
def classroom_params
params.require(:classroom).permit(:teacher_id, :name, :code)
end
end
Pins_controller
class PinsController < ApplicationController
before_action :set_pin, only: [:show, :edit, :update, :destroy]
respond_to :html
def show
respond_with(#pin)
end
def new
#pin = Pin.new
#emotions = Emotion.all
#causes = Cause.all
#school = School.find(params[:school])
#classrooms = #school.classrooms
respond_with(#pin)
authorize #pin
end
def edit
end
def create
code = params[:pin][:code]
#classroom = Classroom.where('code LIKE ?', code).first
unless #classroom
flash[:error] = "Classroom code incorrect"
#emotions = Emotion.all
#causes = Cause.all
render :new
else
params[:pin][:classroom_id] = #classroom.id
#pin = Pin.new(pin_params)
#pin.save
params[:pin][:cause_ids].each do |cause_id|
#cause = Cause.find(cause_id)
#pin.causes << #cause
end
params[:pin][:emotion_ids].each do |emotion_id|
#emotion = Emotion.find(emotion_id)
#pin.emotions << #emotion
end
if #pin.save
redirect_to signout_path and return
end
respond_with(#pin)
authorize #pin
end
end
def update
#pin.update(pin_params)
respond_with(#pin)
authorize #pin
end
def destroy
#pin.destroy
respond_with(#pin)
authorize #pin
end
private
def set_pin
#pin = Pin.find(params[:id])
authorize #pin
end
def pin_params
params.require(:pin).permit(:user_id, :question, :question1, :question2,
:question3, :question4, :question5, :classroom_id, :sad,
:happy, :mad, :brave, :embarrassed, :sorry, :frustrated,
:silly, :left_out, :excited, :hurt, :jealous, :confused,
:proud, :other)
end
end
School.rb model
class School < ActiveRecord::Base
has_many :users
has_many :classrooms
validates_uniqueness_of :code
def classrooms
self.users.classrooms
end
def students
self.users.students
end
def teachers
self.users.teachers
end
def admins
self.users.admins
end
end
User Model
class User < ActiveRecord::Base
devise :timeoutable, :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :pins
has_many :reflections
has_many :classrooms, :foreign_key => :teacher_id
belongs_to :school
validates :name, presence: true
validates :role, presence: true
# validates :school, presence: true
scope :students, -> { where(role: "student") }
scope :teachers, -> { where(role: "teacher")}
scope :teachers_and_admins, -> { where(:role => ["teacher", "admin"]) }
scope :admins, -> { where(role: "admin" ) }
scope :online, lambda{ where("updated_at > ?", 15.days.ago) }
def online?
updated_at > 15.days.ago
end
def admin?
role == "admin"
end
def teacher?
role == "teacher"
end
def student?
role == "user" || role == "student"
end
def superadmin?
role == "superadmin"
end
def admin_of_school?(school)
self.admin? && self.school == school
end
def teacher_of_school?(school)
self.teacher? && self.school == school
end
def admin_or_teacher_of_school?(school)
self.admin_of_school?(school) || self.teacher_of_school?(school)
end
end
classroom model
class Classroom < ActiveRecord::Base
belongs_to :school
belongs_to :teacher, :class_name => "User"
has_and_belongs_to_many :users
has_many :pins
has_many :reflections
validates_presence_of :school
validates_presence_of :teacher
validates :code, :uniqueness => { :scope => :school_id }
end
I found a similar answer here: Rails only give records that "belong_to"
But it didn't help me understand how to limit the drop down list to only Classrooms of the current school.
Any ideas on how to handle his situation?
You already select the school in the controller, so just use it in the view, instead of doing a Classroom.all do a #school.classrooms
<%= select_tag "pin[code]",
options_from_collection_for_select(#school.classrooms, "code", "code")
%>
This part isn't an answer, but improvements and fixes to your code
First the User model can stay as it is
class User < ActiveRecord::Base
#devise place holder
# assocciations
has_many :pins
has_many :reflections
has_many :classrooms, foreign_key: :teacher_id
belongs_to :school
#validations place holder
#scopes
scope :students, -> { where(role: "student") }
scope :teachers, -> { where(role: "teacher")}
scope :admins, -> { where(role: "admin" ) }
scope :teachers_and_admins, -> { teachers.admins }
scope :online, -> { where("updated_at > ?", 15.days.ago) }
# some check methods
end
classroom class
class Classroom < ActiveRecord::Base
belongs_to :school
belongs_to :teacher, ->{ User.teachers }, class_name: 'User'
has_many :pins
has_many :reflections
end
school class
class School < ActiveRecord::Base
has_many :users
has_many :admins, ->{ User.admins }, class: User
has_many :students, ->{ User.students }, class: User
has_many :teachers, ->{ User.teachers }, class: User
has_many :classrooms
end
Now the controllers
You need to clean up the duplications, so here's an example from ur pins#new action
def new
#pin = Pin.new
prepare_lookups
respond_with(#pin)
authorize #pin
end
def prepare_lookups
#emotions = Emotion.all
#causes = Cause.all
#school = School.find(params[:school])
#classrooms = #school.classrooms
end
I removed the common code to a separate method, and call it whenever i need, of course you can add or remove from that method according to your needs.
Anyways, I think you should read more about active record assocciations and the conventions in writing controller actions

Undefined method `keys' for nil:NilClass after include Paperclip

Im new in ruby on rails and after I included papperclip i get this error if i try to change photo.
If I change create method like in a README.md on paperclip`s github:
def create
#hotel = Hotel.create( hotel_params )
end
private
# Use strong_parameters for attribute whitelisting
# Be sure to update your create() and update() controller methods.
def hotel_params
params.require(:hotel).permit(:name, :photo, :room_description, :price_for_room, :breakfast, :country, :state, :city, :street)
end
I get the same error in Create method. Could you help me? What am I missing here?
Ruby 1.9.3
Rails 4
Paperclip 4.2.0
Error screenshot
hotels_controller.rb
class HotelsController < ApplicationController
before_action :signed_in_user, except: [:index, :show]
def index
#hotels = Hotel.paginate(:page => params[:page], :per_page => 5)
end
def show
#hotel = Hotel.find(params[:id])
#comments = #hotel.comments
end
def new
#hotel = Hotel.new
end
def edit
#hotel = Hotel.find(params[:id])
end
def create
#hotel = current_user.hotels.new(params[:hotel])
if #hotel.save
redirect_to #hotel, notice: "Hotel was successfully created."
else
render "new"
end
end
def update
#hotel = Hotel.find(params[:id])
if #hotel.update_attributes(params[:hotel])
redirect_to #hotel, notice: "Hotel was successfully updated."
else
render "edit"
end
end
def destroy
#hotel = Hotel.find(params[:id])
#hotel.destroy
redirect_to hotels_url
end
end
hotel.rb
class Hotel < ActiveRecord::Base
has_many :comments
belongs_to :user
has_many :ratings
has_many :raters, :through => :ratings, :source => :users
validates :name, presence: true, length: { minimum: 5 }
validates :room_description, presence: true
validates :price_for_room, presence: true, numericality: true
validates_associated :comments
has_attached_file :photo, :styles => { :medium => "500x500>", :thumb => "100x100>" }, :default_url => "/images/:style/missing.png"
validates_attachment_content_type :photo, :content_type => /\Aimage\/.*\Z/
def update_average_rating
#value = 0
self.ratings.each do |rating|
#value = #value + rating.value
end
#total = self.ratings.size
update_attributes(average_rating: #value.to_f / #total.to_f)
end
end
As you are using Rails4, your update action should be like this
def update
#hotel = Hotel.find(params[:id])
if #hotel.update_attributes(hotel_params) #Here
redirect_to #hotel, notice: "Hotel was successfully updated."
else
render "edit"
end
end

creating an object with has_many association results in item can not be blank

I have following associations and the related controller, in my form I am adding every field as it should be. But I still get an error Ratings item can't be blank when I try to create an Item. I am using Rails 4.0 . I did searched extensively for this but could not still find what I am doing wrong. Thankyou!
class Item < ActiveRecord::Base
has_many :ratings, dependent: :destroy
accepts_nested_attributes_for :ratings, :allow_destroy => true
validates :name , :length => { minimum: 3 }
validates :category , :length => { minimum: 3 }
end
class Ratings < ActiveRecord::Base
belongs_to :user
belongs_to :item
default_scope -> { order('created_at DESC') }
validates :user_id, :presence => true
validates :item_id, :presence => true
validates_numericality_of :rating, :greater_than_or_equal_to => 0
validates_numericality_of :rating, :less_than_or_equal_to => 5
end
class ItemsController < ApplicationController
before_action :set_item, only: [:show]
before_action :user_signed_in?, only: :create
def create
#item = Item.new
#rating = #item.ratings.build
#rating.comment = params[:item][:ratings_attributes][:comment]
#rating.rating = params[:item][:ratings_attributes][:rating]
#rating.user_id = current_user.id
#item.name = params[:item][:name]
#item.url = params[:item][:url]
#item.full_address = params[:item][:full_address]
#item.city = params[:item][:city]
#item.country = params[:item][:country]
#item.category = params[:item][:category]
respond_to do |format|
if #item.save
#TODO create rating here (First rating of an Item)
flash[:success] = "Welcome to inmyopnion"
format.html { redirect_to #item, notice: 'Item was successfully created.' }
format.json { render action: 'show', status: :created, location: #item }
else
format.html { render action: 'new' }
format.json { render json: #item.errors, status: :unprocessable_entity }
end
end
end
def new
#item = Item.new
end
def show
end
def destroy
end
private
def set_item
#item = Item.find(params[:id])
end
def item_params
params.require(:item).permit(:name, :url, :full_address, :city, :country, :category, :ratings_attributes => [:rating, :comment])
end
def user_signed_in?
#TODO: should display should sign in to rate an item
redirect_to(root_url) unless signed_in?
end
end
Simplify your controller! Since you are allowing nested_attributes this should be sufficient:
#item = Item.create(params[:item])
The problem might be caused by #rating object not being saved.
I got it working by commenting the below given line in
class Ratings < ActiveRecord::Base
validates :item_id, :presence => true
but my association rspec test fails and saves a Ratings without an item_id.
Rest of the code is similar to what I posted as
#item = Item.create(params[:item])
gives ActiveModel::ForbiddenAttributesError
Alright much playing with the code and docs of nested_attributes finally a working program that validates association too. These are the changes (marked in between ** .... **) listed below
class Item < ActiveRecord::Base
has_many :ratings, dependent: :destroy, **inverse_of: :item**
accepts_nested_attributes_for :ratings, :allow_destroy => true
validates :name , :length => { minimum: 3 }
validates :category , :length => { minimum: 3 }
end
class Ratings < ActiveRecord::Base
belongs_to :user
belongs_to :item, **inverse_of: :ratings**
default_scope -> { order('created_at DESC') }
validates :user_id, :presence => true
validates_presence_of :item
validates_numericality_of :rating, :greater_than_or_equal_to => 0
validates_numericality_of :rating, :less_than_or_equal_to => 5
end
Still not able to create one from #item = Item.create(params[:item]) which still gives an gives
ActiveModel::ForbiddenAttributesError as suggested by #BroiSatse and also the docs of nested_attributes that should not be the case
the problem might be in
class ItemsController < ApplicationController
def item_params
params.require(:item).permit(:name, :url, :full_address, :city, :country, :category, :ratings_attributes => [:rating, :comment])
end
will work on to resolve that too and post an answer if I find a solution.

Polymorphic nested form results in AssociationTypeMismatch

Models:
class User < ActiveRecord::Base
belongs_to :role, :polymorphic => true
class Admin < ActiveRecord::Base
has_one :user, :as => :role
class Dealer < ActiveRecord::Base
has_one :user, :as => :role
class Buyer < ActiveRecord::Base
has_one :user, :as => :role
Dealer controller:
def new
#dealer = Dealer.new
respond_to do |format|
format.html
format.xml { render :xml => #dealer }
end
end
def create
#dealer = Dealer.new(params[:dealer])
respond_to do |format|
if #dealer.save
flash[:notice] = 'Dealer was successfully created.'
format.html { redirect_to [:admin, #dealer] }
format.xml { render :xml => #dealer, :status => :created, :location => #dealer }
else
format.html { render :action => "new" }
format.xml { render :xml => #dealer.errors, :status => :unprocessable_entity }
end
end
end
Error message:
ActiveRecord::AssociationTypeMismatch in Admin/dealersController#create
User(#41048900) expected, got HashWithIndifferentAccess(#23699520)
Request Parameters:
{"authenticity_token"=>"+GkeOOxVi1Fxl7ccbV0Ctt5R6shyMlF+3UWgRow2RdI=",
"dealer"=>{"gender"=>"m",
"forename"=>"",
"surname"=>"",
"company"=>"",
"address1"=>"",
"address2"=>"",
"zipcode"=>"",
"city"=>"",
"country"=>"1",
"user"=>{"email"=>"",
"password"=>""},
"phone"=>"",
"mobile"=>"",
"fax"=>""},
"commit"=>"Submit"}
I guess my problem is that Rails doesn't convert the "user" hash inside the request hash into a User object — but why and how can I make Rails to do that?
Just spent several hours on this including bumping into this thread. Finally found on another forum:
the attribute setter expects to get your params in this form
{ ...,
"user_attributes" => {"email" => "", "password" => ""},
... }
NOT
{ ...,
"user" => {"email" => "", "password" => ""},
... }
In order to get this, your view should look something like this:
form_for #dealer do |f|
...
f.fields_for :user, #dealer.user do |u|
...
end
...
end
This works for me on Rails 3.0.3

Resources