Polymorphic status associations - ruby-on-rails

I've been developing a web app in Rails 4, and used the Michael Hartl tutorial as the backbone for it, but using Devise as well. After building some of it, I went back and changed the Status model to be polymorphic in order to allow users, venues and groups to be able to post a status. I've got a certain way with this so far, but am now stuck. When trying to view a user's profile, I am currently getting this error:
NoMethodError in Users#show
Showing /home/app_name/app/views/users/show.html.erb where line #6 raised:
undefined method `avatar' for #<ActiveRecord::AssociationRelation::ActiveRecord_AssociationRelation_Status:0xb6859fd8>
Extracted source (around line #6):
<aside class="col-md-4">
<section>
<h1>
<%= image_tag #user.avatar.url(:thumb) %>
<%= #user.name %>
</h1>
</section>
The user definitely has an avatar, and as far as I can work out it seems to be having trouble working out the association through the polynomial relationship. It's on the user#show page, so presumably I shouldn't change "user" to "statusable", I have already tried this anyway, but don't understand where the problem is coming from. The profile shows a feed of statuses at present and an avatar for the user in each.
Any help would be greatly appreciated.
EDIT
Here are my user and status models:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable, :omniauth_providers => [:facebook, :twitter, :google_oauth2]
before_save { self.username = username.downcase }
validates :first_name, presence: true,
length: { maximum: 25 }
validates :last_name, presence: true,
length: { maximum: 25 }
VALID_USERNAME_REGEX = /\A[\w+\-._]+\z/i
validates :username, presence: true,
length: { maximum: 20 },
format: { with: VALID_USERNAME_REGEX },
uniqueness: { case_sensitive: false }
validates :email, presence: true
validates :password, presence: true
validates :birthday, presence: true
validates :gender, presence: true
validates :postcode, presence: true
validates_format_of :postcode, :with => /\A([A-PR-UWYZ]([0-9]{1,2}|([A-HK-Y][0-9]|[A-HK-Y][0-9]([0-9]|[ABEHMNPRV-Y]))|[0-9][A-HJKS-UW])\s?[0-9][ABD-HJLNP-UW-Z]{2}|(GIR\ 0AA)|(SAN\ TA1)|(BFPO\ (C\/O\ )?[0-9]{1,4})|((ASCN|BBND|[BFS]IQQ|PCRN|STHL|TDCU|TKCA)\ 1ZZ))\z/i, :message => "invalid postcode"
has_attached_file :avatar, :styles => { :medium => "300x300", :thumb => "100x100", :micro => "30x30", :large => "500x500>" }, :default_url => "/images/:style/missing.jpg"
validates_attachment_content_type :avatar, :content_type => /\Aimage\/.*\Z/
geocoded_by :address
after_validation :geocode
self.per_page = 20
def address
[town, postcode, country].compact.join(', ')
end
def feed
Status.from_users_favourited_by(self)
end
def self.all_except(user)
where.not(id: user)
end
end
def name
first_name + " " + last_name
end
has_many :statuses, as: :statusable, dependent: :destroy
accepts_nested_attributes_for :statuses
end
class Status < ActiveRecord::Base
before_create :set_latlong
belongs_to :statusable, polymorphic: true
default_scope -> { order('created_at DESC') }
validates :content, presence: true, length: { maximum: 140 }
validates :statusable_id, presence: true
validates :statusable_type, presence: true
def set_latlong
self.latitude = statusable.latitude
self.longitude = statusable.longitude
end
def self.from_users_favourited_by(user)
favourited_user_ids = "SELECT favourite_id FROM favouriteusers
WHERE favourited_id = :user_id"
where("statusable_id IN (#{favourited_user_ids}) OR statusable_id = :user_id", user_id: user.id)
end
end
Here is the Status controller:
class StatusController < ApplicationController
before_action :authenticate_user!, only: [:create, :destroy]
before_filter :load_statusable
def new
#status = Status.new(status_params)
end
def create
#statusable = load_statusable
#status = #statusable.statuses.build(status_params)
if #status.save
flash[:success] = "Status created!"
redirect_to root_url
else
#feed_items = []
render 'static_pages/home'
end
end
def destroy
#status.destroy
redirect_to root_url
end
private
def status_params
params.require(:status).permit(:content)
end
def load_statusable
resource, id = request.path.split('/')[1, 2]
resource_name = resource.singularize.classify
if resource_name = "user"
#statusable = current_user
else
#statusable = resource_name.constantize.find(id)
end
end
end
And finally the status schema:
create_table "statuses", force: true do |t|
t.string "content"
t.integer "statusable_id"
t.string "statusable_type"
t.float "latitude"
t.float "longitude"
t.datetime "created_at"
t.datetime "updated_at"
end
I've taken a few bits out to make it easier to read, but hopefully have left in the bits needed.
EDIT
class UsersController < ApplicationController
before_filter :authenticate_user!, only: [:index, :show,:edit,
:update, :destroy, :favourite_users, :favourited_users]
def index
#users = User.all_except(current_user).paginate(page: params[:page]).order('created_at DESC')
end
def show
#user = User.find(params[:id])
#user = #user.statuses.paginate(page: params[:page])
end
def new
#user = User.new
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
# Handle a successful update.
else
render 'edit'
  end
end
def create
#user = User.create( user_params )
end
def favourite_users
#title = "Favourites"
#user = User.find(params[:id])
#users = #user.favourite_users.paginate(page: params[:page])
render 'show_favourite'
end
def favourited_users
#title = "Listed as a Favourite by"
#user = User.find(params[:id])
#users = #user.favourited_users.paginate(page: params[:page])
render 'show_favourite'
end

Well, it sure looks like #user does not contain a User but rather a ActiveRecord::AssociationRelation::ActiveRecord_AssociationRelation_Status. No wonder it doesn't have an avatar method! Check your controller code to see what #user is actually set to. If you can't make sense of what's going on, post the controller here. The User model and schema will probably be needed, too.

Related

How to notify user when someone follows?

How can we notify a user when he has a new follower?
create_notification is suppose to trigger a notification, but I get different errors with different attempts.
relationship.rb
class Relationship < ActiveRecord::Base
after_create :create_notification
has_many :notifications
belongs_to :follower, class_name: "User"
belongs_to :followed, class_name: "User"
validates :follower_id, presence: true
validates :followed_id, presence: true
private
def create_notification # How do we need to rewrite this?
notifications.create(
followed_id: followed_id,
follower_id: follower_id,
user: follower_id.user,
read: false
)
end
end
I used this tutorial as a guide for building my notifications and the Hartl tutorial for building the users and relationships.
relationships_controller.rb
class RelationshipsController < ApplicationController
before_action :logged_in_user
def create # The notification is trigger after create.
#user = User.find(params[:followed_id])
current_user.follow(#user)
respond_to do |format|
format.html { redirect_to #user }
format.js
end
end
def destroy
#user = Relationship.find(params[:id]).followed
current_user.unfollow(#user)
respond_to do |format|
format.html { redirect_to #user }
format.js
end
end
end
Sorry my user model is a bit messy.
class User < ActiveRecord::Base
acts_as_tagger
acts_as_taggable
has_many :notifications
has_many :activities
has_many :activity_likes
has_many :liked_activities, through: :activity_likes, class_name: 'Activity', source: :liked_activity
has_many :liked_comments, through: :comment_likes, class_name: 'Comment', source: :liked_comment
has_many :valuation_likes
has_many :habit_likes
has_many :goal_likes
has_many :quantified_likes
has_many :comment_likes
has_many :authentications
has_many :habits, dependent: :destroy
has_many :levels
has_many :combine_tags
has_many :valuations, dependent: :destroy
has_many :comments
has_many :goals, dependent: :destroy
has_many :quantifieds, dependent: :destroy
has_many :results, through: :quantifieds
has_many :notes
accepts_nested_attributes_for :habits, :reject_if => :all_blank, :allow_destroy => true
accepts_nested_attributes_for :notes, :reject_if => :all_blank, :allow_destroy => true
accepts_nested_attributes_for :quantifieds, :reject_if => :all_blank, :allow_destroy => true
accepts_nested_attributes_for :results, :reject_if => :all_blank, :allow_destroy => true
has_many :active_relationships, class_name: "Relationship",
foreign_key: "follower_id",
dependent: :destroy
has_many :passive_relationships, class_name: "Relationship",
foreign_key: "followed_id",
dependent: :destroy
has_many :following, through: :active_relationships, source: :followed
has_many :followers, through: :passive_relationships, source: :follower
attr_accessor :remember_token, :activation_token, :reset_token
before_save :downcase_email
before_create :create_activation_digest
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }, unless: -> { from_omniauth? }
has_secure_password
validates :password, length: { minimum: 6 }
scope :publish, ->{ where(:conceal => false) }
User.tag_counts_on(:tags)
def count_mastered
#res = habits.reduce(0) do |count, habit|
habit.current_level == 6 ? count + 1 : count
end
end
def count_challenged
#challenged_count = habits.count - #res
end
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_initialize.tap do |user|
user.provider = auth.provider
user.image = auth.info.image
user.uid = auth.uid
user.name = auth.info.name
user.oauth_token = auth.credentials.token
user.oauth_expires_at = Time.at(auth.credentials.expires_at)
user.password = (0...8).map { (65 + rand(26)).chr }.join
user.email = (0...8).map { (65 + rand(26)).chr }.join+"#mailinator.com"
user.save!
end
end
def self.koala(auth)
access_token = auth['token']
facebook = Koala::Facebook::API.new(access_token)
facebook.get_object("me?fields=name,picture")
end
# Returns the hash digest of the given string.
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# Returns a random token.
def User.new_token
SecureRandom.urlsafe_base64
end
# Remembers a user in the database for use in persistent sessions.
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
# Forgets a user. NOT SURE IF I REMOVE
def forget
update_attribute(:remember_digest, nil)
end
# Returns true if the given token matches the digest.
def authenticated?(attribute, token)
digest = send("#{attribute}_digest")
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end
# Activates an account.
def activate
update_attribute(:activated, true)
update_attribute(:activated_at, Time.zone.now)
end
# Sends activation email.
def send_activation_email
UserMailer.account_activation(self).deliver_now
end
def create_reset_digest
self.reset_token = User.new_token
update_attribute(:reset_digest, User.digest(reset_token))
update_attribute(:reset_sent_at, Time.zone.now)
end
# Sends password reset email.
def send_password_reset_email
UserMailer.password_reset(self).deliver_now
end
# Returns true if a password reset has expired.
def password_reset_expired?
reset_sent_at < 2.hours.ago
end
def good_results_count
results.good_count
end
# Follows a user.
def follow(other_user)
active_relationships.create(followed_id: other_user.id)
end
# Unfollows a user.
def unfollow(other_user)
active_relationships.find_by(followed_id: other_user.id).destroy
end
# Returns true if the current user is following the other user.
def following?(other_user)
following.include?(other_user)
end
private
def from_omniauth?
provider && uid
end
# Converts email to all lower-case.
def downcase_email
self.email = email.downcase unless from_omniauth?
end
# Creates and assigns the activation token and digest.
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
end
users_controller.rb
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update, :destroy,
:following, :followers]
before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: :destroy
def tag_cloud
#tags = User.tag_counts_on(:tags)
end
def index
#users = User.paginate(page: params[:page])
end
def show
#user = User.find(params[:id])
if current_user == #user
#habits = #user.habits
#valuations = #user.valuations
#accomplished_goals = #user.goals.accomplished
#unaccomplished_goals = #user.goals.unaccomplished
#averaged_quantifieds = #user.quantifieds.averaged
#instance_quantifieds = #user.quantifieds.instance
else
#habits = #user.habits.publish
#valuations = #user.valuations.publish
#accomplished_goals = #user.goals.accomplished.publish
#unaccomplished_goals = #user.goals.unaccomplished.publish
#averaged_quantifieds = #user.quantifieds.averaged.publish
#instance_quantifieds = #user.quantifieds.instance.publish
end
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
#user.send_activation_email
flash[:info] = "Please check your email to activate your account."
redirect_to root_url
else
render 'new'
end
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
flash[:success] = "Profile updated"
redirect_to #user
else
render 'edit'
end
end
def destroy
User.find(params[:id]).destroy
flash[:success] = "User deleted"
redirect_to users_url
end
def following
#title = "Following"
#user = User.find(params[:id])
#users = #user.following.paginate(page: params[:page])
render 'show_follow'
end
def followers
#title = "Followers"
#user = User.find(params[:id])
#users = #user.followers.paginate(page: params[:page])
render 'show_follow'
end
private
def user_params
if params[:conceal] = true
params.require(:user).permit(:name, :email, :tag_list, :password, :conceal, :password_confirmation, valuations_attributes: [:name, :tag_list, :conceal], activities_attributes: [:conceal, :action, :trackable_id, :trackable_type])
else
params[:user][:valuations][:conceal] = false
params.require(:user).permit(:name, :image, :tag_list, :email, :password, :password_confirmation, valuations_attributes: [:name, :tag_list], activities_attributes: [:action, :trackable_id, :trackable_type])
end
end
# Before filters
# Confirms a logged-in user.
def logged_in_user
unless logged_in?
store_location
flash[:danger] = "Please log in."
redirect_to login_url
end
end
# Confirms the correct user.
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
# Confirms an admin user.
def admin_user
redirect_to(root_url) unless current_user.admin?
end
end
schema.rb
create_table "notifications", force: true do |t|
t.integer "habit_id"
t.integer "quantified_id"
t.integer "valuation_id"
t.integer "goal_id"
t.integer "comment_id"
t.integer "user_id"
t.integer "habit_like_id"
t.integer "quantified_like_id"
t.integer "valuation_like_id"
t.integer "goal_like_id"
t.integer "comment_like_id"
t.integer "likes"
t.integer "followed_id"
t.integer "follower_id"
t.boolean "read"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "relationships", force: true do |t|
t.integer "follower_id"
t.integer "followed_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Please let me know if you need further explanation or code :-] Keep the dream alive!
You could simply extend your User#follow method to something like this:
# Follows a user.
def follow(other_user)
active_relationships.create(followed_id: other_user.id)
UserMailer.new_follower(other_user).deliver_now
end
Then add a new_follower(user) method to your UserMailer in a similar way than the already existing password_reset method.

Rails Posts relations

The code of my project is below:
posts_controller.rb:
class PostsController < ApplicationController
before_action :signed_out_user
def index
end
def create
#post = current_user.posts.build(post_params)
if #post.save
flash[:success] = "Post created!"
redirect_to root_url
else
render 'welcome/home'
end
end
def destroy
end
private
def post_params
params.require(:post).permit(:content, :title)
end
end
welcome_controller.rb:
class WelcomeController < ApplicationController
layout "application"
def index
end
def about
end
def home
#post = current_user.posts.build if signed_in?
end
end
user_controller.rb
class UsersController < ApplicationController
.
.
.
def show
#user=User.find(params[:id])
#posts = #user.posts.paginate(page: params[:page])
end
.
.
.
end
user.rb:
class User < ActiveRecord::Base
has_many :posts, dependent: :destroy
before_save { self.email = email.downcase }
before_create :create_remember_token
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
validates :email, presence: true,
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, length: { minimum: 6 }
def User.new_remember_token
SecureRandom.urlsafe_base64
end
def User.encrypt(token)
Digest::SHA1.hexdigest(token.to_s)
end
private
def create_remember_token
self.remember_token = User.encrypt(User.new_remember_token)
end
end
post.rb:
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :id
default_scope -> { order('created_at DESC') }
validates :user_id, presence: true
validates :content, presence: true, length: { maximum: 140 }
validates :title, presence: true, length: { maximum: 85 }
end
This code allows creating posts with a title and displaying it on user show action (like Twitter, Michael Hartl guide). Q: How i should modify this, that besides displaying post on user profile, it also create a new page with same post contnent,that could be commented,shared or onother action (only in created page!not user profile). After all, Q: how to display ALL USERS' posts on main page, I'm trying to do next:
welcome_controller.rb:
class WelcomeController < ApplicationController
layout "application"
def index
end
def about
end
def home
#post = current_user.posts.build if signed_in?
#articles = User.all.posts.paginate(page: params[:page])
end
end
_post.html.erb:
<li>
<span class="article"><%= post.title %></span>
<span class="content"><%= post.content %></span>
<span class="timestamp">
<div id="info">
<p>Posted <%= time_ago_in_words(post.created_at) %></p>
</div>
</span>
</li>
home.html.erb:
.
.
.
<div id="articles">
<%= render #post %>
</div>
.
.
.
but I've got the following error:
NoMethodError in WelcomeController#home
undefined method `posts' for #<ActiveRecord::Relation::ActiveRecord_Relation_User:0x4d151d8>
To increase intelligibility of my description, I'm attach the picture.
You can't do posts of all users, so this:
#articles = User.all.posts.paginate(page: params[:page])
won't work. Given this is not a polymorphic association, you should be able to just do:
#articles = Post.all.paginate(page: params[:page])

Unable to make after_create to work

I'm creating a ticket booking app for my sample project using Ruby on Rails 4.1. Three are three models - Events, Tickets and Bookings. Events have many tickets and bookings. Tickets have many bookings and they belong to events. Bookings belongs to events and tickets.
Here's the ticket model:
class Ticket < ActiveRecord::Base
belongs_to :event
has_many :bookings
belongs_to :user
validates :ticket_name, :terms_conditions, presence: true
validates_date :booking_start_date, on: :create, on_or_after: :today
validates_date :booking_end_date, after: :booking_start_date
validates :ticket_price, presence: true, numericality: true
validates :ticket_quantity, :minimum_quantity, :maximum_quantity, presence: true, numericality: { only_integer: true }
before_create :check_start_date
before_update :check_start_date
def check_start_date
if (self.booking_start_date >= DateTime.now) && (self.booking_end_date != DateTime.now)
self.status = 'Open'
else
self.status = 'Closed'
end
end
def maximum_tickets_allowed
(1..maximum_quantity.to_i).to_a
end
end
The bookings model:
class Booking < ActiveRecord::Base
belongs_to :event
belongs_to :ticket
has_many :charges
validates :buyer_name, presence: true
validates :order_quantity, presence: true, numericality: { only_integer: true }
validates :email, presence: true, format: { with: /\A[^#\s]+#([^#.\s]+\.)+[^#.\s]+\z/ }
def total_amount
ticket.ticket_price.to_i * order_quantity.to_i
end
def check_ticket_count
count = ticket.ticket_quantity.to_i - order_quantity.to_i
ticket.update_attribute(:ticket_quantity, count)
end
end
The bookings controller:
class BookingsController < ApplicationController
before_action :authenticate_user!, only: [:index, :destroy]
def index
#event = Event.find(params[:event_id])
##bookings = #event.bookings.all
#bookings = #event.bookings.paginate(page: params[:page], per_page: 10)
end
def new
#event = Event.find(params[:event_id])
#ticket = #event.tickets.find(params[:ticket_id])
#booking = Booking.new
end
def create
#event = Event.find(params[:event_id])
#ticket = #event.tickets.find(params[:ticket_id])
#booking = #event.bookings.create(booking_params)
#booking.ticket = #ticket
Stripe.api_key = Rails.configuration.stripe[:secret_key]
#token = params[:stripeToken]
#amount = #booking.total_amount
begin
customer = Stripe::Customer.create(
:email => #booking.email,
:card => params[:stripeToken]
)
charge = Stripe::Charge.create(
:customer => customer.id,
:amount => #amount,
:currency => "usd",
#:card => token
)
flash[:notice] = "Thanks for the order"
rescue Stripe::CardError => e
flash[:danger] = e.message
end
if #booking.save
BookingMailer.booking_confirmation_user(#booking).deliver
flash[:notice] = "You've successfully booked the tickets!"
redirect_to [#event, #booking]
else
render 'new'
end
end
def show
#event = Event.find(params[:event_id])
#booking = #event.bookings.find(params[:id])
end
def destroy
#event = Event.find(params[:event_id])
#booking = #event.bookings.find(params[:id])
#booking.destroy
redirect_to event_bookings_path
end
private
def booking_params
params.require(:booking).permit(:buyer_name, :email, :mobile, :address, :order_quantity, :ticket_id)
end
end
The check_ticket_count method in Booking.rb works fine as long as I don't an add after_create :check_ticket_count method. The moment I add that after_create method, the app throws the "undefined method `ticket_quantity' for nil:NilClass" error. How to get past this?
Looks like you should first associate ticket with booking, and only then create booking.
#booking = #event.bookings.new(booking_params)
#booking.ticket = #ticket
#booking.save
Hopefully you will ask questions with less code next time.

Rails 4 - undefined method 'attributes' for nil:NilClass

I have a many-to-many relationship between Users and Documents. Users can invite other users to collaborate on their documents by entering their email. The application will look up a user by email and set the user_id in DocumentUser. If a user is not found, I am getting this error: Rails 4 - undefined method 'attributes' for nil:NilClass. This error occurs in DocumentUsersControlller.create. How can I prevent this error using best practices? Also, why doesn't the presence validator prevent this error?
Here are the relevant controllers and models:
class DocumentUsersController < ApplicationController
def new
#document = Document.find_by link_key: params[:document_id]
#document_user = #document.document_users.build
end
def create
#document = Document.find_by link_key: params[:document_id]
#document_user = #document.document_users.build(document_user_params)
user = User.find_by email: params[:document_user][:email]
#document_user.user = user if user
respond_to do |format|
if #document_user.save #error occurs here if user is Nil
format.html { redirect_to #document, notice: 'User Invitation was sent.'}
format.json { render action: 'show', status: :created, location: #document_user }
else
format.html { render action: 'new' }
format.json { render json: #document_user.errors, status: :unprocessable_entity }
end
end
end
def destroy
end
private
def document_user_params
params.require(:document_user).permit(:email, :admin, :editor, :viewer)
end
end
class DocumentUser < ActiveRecord::Base
include KeyCreator
belongs_to :document
belongs_to :user
before_create :before_create
attr_accessor :email
validates :user, uniqueness: { message: "has already been invited" }
validates :user, presence: true
validate :email_matches_user
def to_param
"#{invite_key}".parameterize
end
private
def before_create
now = DateTime.now
self.added_date = now
self.modified_date = now
self.invite_key = create_key
self.api_key = create_key
end
def email_matches_user
unless User.exists? email: email
errors.add(:email, "does not match any existing user")
end
end
end
class User < ActiveRecord::Base
include KeyCreator
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable
has_many :document_users
has_many :documents, :through => :document_users
before_create :before_create
private
def before_create
self.api_key = create_key
end
end
class Document < ActiveRecord::Base
include KeyCreator
has_many :document_users, dependent: :destroy
has_many :users, :through => :document_users
before_create :before_create
def self.created_by(user)
Document.joins(:document_users).where(:document_users => {:user => user, :creator => true}).order(:name)
end
def self.guest_in(user)
Document.joins(:document_users).where(:document_users => {:user => user, :creator => false}).order(:name)
end
def to_param
"#{link_key}".parameterize
end
private
def before_create
now = DateTime.now
self.content = nil
self.created_date = now
self.modified_date = now
self.deleted_date = nil
self.api_key = create_key
self.link_key = create_key
end
end
Change your email_matches_user. It should be matching
email: with self.email:
def email_matches_user
unless User.exists? email: self.email
errors.add(:email, "does not match any existing user")
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.

Resources