Create review in two scopes at once - ruby-on-rails

I have two types of users, one that can create movies and one that can create reviews:
class User < ActiveRecord::Base
has_many :created_movies, foreign_key: 'creator_id', class_name: 'Movie'
has_many :reviewed_movies, foreign_key: 'reviewer_id', class_name: 'Review'
end
class Review < ActiveRecord::Base
belongs_to :movie
belongs_to :reviewer, class_name: 'User'
end
class Movie < ActiveRecord::Base
has_many :reviews
belongs_to :creator, class_name: 'User'
end
What I want to do is create the particular review and set the reviewer_id to the current_user at once. Here is my code for Review#create:
def create
#movie = Movie.find(params[:movie_id])
#review = #movie.reviews.merge(current_user.reviewed_movies).create(review_params)
if #review.save
.
.
.
end
This does not make sense to me as I do have current_user defined in my SessionsHelper and have 'include SessionsHelper' in my application controller. Code for current_user:
def current_user
#current_user ||= User.find_by(id: session[:user_id])
end
I've been stuck on this problem for a couple hours and would appreciate any assistance on how I can get past it, thanks!

The following code will create the new review and assign it to the current_user:
def create
#movie = Movie.find(params[:movie_id])
#review = #movie.reviews.build(review_params)
#review.reviewer = current_user
if #review.save
# ...
end
or alternatively:
def create
#review = Review.new(review_params)
#review.movie = Movie.find(params[:movie_id])
#review.reviewer = current_user
if #review.save
# ...
end

Related

Rails Shopping Order History

I am new in Rails and I want to create shopping History. I think my association doesn't work correctly or my function current order
My model
User
Order
Order_line_item
Order_item
Cart
So my Db Looks like this
When I click "Buy" button in cart Oder_items create in Order_line_items. For example you can order 1 item or several items. The quantity of you order save in Order Line Items. So the order_line_items save in Order. It's order history. This is how My logic work. I think so. I can see any order by id. Like this
Association
class User < ApplicationRecord
has_many :cart_items
has_many :order_items
has_many :order_line_items, through: :order_items
has_one :cart, dependent: :destroy
has_one :order, dependent: :destroy
end
class Order < ApplicationRecord
has_many :order_line_items
has_many :order_items
belongs_to :user
end
class OrderLineItem < ApplicationRecord
has_many :order_items
belongs_to :order
has_one :user, through: :order
end
class OrderItem < ApplicationRecord
belongs_to :order_line_items
belongs_to :order
belongs_to :product
# has_one :user, through: :order_line_items, :source => :order
end
Application Helper
module ApplicationHelper
def current_order_line(order_line)
if OrderItem.find_by(order_line_id: order_line[:id]).present?
OrderItem.find_by(order_line_id: order_line[:id])
else
current_order.order_line_items || current_order.build_order_line_items
end
end
#I think this code doesn't work property
def current_order
if session[:order_id].present?
Order.find(session[:order_id])
else
current_user.order || current_user.build_order
end
end
def current_cart
if session[:cart_id].present?
Cart.find(session[:cart_id])
else
current_user.cart || current_user.build_cart
end
end
end
My Controllers
class OrdersController < ApplicationController
def show
#order_line_items = current_order.order_line_items
end
end
class OrderItemsController < ApplicationController
def create
#order_line_items = current_order_line_items
#order_item = #order_line_items.order_items.new(order_params)
#order_line_items.save
redirect_to my_orders_path
OrderMailer.order_confirmation(current_user, #order_item).deliver_now
flash[:success] = "Order has been confirmed"
session[:order_id] = #order.id
session[:user_id] = current_user.id
def order_params
params.require(:order_item).permit(:product_id)
end
end
#There I need also call order_line_item to create order. I don't know how
class OrderLineItemsController < ApplicationController
def show
#order_items = current_order.order_items
end
class CartsController < ApplicationController
def show
#cart_items = current_cart.cart_items
#order_line_item = current_order.order_line_items.new
#order_item = current_order_line(#order_line_item.id).order_items #I got error here
end
Error message:
NoMethodError in CartsController#show
undefined method `[]' for nil:NilClass
I think I made a mistake with my association and my function current_order_line doesn't work property. I will be pressure If you can help me!
Maybe I think It can be work
module ApplicationHelper
def current_order_line
if session[:order_line_id].present?
OrderLineItem.find(session[:order_line_id])
else
current_order.order_line_items || current_order.build_order_line_items
end
end
class CartsController < ApplicationController
def show
#cart_items = current_cart.cart_items
#order_line_item = current_order.order_line_items.new
#order_item = current_order_line.order_items.new
end
So then I got this error message
NoMethodError in CartsController#show
undefined method `order_items' for #<ActiveRecord::Associations::CollectionProxy []>
I believe that your issue might be here.
def current_order_line
if session[:order_line_id].present?
OrderLineItem.find(session[:order_line_id])
else
current_order.order_line_items || current_order.build_order_line_items
end
end
If I look at the if block OrderLineItem.find(session[:order_line_id]), you are returning a OrderLineItem record.
However, the else block current_order.order_line_items || current_order.build_order_line_items does not return a OrderLineItem record. Instead, you are returning a relationship and not a single record.
When you do current_order.order_line_items, you are returning an ActiveRecord collection (not a single record). And ActiveRecord does not have a order_items method; that method is defined on OrderItem
If you want to keep the current design, what you need to do is return an OrderLineItem from current_order_line. I think the following should work (syntactically, but not sure if that makes sense in terms of the business logic):
def current_order_line
if session[:order_line_id].present?
OrderLineItem.find(session[:order_line_id])
else
if !current_order.order_line_items
current_order.build_order_line_items
end
current_order.order_line_items.first
end
end

has_many :through add extra param in join table in one call (object creation)

I have the following code letting a user to create a new album through a join table with an extra params (creator).
In order to do it, my controller does 2 requests (one for creating the album object and the collaboration object / the other to update the collaboration object with the extra params).
I would like to know if there is a way to do this call with only one request. (add the extra "creator" params in the same time than the album creation)
Thank you.
albums_controller.rb
class AlbumsController < ApplicationController
def new
#album = current_user.albums.build
end
def create
#album = current_user.albums.build(album_params)
if current_user.save
#album.collaborations.first.update_attribute :creator, true
redirect_to user_albums_path(current_user), notice: "Saved."
else
render :new
end
end
private
def album_params
params.require(:album).permit(:name)
end
end
Album.rb
class Album < ApplicationRecord
# Relations
has_many :collaborations
has_many :users, through: :collaborations
end
Collaboration.rb
class Collaboration < ApplicationRecord
belongs_to :album
belongs_to :user
end
User.rb
class User < ApplicationRecord
has_many :collaborations
has_many :albums, through: :collaborations
end
views/albums/new
= simple_form_for [:user, #album] do |f|
= f.input :name
= f.button :submit
You can just add associated objects on the new album instance:
#album = current_user.albums.new(album_params)
#album.collaborations.new(user: current_user, creator: true)
When you call #album.save ActiveRecord will automatically save the associated records in the same transaction.
class AlbumsController < ApplicationController
def new
#album = current_user.albums.new
end
def create
#album = current_user.albums.new(album_params)
#album.collaborations.new(user: current_user, creator: true)
if #album.save
redirect_to user_albums_path(current_user), notice: "Saved."
else
render :new
end
end
private
def album_params
params.require(:album).permit(:name)
end
end
You are also calling current_user.save and not #album.save. The former does work due to fact that it causes AR to save the associations but is not optimal since it triggers an unessicary update of the user model.

DB rolls back on create action

I'm trying to create a form with a series of checks to prevent duplicates during the simultaneous creation of three model records: one for the parent (assuming it doesn't exist), one for its child (assuming it doesn't exist), and one for a join table between the child and the User (to allow the User to have their own copy of the Song object).
In the current state of the code, The checks seemingly pass, but
the server logs show ROLLBACK, and nothing gets saved
to the database EXCEPT the parent object (artist).
When I try to use the ids of the object, I get the error undefined method id for nil:NilClass, or "couldn't find object without an ID".
The following code is in my controller:
class SongsController < ApplicationController
before_action :authenticate_user!
def create
#artist = Artist.find_by(name: params[:artist][:name].strip.titleize) #look for the artist
#song = Song.find_by(title: params[:artist][:songs_attributes]["0"][:title].strip.titleize)
if #artist.present? && #song.present?
#user_song = current_user.user_songs.find(#song_id)
if #user_song.present?
render html: "THIS SONG IS ALREADY IN YOUR PLAYLIST"
render action: :new
else
#user_song = UserSong.create(user_id: current_user.id, song_id: #song.id)
redirect_to root_path
end
elsif #artist.present? && !#song.present?
#song = #artist.songs.build(title: params[:artist][:songs_attributes]["0"][:title].strip.titleize, lyrics: params[:artist][:songs_attributes]["0"][:lyrics].strip)
#user_song = UserSong.create(user_id: current_user.id, song_id: #song.id)
redirect_to root_path
elsif !#artist.present?
#artist = Artist.create(name: params[:artist][:name].strip.titleize)
#song = #artist.songs.build(title: params[:artist][:songs_attributes]["0"][:title].strip.titleize, lyrics: params[:artist][:songs_attributes]["0"][:lyrics].strip)
#user_song = UserSong.create(user_id: current_user.id, song_id: #song.id)
redirect_to root_path
else
render html: "SOMETHING WENT WRONG. CONTACT ME TO LET ME KNOW IF YOU SEE THIS MESSAGE"
end
end
def index
#songs = Song.all
end
def new
#artist = Artist.new
#artist.songs.build
#user_song = UserSong.new(user_id: current_user.id, song_id: #song_id)
end
def show
#song_id = params["song_id"]
#song = Song.find(params[:id])
end
def destroy
UserSong.where(:song_id => params[:id]).first.destroy
flash[:success] = "The song has been from your playlist"
redirect_to root_path
end
def edit
#song = Song.find(params[:id])
#artist = Artist.find(#song.artist_id)
end
def update
end
private
def set_artist
#artist = Artist.find(params[:id])
end
def artist_params
params.require(:artist).permit(:name, songs_attributes: [:id, :title, :lyrics])
end
def set_song
#song = Song.find(params["song_id"])
end
end
The models:
class Artist < ApplicationRecord
has_many :songs
accepts_nested_attributes_for :songs, reject_if: proc { |attributes| attributes['lyrics'].blank? }
end
class Song < ApplicationRecord
belongs_to :artist
has_many :user_songs
has_many :users, :through => :user_songs
end
class UserSong < ApplicationRecord
belongs_to :song
belongs_to :user
end
Sorry if I haven't abstracted enough. Not really sure how, given that there's no error message, just a rollback (without any validations present in any of the controllers).
Thanks to #coreyward and his pointing out of the fat-model skinny-controller lemma (never knew that was a thing), I was able to cut the code down and arrive at a solution immediately. In my models, I used validates_uniqueness_of and scope in order to prevent duplication of records. In my controller, I used find_or_create_by to seal the deal.
To whom it may concern, the final code is as follows:
class SongsController < ApplicationController
before_action :authenticate_user!
def create
#artist = Artist.find_or_create_by(name: params[:artist][:name].strip.titleize)
#song = #artist.songs.find_or_create_by(title: params[:artist][:songs_attributes]["0"][:title].strip.titleize) do |song|
song.lyrics = params[:artist][:songs_attributes]["0"][:lyrics].strip
end
#user_song = current_user.user_songs.find_or_create_by(song_id: #song.id) do |user_id|
user_id.user_id = current_user.id
end
redirect_to root_path
end
class Song < ApplicationRecord
validates_uniqueness_of :title, scope: :artist_id
belongs_to :artist
has_many :user_songs
has_many :users, :through => :user_songs
end
class Artist < ApplicationRecord
validates_uniqueness_of :name
has_many :songs
accepts_nested_attributes_for :songs, reject_if: proc { |attributes| attributes['lyrics'].blank? }
end
class UserSong < ApplicationRecord
validates_uniqueness_of :song_id, scope: :user_id
belongs_to :song
belongs_to :user
end

How to require users to request group membership?

I have a User model, a group model (Cliq), and a group_membership model (Cliq_Membership). Everything seems to be working fine so far. I currently have it so that when a User creates a group they "own" it and when an "owner" leaves the group (destroys their group membership) the entire group is destroyed. A group has one owner and many members. I want to make it so that a User has to request to be a "member". I want the "owner" to be the only one to see the requests and accept/deny the requests.
For Clarity:
I want users to have to request to be group members
I want Cliqs to be able to request Users to be members
Only the owner should be able to see/accept/deny friend requests
I want the relationship to be "two-way/self-referential"; that is, I want the User/Member to be shown as being included in the group and the group as having another member
Cliqs = Groups
How do you accomplish this?
Here is my code so far:
Models:
class User < ActiveRecord::Base
has_many :uploads
has_one :owned_cliq, foreign_key: 'owner_id', class_name: 'Cliq', dependent: :destroy
has_many :cliq_memberships
has_many :cliqs, through: :cliq_memberships
end
class CliqMembership < ActiveRecord::Base
belongs_to :cliq
belongs_to :user
end
class Cliq < ActiveRecord::Base
belongs_to :owner, class_name: 'User'
has_many :cliq_memberships, dependent: :destroy
has_many :members, through: :cliq_memberships, source: :user
end
Controllers:
class CliqMembershipsController < ApplicationController
def create
#Cliq or Cliq_ID?
#cliq = Cliq.find(params[:cliq])
#cliq_membership = current_user.cliq_memberships.build(cliq: #cliq)
#cliq.members << current_user
if #cliq_membership.save
flash[:notice] = "Joined #{#cliq.name}"
else
flash[:notice] = "Not able to join Cliq."
end
redirect_to cliq_url
end
def destroy
#cliq_membership = current_user.cliq_memberships.find(params[:id])
#cliq = #cliq_membership.cliq
if #cliq.owner == current_user
#cliq.destroy
flash[:notice] = "Cliq has been deleted."
redirect_to current_user
else
#cliq_membership.destroy
flash[:notice] = "You left the Cliq."
redirect_to current_user
end
end
end
class CliqsController < ApplicationController
def show
#cliq = Cliq.find(params[:id])
end
def new
#cliq = Cliq.new(params[:id])
end
def create
#cliq = current_user.build_owned_cliq(cliq_params)
#cliq.members << current_user
if #cliq.save
redirect_to current_user
else
redirect_to new_cliq_path
end
end
def destroy
##cliq = current_user.owned_cliq.find(params[:id])
#lash[:alert] = "Are you sure you want to delete your Cliq? Your Cliq and all of its associations will be permanently deleted."
##cliq.destroy
#if #cliq.destroy
#redirect_to current_user
#flash[:notice] = "You deleted the Cliq."
#else
#redirect_to current_user
#set up error handler
#flash[:notice] = "Failed to delete Cliq."
#end
end
def cliq_params
params.require(:cliq).permit(:name, :cliq_id)
end
end
You can create another model and controller for handling user requests
create request.rb model
class Request < ActiveRecord::Base
belongs_to :user
belongs_to :cliq
end
create requests_controller.rb.
class RequestsController < ApplicationController
before_action :set_group
before_action :auth_group_owner
before_action :find_request, except: [:index, :create]
def index
end
def create
#grp.requests.where(user_id: current_user.id).first_or_create
# redirect the user
end
def approv
# add the user to the group
#request.destroy
# redirect
end
def destroy
#delete the request
end
private
def set_group
#find group #grp
end
def auth_group_owner
if current_user != #grp.owner
redirect
end
end
def find_request
#find request
end
end
your routes.rb
resources :groups do
resources :requests, only: [:index, :destroy] do
member do
get 'approv'
end
end
end
The following solution should work without creating a new model for requests. Adding a new boolean field to CliqMembership model to store whether a particular cliq_memberhip is confirmed or not is sufficient. (Let's call that field 'confirmed', for example)
class User < ActiveRecord::Base
has_many :cliq_memberships
has_many :cliqs, through: :cliq_memberships
has_many :confirmed_memberships, -> { confirmed }, class_name: "CliqMembership"
has_many :confirmed_cliqs, through: :confirmed_memberships, source: :cliq
end
class CliqMembership < ActiveRecord::Base
belongs_to :cliq
belongs_to :user
scope :confirmed, -> { where(confirmed: true) }
end
class Cliq < ActiveRecord::Base
has_many :cliq_memberships, dependent: :destroy
has_many :members, through: :cliq_memberships, source: :user
has_many :confirmed_memberships, { confirmed }, class_name: "CliqMembership"
has_many :confirmed_members, through: :confirmed_memberships, source: :user
end
With this, you can set the value of confirmed field to false by default when a new cliq_membership is created by a user. Until the owner update's that particular cliq_membership to change the value of confirmed to true.
Assuming user & cliq are instances of User model & Cliq model respectively, you can now use user.confirmed_cliqs and cliq.confirmed_members.
Edit:
In order to restrict the edit & update actions on cliq_membership to only the cliq owner, you can use a before filter.
class CliqMembershipsController < ApplicationController
before_action :cliq_owner, only: [:edit, :update]
def edit
#cliq_membership = CliqMembership.find(params[:id])
end
def update
#cliq_membership = CliqMembership.find(params[:id])
#cliq_membership.update_attributes(cliq_membership_params)
end
private
def cliq_membership_params
params.require(:cliq_membership).permit(:cliq_id, :user_id, :confirmed)
end
def cliq_owner
#cliq = CliqMembership.find(params[:id]).cliq
redirect_to root_url unless #cliq.owner == current_user
end
end
Hope it works for you.

Rails create action with multiple belongs_to

Trying to figure out a better way of assigning a review it's associated models.
I have the following classes:
class User < ActiveRecord::Base
has_many :reviews, dependent: :destroy
end
class Review < ActiveRecord::Base
belongs_to :user
belongs_to :restaurant
end
class Restaurant < ActiveRecord::Base
has_many :reviews, dependent: :destroy
end
Pretty straightforward stuff. A review must have a restaurant and a user. My create action looks like this:
def create
#restaurant = Restaurant.find(params[:restaurant_id])
#review = #restaurant.reviews.build(review_params)
#review.user = current_user
if #review.save
redirect_to #restaurant
else
render 'new'
end
end
private
def review_params
params.require(:review).permit(:content)
end
Currently I build the review for the restaurant and then I assign the review's user to the current user.
This all works fine but is there a cleaner way to build the associations?
Is there a way to add additional arguments to the build method alongside the strong params?
I looked at accepts_nested_attributes_for but I couldn't get it to work.
Thanks!
You can use merge in the review_params like below
def review_params
params.require(:review).permit(:content).merge(user_id: current_user.id)
end
so that you can erase this line #review.user = current_user in the create method
In your form, you can put a hidden field with the user_id that you want to assign:
<%= f.hidden_field :user_id, value: #user.id %>
Then, add it to your review_params:
params.require(:review).permit(:content, :user_id)

Resources