I've already setup a notification for whenever a user posts a review on a book, the book.user gets notified in this partial: views/notifications/_book.html.erb. At the same time, I trying to create notification to a user's followers for whenever the user they're following posts a new book. IMO, supposed to create a book partial to render the notification view but I already have a duplicate of the book partial to show reviews notifications. Now I don't know if I can still tweak in in the same file or create something else. I'm using https://github.com/rails-engine/notifications.
I've implemented following relationships which is working well on my app from this tutorial https://www.devwalks.com/lets-build-instagram-with-ruby-on-rails-part-6-follow-all-the-people/
This is what I've done so far with the codes
user.rb
has_many :books, dependent: :destroy
has_many :chapters, dependent: :destroy
has_many :reviews, dependent: :destroy
has_many :genres
has_many :ratings
review.rb
belongs_to :book
belongs_to :user
after_commit :create_notifications, on: :create
private
def create_notifications
Notification.create do |notification|
notification.notify_type = 'book'
notification.actor = self.user
notification.user = self.book.user
notification.target = self
notification.second_target = self.book
end
end
views/notifications/_book.html.erb
<div class=''>
<%= link_to notification.actor.username, main_app.profile_path(notification.actor.username) %> reviewed
<%= link_to notification.second_target.title, main_app.book_path(notification.second_target) %>
</div>
<div class=''>
<% unless notification.target.blank? %>
<div class="review-rating" data-score="<%= notification.target.rating %>"></div>
<%= notification.target.comment %>
<% end %>
</div>
book.rb
belongs_to :user
has_many :chapters, dependent: :destroy
after_commit :create_notifications, on: :create
def create_notifications
self.user.followers.each do |follower|
Notification.create(notify_type: 'book',
actor: self.user,
user: follower,
target: self,
second_target: self.book)
end
end
So, if I've to render in the same partial, how should I? or maybe I've to do it another way?
After lots of studying I got to know that a notify_type partial must be must be created which must also correspond to the book's notify_type.
i.e I created a
notify_type: "new_book"
Name of partial= '_new_book.html.erb'
Related
In my Rails 7 app I have parks and users. Users can mark parks as favorite and visited. I want to allow the currently logged in user to filter parks based on both whether they've favorited the park, AND whether they've visited the park.
Currently, the filters work if I only include one of them. But if I add both filters to the form, I get the following error:
ActiveRecord::StatementInvalid in Parks#index
PG::UndefinedTable: ERROR: missing FROM-clause entry for table "favorited_users_parks"
LINE 1: ...rks"."id" AND NOT ("users"."id" NOT IN (44))) AND "favorited...
Note - this seems very very similar to the issues described here, but I'm using Rails 7 and in this thread they claim the issue was resolved after updating to Rails 6.1.0: https://github.com/activerecord-hackery/ransack/issues/1119
And this thread seems to have some solutions that worked for other people but I don't understand how to use joins and ransacks well enough to understand how it applies to my own code: https://github.com/activerecord-hackery/ransack/issues/542
Here is the code:
views/parks/filters.erb
<div class="form-group">
<%= f.label :favorited_users, 'Saved to favorites' %>
<%= f.check_box :favorited_users_id_in, { class: "form-check-input" }, current_user&.id %>
</div>
<div class="form-group">
<%= f.label :visited_users_id, 'Not yet visited' %>
<%= f.check_box :visited_users_id_not_in, { class: "form-check-input" }, current_user&.id %>
</div>
controllers/parks_controller.rb
class ParksController < ApplicationController
def index
#parks = #q.result(distinct: true).paginate(page:params[:page], :per_page => 24)
end
end
models/park.rb
class Park < ApplicationRecord
has_many :visits, dependent: :destroy
has_many :visited_users, through: :visits, source: :user
has_many :favorites, dependent: :destroy
has_many :favorited_users, through: :favorites, source: :user
end
models/user.rb
class User < ApplicationRecord
has_many :visits, dependent: :destroy
has_many :visited_parks, through: :visits, source: :park
has_many :favorites, dependent: :destroy
has_many :favorited_parks, through: :favorites, source: :park
end
models/favorite.rb
class Favorite < ApplicationRecord
belongs_to :user
belongs_to :park
end
models/visit.rb
class Visit < ApplicationRecord
belongs_to :user
belongs_to :park
end
I haven't looked too much into it, seems the issue is still present. Solution is just sitting in a comment:
https://github.com/activerecord-hackery/ransack/issues/542#issuecomment-245700688
def index
#q = Park.ransack(params[:q]) # this is just an object for the form
#parks = search(Park.all) # this is for actual search results
end
private
def search(collection)
results = collection
params[:q]&.each { |k, v| results = results.ransack(k => v).result }
results
end
Just changing the order of the inputs also works.
You might want to skip hidden input for checkboxes, otherwise you send 0 when unchecked:
# vvvvvvvvvvvvvvvvvvvvv
<%= f.check_box :favorited_users_id_in, { include_hidden: false, class: "form-check-input" }, current_user&.id %>
# or like this vvv
<%= f.check_box :favorited_users_id_in, { class: "form-check-input" }, current_user&.id, nil %>
I have a pretty basic Rails 4 app, and am using Cocoon's nested forms to manage the has_many... :through model association.
class Student < ActiveRecord::Base
has_many :evaluations
has_many :assessments, through: :evaluations
# ... etc
end
class Evaluation < ActiveRecord::Base
belongs_to :student
belongs_to :assessment
# ... etc
end
class Assessment < ActiveRecord::Base
has_many :evaluations
has_many :students, through: :evaluations
accepts_nested_attributes_for :evaluation, reject_if: :all_blank
# ... etc
end
When I use Cocoon in the View, I want to use the New Assessment view to pre-fill all the Student records in order to create a new Evaluation for each one. I don't want to have to do some hacky logic on the controller side to add some new records manually, so how would I structure the incoming request? With Cocoon I see that requests have some number in the space where the id would go (I've replaced these with ?? below).
{"utf8"=>"✓", "authenticity_token"=>"whatever", "assessment"=>{"description"=>"quiz 3", "date(3i)"=>"24", "date(2i)"=>"10", "date(1i)"=>"2015", "assessments_attributes"=>{"??"=>{"student_id"=>"2", "grade" => "A"}, "??"=>{"student_id"=>"1", "grade" => "B"}, "??"=>{"student_id"=>"3", "grade"=>"C"}}, }}, "commit"=>"Create Assessment"}
I see in the Coccoon source code that this is somehow generated but I can't figure out how it works with the Rails engine to make this into a new record without an ID.
What algorithm should I use (or rules should I follow) to fill in the id above to make a new record?
"??"
Never a good sign in your params.
With Cocoon I see that requests have some number in the space where the id would go
That ID is nothing more than the next ID in the fields_for array that Rails creates. It's not your record's id (more explained below).
From your setup, here's what I'd do:
#app/models/student.rb
class Student < ActiveRecord::Base
has_many :evaluations
has_many :assessments, through: :evaluations
end
#app/models/evaluation.rb
class Evaluation < ActiveRecord::Base
belongs_to :student
belongs_to :assessment
end
#app/models/assessment.rb
class Assessment < ActiveRecord::Base
has_many :evaluations
has_many :students, through: :evaluations
accepts_nested_attributes_for :evaluations, reject_if: :all_blank
end
This will allow you to do the following:
#app/controllers/assessments_controller.rb
class AssessmentsController < ApplicationController
def new
#assessment = Assessment.new
#students = Student.all
#students.each do
#assessment.evaluations.build
end
end
end
Allowing you:
#app/views/assessments/new.html.erb
<%= form_for #assessment do |f| %>
<%= f.fields_for :evaluations, #students do |e| %>
<%= e.hidden_field :student_id %>
<%= e.text_field :grade %>
<% end %>
<%= f.submit %>
<% end %>
As far as I can tell, this will provide the functionality you need.
Remember that each evaluation can connect with existing students, meaning that if you pull #students = Student.all, it will populate the fields_for accordingly.
If you wanted to add new students through your form, it's a slightly different ballgame.
Cocoon
You should also be clear about the role of Cocoon.
You seem like an experienced dev so I'll cut to the chase - Cocoon is front-end, what you're asking is back-end.
Specifically, Cocoon is meant to give you the ability to add a number of fields_for associated fields to a form. This was discussed in this Railscast...
Technically, Cocoon is just a way to create new fields_for records for a form. It's only required if you want to dynamically "add" fields (the RailsCast will tell you more).
Thus, if you wanted to just have a "static" array of associative data fields (which is I think what you're asking), you'll be able to use fields_for as submitted in both Max and my answers.
Thanks to #rich-peck I was able to figure out exactly what I wanted to do. I'm leaving his answer as accepted because it was basically how I got to my own. :)
assessments/new.html.haml (just raw, no fancy formatting)
= form_for #assessment do |f|
= f.fields_for :evaluations do |ff|
.meaningless-div
= ff.object.student.name
= ff.hidden_field :student_id, value: ff.object.student_id
= ff.label :comment
= ff.text_field :comment
%br/
assessments_controller.rb
def new
#assessment = Assessment.new
#students = Student.all
#students.each do |student|
#assessment.evaluations.build(student: student)
end
end
I'm using Rails 4 and am having trouble figuring out how to set up my models so that one user can leave feedback for another user.
I have tried to follow the answer to the attached question: rails 4 users reviews for users how to do this
My models are: User, Profile, Feedback
The associations between models are:
User.rb
has_one :profile
has_many :feedbacks
accepts_nested_attributes_for :feedbacks
Profile.rb: belongs_to :user
Feedback.rb:
belongs_to :user
has_one :reviewer, :class_name => 'User', :foreign_key => 'reviewer'
The feedback table has attributes called :comment (text), :reviewer (integer) and :created_at(datetime). The reviewer needs to be the user id of the user who left the feedback.
The user model has attributes for :first_name and :last_name.
In my profile show page, I have a link to the feedback partial:
<%= render "feedbacks/feed" %>
That partial then has:
<div class="containerfluid">
<div class="row">
<div class="col-md-10 col-md-offset-1">
<% if can? :read, #feedback && #user.feedback.comment.!blank %>
<%= #profile.user.feedbacks.each do |feedback|%>
<div class="row">
<div class="col-md-10 col-md-offset-1">
<%= #feedback.comment %>
</div>
</div>
<div class="row">
<div class="col-md-3">
<div class="logobox">
<%= "#{#user.feedback.reviewer.first_name} #{#user.feedback.reviewer.last_name}" %>
</div>
</div>
<div class="col-md-3">
<div class="logobox">
<%= #feedback.try(:created_at).try(:strftime, '%d %B %Y') %>
</div>
</div>
<% end %>
<% else %>
<%= render html: "No feedback available".html_safe %>
</div>
<% end %>
</div>
</div>
</div>
What I want from the above is to display the feedback to any user who can read the feedback on the user's (receiving the feedback) profile page. If there is no feedback or the current_user cannot view the feedback, then the else statement should be applied.
I have several problems with the above:
I don't know how to traverse models to link to the feedback. The feedback partial is being displayed in the profile show page. Feedback belongs to user and profile belongs to user.
I am getting an error pointing to the first line of the above (being <% if can? :read, #feedback && user.feedback.comment.!blank? %>. The error says it is expecting 'then'. I have never used 'then' before and it doesn't work when i just type that after blank.
I have an integer attribute in my feedbacks table called :reviewer. I want to store the user id of the user who created the feedback (on the other user) in that field. I don't know how to do this. The SO post copied above suggests I put a line in my feedback model which says: has_one :reviewer, :class_name => 'User', :foreign_key => 'reviewer'. I've tried this but I can't understand what it is doing.
Can anyone help? Perhaps there is another way to approach this problem and I would appreciate help finding it. The SO post I did manage to find was voted too broad, but I can't find any other references with more specific aspects of this problem set out.
My approach would have been using polymorphic associations. Create a polymorphic model.
class Feedback < ActiveRecord::Base
belongs_to :reviewer, polymorphic: true
belongs_to :reviewable, polymorphic: true
end
After my polymorphic model is set up I create the concerns which you can include as behaviours in your user model.
module Reviewer
extend ActiveSupport::Concern
included do
has_many :given_reviews, as: :reviewer, dependent: :destroy
end
end
module Reviewable
extend ActiveSupport::Concern
included do
has_many :received_reviews, as: :reviewable, dependent: :destroy
end
end
Your user model should look something like this.
class User
include Reviewer #user is behaving as a reviewer
include Reviewable #user is behaving as a reviewable entity
end
After all this is set up you have a reusable module to start with which will work with any other model as well.
userA.given_reviews #reviews posted by the user A
userA.received_reviews #reviews other people have given about user A
The feedback class is a join from user-reviewer. It should have two foreign keys on it. For user and reviewer. Make them both belongs_to.
User.rb
has_one :profile
has_many :feedbacks
has_many :reviewers, :through => :feedbacks, :class_name => 'User'
has_many :created_feedbacks, :class_name => 'Feedback', :foreign_key => 'reviewer_id'
has_many :reviewed_users, :through => :feedbacks_left, :source => :user, :class_name => 'User'
# Not sure if you need accepts nested attributes.
Feedback.rb:
belongs_to :user
belongs_to :reviewer, :class_name => 'User' # foreign_key should be reviewer_id
Note - if you use has_one or has_many, it expects the foreign key to be on the associating class. In the way you have it written up, that would mean the feedback_id is on the user, but that doesn't make sense. Make it a belongs_to instead.
Change reviewer to reviewer_id and add it to the feedback model. Then the reviewer method generated by the database column won't clobbered by the reviewer method that comes from the belongs_to.
After that, you'll be good.
Ok, am still a newbie in ruby on rails trying to learn my way around. I have two models (User model and Comment model). Basically a user has a simple profile with an 'about me' section and a photo's section on the same page. Users must be signed in to comment on other users profiles.
My User Model
class User < ActiveRecord::Base
attr_accessible :email, :name, :username, :gender, :password, :password_confirmation
has_secure_password
has_many :comments
.
.
end
My Comment Model
class Comment < ActiveRecord::Base
belongs_to :user
attr_accessible :content
.
.
end
In my comments table, I have a user_id column that stores the id of the user whose profile has been commented on and a commenter_id column that stores the id of the user commenting on the profile.
Comment Form
<%= form_for([#user, #user.comments.build]) do |f| %>
<%= f.text_area :content, cols: "45", rows: "3", class: "btn-block comment-box" %>
<%= f.submit "Comment", class: "btn" %>
<% end %>
My comments Controller
class CommentsController < ApplicationController
def create
#user = User.find(params[:user_id])
#comment = #user.comments.build(params[:comment])
#comment.commenter_id = current_user.id
if #comment.save
.........
else
.........
end
end
end
This works fine storing both user_id and commenter_id in the database. My problem comes when displaying the user comments on the show page. I want to get the name of the user who commented on a specific profile.
In my user controller
def show
#user = User.find(params[:id])
#comments = #user.comments
end
I want to get the name of the user from the commenter_id but it keeps throwing errors undefined method 'commenter' for #<Comment:0x007f32b8c37430> when I try something like comment.commenter.name. However, comment.user.name works fine but it doesn't return what I want. Am guessing am not getting the associations right.
I need help getting the correct associations in the models so as to get the name from the commenter_id.
My last question, how do I catch errors in the comments form? Its not the usual form_for(#user) where you do like #user.errors.any?.
routes.rb
resources :users do
resources :comments, only: [:create, :destroy]
end
Try something like this in your models
class User < ActiveRecord::Base
has_many :received_comments, :class_name => "Comment", :foreign_key => "user_id"
has_many :given_comments, :class_name => "Comment", :foreign_key => "commenter_id"
end
class Comment < ActiveRecord::Base
belongs_to :user # comment about profile
belongs_to :commenter, :class_name => "User", :foreign_key => "commenter_id"
end
check out: http://guides.rubyonrails.org/association_basics.html
you can probably come up with better naming on the has_many collections, received and given were the best I could do on short notice :)
Note: foreign_key is option in many cases, left it in above - i think it helps with clarity
has_many fk refers to the the column in the many table (other table)
belongs_to fk refers to the column in the many table (this table)
I am trying to create a dashboard where users (User model) who has clicked "attending" (which is a flaggable) to an event (Event model) which is connected to a Collection (Collection model) will be able to see what are the Events they are going for.
My question however, is simply how to loop through all possible arrays in order for me to get all the associated IDs for the Events that the User has clicked "attending".
So for example, in my home page I want to display all possible events that the user is attending:
user_statistics.html.erb
<div class="span3 events">
<h3>Events</h3>
<% if #events.empty? %>
<p>You are currently not attending any events.</p>
<% else %>
<p>You are attending: <b><%= pluralize(#events.count, "event") %></b></p>
<p>Event 1: <%= #event1_name %> on Date: <%= #event1.date %> at Time:<%= #event1.time %></p>
<% end %>
</div>
pages_controller.rb
def home
#title = "Title"
#user = current_user
if current_user
#post = Post.new
#feed_items = current_user.feed
#user_following = #user.following
#user_followers = #user.followers
#events = #user.flaggings.with_flag(:attending)
#event1 = Event.find(#events[0].flaggable_id)
#event1_name = Collection.find(#event1.collection_id).name
end
end
I have set the #event1 array to 0 to access the first flag for 'attending', and then get the flaggable_id so I have the id to call up the Collection.
My issue is that if I have several events, how do I go about looping through to all the arrays to ensure I can pull out all the Collections?
For the first User who has clicked "attending" for 2 events, this is the data:
in IRB, for User.first who is attending 2 events
User Load (0.3ms) SELECT "users".* FROM "users" LIMIT 1
MakeFlaggable::Flagging Load (0.4ms) SELECT "flaggings".* FROM "flaggings" WHERE
"flaggings"."flagger_id" = 1 AND "flaggings"."flagger_type" = 'User' AND
"flaggings"."flag" = 'attending'
[#<MakeFlaggable::Flagging id: 16, flaggable_type: "Event", flaggable_id: 3,
flagger_type: "User", flagger_id: 1, flag: "attending", created_at: "2012-02-20 09:26:36",
updated_at: "2012-02-20 09:26:36">, #<MakeFlaggable::Flagging id: 18, flaggable_type:
"Event", flaggable_id: 4, flagger_type: "User", flagger_id: 1, flag: "attending",
created_at: "2012-02-20 10:38:00", updated_at: "2012-02-20 10:38:00">]
You can see that the user has flagged 'attending' for 2 events, which are stored in arrays. Hence if I have 2 events, I would ideally want to loop through such that I have 2 arrays.
I find it quite confusing to implement this... any help would be much appreciated!
user.rb partial code
Class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation, :user_bio,
:shop, :cover_photo, :avatar, :remote_image_url
has_secure_password
mount_uploader :cover_photo, ImageUploader
mount_uploader :avatar, ImageUploader
make_flagger
scope :shop, where(shop: true)
has_many :posts, dependent: :destroy
has_many :relationships, dependent: :destroy,
foreign_key: "follower_id"
has_many :reverse_relationships, dependent: :destroy,
foreign_key: "followed_id",
class_name: "Relationship"
has_many :following, through: :relationships, source: :followed
has_many :followers, through: :reverse_relationships, source: :follower
has_many :collections, dependent: :destroy
...
end
collections.rb partial code
class Collection < ActiveRecord::Base
attr_accessible :name, :description, :image, :remote_image_url
belongs_to :user
has_many :products, dependent: :destroy
has_many :events, dependent: :destroy
mount_uploader :image, ImageUploader
make_flaggable :like
...
end
events.rb partial code
class Event < ActiveRecord::Base
attr_accessible :date, :time, :description
belongs_to :collections
make_flaggable :attending
...
end
It's not 100% clear from your post, but it sounds like you need a named scope for Events where the attending flag is set and a has_many :through association to let the User have Events. With those two bits together, you could do something like:
User.first.events.attending
and AREL will take care of wiring that all up into a nice fast query for you.
Is flaggable a gem or have you written it as a polymorphic class?
This line is kind of nasty (sorry):
#events = #user.flaggings.with_flag(:attending)
The #events variable isn't holding events - it's holding a collection of flaggings. As a naming convention, this is obviously bad.
Also, this line is probably redundant if you've set up (or used a gem) for the flaggable polymorphic relationship:
#event1 = Event.find(#events[0].flaggable_id)
#This could be rewritten as:
#event1 = #events.first.flaggable
Or even nicer, combine the two previous lines into:
##events = #user.flaggings.with_flag(:attending)
##event1 = Event.find(#events[0].flaggable_id)
#becomes:
flaggings = #user.flaggings.with_flag(:attending)
#events = flaggings.map(&:flaggable)
#jxpx777 makes a very good point about named scope and hmt associations. This is probably the way forward. You could do something like this:
Class User < AR...
has_many :attendances, :class => 'Flagging', :conditions => {:flag => 'attending'} #You might need to add :type => "Event" if you use flaggings elsewhere...
has_many :events_attending, :class => 'Event', :through => :attendances, :source => :flaggable_id #You'll probably have to change a lot of these variable names - I can't see the rest of your source code...
That will then give you ability to just do:
#events = #user.events_attending.include(:collection)
Caveat - none of this code is tested - it's all off the top of my head, but it should point you in the right direction at least
Okay, I managed to solve my own problem albeit in a very dirty manner...
pages_controller.rb
def home
#title = "Simplifying and socializing online shopping - Ruuva"
#user = current_user
if current_user
#post = Post.new
#feed_items = current_user.feed
#user_following = #user.following
#user_followers = #user.followers
#events_attending = #user.flaggings.with_flag(:attending)
end
end
_user_statistics.html.erb partial
<h3>Events</h3>
<% if #events_attending.empty? %>
<p>You are currently not attending any events.</p>
<% else %>
<p>You are attending these events:</p>
<ol>
<% #events_attending.each do |e| %>
<% unless Event.find_by_id(e.flaggable_id).nil? %>
<li>
Collection: <%= link_to Collection.find(Event.find(e.flaggable_id).collection_id).name, event_path(e.flaggable_id) %>
<br>happening on: <%= Event.find(e.flaggable_id).date %> at <%= Event.find(e.flaggable_id).time %>
</li>
<% end %>
<% end %>
</ol>
<% end %>
I'm pretty sure the code is very bad, and I DO used several 'shortcuts' issues that will bite me back in the future... but an mvp is an mvp. Thanks guys for the help, though! :)