Add/Update users to Articles Rails - ruby-on-rails

I have an article with a many relationship to users and vice versa. When I create an article signed in as a user, the article is created with the relationship to user. So the relationship is working. I want other users to be able to join this article, so essentially, I would like a button to push the current_user to the array/list of many users.
I am at a complete loss at how to go about this process... Any help is appreciated

So users can have many articles and each article can belong to several users? Sounds like a has_and_belongs_to_many relationship. Have a look at the relevant Rails documentation:
http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association
In short you'd have a articles_users table, where each row consists of article_id and user_id. When adding a new users to an article, you just create another record in that table.
Alternatively you could look at has_many :through if you believe you'll work with that relationship as a separate entity. I.e. article has_many :users, through: authors.
http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
To help you decide, the guide offers some advice:
http://guides.rubyonrails.org/association_basics.html#choosing-between-has-many-through-and-has-and-belongs-to-many

#app/models/user.rb
class User < ActiveRecord::Base
has_many :written_articles, class_name: "Article", foreign_key: :user_id
has_and_belongs_to_many :articles
end
#app/models/article.rb
class Article < ActiveRecord::Base
belongs_to :user #-> for the original owner
has_and_belongs_to_many :users
end
The above is a has_and_belongs_to_many association, which gives you the ability to add users to the article:
#config/routes.rb
resources :articles do
match "users/:id", to: :users, via: [:post, :delete] #-> url.com/articles/:article_id/users/:id
end
#app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
def users
#article = Article.find params[:article_id]
#user = User.find params[:id]
if request.post?
#article.users << #user
elsif request.delete?
#article.users.delete #user
end
#redirect somewhere
end
end
This will allow you to use:
<%= link_to "Add User", article_user_path(#article, #user), method: :post %>
<%= link_to "remove User", article_user_path(#article, #user), method: :delete %>

Related

Rails - How to create multiple "matches" in one action between a user and opportunities?

I'm looking for the easiest and the most clever way to create interest_id(match) in one-click.
Here is my MVC :
user.rb
class User < ApplicationRecord
has_many :interests, through: :opportunities
end
interest.rb
class Interest < ApplicationRecord
belongs_to :opportunity
belongs_to :user
end
opportunity.rb
class Opportunity < ApplicationRecord
has_many :interests
end
InterestsController.rb
def create
#user = current_user
#opportunities = Opportunity.all
#interest = Interest.new(interest_params)
if #interest.save!
redirect_to user_interests_path, notice: 'it works'
else
render :new, notice:"it doesn't work"
end
end
def interest_params
params.permit(
:user_id,
:opportunity_id)
end
user/show
<%= link_to "Match", user_interests_path(#user), class:"btn btn-primary", :method => :post %>
For now, I can't pass opportunities (nil). Could you please advise me about the easiest way to create interests? (New on RoR for 6 months).
Many thanks for your help.
If I understand correctly your relation schema, the Interest is the join record associating a User to (eventually) many Opportunity, and vice-versa (many-to-many relationship).
With that being said (and please correct me if I am wrong), you can do the following to achieve what you want:
# in user/show
<% #opportunities.each do |opportunity| %>
<%=
link_to "Match opportunity #{opportunity.id}",
user_interests_path(#user, opportunity_id: opportunity.id),
class: "btn btn-primary",
method: :post
%>
<% end %>
# in interests_controller
def create
if current_user.interests.create(opportunity_id: opportunity_id_param)
redirect_to user_interests_path, notice: 'it works'
else
render :new, notice: "it doesn't work"
end
end
private
def opportunity_id_param
params.require(:opportunity_id)
end
This suggested code:
requires the opportunity_id param for the interests#create action
use current_user to automatically set the user_id on the Interest model, so the end-users can't send a user_id that are not theirs (if they could, then each user could create interest for other users without their agreement... security flaw)
On a side note, I strongly advise you to not select all existing Opportunity record and display it on your page: it does not scale. Someday, you will end up with hundreds of Opportunity records, making this list too big from a User Experience perspective.
I suggest a smarter approach, for example some kind of ordering + limit: max of 10 records ordered by "most interest", which can be accomplished by the following:
# in controller
#popular_opportunities = Opportunity
.joins('LEFT JOINS interests ON interests.opportunity_id = opportunities.id')
.order('count(interests.*) DESC, opportunities.id')
.limit(10)
And then in the view, simply use #populator_opportunities instead of #opportunities.
Other options, like pagination, are also efficient in this case but IMO relevant ordering is the minimum.
First, you need to pass the ids of the opportunities you want to create interest some way, the best is a form, with checkboxes like MrShemek said, or a multi select dropdown.
I think you probably made some mistakes in User and Opportunity with the has_many and belong_to part:
class User < ApplicationRecord
has_many :interests
has_many :opportunities, through: :interests
# interest is the one that links user and opportunity, it has the references for both user and opportunities
end
class Opportunity < ApplicationRecord
has_many :interests
has_many :users, through: :interests
end
then in controller you could do
def create
#user = current_user
#opportunities = Opportunity.all
#user.opportunity_ids = interest_params[:opportunity_ids] # it will create the interrests automatically for the given ids (because the relations of has_many through)
if #user.save!
redirect_to user_interests_path, notice: 'it works'
else
render :new, notice:"it doesn't work"
end
end

Include array when creating a model

I have a discussion forum where users can see a list of unread posts. The way I'm doing this is to use a Look, User and Post model:
class Look < ActiveRecord::Base
belongs_to :post
belongs_to :user
end
class User < ActiveRecord::Base
has_many :posts, through: :looks
has_many :looks
end
class Post < ActiveRecord::Base
belongs_to :user
has_many :looks
has_many :users, through: :looks
end
So the way this works is that there is a list of all post IDs a user has viewed. It's created through the 'show' method:
def show
if current_user
viewer = current_user
view_ids = viewer.posts.pluck(:id).uniq
not_viewed = Post.where("id not in (?)", view_ids)
not_viewed_ids = not_viewed.pluck(:id)
unless Post.find(params[:id]).in?(not_viewed_ids)
Look.create(user: current_user, post: #post, viewstamp: Time.now)
end
end
end
This all works fine so far. The problem is I want to create a Look for all posts, so that I can essentially 'mark all as read'. This line works fine for creating a Look for the current post:
unless Post.find(params[:id]).in?(not_viewed_ids)
Look.create(user: current_user, post: #post, viewstamp: Time.now)
end
...but how do I make one that creates a Look for every post? Like this:
Look.create(user: current_user, post: [NEED ARRAY OF POSTS HERE], viewstamp: Time.now)
The reason I want to do this is so a user can mark all posts as read.
You can create the Look automatically just by adding the users to the posts.
Post.all.each { |p| p.users << current_user; p.save }

Rails Polymorphic associations without nesting forms

I am still new to rails and I am trying to figure out how to implement a polymorphic association without using a nested route or form. I tried searching but everything seemed to be about nesting forms or adding comments, which is not what I am trying to do.
Here are my models
Article.rb
class Article < ActiveRecord::Base
belongs_to :articable, polymorphic: true
end
Organization.rb
class Organization < ActiveRecord::Base
has_many :articles, as: :articable
end
People.rb
class People < ActiveRecord::Base
has_many :articles, as: :articable
end
I want to implement a 'New Article' link from a Organization or People show page and have the correct article_id and article_type entered. What would the correct syntax be to generate this link?
Thanks!
Routes:
resource :people do
resource :articles
end
resource :organizations do
resource :articles
end
ArticlesController:
def create
article = Article.new(params[:article])
if params[:people_id]
people = People.find(params[:people_id])
people.articles << article
else
organization = Organization.find(params[:organization_id])
organization.articles << article
end
article.save
end
Organizations view:
link_to new_organization_article_path(#organization)...

Nested Resource - Controller to filter by current_user also

Thanks to some help in another question, I now have a nested resource setup, and it is nearly working the way I need it to. This is a follow-up question about the controller.
Expenses have both a user, and a project that they belong to.
I would like to visit /projects/5/expenses, and see a list of all the expenses for that project, (which IS working), but also have it sensitive to the user that is currently signed in, so they only see their own expenses.
first the models:
class Expense < ActiveRecord::Base
attr_accessible :amount, :project_id, :user_id
belongs_to :project
belongs_to :user
end
each of the other models has "has_many :expenses", to complete the relationship.
so my route looks like:
resources :projects do
resources :expenses
end
And
class ExpensesController < ApplicationController
def index
#user = current_user
#project = Project.find(params[:project_id])
#expense_list = #project.expenses.all
end
How can I filter my #expense_list further by only showing the current_user's expenses?
You need a an additional condition to query the expenses based on the user that they belong to.
I would suggest that you create a scope in your Expense model
scope :for_user, lambda{ |user|
where( :user_id => user.id )
}
And you can do this in the controller:
#expense_list = #project.expenses.for_user(current_user).all

Retrieve all posts where the given user has commented, Ruby on Rails

I have users, posts and comments. User can post only one comment to each post.
class User < ActiveRecord::Base
has_many :posts
has_many :comments
end
class Post < ActiveRecord::Base
has_many :comments
belongs_to :user
end
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :post
end
On userpage (http://host/users/1 for example) I want to show all posts where the given user has commented. Each post then will have all other comments.
I can do something like this in my User controller:
def show
#user = User.find(params[:user_id])
#posts = []
user.comments.each {|comment| #posts << comment.post}
end
This way I will find User, then all his comments, then corresponding post to each comment, and then (in my view) for each post I will render post.comments. I'm totally new in Rails, so I can do this =) But I think it's somehow bad and there is a better way to do this, maybe I should use scopes or named_scopes (don't know yet what this is, but looks scary).
So can you point me out to the right direction here?
You could define an association which retrieves all the posts with comments in a single query. Keeping it in the model reduces the complexity of your controllers, enables you to reuse the association and makes it easier to unit test.
class User < ActiveRecord::Base
has_many :posts_with_comments, :through => :comments, :source => :post
# ...
end
:through is an option for has_many to specify a join table through which to perform the query. We need to specify the :source as Rails wouldn't be able to infer the source from :post_with_comments.
Lastly, update your controller to use the association.
def show
#user = User.find(params[:user_id])
#posts = #user.posts_with_comments
end
To understand more about :through and :source take a look at the documentation.
When you got the user, you have the associations to his posts and each post has his comments.
You could write:
(I don't know the names of your table fields, so i named the text text)
# In Controller
#user = User.find(params[:user_id]).include([:posts, :comments])
# In View
#user.posts.each do |post|
post.text
# Comments to the Post
post.comments.each do |comment|
comment.text
end
end
I haven't tested the code, so there could be some errors.

Resources