undefined method error 'Public Activity Gem' - ruby-on-rails

I have a simple rails app and i am trying to add an activity feed on it. For that i am using the Public Activity Gem and was following the this Railscast
but now it's giving me an error:
undefined method `shipment' for #<Shipment:0x6a96578>
My codes are:
activities_controller.rb
class ActivitiesController < ApplicationController
def index
#activities = PublicActivity::Activity.order("created_at desc")
end
end
My Activity index.html.erb file
<h1>feeds</h1>
<% #activities.each do |activity| %>
<div class="activity">
<%= link_to activity.owner.full_name, activity.owner if activity.owner %>
added comment to <%= link_to activity.trackable.shipment.name.activity.trackable.shipment %>
</div>
<% end %>
My comment.rb file
class Comment < ActiveRecord::Base
include PublicActivity::Model
tracked owner: ->(controller, model) { controller && controller.current_user }
belongs_to :shipment
belongs_to :user
end
I have a simple rails app and i am trying to add an activity feed on it. For that i am using the Public Activity Gem and was following the this Railscast
but now it's giving me an error:
undefined method `user_path' for #<#:0x4a90a30>
My codes are:
activities_controller.rb
class ActivitiesController < ApplicationController
def index
#activities = PublicActivity::Activity.order("created_at desc")
end
end
My Activity index.html.erb file
<h1>feeds</h1>
<% #activities.each do |activity| %>
<div class="activity">
<%= link_to activity.owner.full_name, activity.owner if activity.owner %>
added comment to <%= link_to activity.trackable.shipment.name.activity.trackable.shipment %>
</div>
<% end %>
My comment.rb file
class Comment < ActiveRecord::Base
include PublicActivity::Model
tracked owner: ->(controller, model) { controller && controller.current_user }
belongs_to :shipment
belongs_to :user
end
My routes.rb file
Rails.application.routes.draw do
get 'activities/index'
get 'profiles/show'
get 'pages/homepage'
devise_for :users, :controllers => {:registrations => "registrations"}
resources :shipments do
member do
get "like", to: "shipments#upvote"
end
resources :comments
end
# The priority is based upon order of creation: first created -> highest priority.
# See how all your routes lay out with "rake routes".
root "shipments#index"
get '/:id', to: 'profiles#show'
get "mailbox/inbox" => "mailbox#inbox", as: :mailbox_inbox
get "mailbox/sent" => "mailbox#sent", as: :mailbox_sent
get "mailbox/trash" => "mailbox#trash", as: :mailbox_trash
resources :conversations do
member do
post :reply
post :trash
post :untrash
resources :users
end
end
end
stack trace
Rendered activities/index.html.erb within layouts/application (31.2ms)
Completed 500 Internal Server Error in 109ms (ActiveRecord: 78.0ms)
ActionView::Template::Error (undefined method `shipment' for #<Shipment:0x4dc44b
0>):
2: <% #activities.each do |activity| %>
3: <div class="activity">
4: <%= link_to activity.owner.profile_name, activity.owner if activity.o
wner %>
5: added comment to <%= link_to activity.trackable.shipment.name, activi
ty.trackable.shipment %>
6: <% end %>
app/views/activities/index.html.erb:5:in `block in _app_views_activities_index
_html_erb___51428318_40366872'
app/views/activities/index.html.erb:2:in `_app_views_activities_index_html_erb
___51428318_40366872'

What Ryan fails to mention in the Railscast is that you may get activities belonging to different types of trackables.
So if you have set up tracking for the shipment model:
class Shipment < ActiveRecord::Base
include PublicActivity::Model
tracked
# ...
belongs_to :user
has_many :comments, dependent: :destroy
end
And created a few shipment records and a few commends than your activities table will contains something like this
id trackable_type # ...
1 Shipment
2 Shipment
3 Comment
Here are two possible solutions:
1. Filter the query for one type of trackable.
#activities = PublicActivity::Activity.order("created_at desc")
.where(trackable_type: "Comment")
2. Use different partials for each type of trackable:
<% #activities.each do |activity| %>
<%= render partial "activities/#{ activity.trackable.model_name.singular }",
activity: activity,
object: activity.trackable,
owner: activity.owner
%>
<% end %>
What this does is render app/views/activities/_comment.html.erb or _shipment.html.erb depending on the type. We also setup some local variables so that we don't have to write activity.foo.bar.baz.
We also use the object option which creates an instance variable (an # variable) with the same name as the partial. So in it would be #shipment in _shipment.html.erb.
So in your _comment.html.erb you would do:
<div class="activity">
<%= link_to owner.full_name, owner if owner %>
added comment to <%= link_to #comment.shipment.name, #comment.shipment %>
</div>
See also:
http://guides.rubyonrails.org/layouts_and_rendering.html#using-partials
https://github.com/chaps-io/public_activity#displaying-activities

Related

Ruby on Rails: Add form for another model on a form

Within my Ruby on Rails application I am trying to implement a relationship between Group and Contact, whereby one group can contain many contacts and one contact can be part of many groups. I am using a model called Contactgroup to deal with this relationship, and so the tables are:
Group (id, name)
Contact (id, firstname, surname)
Contactgroup (group_id, contact_id)
With example data being:
Groups:
ID Name
1 Singers
2 Drummers
Contacts:
ID Firstname Surname
1 Freddy Mercury
2 Roger Taylor
3 Kurt Cobain
4 Dave Grohl
Contact Groups:
Group_ID Contact_ID
1 1
1 3
1 4
2 2
2 4
What I am trying to do is get it so that when a user creates a group, they can select the contacts that they want to add to that group. This means that there is the group form, whereby the user types the group name, and on this form I want to display checkboxes for each of the user's contacts so that the user can select the contacts they want to add to the group, and when they click submit the new group will be saved in the Group table and the new contact group records will be saved in the Contactgroup table.
This is the app/views/groups/_form.html.erb code:
<%= form_for #group do |f| %>
<% if #group.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(#group.errors.count, "error") %> prohibited this group from being saved:
</h2>
<ul>
<% #group.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= f.label :name %><br>
<%= f.text_field :name %>
</p>
<h2>Add members:</h2>
<%= form_for([#group, #group.contactgroups.build]) do |f| %>
<p>
<%= f.collection_check_boxes(:contact_id, #contacts, :id, :firstname) %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
<p>
<%= f.submit %>
</p>
<% end %>
On here you can see the code I am trying to use to do this:
<h2>Add members:</h2>
<%= form_for([#group, #group.contactgroups.build]) do |f| %>
<p>
<%= f.collection_check_boxes(:contact_id, #contacts, :id, :firstname) %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
<p>
<%= f.submit %>
</p>
<% end %>
I have got this from rails guides (http://guides.rubyonrails.org/getting_started.html) but I get the error undefined methodcontactgroups' for #` and don't think this will give me what I want.
My routes file is:
Rails.application.routes.draw do
get 'sessions/new'
get 'sessions/create'
get 'sessions/destroy'
resources :users
get 'welcome/index'
root 'welcome#index'
resources :contacts
resources :groups do
resources :contactgroups
end
resources :contactgroups
get 'sessions/new'
get 'sessions/create'
get 'sessions/destroy'
controller :sessions do
get 'login' => :new
post 'login' => :create
get 'logout' => :destroy
end
end
My groups_controller:
class GroupsController < ApplicationController
def index
#groups = Group.where(user_id: session[:user_id])
end
def show
#group = Group.find(params[:id])
#members = Contactgroup.where(group_id: #group.id)
end
def new
#group = Group.new
#contacts = Contact.where(user_id: session[:user_id])
end
def edit
#group = Group.find(params[:id])
end
def create
#group = Group.new(group_params)
#group.user_id = session[:user_id]
if #group.save
redirect_to #group
else
render 'new'
end
end
def update
#group = Group.find(params[:id])
if #group.update(group_params)
redirect_to #group
else
render 'edit'
end
end
def destroy
#group = Group.find(params[:id])
#group.destroy
redirect_to groups_path
end
private
def group_params
params.require(:group).permit(:name, :user_id)
end
end
And contactgroups_controller:
class ContactgroupsController < ApplicationController
def destroy
#contactgroup = Contactgroup.find(params[:id])
#contactgroup.destroy
redirect_to(:back)
end
end
My models are as follows:
Contact.rb:
class Contact < ActiveRecord::Base
end
Group.rb:
class Group < ActiveRecord::Base
end
Contactgroup.rb:
class Contactgroup < ActiveRecord::Base
belongs_to :contact
belongs_to :group
end
There must be a simple solution to solve this as I assume it is commonly done on other systems, but I am not sure how to do this.
Can someone please help.
You cannot use form inside form. The correct way to use collection_check_boxes is following.
Replace
<%= form_for([#group, #group.contactgroups.build]) do |f| %>
<p>
<%= f.collection_check_boxes(:contact_id, #contacts, :id, :firstname) %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
With just
<p>
<%= f.collection_check_boxes(:contact_ids, #contacts, :id, :firstname) %>
</p>
This was much simpler than initially thought/suggested.
What I needed to do was change the models to:
Contactgroup
belongs_to :contact
belongs_to :group
Contact
has_many :contactgroups
has_many :groups, through: :contactgroups, :dependent => :destroy
Group
has_many :contactgroups
has_many :contacts, through: :contactgroups, :dependent => :destroy
In the groups_controller I needed to change the new method and params to:
def new
#group = Group.new
#group.contactgroups.build
end
private
def group_params
params.require(:group).permit(:name, :user_id, { contact_ids: [] })
end
And then add the following line of code into app/views/groups/_form.html.erb:
<%= f.collection_check_boxes :contact_ids, Contact.where(user_id: session[:user_id]), :id, :firstname ,{ prompt: "firstname" } %>
This provides me with a checkbox for each contact, and allows contactgroup records to be created from the group form.
Ok so the issue is very simple. You are calling #group.contactgroups but you haven't actually set up that association on the group model yet. only have associations set up from the contactgroup side. So you can do contactgroup.group but not group.contactgroups
Your best bet is to actually model this as habtm - as I mentioned earlier. This is how you'd do that:
Contact.rb:
class Contact < ActiveRecord::Base
has_and_belongs_to_many :groups
end
Group.rb:
class Group < ActiveRecord::Base
has_and_belongs_to_many :contacts
end
Note: you still have the concept of the contact-group for HABTM but using Rails standard naming it would be in your database as the contacts_groups table. Then you could build your forms that way.
With a quick google, here's a S/O question on using checkboxes with HABTM (haven't vetted it for usefulness to your situation): Rails 4 - checkboxes for has_and_belongs_to_many association
Using HABTM is Rails standard practice for lots of very good reasons. It really does actually fit your situation (honest!) and it does not actually break the requirement you have of wanting to see it in the SQL (seriously!).
Give it a try first :)
I can tell you how to break Rails conventions... but it's generally well-understood that you shouldn't break conventions until you know what the conventions are there for.

Voting for nested objects using the Acts As Votable gem

I have a Restaurant model that has_many :dishes, through: dish_categories. I found a post that shows how to write the view code necessary to get things going for the Acts As Votable gem. My situation differs being that the dish model is the nested resource that's being voted upon.
I tried translating the provided code but to no avail. At this point should I create a new controller for dishes and place the votable actions there? If so how would I setup my route so I can accomplish this on my restaurant's show page?
Models
class Restaurant < ActiveRecord::Base
has_many :dish_categories, dependent: :destroy
has_many :dishes, through: :dish_categories
end
class DishCategory < ActiveRecord::Base
belongs_to :restaurant
has_many :dishes, dependent: :destroy
delegate :name, to: :dish_category, prefix: "category"
delegate :restaurant, to: :dish_category
end
class Dish < ActiveRecord::Base
belongs_to :dish_category
end
Restaurants Controller
...
def upvote
#restaurant = Restaurant.find(params[:id])
#dish = Dish.find(params[:id])
#dish.liked_by current_user
redirect_to #restaurant
end
def downvote
#restaurant = Restaurant.find(params[:id])
#dish = Dish.find(params[:id])
#dish.disliked_by current_user
redirect_to #restaurant
end
...
Routes
resources :restaurants do
member do
put "upvote", to: "restaurants#upvote"
put "downvote", to: "restaurants#downvote"
end
end
Restaurants - Show View
...
<% #restaurant.dishes.each do |dish| %>
<div>
<h2><%= dish.category_name %></h2>
<b><%= dish.name %></b>
<%= link_to "Upvote", like_restaurant_path(dish), method: :put %>
<%= link_to "Downvote", dislike_restaurant_path(dish), method: :put %>
</div>
<% end %>
A number of things needed to be done to get this to work. The first order of business was moving my controller action to my dishes controller. I also added two more actions: unlike and undislike for toggle functionailty.
NOTE: Logic for authenticating non-registered for users to liking/disliking dishes would still need to be written but this should help get you started.
Dishes Controller
class DishesController < ApplicationController
before_action :load_restaurant_and_dish, only: [:like, :unlike, :dislike, :undislike]
def like
#dish.liked_by current_user
redirect_to #restaurant
end
def unlike
#dish.unliked_by current_user
redirect_to #restaurant
end
def dislike
#dish.disliked_by current_user
redirect_to #restaurant
end
def undislike
#dish.undisliked_by current_user
redirect_to #restaurant
end
private
def load_restaurant_and_dish
#dish = Dish.find(params[:id])
#restaurant = #dish.restaurant
end
end
Next was configuring my routes to correspond with my restaurant and dish models:
Routes
resources :restaurants do
resources :dishes, only: [:like, :unlike, :dislike, :undislike] do
member do
put "like", to: "dishes#like"
put "unlike", to: "dishes#unlike"
put "dislike", to: "dishes#dislike"
put "undislike", to: "dishes#undislike"
end
end
end
I ended up refactoring my show view and created a few partials to reduce clutter now that there's a little bit of logic involved:
Restaurants - Show View
...
<%= render "restaurants/dish_partials/dishes" %>
...
Dishes Partial
<% #dishes.each do |dish| %>
<div>
<h2><%= dish.category_name %></h2>
<span><b><%= dish.name %></b></span>
<%= render "restaurants/dish_partials/like_toggle", dish: dish %>
</div>
<% end %>
Like Toggle Partial
<% if current_user.liked? dish %>
<%= link_to "Unlike", unlike_restaurant_dish_path(#restaurant, dish), method: :put %>
<% else %>
<%= link_to "Like", like_restaurant_dish_path(#restaurant, dish), method: :put %>
<% end %>
<% if current_user.disliked? dish %>
<%= link_to "Undislike", undislike_restaurant_dish_path(#restaurant, dish), method: :put %>
<% else %>
<%= link_to "Dislike", dislike_restaurant_dish_path(#restaurant, dish), method: :put %>
<% end %>

How to link_to correct show page from feed?

If a user clicks on:
activities/valuations/_create.html.erb
<%= link_to valuation_path(activity) do %>
<%= activity.trackable.name %>
<% end %>
he is directed to, for example, the error:
Couldn't find Valuation with 'id'=24
because the valuation is actually 7. This is because this line of code is trying to find the valuation show page by looking at the id number as an activity instead of the id number as a valuation.
To fix it I tried:
<%= link_to valuation_path(#valuation) do %>
<%= activity.trackable.name %>
<% end %>
but that gives the error:
ActionController::UrlGenerationError in Activities#index
No route matches {:action=>"show", :controller=>"valuations",
:id=>nil} missing required keys: [:id]
How do we rewrite the code to take the user to the correct valuations show page?
activities_controller
class ActivitiesController < ApplicationController
def index
#activities = Activity.order("created_at desc").paginate(:page => params[:page])
end
def show
redirect_to(:back)
end
def like
#activity = Activity.find(params[:id])
#activity_like = current_user.activity_likes.build(activity: #activity)
if #activity_like.save
#activity.increment!(:likes)
flash[:success] = 'Thanks for liking!'
else
flash[:error] = 'Two many likes'
end
redirect_to(:back)
end
end
activity.rb
class Activity < ActiveRecord::Base
self.per_page = 20
has_many :notifications
belongs_to :user
belongs_to :trackable, polymorphic: true
def conceal
trackable.conceal
end
def page_number
(index / per_page.to_f).ceil
end
private
def index
Activity.order(created_at: :desc).index self
end
end
activities/index
<% #activities.each do |activity| %>
<%= link_to activity.user.name, activity.user %></b>
<%= render "activities/#{activity.trackable_type.underscore}/#{activity.action}", activity: activity %>
<% end %>
it seems like you want to do a nested resource,
when you have activities/valuations/_create.html.erb , that means you should have a nested route
#routes.rb
resources :activities do
resources :valuations
end
So your link should be something like
<%= link_to 'link', activities_valuataions_path(activity, #validation) %>
if you could post your routes
rake routes
it should be easy to track down the problem

Deleting a HABTM object

I have the following code which gives me an ActionView::Template::Error (undefined methodroster_path' for #<#:0x007fe34005c208>):`
While in the background it deletes the association player_roster (Has and belongs to many), but i want to delete it when i press the link.
The roster path is nested within a team, but the issue is regarding roster and players.
<%= form_for [#team, #roster] do |f| %>
<% #players.each do |player| %>
<%= player.gamertag %>
<%= link_to "Delete", player.rosters.delete(#roster) %>
<% end %>
<% end %>
:Update
Player.rb
class Player < ActiveRecord::Base
has_and_belongs_to_many :rosters
belongs_to :country
mount_uploader :picture, PictureUploader
end
Roster.rb
class Roster < ActiveRecord::Base
has_and_belongs_to_many :players
has_many :placements
belongs_to :team, touch: true
end
The way you are doing it now will call your delete when the page loads. You can't link to arbitrary Ruby code, you need to link to a route and controller action which will perform your logic.
<%= form_for [#team, #roster] do |f| %>
<% #players.each do |player| %>
<%= player.gamertag %>
<%= link_to "Delete", player_roster_path(player, #roster), method: :delete %>
<% end %>
<% end %>
This link will route to players/:id/rosters/:id with the DELETE HTTP action, which Rails will route to the destroy method.
class RostersController < ApplicationController
def destroy
#player = Player.find(params[:player_id])
#roster = Roster.find(params[:id])
#player.rosters.destroy(#roster)
# redirect/render
end
end
You also will need to setup player_roster_path as a route in config/routes.rb
resources :players do
resources :rosters, only: [:destroy] # you may have other routes here as well
end

NoMethodError in StaticPages#Home (undefined method 'email' for nil:NilClass

I'm trying to extend the Rails Tutorial Sample App to include replies.
I created a Recipient model that contains a user_id to designate the person to whom the reply is addressed and a micropost_id.
I added the following to my User model.
class User < ActiveRecord::Base
...
has_many :replies, foreign_key: "user_id", class_name: "Recipient", dependent: :destroy
has_many :received_replies, through: :replies, source: :micropost
...
def feed
Micropost.from_followed_by_and_replying_to(self)
end
...
end
And this to my Micropost model:
class Micropost < ActiveRecord::Base
belongs_to :user
...
has_many :recipients, dependent: :destroy
has_many :replied_users, through: :recipients, :source => "user"
...
def self.from_followed_by_and_replying_to(user)
followed_ids = "SELECT followed_id FROM relationships
WHERE followed_id = :user_id"
replier_ids = "SELECT micropost_id FROM recipients
WHERE user_id = :user_id"
where("user_id in (#{followed_ids})
OR id in (#{replier_ids}) OR user_id = :user_id",
user_id: user.id)
end
...
end
The StaticPages#home action loads the feed:
class StaticPagesController < ApplicationController
def home
if signed_in?
#micropost = current_user.microposts.build
#feed_items = current_user.feed.paginate(page: params[:page])
end
end
...
end
Then when signed in and visiting the home page, I get NoMethodError in StaticPages#Home for the shared feed_item partial (app/views/shared/_feed_item.html.erb) at this line:
<%= link_to gravatar_for(feed_item.user), feed_item.user %>
It's undefined method 'email' for nil:NilClass (presumably from user.email which the gravatar_for helper method uses.
When I call Micropost.from_followed_by_and_replying_to([some user]) in the rails console, it has no trouble returning both the microposts from followed users as well as replies, so I don't think my db querying is incorrect here. Any help is appreciated, I'm really stumped.
edit: (removed some HTML from these)
app/views/static_pages/home.html.erb:
<% if signed_in? %>
...
<%= render 'shared/user_info' %>
<%= render 'shared/stats' %>
<%= render 'shared/micropost_form' %>
<%= render 'shared/feed' %>
...
<% else %>
...
<% end %>
app/views/shared/_feed.html.erb:
<% if #feed_items.any? %>
<%= render partial: 'shared/feed_item', collection: #feed_items %>
<%= will_paginate #feed_items %>
<% end %>
app/views/shared/_feed_items.html.erb:
<li id="<%= feed_item.id %>">
<%= link_to gravatar_for(feed_item.user), feed_item.user %>
<%= link_to feed_item.user.name, feed_item.user %>
...
</li>
If gravatar_for calls email on the user you pass to it, then the error message is telling you that feed_item.user is nil.
Try putting <% raise feed_item.user %> the line before the link_to, and see if it is indeed nil. Also, a stack trace of the error is one of the most useful things you can put in a SO question.

Resources