Rails 4 - Invite team mates to project - ruby-on-rails

Im trying to add functionality to my Rails 4 app which allows a user (who creates a project) to invite others to join their project team.
I found this tutorial, which I've found helpful: https://coderwall.com/p/rqjjca/creating-a-scoped-invitation-system-for-rails
To this point, I have the following set up:
User
has_one :profile, dependent: :destroy
Profile
belongs_to :user
has_many :teams, foreign_key: "team_mate_id"
has_many :team_projects, through: :teams, source: :project
has_many :invitations, :class_name => "Invite", :foreign_key => 'recipient_id'
has_many :sent_invites, :class_name => "Invite", :foreign_key => 'sender_id'
Project
belongs_to :profile
has_one :team
has_many :team_mates, through: :team
has_many :invites
Invite
belongs_to :project
belongs_to :sender, :class_name => 'Profile'
belongs_to :recipient, :class_name => 'Profile'
Team
belongs_to :project
belongs_to :team_mate, class_name: "Profile"
In my form, I have:
<%= simple_form_for(#invite, :url => invites_path) do |f| %>
<%= f.hidden_field :project_id, :value => #invite.project_id %>
<%= f.label :email %>
<%= f.email_field :email %>
<%= f.input :expiry, :as => :date_picker, :label => "When do you need a response to this invitation?" %>
<%= f.submit 'Send' %>
<% end %>
Then in my show (rendered on the projects show) I have:
<%= render :partial => 'projects/invite_team_mate' %>
In my invites controller, I have:
class InvitesController < ApplicationController
def new
#invite = Invite.new
end
def create
#invite = Invite.new(invite_params)
#invite.sender_id = current_user.profile.id
if #invite.save
#if the user already exists
if #invite.recipient != nil
#send existing user email invitation to join project team
InviteMailer.existing_user_invite(#invite).deliver
#Add the user to the user group - inivte rsvp pending
#invite.recipient.project.push(#invite.project)
else
#send new user email invitation to join as a user and this project team
#invite.recipient.project.push(#invite.project)
# InviteMailer.new_user_invite(#invite, new_user_registration_path(:invite_token => #invite.token)).deliver
end
else
# oh no, creating an new invitation failed
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_invite
#invite = Invite.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def invite_params
params[:invite].permit(:email)
end
end
I can't figure out what else needs to happen to make this work.
When I save all this and try to invite an email address, I get this error:
undefined method `project' for nil:NilClass
That happens despite the form I use to send the invite being shown on the projects show page.

You need to add project_id and recipient_id to your invite_params, and add the recipient to your form (as a text_field, or hidden field, depending on your use case):
# controller
def invite_params
params[:invite].permit(:email, :project_id, :recipient_id)
end
# form
<%= simple_form_for(#invite, :url => invites_path) do |f| %>
...
<%= f.hidden_field :recipient_id, :value => get_recipient_id %>
...
<% end %>

Error is due to #invite.project_id, because #invite has no data so it's throwing error
<%= f.hidden_field :project_id, :value => #invite.project_id %>
replace this with or with some other desired logic
select_tag "people", options_from_collection_for_select(#projects, "id", "name")
In controlller
def new
#invite = Invite.new
#projects = current_user.team_projects // here you have to add your logic, for which project you want to invite or let me know
end

I'm finding following code very strange
if #invite.recipient != nil
...
#Add the user to the user group - inivte rsvp pending
#invite.recipient.project.push(#invite.project)
else
#send new user email invitation to join coalfacer and this project team
#invite.recipient.project.push(#invite.project)
...
end
How is that you call the same code #invite.recipient. even if #invite.recipient is Nil?!
By the way, ensure you understand why this code is written for in the controller, what it means
def invite_params
params[:invite].permit(:email)
end
For your convenience, refrain from coping code you don't understand. Also, even if you do so, do that in small portions and try after each one, so you can localize the error if any. Working in small increments is essential. You can't do a horde of changes and then just ask "what's wrong about his plenty of code".
Finally, I suggest you write specific questions, using MCVE principle. You have to extract specific portions of your code relevant to the issue, and be specific on the problem. If you put whole bunch of code, including irrelevant one, it's much much harder to help.

Related

creating new association while creating the related record in active admin

I've these two models
class Case < ActiveRecord::Base
belongs_to :client, :class_name => 'User'
end
class User < ActiveRecord::Base
has_one :requested_case, :class_name => 'Case', :foreign_key => :requested_case_id
end
and I want to create adminstration Interface for Case model using Active Admin, so when I create new case I can create new client for it in the same time, so I wrote the following lines of codes in the app/admin/cases.rb file
ActiveAdmin.register Case do
form do |f|
f.inputs "Basic Details"
f.input :title
f.input :Description
end
f.inputs :name => "Client Details", :for => :client do |c|
c.input :name
c.input :mobile
end
f.buttons
end
end
so when I filed the inputs of client and click submit I got this error
ActiveRecord::AssociationTypeMismatch in Admin::CasesController#create
User(#-625154418) expected, got ActiveSupport::HashWithIndifferentAccess(#82665960)
so any help please what's missing here?
Just add to your app/admin/cases.rb file
controller do
def new
#case = Case.new
#case.build_client
end
end
and don't forget to add accepts_nested_attributes_for to your case model
accepts_nested_attributes_for :client

Not showing user during iteration if user already belongs to "group" relationship

I have this relationship where User can create a document(trip) and invite other users to a group that belongs to that document. My relationship indicates that "Group" has a user_id and trip_id column, so for every user I invite, a new Group record will be created in the database.
When I am inviting other users, I only want users who are NOT in the group to appear. Users who are already in the group should not show up, but my view still shows the users.
I've been playing around with <% if !friend.trips.include?(#trip)%>, but I can't seem to get the correct view. The record is being created in the database correctly.
Also, when I am viewing groups/new.html.erb, this is the url http://localhost:3000/groups/new?id=2, where the id is the trip_id.
My question:
Am I using restful convention? That is, should I be using the new method here (as is) or should I be using the index method instead?
How do I iterate through each friend's groups to make sure that none of the group's trip_id is equivalent to #trip.id?
Thanks!
view (/groups/new.html.erb)
<% if !#friends.blank? %>
<% #friends.each do |friend| %>
<% if !friend.trips.include?(#trip)%>
<%= link_to groups_path(:user_id => friend.id, :trip_id => #trip.id),
:method => :post, :action => 'create' do %>
<div id="addfriend_totrip_button_groupsnew">add friend to trip</div>
<% end %>
<% end %>
<% end %>
<% end %>
groups_controller.rb
class GroupsController < ApplicationController
before_filter :authenticate, :only => [:update, :create, :destroy]
def new
#trip = Trip.find(params[:id])
#user = User.find(current_user)
#group = Group.new
#friends = #user.friends.all
end
def create
#trip = Trip.find(params[:trip_id])
#user = User.find(params[:user_id])
#group = Group.create(:user_id => #user.id, :trip_id => #trip.id)
if #group.save
flash[:success] = "Friend added to group."
redirect_to groups_path(:id => #trip.id)
else
flash[:error] = "Could not add friend."
redirect_to root_path
end
end
end
user.rb
class User < ActiveRecord::Base
has_many :trips, :through => :groups
has_many :trips, :dependent => :destroy
has_many :groups
end
trip.rb
class Trip < ActiveRecord::Base
belongs_to :user
belongs_to :traveldeal
has_many :groups
has_many :users, :through => :groups
end
group.rb
class Group < ActiveRecord::Base
belongs_to :trip
belongs_to :user
end
First of all, you have has_many :trips called twice in your User model. I understand you have two different types of User-Trip relationships (one directly, and one through Group), but you can't give both the same name, otherwise one will hide the other. Try defining your User model like this:
class User < ActiveRecord::Base
has_many :group_trips, :through => :groups,
:class_name => "Trip"
has_many :trips, :dependent => :destroy
has_many :groups
def all_trips
Trip.joins(:groups).where({:user_id => self.id} | {:groups => {:user_id => self.id}})
end
end
There's also the problem that you're searching the friend's list of groups for a Trip object. Try changing that line to:
<% if !friend.all_trips.include?(#trip) %>
Or without the new method, something like this should work:
<% if !friend.groups.where(:trip_id => #trip.id).first %>
I don't see anything un-RESTful about your approach. RESTful in general means stateless. I.e. the only thing a response depends on is the HTTP method and the address. So as long as your not keeping state information in, say, the session, you should be following REST.

How to apply tags with acts_as_taggable_on using check boxes?

I would like to assign two different "types" of tags (sector categories and free tagging) to a Company model using acts_as_taggable_on. NB: I'm new to RoR!
This is easy to do if just using standard text input fields, but I would like to use check-boxes on one type (a fixed sector category tag that is predefined), and then allow the user to add comma separated tags in an input field.
I have played around with this problem in various ways,... one inspired by this question...but I cannot get it to work
Here is what I have so far:
# models/company.rb
class Company ...
acts_as_taggable_on :tags, :sectors
has_many :taggings,
:as => :taggable,
:include => :tag,
:class_name => "ActsAsTaggableOn::Tagging",
:conditions => { :taggable_type => "Company" }
has_many :sector_tags,
:through => :taggings,
:source => :tag,
:class_name => "ActsAsTaggableOn::Tag",
:conditions => {:context => "sectors"}
end
in the form (using simple_form gem) I have...
# views/companies/_form.html.haml
= simple_form_for #company do |f|
= f.input :name
= f.association :sector_tags, :as => :check_boxes, :hint => "Please click all that apply"
= f.input :tag_list
= f.button :submit, "Add company"
And in my Company controller I have
# controllers/companies_controller.rb
def create
#company = current_user.companies.build(params[:company])
if #company.save
...
end
But this causes a validation error:
ActiveRecord::RecordInvalid in CompaniesController#create
Validation failed: Context can't be blank
Can anyone hint at how I can do this right?
A related question is if this is a good way to do it at all? Would I be better off just using a Category model for assigning sector tags through a joint model?
Thanks!
Well, I solved my problem. And it turned out to be quite simple. Alas, I ended up creating a separate Sector model through a joint "sectorizations" table. But if anyone is interested, I just wanted to update on what I did in the case above...
In my company model
# models/company.rb
class Company ...
acts_as_taggable_on :tags, :sectors
...
end
in the form
# views/companies/_form.html.haml
= simple_form_for #company do |f|
= f.input :name
= f.input :sector_list, :as => :check_boxes, :collection => #sectors, :hint => "Please check all that apply"
= f.input :tag_list
= f.button :submit, "Add company"
and in the company controller (create)
# controllers/company_controllers.rb
def new
#company = Company.new
#sectors = get_sectors
end
def get_sectors
sectors = []
for sector in Company.sector_counts
sectors << sector['name']
end
return sectors
end
It seems that act_as_taggable_on uses single table inheritance, so you actually don't need to create any additional tables. However you do need to follow their convention (that they never stated) as follows:
//add to model
attr_accessible :yourfieldname_list
acts_as_taggable_on :yourfieldname
//view
<%= f.text_field :yourfieldname_list %>

Rails: validate presence of parent_id in has_many association

I have a projects resource that has many tasks. I want to ensure that every task has a project_id by adding validates_presence_of :project_id to the tasks model.
However, when creating a new project with tasks, the project_id won't be available until the record saves, therefore I can't use validates_presence_of :project_id.
So my question is, how do I validate presence of project_id in the task model? I want to ensure every task has a parent.
...
class Project < ActiveRecord::Base
has_many :tasks, :dependent => :destroy
accepts_nested_attributes_for :tasks, :allow_destroy => true
...
class Task < ActiveRecord::Base
belongs_to :project
validates_presence_of :project_id
Your code works:
If you validates_presence_of :project, then as long as the project is there, it will validate. But if your project is unsaved, you could still save the task.
If you validates_presence_of :project_id, then the integer must be there, indicating a saved value.
Here's rSpec that proves the point. If you validate :project_id, you can't save a task without saving the Project.
class Task < ActiveRecord::Base
belongs_to :project
end
/specs/model_specs/task_spec.rb
require File.dirname(__FILE__) + '/../spec_helper'
describe Task do
before(:each) do
#project = Project.new
end
it "should require a project_id, not just a project object" do
task = Task.new
task.project = #project
Task.instance_eval("validates_presence_of :project_id")
task.valid?.should == false
end
it "should not be valid without a project" do
task = Task.new
task.project = #project
Task.instance_eval("validates_presence_of :project")
task.valid?.should == false
task.save.should == false
end
end
See here for the definitive answer :
class Project < ActiveRecord::Base
has_many :tasks, :dependent => :destroy, :inverse_of => :project
accepts_nested_attributes_for :tasks, :allow_destroy => true
class Task < ActiveRecord::Base
belongs_to :project
validates_presence_of :project
Not so elegant if you ask me... It should transparently validate.
Maybe I don't understand something, but it looks like you are trying to cheat rails. Why don't you just do like this:
class Task < ActiveRecord::Base
belongs_to :project
validate_presence_of :project
end
Take a look at this:
https://rails.lighthouseapp.com/projects/8994/tickets/2815-nested-models-build-should-directly-assign-the-parent
One thing I have done in the past is add: validates_presence_of :parent_id, :on => :update. Not great but it helps tighten the net a little.
I think you're having the same issue I dealt with. I have two models, Account and User, and when the account is created the first user is created through a #account.users.build. The User model has a validates_presence_of :account validation.
To make the first user pass validation, I added the following code to my Account model:
before_validation_on_create :initialize_users
def initialize_users
users.each { |u| u.account = self }
end
In reality you need both:
validates_presence_of project
validates_presence_of project_id
That way the task will not be saved in either of the following cases assuming that you have only 2 valid projects in the database, i.e. project id 99 is invalid:
task.project_id = 99
task.save
task.project = Project.new
task.save
I hope this is of help to someone.
Your Project class must define
accepts_nested_attributes_for :tasks
See Nested Model Form on Railscasts for more details on how to make the form.
EDIT:
In your form you should have something like this:
_form.html.erb
<% form_for #project do |f| %>
# project fields...
<% f.fields_for :tasks do |builder| %>
<%= render 'task_fields', :f => builder %>
<% end %>
<p><%= link_to_add_fields "Add task", f, :tasks %></p>
<%= f.submit %>
<% end %>
_task_fields.html.erb
<%= f.label :name, "Task name:" %>
<%= f.text_field :name %>
# task fields...
<%= link_to_remove_fields "Delete task", f, :tasks %>
link_to_add_fields and link_to_remove_fields are methods defined in application_helper to add/delete fields dynamically.

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