Proper Usage of Rails After_Filter - ruby-on-rails

In a small app I am building, I have a controller that creates an exchange. When a user creates an exchange they are simultaneously the organizer of the exchange and a participant in the exchange. Participants are tracked by a join table that joins a user_id and an exchange_id. Organizers are tracked by a foreign user_id key in the exchange table.
I am trying to figure out where to put the code that will automatically create a new membership record for the organizer of the exchange. Should I put this in the exchange_controller's create action itself, or in an after_filter triggered by the create action? Or maybe somewhere else? Part of the problem is that I could not find any good examples of proper after_filter use (guides.rubyonrails.org only had sparse mention of it), so any links pointing in the correct direction would be appreciated as well.
Here is relevant model code:
app/models/user.rb:
# Returns array of exchanges user is participating in
has_many :participations,
:through => :memberships,
:source => :exchange
# Returns array of exchanges user has organized
has_many :organized_exchanges,
:foreign_key => :organizer_id,
:class_name => "Exchange"
app/models/membership.rb:
class Membership < ActiveRecord::Base
attr_accessible :exchange_id, :user_id, :role
belongs_to :exchange
belongs_to :user
end
app/modles/exchange.rb:
belongs_to :organizer,
:foreign_key => :organizer_id,
:class_name => "User"
has_many :memberships, :dependent => :destroy
has_many :participants,
:through => :memberships,
:source => :user
And here is the relevant controller code:
app/controllers/exchanges_controller.rb:
def create
#exchange = Exchange.new(params[:exchange])
#exchange.organizer_id = current_user.id
if #exchange.save
redirect_to exchange_path(#exchange.id)
else
render 'new'
end
end

after_filter is a completely different thing in this context. It is called when your view is completely processed and so you want to call some action to do something.
You can use after_create callback that is triggered when a record is created in the database.
In your case, a user is creating an exchange and so after the exchange is created, the after_create callback is triggered and you can apply your functionality over there to make the current user who created the exchange to be a participant.
The way to write in a model is like this:
after_create :do_something
def do_something
something.do!
end
Note: It is not good to use after_save here because it is triggered every time you save a record or even if you update a record.
There is a nice SO post that clearly tells you the difference between the after_create and after_save.
See this SO post for the difference between the two.
More on the callbacks is here.

Related

group creator transfered to other user if group creator leaves group

First question: I have a group model that belongs_to :creator, :class_name => "User" and if the creator leaves I want someone else in the group to become the "creator". How would I do this?
As a second question, I would also like to give the leaving creator to choose a new creator when leaving, or let it be automatically assigned.
as of right now these are what my models look like:
class Group < ActiveRecord::Base
belongs_to :creator, :class_name => "User"
has_many :members, :through => :memberships
has_many :memberships, :foreign_key => "new_group_id"
and my User model:
class User < ActiveRecord::Base
has_many :groups, foreign_key: :creator_id
has_many :memberships, foreign_key: :member_id
has_many :new_groups, through: :memberships
As a third question, I would like the group to be destroyed when the creator leaves. How can I set up this kind of relation?
These are 3 questions, and the first two are quite open, so I'd try to answer all of them in order making some assumptions down the road.
First question
This depends on what do you want the behavior for choosing a new creator to be. Do you want it to be automatically assigned from the current members? Do you want to give other members to have the change to auto-assign themselves as creator? In the latter you need to provide your members a full UI (routes, controllers, views) for that purpose, so I'll show you how would I code the first option.
First, I'd encapsulate the group leaving logic into its own method on the Group model, that we'll use in the controller for this purpose. On Group we define the logic for assigning as new creator. The logic here will be to pass the method a new_creator if we have it, or default to the first of the members if not given.
class Group < ActiveRecord::Base
def reassign(new_creator = nil)
new_creator ||= members.first
if new_creator
self.creator = new_creator
save
else
false
end
end
end
As an alternative approach, you can move this logic into an Observer (or any alternative) that will observe on Group model for the attribute creator_id to be nulled.
Second question
This one will involve a full UI that you'll need to figure out yourself according to your specifications. In short, I'd create a new action in your GroupsController for members to leave groups like this:
# config/routes.rb
resources :groups do
member do
get :leave
patch :reassign
end
end
# app/controllers/groups_controller.rb
class GroupsController < ApplicationController
def leave
#group = Group.find(params[:id])
end
def reassign
#group = Group.find(params[:id])
if #group.reassign(params[:new_creator_id])
redirect_to my_fancy_path
else
render :leave
end
end
end
In your views you'll have your form_for #group with the new member candidates (possible with a select_tag :new_creator_id) and the rest of your UI as you prefer.
Third question
This is the easiest. You just define your association like this and you'll get all User groups destroyed after the user is deleted.
class User < ActiveRecord::Base
has_many :groups, foreign_key: :creator_id, dependent: :destroy
end

how to create a challenge between user and a friend similar to Facebook in rails app?

I have a rails app where users have friends list. Now i have to create a challenge similar to facebook challenge where the user can finish the process(playing game) and he can challenge his friend and his friend can accept or deny the request and if accepted, after the process(playing game) finished the message to both user has to be sent which contains who has won.
How can i do this? please help me.
Sounds like you want a new model called Challenge. This might have a couple of associations:
class Challenge < ActiveRecord::Base
belongs_to :sender, class_name: "User", inverse_of: :sent_challenges
belongs_to :receiver, class_name: "User", inverse_of: :received_challenges
end
The corresponding associations on the User could be
class User < ActiveRecord::Base
# ...
has_many :sent_challenges,
class_name: "Challenge", foreign_key: "sender_id", inverse_of: :sender
has_many :received_challenges,
class_name: "Challenge", foreign_key: "receiver_id", inverse_of: :receiver
end
Then you could perhaps have a method on your User to send a challenge
def send_challenge(friend)
sent_challenges.create(receiver: friend)
end
You might have some actions on your ChallengesController:
def index
#challenges = current_user.received_challenges
end
def create
#challenge = current_user.send_challenge(params[:friend_id])
# now the sender plays the game
render :game
end
def accept
#challenge = current_user.received_challenges.find(params[:id])
# now the receiver plays the game
render :game
end
def deny
current_user.received_challenges.destroy(params[:id])
redirect_to challenges_url
end
def complete
# happens at the end of the game
# work out the winner
# send the emails
end
and of course you'll need to add the corresponding routes to hook it all up, and write views for the index and game. Maybe you'd put links on your friend list that directed to the create action so that people could issue challenges.
Notice how I put everything through current_user.received_challenges instead of just doing a basic Challenge.find(params[:id]) - if you did that, anybody could accept the challenge just by guessing the id! Yikes!
I said "perhaps" and "maybe" a lot because there are different ways you could tackle this. But I hope that's enough to get you started. If not, I would suggest trying a Rails tutorial - Michael Hartl's is the classic.
Do you got the has_many :through relation already?
You need to pass a :source to the users table because a user can also be a friend. This would look like this:
class User < ActiveRecord::Base
has_many :friends
has_many :users, :source => :friend, :through => :friends
end
PS: You need to create the migration for the friends table and run it.
Its possible to add more columns to the join table (friends). There you could add the relationship_status. So that you have in the end:
Friends Table
ID | User_id | Friend_id | relationship_status
based on the relationship_status you can solve your issue!

before_destroy on a relationship model

I am using a has_many through association and having trouble getting the before_destroy call back to trigger. I am using a Relating class to relate models.
class Relating < ActiveRecord::Base
belongs_to :relater, :polymorphic => true
belongs_to :related, :polymorphic => true
before_destroy :unset_reminders
end
For example, a user can add TvShows to a list of favorites, User.rb:
has_many :tv_shows, :through => :relateds, :source => :related, :source_type => 'TvShow'
The problem I am having, has to do with deleting this Relating record.
I can relate users and tv shows by:
user = User.find(1)
show = TvShow.find(1)
user.tv_shows << show
But when I want to remove this association, the before_destroy is not triggered by:
user.tv_shows.delete(show)
However, if I destroy the relating record manually, it does trigger the callback:
r = Relating.find(8012)
r.destroy
How can I get the before destroy to be triggered for this?
Thanks
The delete method does not trigger callbacks as mentioned in the docs here. Try destroy instead.
Update: I didn't realize you were trying to destroy the join record and not the show itself. I'm surprised delete works at all but perhaps that is a feature of has_many :through. How about:
user.relateds.where(tv_show_id: show.id).destroy

RESTfully destroy polymorphic association in Rails?

How do I destroy the association itself and leave the objects being associated alone, while keeping this RESTful?
Specifically, I have these models:
class Event < ActiveRecord::Base
has_many :model_surveys, :as => :surveyable, :dependent => :destroy, :include => :survey
has_many :surveys, :through => :model_surveys
end
class ModelSurvey < ActiveRecord::Base
belongs_to :survey
belongs_to :surveyable, :polymorphic => true
end
class Survey < ActiveRecord::Base
has_many :model_surveys
end
That's saying that the Event is :surveyable (ModelSurvey belongs_to Event). My question is, without having to create a ModelSurveysController, how do I destroy the ModelSurvey, while leaving the Event and Survey alone?
Something with map.resources :events, :has_many => :model_surveys? I'm not quite sure what to do in this situation. What needs to happen with the routes, and what needs to happen in the controller? I'm hoping the url could look something like this:
/events/:title/model_surveys/:id
Thanks for your help,
Lance
In Rails 2.3 you have accepts_nested_attributes_for which would let you pass an array of ModelSurveys to the event in question. If you allow destroy through the nested attributes declaration, you'll be able to pass event[model_surveys][1][_destroy]=1 and the association will be removed. Check out the api docs.
Resources domain != model domain
The domain of the controller is not the same as that of the models. It's perfectly fine to update multiple models by changing the state of a resource.
In your case that means doing a PUT or POST to either the Event or the Survey which contains a list of ids for the other. The model for one will update the association.
PUT or POST
Some people (but not Roy Fielding) believe that you should use a PUT to update the resource and provide all of the state again, others feel that a POST with the partial state (ala PATCH) is sufficient.

How do I create/maintain a valid reference to a particular object in an ActiveRecord association?

Using ActiveRecord, I have an object, Client, that zero or more Users (i.e. via a has_many association). Client also has a 'primary_contact' attribute that can be manually set, but always has to point to one of the associated users. I.e. primary_contact can only be blank if there are no associated users.
What's the best way to implement Client such that:
a) The first time a user is added to a client, primary_contact is set to point to that user?
b) The primary_contact is always guaranteed to be in the users association, unless all of the users are deleted? (This has two parts: when setting a new primary_contact or removing a user from the association)
In other words, how can I designate and reassign the title of "primary contact" to one of a given client's users? I've tinkered around with numerous filters and validations, but I just can't get it right. Any help would be appreciated.
UPDATE: Though I'm sure there are a myriad of solutions, I ended up having User inform Client when it is being deleted and then using a before_save call in Client to validate (and set, if necessary) its primary_contact. This call is triggered by User just before it is deleted. This doesn't catch all of the edge cases when updating associations, but it's good enough for what I need.
My solution is to do everything in the join model. I think this works correctly on the client transitions to or from zero associations, always guaranteeing a primary contact is designated if there is any existing association. I'd be interested to hear anyone's feedback.
I'm new here, so cannot comment on François below. I can only edit my own entry. His solution presumes user to client is one to many, whereas my solution presumes many to many. I was thinking the user model represented an "agent" or "rep" perhaps, and would surely manage multiple clients. The question is ambiguous in this regard.
class User < ActiveRecord::Base
has_many :user_clients, :dependent => true
has_many :clients, :through => :user_client
end
class UserClient < ActiveRecord::Base
belongs_to :user
belongs_to :client
# user_client join table contains :primary column
after_create :init_primary
before_destroy :preserve_primary
def init_primary
# first association for a client is always primary
if self.client.user_clients.length == 1
self.primary = true
self.save
end
end
def preserve_primary
if self.primary
#unless this is the last association, make soemone else primary
unless self.client.user_clients.length == 1
# there's gotta be a more concise way...
if self.client.user_clients[0].equal? self
self.client.user_clients[1].primary = true
else
self.client.user_clients[0].primary = true
end
end
end
end
end
class Client < ActiveRecord::Base
has_many :user_clients, :dependent => true
has_many :users, :through => :user_client
end
Though I'm sure there are a myriad of solutions, I ended up having User inform Client when it is being deleted and then using a before_save call in Client to validate (and set, if necessary) its primary_contact. This call is triggered by User just before it is deleted. This doesn't catch all of the edge cases when updating associations, but it's good enough for what I need.
I would do this using a boolean attribute on users. #has_one can be used to find the first model that has this boolean set to true.
class Client < AR::B
has_many :users, :dependent => :destroy
has_one :primary_contact, :class_name => "User",
:conditions => {:primary_contact => true},
:dependent => :destroy
end
class User < AR::B
belongs_to :client
after_save :ensure_only_primary
before_create :ensure_at_least_one_primary
after_destroy :select_another_primary
private
# We always want one primary contact, so find another one when I'm being
# deleted
def select_another_primary
return unless primary_contact?
u = self.client.users.first
u.update_attribute(:primary_contact, true) if u
end
def ensure_at_least_one_primary
return if self.client.users.count(:primary_contact).nonzero?
self.primary_contact = true
end
# We want only 1 primary contact, so if I am the primary contact, all other
# ones have to be secondary
def ensure_only_primary
return unless primary_contact?
self.client.users.update_all(["primary_contact = ?", false], ["id <> ?", self.id])
end
end

Resources