I have like and dislike buttons that are working, but the error message for voting more than once is not.
The object type that User can vote on is called Course. I'm listing all courses on courses index page along with their like/dislike buttons. I am trying to make it so if you try to like or dislike the same course object twice, the page renders a flash message informing you that you can only vote once.
Here is the controller for likes.
#likeables_controller.rb
class LikeablesController < ApplicationController
before_action :logged_in_user
def create
course = Course.find(params[:liked_id])
count = course.likers.count
current_user.like(course)
new_count = course.likers.count
if new_count > count
redirect_to courses_url
else
# This isn't working
flash[:danger] = "You can only vote on a course once!"
redirect_to courses_url
end
end
def destroy
like = Likeable.find(params[:id])
like.destroy
redirect_to courses_url
end
end
My courses index is rendering courses partial like so:
# index.html.erb
<%= render #courses %>
The course partial in turn renders the like and dislike buttons
# _course.html.erb
<ul class="stats">
<li>
<%= render partial: 'dislike', locals: {course: course} %>
</li>
<li>
<%= render partial: 'like', locals: {course: course} %>
</li>
</ul>
# _like.html.erb
<%= form_for(current_user.likeables.build, remote: true) do |f| %>
<div><%= hidden_field_tag :liked_id, course.id %></div>
<%= button_tag type: 'submit', class: "btn" do %>
<i class="fa fa-thumbs-o-up" aria-hidden="true"></i>
<span id="likers"><%= course.likers.count %></span>
<% end %>
<% end %>
And incase it's needed, here are the relevant parts of my models and schema:
create_table "dislikeables", force: :cascade do |t|
t.integer "disliker_id"
t.integer "disliked_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["disliked_id"], name: "index_dislikeables_on_disliked_id"
t.index ["disliker_id", "disliked_id"],
name: "index_dislikeables_on_disliker_id_and_disliked_id", unique: true
t.index ["disliker_id"], name: "index_dislikeables_on_disliker_id"
end
create_table "likeables", force: :cascade do |t|
t.integer "liker_id"
t.integer "liked_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["liked_id"], name: "index_likeables_on_liked_id"
t.index ["liker_id", "liked_id"],
name: "index_likeables_on_liker_id_and_liked_id", unique: true
t.index ["liker_id"], name: "index_likeables_on_liker_id"
end
My models:
# user.rb
class User < ApplicationRecord
# User has many courses
has_many :courses, dependent: :destroy
has_many :likeables, foreign_key: "liker_id",
dependent: :destroy
has_many :likes, through: :likeables, source: :liked
has_many :dislikeables, foreign_key: "disliker_id",
dependent: :destroy
has_many :dislikes, through: :dislikeables, source: :disliked
def like(course)
likes << course
end
def dislike(course)
dislikes << course
end
end
# course.rb
class Course < ApplicationRecord
has_one :user
has_many :likeables, foreign_key: "liked_id",
dependent: :destroy
has_many :likers, through: :likeables, source: :liker
has_many :dislikeables, foreign_key: "disliked_id",
dependent: :destroy
has_many :dislikers, through: :dislikeables, source: :disliker
def liker?(user)
likes.include?(user)
end
def disliker?(user)
dislikes.include?(user)
end
end
# likeable.rb
class Likeable < ApplicationRecord
belongs_to :liked, class_name: "Course"
belongs_to :liker, class_name: "User"
validates :liked_id, presence: true
validates :liker_id, presence: true
end
# dislikeable.rb
class Dislikeable < ApplicationRecord
belongs_to :disliked, class_name: "Course"
belongs_to :disliker, class_name: "User"
validates :disliked_id, presence: true
validates :disliker_id, presence: true
end
there's a problem in your action,
def create
like = current_user.likes.where(liked_id: params[:liked_id]).first_or_initialize
if like.new_record? && like.save
redirect_to courses_url
else
redirect_to courses_url, flash: { danger: "You can only vote on a course once!" }
end
end
Maybe in controller try:
redirect_to courses_url, flash: { danger: "You can only vote on a course once!" }
And make sure in the application layout, you have the renderer for flash messages.
Try this one
redirect_to courses_url, alert: "You can only vote on a course once!"
Related
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>
This question follows up on Rails has_many :through association: save instance into join table and I am restating things here for more clarity.
In our Rails app, there are 3 models:
class User < ActiveRecord::Base
has_many :administrations, dependent: :destroy
has_many :calendars, through: :administrations
end
class Administration < ActiveRecord::Base
belongs_to :user
belongs_to :calendar
end
class Calendar < ActiveRecord::Base
has_many :administrations, dependent: :destroy
has_many :users, through: :administrations
end
And here are the corresponding migrations:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :first_name
t.string :last_name
t.string :email
t.integer :total_calendar_count
t.integer :owned_calendar_count
t.timestamps null: false
end
end
end
class CreateAdministrations < ActiveRecord::Migration
def change
create_table :administrations do |t|
t.references :user, index: true, foreign_key: true
t.references :calendar, index: true, foreign_key: true
t.string :role
t.timestamps null: false
end
end
end
class CreateCalendars < ActiveRecord::Migration
def change
create_table :calendars do |t|
t.string :name
t.timestamps null: false
end
end
end
Here is what we are trying to accomplish:
When a logged in user (current_user) creates a calendar, we should:
Create a new #calendar and save it to the Calendar table
Assign the "Creator" role to the user (current_user) for this newly created calendar through the Role column in the Administration table
Increment the total_calendar_count and the owner_calendar_count columns of the User table
In order to do that, we think we need to work on calendars#create.
In the CalendarsController, we already have the following code:
def create
#calendar = current_user.calendars.create(calendar_params)
if #calendar.save
flash[:success] = "Calendar created!"
redirect_to root_url
else
render 'static_pages/home'
end
end
And we collect data from users through the following _calendar_form.html.erb form:
<%= form_for(#calendar) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_field :name, placeholder: "Your new calendar name" %>
</div>
<%= f.submit "Create", class: "btn btn-primary" %>
<% end %>
We are considering updating the controller as follows:
def create
#calendar = current_user.calendars.create(calendar_params)
#current_user.total_calendar_count += 1
#current_user.owned_calendar_count += 1
current_user.administrations << #calendar.id
#calendar.administration.role = 'Creator'
if #calendar.save
flash[:success] = "Calendar created!"
redirect_to root_url
else
render 'static_pages/home'
end
end
ActiveRecord::AssociationTypeMismatch in CalendarsController#create
Administration(#70307724710480) expected, got Fixnum(#70307679752800)
unless record.is_a?(reflection.klass) || record.is_a?(reflection.class_name.constantize)
message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
raise ActiveRecord::AssociationTypeMismatch, message
end
end
app/controllers/calendars_controller.rb:7:in `create'
How can we make it work?
This line is actually causing the error: current_user.administrations << #calendar.id.
current.administrations expects an object of type Administration while you are passing a Fixnum into it.
You can accomplish the same functionality in the following way:
current_user.administrations.create(calendar_id: #calendar.id)
Edit:
As OP asked in comments that it is a good practice or not. See, there is rule that says that controllers should be skinny, and models should be fatty. Well, it means you should try to write minimum code, and all the logic and fetching of objects should be there in models. But that isn't the case in your code scenario. You should move your code into model, and then call that into your controller.
Here's how:
class User < ActiveRecord::Base
def add_calendar_and_role(calendar_id, role)
self.administrations.find_by(calendar_id: calendar_id).update(role: role)
end
end
This way, your code reduces to just:
current_user.add_calendar_and_role(#calendar.id, 'Creator')
And on the same way, you can further refactor your controller code.
How can we give the user the option to make activities private? This would give users privacy for posts they want for their eyes only.
Here was one of my attempts, which gives me:
NoMethodError in ActivitiesController#index
undefined method 'public_activities' for line: #activities = Activity.public_activities.order("created_at desc").where(current_user.following_ids)
class ActivitiesController < ApplicationController
def index #Added .public_activities
#activities = Activity.public_activities.order("created_at desc").where(user_id: current_user.following_ids)
end
end
class Activity < ActiveRecord::Base
belongs_to :user
has_many :comments, as: :commentable
belongs_to :trackable, polymorphic: true
def public?
!private
end
end
create_table "activities", force: true do |t|
t.boolean "private", default: false
t.integer "user_id"
t.string "action"
t.integer "trackable_id"
t.string "trackable_type"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
class User < ActiveRecord::Base
has_many :activities
def public_activities
activities.find(&:public?)
end
end
And in one of the _forms, such as #valuations or #goals, is where the user can make the distinction via his submission:
<%= button_tag(type: 'submit', class: "btn", id: "gold") do %>
<span class="glyphicon glyphicon-plus"></span> Public
<% end %>
<%= button_tag(type: 'submit', class: "btn") do %>
<% :private %><span class="glyphicon glyphicon-plus"></span> Private
<% end %>
Much of this code was inspired from the answer here: How to use private submit to hide from profile?
Thank you!
class User < ActiveRecord::Base
has_many :activities
def public_activities
activities.find(&:public?)
end
end
This has defined a new instance method called public_activities - you will only be able to use it on an instance of a user
class ActivitiesController < ApplicationController
def index #Added .public_activities
#activities = Activity.public_activities.order("created_at desc").where(current_user.following_ids)
end
end
Here you are trying to call a class method on the Activity class instead.
If you want to do the above, then you'll need to create a scope on the Activity class.
in which case, it's better not to repeat the "activities" part in the name, but just call it "public"
eg
class Activity < ActiveRecord::Base
belongs_to :user
has_many :comments, as: :commentable
belongs_to :trackable, polymorphic: true
scope :public, ->{ where(:private => false) }
def public?
private == true ? false : true
end
end
class ActivitiesController < ApplicationController
def index
#activities = Activity.public.order("created_at desc").where(current_user.following_ids)
end
end
I am trying to make a forum application with Rails 4. I want users to have many forums and so I know I need a many-to-many relationship. I have a form to save the title and the description of the new forum. I Have 3 tables so far, users, forums, and forums_users. Everything works great when I create a new form and it gets added to the forums database. My question is how do I get the information to go to the forums_users table? Because right now when I submit my form, it does not add the information to the association table.
Here is my migration file for forums.
def up
create_table :forums do |t|
t.string :title
t.text :description
t.string :logo
t.boolean :is_active, default: true
t.timestamps
end
add_index :forums, :title
create_table :forums_users, id: false do |t|
t.belongs_to :forum, index: true
t.belongs_to :user, index: true
end
end
def down
drop_table :forums
drop_table :forums_users
end
These are my models.
class Forum < ActiveRecord::Base
has_and_belongs_to_many :users
end
class User < ActiveRecord::Base
has_and_belongs_to_many :forums
end
Here is my create method in the Forum Controller
def create
#forum = Forum.new(forum_params)
#forum.save
respond_to do |format|
format.html{redirect_to admin_path, notice: 'New forum was successfully created.'}
end
end
private
def forum_params
params.require(:forum).permit(:title, :description, :logo, :is_active)
end
And here is the form you submit.
= simple_form_for(:forum, url: {action: :create, controller: :forums}) do |f|
= f.error_notification
.form-inputs
= f.input :title, autofocus: true
= f.input :description, as: :text
.form-actions
= f.button :submit
Thank you in advance.
If you want to get the data from your join table forum_users then use has_many :through
class Forum < ActiveRecord::Base
has_many :users, through: :forum_users
end
class User < ActiveRecord::Base
has_many :forums, through: :forum_user
end
class ForumUser < ActiveRecord::Base
belongs_to :user
belongs_to :forum
end
Now you can access/fetch the forum_users table data using UserForum Model
Create the forum using a reference to the current user, for example:
#forum = current_user.forums.create(forum_params)
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