Wrong posts showing up in other board - ruby-on-rails

I current have my project set up like this:
resources :boards, :path => '' do
resources :posts, :path => 'thread' do
resources :replies
On /board1/ only posts from board1 show, same for board2. In /board1/thread/1/ it shows post 1 and the replies to it.
However in /board2/thread/1/ the post that is showing is from board1/thread/1/, and in the reverse board1/thread/2/ shows the post from board2/thread/2/.
Each post has a related board_id in the db, and each reply has the related post_id in the db.
How can I keep these separate?
class Board < ActiveRecord::Base
has_many :posts
has_many :replies, through: :posts
include FriendlyId
friendly_id :name, use: :slugged
accepts_nested_attributes_for :posts, :replies
end
class Post < ActiveRecord::Base
belongs_to :board
has_many :replies, dependent: :destroy
accepts_nested_attributes_for :replies
include FriendlyId
friendly_id :pid, use: :slugged
after_create :set_pid
def set_pid
post_max = self.board.posts.maximum(:pid)
reply_max = self.board.replies.maximum(:pid)
if post_max.to_i < reply_max.to_i
self.update_attributes(:pid => reply_max.to_i + 1)
else
self.update_attributes(:pid => post_max.to_i + 1)
end
end
end
Code to display post in /:board_id/show:
<% #board.posts.find_each do |post| %>
<%= post.subject %>
<%= post.name %>
<%= post.email %>
<%= post.created_at %>
No.<%= post.pid %>
<%= link_to "[reply]", board_posts_path(#board, #post)%>
<br>
<%= post.comment %><br><br>
<%= render "replies/replies" %>
<% end %>
Code to display post in /:board_id/thread/:id:
<p>
<%= #post.subject %>
<%= #post.name %>
<%= #post.email %>
<%= #post.created_at %>
No.<%= #post.pid %>
<br>
<%= #post.comment %>
</p>
Edit:
class RepliesController < ApplicationController
def create
#board = Board.friendly.find(params[:board_id])
#post = Post.friendly.find(params[:post_id])
#reply = #post.replies.create(reply_params)
redirect_to #board
end
private
def reply_params
params.require(:reply).permit(:name, :email, :subject, :comment, :pid)
end
end
class PostsController < ApplicationController
def show
#boards = Board.all
#replies = Reply.all
#post = Post.friendly.find(params[:id])
end
def create
#board = Board.friendly.find(params[:board_id])
#post = #board.posts.create(post_params)
if #post.save
redirect_to #board
else render #board
end
end
private
def post_params
params.require(:post).permit(:name, :email, :subject, :comment, :pid)
end
end

The missing part here is the RepliesController which is the source of the problem if I got the question correctly.
Most probably you have there something like #replies = current_post.replies which fetch all replies of the given post regardless of the current board. Scoping post by board will solve the problem:
current_post = Post.find_by(board_id: params[:board_id], id: params[:post_id])
if current_post
#replies = current_post.replies
end

On your friendly_id declaration in the Post model, you don't have the pid as globally unique. Use this form of friendly_id, instead:
friendly_id :pid, use: :scoped, scope: :board
In this way, duplicate friendly_id values for pid are kept separate by the board that they belong to. This is necessary for slugging nested resources properly. The :scoped value says that it's for nested (scoped) models, and the scope: key indicates that posts is nested within boards. Note that you may have to do this with replies, as well.
You'll also want to make sure that your indexes for your :slug are correct. Typically when the :scope is incorrect, you'll find it when you try to save the record. In this case, it looks like the indexes might not be set correctly to ensure the uniqueness of the board name/post pid combination. Check out Friendly ID 4 Using scoped module for more information.
When you have the indexes sorted out, you'll find that inserting new records will require you to have the friendly_id (based on your pid) already assigned. You may also want to look into using slug candidates to dynamically generate the proper slug at creation time. Also check out slug candidates rails 4 for some usage information.

Related

How can get array values using has_and_belongs_to_many?

Can't save params selected on select box.
Table users:
1id| |name|
1 CR7
2 Messi
Table ejecutives:
1id| |name|
1 Mourinho
2 Guardiola
Table user_ejecutives:
|id| |user_id| |ejecutive_id|
1 1 1
2 2 2
Controller users_controller.rb:
def new
#obj_user = User.new
end
def create
#user = User.new user_params
#user.save
end
def show
#user = User.find(params[:id])
end
private
def user_params
params.require(:user).permit(:name, user_ejecutive_ids: [])
end
Models:
#User.rb
has_many :ejecutives, :through => :user_ejecutives
has_many :user_ejecutives
has_and_belongs_to_many :user_ejecutives, class_name: "User", join_table: "user_ejecutives"#, foreign_key: :user_id, association_foreign_key: :ejecutive_id
#Ejecutive.rb
has_many :user_ejecutives
has_many :users, :through => :user_ejecutives
#UserEjecutive.rb
belongs_to :user
belongs_to :ejecutive
View new.html.erb:
<%= form_for #user do |f| %>
<%= form.text_field :name %>
<%= f.collection_select :user_ejecutive_ids, Ejecutive.all, :id, :name, multiple: true %>
<% end %>
View show.html.erb
<% #user.ejecutives.each do |ejecutive| %>
<%= ejecutive.name %></label>
<% end %>
I'm not getting results on the view show and it show on logs:
SystemStackError (stack level too deep):
If you're just trying to populate the join table (user_ejecutives), you'll want to populate the singular_colletion_ids method:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new user_params
#user.save
end
private
def user_params
params.require(:user).permit(:name, user_ejecutive_ids: [])
end
end
#app/views/users/new.html.erb
<%= form_for #user do |f| %>
<%= f.collection_select :user_ejecutive_ids, User.all, :id, :name, multiple: true %>
<%= f.submit %>
<% end %>
This will assign new user_ejecutives for each new #user you create.
PS User.all is valid in this instance as you're dealing with a new (uncreated) #user record, hence it won't appear in the db.
If you wanted to create new user_ejecutives with each new #user, you'll want to use accepts_nested_attributes_for, which I can explain if required.
Update
So your error is as follows:
Unpermitted parameter: user_ejecutive_ids
... you also have another error...
NoMethodError (undefined method `each' for nil:NilClass):
This is exactly why I don't like your code. Because it doesn't fit to convention, you've go to evaluate whether the params are present etc.
You'll need to use the controller code I posted - it will populate the other table for you, and fix this NilClass error.
--
Join Table
Your user_ejecutives table is a join table.
Your User model should have the following:
#app/models/user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :user_ejecutives, class_name: "User", join_table: "user_ejecutives", foreign_key: :user_id, association_foreign_key: :ejecutive_id
end
You'll have to remove the id column from your user_ejecutives table (as per the definition here). The importance of this is that it gives you the ability to populate the singular_collection_ids method (in your case user_ejective_ids), as per my recommended code.
Try the following code.
params.require(:user).permit(:name, :user_ejecutives => [])
Hey, I think you have "ejecutive_id" column declared as integer but when loop through "user_ejecutives" you are getting each value as string, May be this is causing the issue, Kindly update your create action to below.
def create
obj_user = User.new(user_params)
if obj_user.save
params[:user_ejecutives].each do |ejecutive|
user_ejecutive = UserEjecutive.create(user_id: obj_user.id, ejecutive_id: ejecutive.to_i)
user_ejecutive.save
end
end
end

set up helper_method :current_patient Rails 4

How can I set a helper method in my app that when I call it, recognizes a current_patient from a certain medic (can be multiple medics, and multiple patients for a medic) and access to patient :id, this would help me to associate:
Medic with a Patient with a Consultations for current_patient
I need to access on a patient and set id (patient) on a Consultation table foreign key :patient_id
in my create action in the controller I have:
def create
#consultation = Consultation.new(consultation_params.merge({:patient_id => current_patient}))
respond_to ...
end
Is a helper method a good way to do this?
How can I do this?
my models:
class Medic < ActiveRecord::Base
has_many :patients
end
class Patient < ActiveRecord::Base
belongs_to :medic, :foreign_key => :medic_id
has_many :consultations
accepts_nested_attributes_for :consultations
end
class Consultation < ActiveRecord::Base
belongs_to :patient, :foreign_key => :patient_id
end
Thanks for help
Lets start with the routes:
resources :patients do
resources :consultations
end
This will give us the routes for consultations nested under the patient:
Prefix Verb URI Pattern Controller#Action
patient_consultations GET /patients/:patient_id/consultations(.:format) consultations#index
POST /patients/:patient_id/consultations(.:format) consultations#create
new_patient_consultation GET /patients/:patient_id/consultations/new(.:format) consultations#new
edit_patient_consultation GET /patients/:patient_id/consultations/:id/edit(.:format) consultations#edit
patient_consultation GET /patients/:patient_id/consultations/:id(.:format) consultations#show
PATCH /patients/:patient_id/consultations/:id(.:format) consultations#update
PUT /patients/:patient_id/consultations/:id(.:format) consultations#update
DELETE /patients/:patient_id/consultations/:id(.:format) consultations#destroy
So lets say we have a PatientsController#index method which shows all the patients. And in our view we have something like this:
<% #patients.each do |patient| %>
<li>
<p><%= patient.name %></p>
</li>
<% end %>
So lets add a link to create the consultation:
<% #patients.each do |patient| %>
<p><%= patient.name %></p>
<ul class="actions">
<li>
<%= link_to "New consultation",
new_patient_consultation_path(patient) %>
</li>
</ul>
<% end %>
Clicking the link would take us to /patients/6/consultations/new.
So in our ConsultationsController we can access the patient id from the params:
class ConsultationsController < ApplicationController
# We use a callback so that we don't need to do
# #patient = Patient.find(params[:id]) in every action
before_action :set_patient
# GET /patients/:patient_id/consultations/new
def new
#consultation = #patient.consultations.new
end
# POST /patients/:patient_id/consultations
def create
#consultation = #patient.consultations.new(consultation_params)
# ...
end
# ...
private
def set_patient
#patient = Patient.find(params[:patient_id])
end
# ...
end
set_patient is just a private method belonging to the controller. This is not really a case where you would use a helper method.
Its often done when dealing with dealing with authentication since you are getting current_user from the session - independently from the params. If you are creating a helper method it should work everywhere.
There is one final thing you need to to get this to work, the form needs to point to /patients/:patient_id/consultations.
# app/views/consultations/_form.html.erb
<% form_for [#patient, #consultation] do |f| %>
# .. rails does all the magic figuring out the url.
<% end %>
http://guides.rubyonrails.org/routing.html#nested-resources

How to make a path to a paginated url?

When user #1 likes/comments on user #2, user #2 gets a notification:
notifications/_notifications.html.erb
<%= link_to "", notification_path(notification.id), method: :delete, class: "glyphicon glyphicon-remove" %> <%= link_to Comment.find_by(notification.comment_id).user.name, user_path(Comment.find_by(notification.comment_id).user.id) %> commented on <%= link_to "your activity", (notification.activity_id) %>
but upon user #2 clicking the notification it doesn't lead anywhere since I removed the activity_path, if I put it back before (notification.activity_id) we get an error:
No route matches
{:action=>"show", :controller=>"activities", > :id=>nil} missing required keys: [:id]
I don't know if this is possible, but upon clicking on the notification user #2 would be taken to the paginated page where the activity is. There are 20 activities per page via the gem will_paginate so if the activity that was commented on is on page 2 then upon clicking on the activity, user #2 should be directed to: http://0.0.0.0:3000/activities?page=2
This would at least narrow down where the comment is on the activity feed for the user.
activities/index.html.erb
<% #activities.each do |activity| %>
<%= link_to activity.user.name, activity.user %>
<%= render "activities/#{activity.trackable_type.underscore}/#{activity.action}", activity: activity %>
<% activity.activity_likers.each do |user| %>
<%= user.name %>
<% end %>
<%= link_to like_activity_path(:id => activity.id), class: "btn", method: :post do %>
<span class='glyphicon glyphicon-thumbs-up'></span> Like
<% end %>
<%= render "comments/comments", comments: activity.comments %>
<%= render "comments/form", new_comment: Comment.new(commentable_id: activity.id, commentable_type: activity.class.model_name), create_url: :activity_comments_path %>
<% end %>
<%= will_paginate #activities %>
activities_controller.rb
class ActivitiesController < ApplicationController
def index
#activities = Activity.order("created_at desc").paginate(:page => params[:page], :per_page => 20)
end
def show
redirect_to(:back)
end
end
Please let me know if you need anymore code if you find that this is possible to do :)
UPDATE
class NotificationsController < ApplicationController
def index
#notifications = current_user.notifications
#notifications.each do |notification|
notification.update_attribute(:read, true)
activity = Activity.find(notification.activity_id) #Gives "Couldn't find Activity with 'id'=". I then removed ".activity_id" to see what happens next.
index = Activity.order(created_at: :desc).index(activity)
page_number = (index / per_page.to_f).ceil #I am then told "undefined local variable or method `per_page'" so I tried making it :per_page to which I then get: "undefined method `to_f' for :per_page:Symbol"
end
end
def destroy
#notification = Notification.find(params[:id])
#notification.destroy
redirect_to :back
end
end
routes.rb
resources :activities do
resources :comments
resources :notifications
member do
post :like
post :notifications
end
end
UPDATE #2
notifications/index.html.erb
<% if !#notifications.blank? %>
<%= render partial: "notifications/notification", collection: #notifications %>
<% else %>
<p>No notifications... yet</p>
<% end %>
comment.rb
class Comment < ActiveRecord::Base
after_create :create_notification
has_many :notifications
has_many :comment_likes
has_many :likers, through: :comment_likes, class_name: 'User', source: :liker
belongs_to :commentable, polymorphic: true
belongs_to :user
belongs_to :activity
private
def create_notification
#activity = Activity.find_by(self.activity)
#user = User.find_by(#activity.user_id).id
Notification.create(
activity_id: self.activity,
user_id: #user,
comment_id: self,
read: false
)
end
end
_create_notifications.rb
class CreateNotifications < ActiveRecord::Migration
def change
create_table :notifications do |t|
t.references :activity, index: true
t.references :comment, index: true
t.references :user, index: true
t.boolean :read
t.timestamps null: false
end
add_foreign_key :notifications, :activities
add_foreign_key :notifications, :comments
add_foreign_key :notifications, :users
end
end
To determine on which page the activity is located, you could do the following:
activity = Activity.find(notification.activity_id)
index = Activity.order(created_at: :desc).index(activity)
The above will determine the index of the activity within all of the activities.
So, let's say you've got 85 activities. You've got 20 activities per page, so in this case you would have 5 pages, right? Alright, let's assume the above index returns 42. To calculate the page number you would have to do this (assuming that you've got a variable called per_page which is 20):
page_number = (index / per_page.to_f).ceil
You've got index 42. Which you'll have to divide by the number of activities per page (it needs to be a float!), so that would be 20.0. That results in 2.1. ceil that and you've got your page number, which would be 3.
So, to create the correct paginated path, you can now do this:
activities_path(page: page_number)
Update
Now that we know that the activity_id isn't correctly set on the notification we can fix that (also note that the comment_id isn't set either). Change the last part of your Comment model to this:
...
belongs_to :activity
validates :activity_id, presence: true
validates :user_id, presence: true
private
def create_notification
Notification.create(activity_id: self.activity_id, user_id: self.user_id, comment_id: self.id, read: false)
end
I've added two validations here to make sure the activity_id and user_id are set. Now as I said before the comment_id isn't set either. That's because the id is only assigned on save, on create you are just setting it up to be saved. So, change the after_create :create_notification to after_save :create_notification to be able to set the comment_id as well.
That should set the activity_id and comment_id. Now for the next part. Getting the correct page number which should be added to the link in your _notification.html.erb partial.
Add these methods to your Activity class:
def page_number
(index / per_page.to_f).ceil
end
private
def index
Activity.order(created_at: :desc).index self
end
Now change the path in your notifications/_notification.html.erb partial to:
activities_path(page: notification.activity.page_number)
Note: If you get an error about the per_page in the page_number method you probably haven't set the value per_page in the model itself (like this; Basically add self.per_page = 20 to your model right below class Activity < ActiveRecord::Base)
If you decide to do it like this, you can remove the , :per_page => 20 part in your ActivitiesController. If not, simply replace per_page.to_f with 20.to_f or 20.0.
Also, remove the 3 lines from your NotificationsController which you've commented out previously to find out whether the activity_id was set or not. You don't need them anymore, since we've placed them in the Activity class.

I want to acts_as_taggable_on :topics and :places in Ruby on Rails

I have a question model in my rails app. A user can add questions. When adding a question a user can assign it topics and a place.
I followed the railscast on how to set up acts_as_taggable_on for tags, in my case topics.
However, I now want to add the place feature. I had a go myself and had a google around but couldn't find much on having 2 acts_as_taggable items.
If someone could advise me on how to setup a second acts_as_taggable feature on the same acts_as_taggable that would be great
My code so far:
Question.rb
acts_as_taggable
acts_as_taggable_on :topics, :places
questions_controller.rb
def topics
if params[:topic].present?
#questions = Question.tagged_with(params[:topic])
else
#questions = Question.postall
end
end
questions/show.html.erb
<p>Topics: <%= raw #question.tag_list.map { |t| link_to t, topic_path(t) }.join(', ') %></p>
form.html.erb
<%= f.input :tag_list, placeholder: "Topics" %>
you have done it right in model..
just add some code to controller and view
def topics
if params[:topic].present?
#questions = Question.tagged_with([params[:topic]], :on => :topics, :any => true)
elsif params[:place].present?
#questions = Question.tagged_with([params[:place]], :on => :places, :any => true)
else
#questions = Question.postall
end

In Rails, how do I use RESTful actions for a resource that is the join in a many to many relationship?

I have the following models:
class User < ActiveRecord::Base
has_many :subscriptions
end
class Subscription < ActiveRecord::Base
belongs_to :user
belongs_to :queue
end
class Queue < ActiveRecord::Base
has_many :subscriptions
end
I want to have some meta-data in the Subscription class and allow users to maintain the details of each of their subscriptions with each subscriptions meta-data. Queues produce messages, and these will be sent to users who have Subscriptions to the Queue.
As I see it the resource I want to have is a list of subscriptions, ie the user will fill in a form that has all the Queues they can subscribe to and set some metadata for each one. How can I create a RESTful Rails resource to achieve this? Have I designed my Subscription class wrong?
I presently have this in my routes.rb:
map.resources :users do |user|
user.resources :subscriptions
end
But this makes each subscription a resource and not the list of subscriptions a single resource.
Thanks.
This can be done quite easily using accepts_nested_attributes_for and fields_for:
First in the User model you do the following:
class User < ActiveRecord::Base
has_many :subscriptions
accepts_nested_attributes_for :subscriptions, :reject_if => proc { |attributes| attributes['queue_id'].to_i.zero? }
# if you hit scaling issues, optimized the following two methods
# at the moment this code is suffering from the N+1 problem
def subscription_for(queue)
subscriptions.find_or_initialize_by_queue_id queue.id
end
def subscribed_to?(queue)
subscriptions.find_by_queue_id queue.id
end
end
That will allow you to create and update child records using the subscriptions_attributes setter. For more details on the possibilities see accepts_nested_attributes_for
Now you need to set up the routes and controller to do the following:
map.resources :users do |user|
user.resource :subscriptions # notice the singular resource
end
class SubscriptionsController < ActionController::Base
def edit
#user = User.find params[:user_id]
end
def update
#user = User.find params[:user_id]
if #user.update_attributes(params[:user])
flash[:notice] = "updated subscriptions"
redirect_to account_path
else
render :action => "edit"
end
end
end
So far this is bog standard, the magic happens in the views and how you set up the params:
app/views/subscriptions/edit.html.erb
<% form_for #user, :url => user_subscription_path(#user), :method => :put do |f| %>
<% for queue in #queues %>
<% f.fields_for "subscriptions[]", #user.subscription_for(queue) do |sf| %>
<div>
<%= sf.check_box :queue_id, :value => queue.id, :checked => #user.subscribed_to?(queue) %>
<%= queue.name %>
<%= sf.text_field :random_other_data %>
</div>
<% end %>
<% end %>
<% end %>
I found this tutorial very useful, as I was trying to relate Users to Users via a Follows join table: http://railstutorial.org/chapters/following-users

Resources