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
Related
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.
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
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.
I'm trying to access a created object that is created using nested attributes when the parent item is saved, and I'm not sure how to do that. My current setup is such:
foo-model.rb
class Foo < ActiveRecord::Base
has_many :bars, dependent: :destroy
accepts_nested_attributes_for :bars
end
bar-model.rb
class Bar < ActiveRecord::Base
belongs_to :foo
end
foos-controller.rb
class FoosController < ApplicationController
def create
#foo = Foo.create(foo_params)
if #foo.save
# What I want to do is essentially this
bar.do_something
# Redirect
redirect_to path
end
end
private
def foo_params
params.require(:foo).permit(:attribute, bar_attributes: [:id, :attribute1])
end
end
First of all, use ::new, not ::create on Foo.
def create
#foo = Foo.new(foo_params)
if #foo.save
After the #save call, you are then free to do any action on the associated Bars:
if #foo.save
#foo.bars.each do |bar|
bar.do_something
end
Foo.create will trigger the 'save' event. Instead, do this
class FoosController < ApplicationController
def create
#foo = Foo.new(foo_params)
if #foo.save
redirect_to path
end
end
end
In the model, add
class Foo < ActiveRecord::Base
has_many :bars, dependent: :destroy
accepts_nested_attributes_for :bars
after_save :bars_do_something
def bars_do_something
bars.each{|b| b.do_something}
end
end
Instead of updating the foo.bars in an each (not a good idea, according to law of Demeter), you could update bar after it's created
class Bar < ActiveRecord::Base
belongs_to :foo
after_create :do_something
def do_something
do_something
end
end
I'm setting up an internal messaging system in my rails app and I'm having trouble getting the message to actually send to another user.
class User < ActiveRecord::Base
# messages and conversations
has_many :user_conversations
has_many :conversations, through: :user_conversations
has_many :messages
class UserConversation < ActiveRecord::Base
belongs_to :user
belongs_to :conversation
before_create :create_user_conversations
accepts_nested_attributes_for :conversation
delegate :subject, to: :conversation
delegate :users, to: :conversation
attr_accessor :to
private
def create_user_conversations
to.each do |recip|
recipient = User.find(recip)
UserConversation.create(user_id: recip, conversation_id: 1)
end
end
end
class Conversation < ActiveRecord::Base
has_many :user_conversations
has_many :users, through: :user_conversations
has_many :messages
accepts_nested_attributes_for :messages
class Message < ActiveRecord::Base
belongs_to :user_conversation
belongs_to :user
And here is my user_conversation_controller:
class UserConversationsController < ApplicationController
def new
#user = User.find(params[:user_id])
#conversation = #user.user_conversations.build
#conversation.build_conversation.messages.build
end
def create
#conversation = UserConversation.new(conversation_params)
#conversation.user = current_user
#conversation.conversation.messages.first.user = current_user
if #conversation.save
redirect_to user_conversation_path(current_user, #conversation)
else
flash[:error] = "There was an error"
render 'new'
end
end
private
def conversation_params
params.require(:user_conversation).permit(:to => [],
conversation_attributes: [:subject,
messages_attributes: [:body]])
end
The error comes in the create_user_conversations method in the UserConversation model. When I try to run
to.each do |recip|
I get an "undefined method 'each' for nil:NilClass" error. However, the "to" array has a value in it, in this case the parameters looked like this:
{"utf8"=>"✓",
"user_conversation"=>{"to"=>["2"],
"conversation_attributes"=>{"subject"=>"Hey",
"messages_attributes"=>{"0"=>{"body"=>"hey"}}}},
"commit"=>"Create User conversation",
"user_id"=>"1"}
Any ideas on why that array isn't getting passed in correctly? Thanks.
You define to as an attr_accessor, which will create get/set methods for an instance variable #to. You're using to as a local variable in your private method create_user_conversations though. This explains the nil:NilClass error.
Try changing the local variable to be an instance variable instead.
I solved my problem by going ahead and adding a recipients_id column to my user_conversations table, then in my UserConversations controller I was able to do
def create
#conversation = UserConversation.new(user_conversation_params)
#conversation.user = current_user
#conversation.conversation.messages.first.user_id = current_user.id
if #conversation.save
UserConversation.recipient_id = #conversation.recipient_id
redirect_to user_conversation_path(current_user, #conversation)
create_user_conversations
else
flash[:error] = "There was an error"
render 'new'
end
end
With the private method create_user_conversations also in my UserConversations controller:
def create_user_conversations
UserConversation.recipient_id.each do |recip|
recipient = User.find(recip)
UserConversation.create(user: recipient, conversation: #conversation.conversation)
end
end
I doubt this is the most elegant way to do this, but it at least gets the job done.