NoMethodError (undefined method `primary_key' for User - ruby-on-rails

I'm using the Public Activity gem to create a activity stream on my app. For that I have this setup.
I have set up a friendship model for my app,
create_table "friendships", force: :cascade do |t|
t.integer "user_id"
t.integer "friend_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
And the model looks like this,
belongs_to :user
belongs_to :friend, :class_name => "User"
And this is my user model,
has_many :friendships
has_many :friends, :through => :friendships
has_many :inverse_friendships, :class_name => "Friendship", :foreign_key => "friend_id"
has_many :inverse_friends, :through => :inverse_friendships, :source => :user
And I have tracked my movie model with the Public Activity gem,
include PublicActivity::Model
tracked owner: -> (controller, model) { controller && controller.current_user }
tracked recipient: -> (controller, model) { controller && controller.current_user.friends }
tracked :params => {
:title => :get_title,
:poster => :get_poster
}
has_and_belongs_to_many :users
When I add a new movie, and thus create a new activity, I get this error in my rails console,
NoMethodError (undefined method primary_key' for User::ActiveRecord_Associations_CollectionProxy:Class):
app/controllers/movies_controller.rb:19:increate'
And that refers to this create def in my movies_controller,
def create
#movie = Movie.find_or_create_by movie_params
current_user.movies << #movie
redirect_to :root
end
What's going wrong here?
* as requested the movie parameters *
private
def movie_params
params.require(:movie).permit(
:title,
:image,
:imdb_rating,
:release_date,
:movie_id,
:backdrop
)
end

Okay, found it! Your problem is here:
tracked recipient: -> (controller, model) { controller && controller.current_user.friends }
I think tracked method asks for a model record(or nil), but your controller.current_user.friends return an association, so it does not have primary_key. You could change it to controller.current_user.friends.try(:first) to verify.

Related

Rails ActionController::ParameterMissing (param is missing or the value is empty

I used this guide as a starting point for creating a messaging system from scratch. I had to modify these to handle messages between User and AdminUser. For some reason, whenever I now try to create a new conversation by clicking in my view the following link:
<li><%= link_to admin.email, conversations_path(sendable_id: current_user.id, recipientable_id: admin.id), method: :post %></li>
I encounter the error:
ActionController::ParameterMissing (param is missing or the value is empty: conversation
Did you mean? controller
authenticity_token
action
recipientable_id):
Params are:
=> #<ActionController::Parameters {"_method"=>"post", "authenticity_token"=>"pHmi9kWBLSc5QSJUPQxfNsqSR1fqWCSCBqEVgRMljhgrxB9g4M0ClsdEi2hBLCTjrLLl774T-mnyK8m40LFhNA", "recipientable_id"=>"1", "sendable_id"=>"2", "controller"=>"conversations", "action"=>"create"} permitted: false>
I am directed to the params.permit line in my controller:
class ConversationsController < BaseController
def index
#users = User.all
#admins = AdminUser.all
#conversations = Conversation.all
end
def create
#conversation = if Conversation.between(params[:sendable_id], params[:recipientable_id]).present?
Conversation.between(params[:sendable_id], params[:recipientable_id]).first
else
Conversation.create!(conversation_params)
end
redirect_to conversation_messages_path(#conversation)
end
private
def conversation_params
params.require(:conversation).permit(:sendable_id, :recipientable_id)
end
end
If I remove require(:conversation) I'll get an error:
Validation failed: Sendable must exist, Recipientable must exist
Models:
class User < ApplicationRecord
has_many :conversations, as: :sendable
has_many :conversations, as: :recipientable
end
class AdminUser < ApplicationRecord
has_many :conversations, as: :sendable
has_many :conversations, as: :recipientable
end
class Conversation < ApplicationRecord
belongs_to :sendable, polymorphic: true
belongs_to :recipientable, polymorphic: true
has_many :messages, dependent: :destroy
validates :sendable_id, uniqueness: { scope: :recipientable_id }
end
class Message < ApplicationRecord
belongs_to :conversation
belongs_to :messageable, polymorphic: true
validates_presence_of :body
end
schema:
create_table "conversations", force: :cascade do |t|
t.string "sendable_type"
t.bigint "sendable_id"
t.string "recipientable_type"
t.bigint "recipientable_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["recipientable_type", "recipientable_id"], name: "index_conversations_on_recipientable"
t.index ["sendable_type", "sendable_id"], name: "index_conversations_on_sendable"
end
You need to help Rails understand which polymorphic models you are referencing; if you only provide ids it fails because Rails also needs the polymorphic type (remember: the type is mandatory so Rails can make the link to the actual table. In your case there are two possible types User and AdminUser)
Simply provide the polymorphic types and also add them to the conversation_params method. From looking at your code I'm guessing this is what you're after:
<li><%= link_to admin.email, conversations_path(sendable_id: current_user.id, sendable_type: 'User', recipientable_id: admin.id, recipientable_type: 'AdminUser'), method: :post %></li>

Follow Request Ruby on Rails

I have implemented a Follower/Following Relationship,i want to extend the functionality ,i.e. in my current implementation User 'A' follows User 'B' without the acknowledgement of User 'B'.I want User 'A' to send a request to User 'B' and then User 'B' either accepts or rejects it.I want it to be like the Instagram model not Facebook model.[User A sends follow request to User B.If User B accepts request then User A follows User B and User B is not following User A,to do so User B has to send a request to User A].
My files:
schema.rb
class CreateFollowJoinTable < ActiveRecord::Migration
def change
create_table 'follows' do |t|
t.integer 'following_id', :null => false
t.integer 'follower_id', :null => false
t.boolean :accepted, default: false
t.timestamps null: false
end
add_index :follows, :following_id
add_index :follows, :follower_id
add_index :follows, [:following_id, :follower_id], unique: true
end
end
app/models/follow.rb
class Follow < ActiveRecord::Base
belongs_to :follower, foreign_key: 'follower_id', class_name: 'User'
belongs_to :following, foreign_key: 'following_id', class_name: 'User'
end
app/models/user.rb
has_many :follower_relationships, foreign_key: :following_id, class_name: 'Follow'
has_many :followers, through: :follower_relationships, source: :follower
has_many :following_relationships, foreign_key: :follower_id, class_name: 'Follow'
has_many :following, through: :following_relationships, source: :following
def follow(user_id)
following_relationships.create(following_id: user_id)
end
def unfollow(user_id)
following_relationships.find_by(following_id: user_id).destroy
end
routes.rb
post ':user_name/follow_user', to: 'relationships#follow_user', as: :follow_user
post ':user_name/unfollow_user', to: 'relationships#unfollow_user', as: :unfollow_user
app/controllers/relationships_controller.rb
class RelationshipsController < ApplicationController
def follow_user
#user = User.find_by! user_name: params[:user_name]
if current_user.follow #user.id
respond_to do |format|
format.html { redirect_to root_path }
format.js
end
end
end
def unfollow_user
#user = User.find_by! user_name: params[:user_name]
if current_user.unfollow #user.id
respond_to do |format|
format.html { redirect_to root_path }
format.js
end
end
end
end
first you need to add status:boolean default:false to your following_relationships class
then you need to make a controller that notify the followed user about a new follower,
then you need make another controller for the followed user to edit the status from false to be true
like
def accept_follower
#relationships = current_user.following_relationships.find_by(follower_id: params[:follower_id])
#relationships.update_attributes(active: true)
end
def ignore_follower
current_user.following_relationships.find_by(follower_id: params[:follower_id]).destroy
end
then you need to fix your query about follower/following only select where status= true
has_many :followers, -> { where(status: true) } through: :follower_relationships, source: :follower

Has_and_belongs_to same model

I have and User model that will need to 'own' itself. Thats because the only way to a user connect with another is if they have this 'bound'.
So... I created a 'join model':
class CreateUserConnections < ActiveRecord::Migration
def change
create_table :user_connections, id: false do |t|
t.string :token
t.integer :user_a_id, :null => false
t.integer :user_b_id, :null => false
t.timestamps
end
add_index :user_connections, :token, unique: true
add_index :user_connections, [:user_a_id, :user_b_id], unique: true
end
end
and
class UserConnection < ActiveRecord::Base
belongs_to :user
belongs_to :user_a, :class_name => 'User'
belongs_to :user_b, :class_name => 'User'
before_create :generate_token
protected
def generate_token
self.token = loop do
random_token = SecureRandom.urlsafe_base64(nil, false)
break random_token unless UserConnection.exists?(token: random_token)
end
end
end
and then created the relation on my user model:
#unlocked users
has_and_belongs_to_many :users,
:join_table => "user_connections",
:foreign_key => "user_a_id",
:association_foreign_key => "user_b_id"
The problem is that when I create a relation with a user like:
User.find(1).users << User.find(2)
It creates the own relation from User 1 to User 2, but I tought that with the many_to_many relation the ownership relation from user 2 to 1 would be automatic.
What I'm missing here?
Thanx in advance
I should create the reverse realationship by myself.

nested form not displaying in Rails

I have a form for a model called isp, which 'has_many' isp accounts. the isp account belongs to to 'isp'.
There is a validation on the isp_account that means it cant be added if there isnt an isp_id, so my reasoning is to created a nested form. I created the nested form like so
= simple_form_for #isp, :html => { :multipart => true } do |f|
= f.input :title
= f.simple_fields_for :isp_accounts do |tag|
= tag.input :title, label: "Tag Name"
however the nested attribute isnt being displayed. There are no errors etc. Why is this? Am I approaching this in the best way? is this the only way?
here's the code
ISP MODEL
class Isp < ActiveRecord::Base
has_many :isp_accounts, dependent: :destroy
has_many :deployments, through: :servers
has_many :servers, through: :isp_accounts
validates :title, presence: true
accepts_nested_attributes_for :isp_accounts
end
ISP ACCOUNTS MODEL
class IspAccount < ActiveRecord::Base
belongs_to :isp
has_many :deployments, through: :servers
has_many :servers, dependent: :destroy
validates :title, presence: true
validate :check_associates
private
def check_associates
associated_object_exists Isp, :isp_id
end
end
ISP ACCOUNT CONTROLLER
....
def new
#isp_account = IspAccount.new
end
def update
#isp_account.update_attributes(isp_accounts_path)
if #isp_account.save
record_saved
return redirect_to(isp_accounts_path)
else
check_for_errors
return render('/isp_accounts/edit')
end
end
private
def get_isp_accounts
#isp_account = IspAccount.all
end
def get_isp_account
#isp_account = IspAccount.find(params_isp_accounts)
end
def params_isp_accounts
params.require(:isp_account).permit!
end
end
....
def new
#isp = Isp.new
end
def update
#isp.update_attributes(params_isp)
if #isp.save
record_saved
return redirect_to(isps_path)
else
check_for_errors
return render('new')
end
end
private
def params_isp
params.require(:isp).permit(:title, isp_accounts_attributes: [:id, :title])
end
def get_isp
#isp = Isp.where(id: params[:id]).first
unless #isp
record_not_found
return redirect_to(isps_path)
end
end
def get_isps
#isp = Isp.all.order(:title)
end
end
SCHEMA
create_table "isp_accounts", force: true do |t|
t.string "title"
t.integer "isp_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "isps", force: true do |t|
t.string "title"
t.datetime "created_at"
t.datetime "updated_at"
end
ok i got it. I was missing the new bit for that attribute in my controller. pretty basic really.
def new
#isp = Isp.new
#isp.isp_accounts.new
end

specify columns from has many through association Rails

I have three models.
User
has_many :boards through => :celebrations
has_many :celebrations, :dependent => :destroy
Board
has_many :celebrations, :dependent => :destroy
has_many :users, :through => :celebrations do
def by_role(role)
find(:all, :conditions => ["celebrations.role = ?", role])
end
end
Celebration
:belongs_to :user
:belongs_to :board
Celebration Table
create_table :celebrations do |t|
t.column :board_id, :int, :null => false
t.column :user_id, :int, :null => false
t.column :role, :string, :null => false
t.column :token, :string
t.column :accepted, :boolean, :default => false
t.timestamps
In the controller:
#board = Board.find(session[:board_id])
#friends = #board.users.by_role("FRIEND")
In the view:
<% for friend in #friends do %>
<%= friend.name %>
<%= friend.email %>
However when I do the following:
<% friend.celebration.accepted %>
I get the following error:
undefined method `celebration' for #<User:0x104788c00>
How can I access the column 'accepted' in the celebrations table returned along with the record using the model extension "by_role(role)".
Thank you for your help in advance.
You can eager load the celebrations like this:
Board model:
Board
has_many :celebrations, :dependent => :destroy
has_many :users, :through => :celebrations do
def by_role(role)
find(:all, :conditions => ["celebrations.role = ?", role], :include => :celebrations)
end
end
With :include you will have one more hit:
Celebration Load (0.2ms) SELECT celebrations.* FROM celebrations WHERE (celebrations.user_id IN (5,6))
that loads the celebrations data for users.
This will avoid in this particular case two hits that would be needed to retrieve celebrations for each user. If you have 10 users you will end with one hit instead of 10.
Eager loading loads all the data you will need to cycle through in association with one query instead of n queries.
The problem remains for the accepted data because you will have an array of celebrations due to the has_many relationship with user.
You can use something like this:
<% friend.celebrations.map{|celeb| celeb.accepted }.join(",") %>

Resources