In my Rails app, I have the following objects:
Group: has_many users through group_membership
GroupMembership: belongs_to user, belongs_to group
User: has_many groups through group_membership
Users can create groups. When this happens, I want to automatically add the user to the group. In my GroupsController, I have the following (extending InheritedResources):
super do |success, failure|
if success
GroupMembership.create(:user_id => current_user, :group_id => ???)
...
end
The problem is I cannot retrieve the object that super created. Is there a way to do this? Or better, is there a way to change the LockGroup model so that it always performs this association?
When the callback is fired, the controller already has the standard instance variable corresponding to the created group: #group !!
class GroupController < InheritedResources::Base
def create
super do |success, failure|
if success
GroupMembership.create(:user_id => current_user, :group_id => #group.id)
...
end
end
end
I assume the params key given for your group is :group. Then you can used the nested_attributes_for option in the model. Then you can set those in a before filter from the create action:
class User < ActiveRecord::Base
accept_nested_attributes_for :group_membership
end
# on your controller
before_filter :add_user, :on => [:create]
def add_user
params[:group][:group_membership_attributes] = {}
params[:group][:group_membership_attributes][:user] = current_user
end
or you build the group membership on user initialize:
class User < ActiveRecord::Base
def after_initialize
build_group_membership
end
end
# on your controller
before_filter :add_user, :on => [:create]
def add_user
params[:group][:user] = current_user
end
and it should automagically work.
Related
Basically my idea is very simple - I want to create a new cart for each new user. The form itself is generated with scaffold and we're talking rails 4.0.1 here.
Is there a way to do that and if so - how? Maybe you can link me some live examples?
You do not need multiple forms to create multiple objects in Rails controller. Assuming that you have relationships like this:
class User < ActiveRecord::Base
has_many :carts #or has_one :cart
end
class Cart < ActiveRecord::Base
belongs_to :user
end
Then it's perfectly acceptable to do this:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new user_params
if #user.save
#user.carts.create # or #user.create_cart
redirect_to user_path
else
render action: :new
end
end
private
def user_params
params.require(:user).permit(...)
end
end
If the new user form happens to include some cart-specific details, then use fields_for to make them available in the form:
= form_for :user do |f|
... f.blah for user fields ...
= fields_for :cart do |cart_fld|
... cart_fld.blah for cart fields ...
and add cart_params to your controller.
I have set up an event booking application with ruby on rails where I have users who can create events and the general public can book events. I am having problems implementing the booking feature. This is what I have done so far.
Created a Booking resource and associated it with the event model. The booking model contains the following attributes
Booker name
Booker Email
event_id
The goal is to "create a booking" for a current event. However I do not know how to pass the "current_event" parameter to the booking controller and I am also not sure how to define a "current_event".
Update your routes file like this (rails 4):
EventManagement::Application.routes.draw do
resources :events do
resources :bookings
end
end
This will give you a "nested route" -- the route to bookings is always "nested" under events. To create a new booking for an event, you'll use the new_event_booking_path(#event) route and to view a list of all the bookings for the event it's just event_bookings_path(#event). Each of these routes will put the event_id into the params hash.
class BookingsController < ApplicationController
before_filter :load_event
def index
#bookings = #event.bookings
end
def new
#booking = #event.bookings.build
end
def create
#booking = #event.bookings.build booking_params
if #booking.save
..
else
...
end
end
private
def load_event
#event = Event.find params[:event_id]
end
def bookings_params
params.require(:bookings).permit(:booker_name, :booker_email)
end
end
Actually I don't think you should have a Booking resource, but rather an Event resource and only a Booking model. The booking should happen in the events_controller, where you can easily specify the current #event.
Were I you, I would do the following.
# app/models/event.rb
class Event < ActiveRecord::Base
has_many :bookings
end
# /models/booking.rb
class Booking < ActiveRecord::Base
belongs_to :event
end
# app/controllers/events_controller.rb
class EventsController < ApplicationController
# POST /events/{:id}/book
def book_new_ticket
#event = Event.find(params[:id])
if #event.bookings.where(email: params[:email]).count > 0
redirect_to '/somewhere', alert: "THIS EMAIL HAS ALREADY BOOKED, YOU FOOL!"
else
Booking.create!(name: params[:name], email: params[:email], event_id: #event.id)
end
end
end
Haven't really run this code, but it's just a simulation.
I am using Rails Inherited_resource gem in my comments controller, and comments is a nested resource so:
resources :projects do
resources :comments do
end
I also have a belongs_to in the comments controller:
belongs_to :project, :finder => :find_by_project_uuid!, :class_name => "Thfz::Project", :polymorphic => true
How can I set the comment's user association to the current_user(user_id) when its created? As user_id is not suppose to be massive assigned.
I tried following:
def begin_of_association_chain
current_user
end
This does set the user id correctly, but I cannot get nested resource working for Project with this.
Same question come when destroy a comment, I will need to find the comment through current_user, so how to achieve this?
So do I have to write my own create and destroy actions?
Thanks :)
Have you tried the following inside comments_controller?
class CommentsController < InheritedResources::Base
before_filter :authenticate_user! # Assuming you are using Devise for authentication
respond_to :html, :xml, :json
belongs_to :project, :finder => :find_by_project_uuid!, :class_name => "Thfz::Project"
def create
#comment = build_resource
#comment.author = current_user
create!
end
end
I am totally new to Ruby, and Rails. Currently, I am using helper methods. How can I write the same code as this in my Model 'User' so as to access all these variables from controller and view?
Writting code this way in helper is 100% functional:
module HomeHelper
def init(user_id)
#friends = Array.new
#followers = Array.new
#user = User.find_by_id(user_id) #Get User
#friends = #user.users #Get all his friends
#
#statuses = Array.new #
#friends.each do |friend| #
#statuses += friend.statuses #Get all statuses for 'a' friend, then loop
end #
#statuses += #user.statuses #
#statuses = #statuses.sort_by {|status| status.created_at}.reverse!
#friendsof = Array.new
#filtered_friendsof = Array.new
#friends.each do |friend|
#friendsof += friend.users
end
#friendsof.each do |friendof|
unless (#friends.include?(friendof))
if #user != friendof
#filtered_friendsof << friendof
end
end
end
end
#filtered_friendsof = #filtered_friendsof.uniq
end
Controller
class HomeController < ApplicationController
def index
#user_id=3
end
end
Model:
class User < ActiveRecord::Base
has_many :statuses
has_and_belongs_to_many(:users,
:join_table => "user_connections",
:foreign_key => "user1_id",
:association_foreign_key => "user2_id")
#has_many :user_connections
end
Home controller:
class HomeController < ApplicationController
def index
#user = User.find(3)
end
end
User model:
class User < ActiveRecord::Base
has_many :statuses
has_and_belongs_to_many :friends,
:class_name => 'User'
:join_table => "user_connections",
:foreign_key => "user1_id",
:association_foreign_key => "user2_id"
def combined_statuses
(friends.map(&:statuses) + statuses).flatten.
sort_by {|status| status.created_at}.reverse!
end
end
Now, you don't need your helper method and in your view you can use:
#user.friends # instead of #friends
#user.combined_statuses # instead of #statuses
I'll let you figure out the rest, but I hope you get the general idea of pushing the logic into the model.
Most of that logic belongs in the User model. Nothing else needs to be actually doing those computations, and the User model has access to all the relevant pieces. There are additionally several other improvements that can be made. I'll try to add comments below to indicate these improvements.
Model
class User < ActiveRecord::Base
has_many :statuses
has_and_belongs_to_many :friends, # now you can just say user.friends
:class_name => 'User', # makes more sense semantically
:join_table => "user_connections",
:foreign_key => "user1_id",
:association_foreign_key => "user2_id"
def friends_statuses
(friends.map(&:statuses).flatten + statuses).sort_by!(&:created_at).reverse
# Ruby has many great methods for Arrays you should use.
# You can often avoid instantiating variables like the empty Arrays you have.
end
def second_order_friends
(friends.map(&:friends).flatten.uniq - friends) - [self]
end
end
Controller
class HomeController < ApplicationController
def index
user = User.find(7) # how do you decide which user you're displaying things for?
# this might be better off in 'show' rather than 'index'
# here you can call all the methods you have for 'User', such as:
# user.friends, user.statuses, user.friends_statuses, user.second_order_friends
# to make things accessible in the view, you just need an #variable, e.g.:
#friends = user.friends
#latest_statuses = user.friends_statuses.first(10)
end
If I have a nested resource like so:
resources :users
resources :posts
end
and a user has_many posts, it is possible to have Rails start numbering based on the parent association in the URL? For example, currently, nesting resources just grabs the ID:
#user.posts.find(params[:id])
This correctly namespaces the posts, only allowing posts from #user... however, is there a way such that the post_id is independent? I.E. I want each user's posts to start at 1, where:
/users/1/posts/1
/users/2/posts/1
Actually refer to two different posts?
It can be quite a bit of work, but basically you can do it with these steps:
Create a migration to add a new attribute to store the specific user-post count. (I used user_post_id)
Override Post's to_param method to use the new value you just created. (It has to be a string.)
to_param is the method that the url and path helpers use.
Create a before_save filter that will actually increment the user_post_id value for each new post.
Change all your controller methods to find on user_post_id
#user = User.find(params[:user_id])
#post = #user.posts.where(:user_post_id => (params[:id])).first
Change all your Views that might not work now
You can see the source here: Custom Nested Resource URL example
Code
migration:
class AddUserPostIdToPosts < ActiveRecord::Migration
def change
add_column :posts, :user_post_id, :integer
end
end
post.rb:
class Post < ActiveRecord::Base
before_save :set_next_user_post_id
belongs_to :user
validates :user_post_id, :uniqueness => {:scope => :user_id}
def to_param
self.user_post_id.to_s
end
private
def set_next_user_post_id
self.user_post_id ||= get_new_user_post_id
end
def get_new_user_post_id
user = self.user
max = user.posts.maximum('user_post_id') || 0
max + 1
end
end
A couple controller methods
posts_controller.rb:
class PostsController < ApplicationController
respond_to :html, :xml
before_filter :find_user
def index
#posts = #user.posts.all
respond_with #posts
end
def show
#post = #user.posts.where(:user_post_id => (params[:id])).first
respond_with [#user, #post]
end
...
end