I have two Models Order(id, user_id,...) and OrderEvent(id, order_id, ...)the data of these are stored in two Objects #order and #order_event.
Now i want to save Order and update the order_id in OrderEvent and
then save the content in the database.
In CakePHP if we had the content structured in a certain way we could save all of these together at once. Is there a way to save the content in a single call so that if there is any validation error both records are not created and fails
Order
--- !ruby/object:Order
attributes:
id:
host_id: 1
user_id: 1
order_no: PH1504-F3D11353
type_of_order: events
order_date: !ruby/object:DateTime 2015-04-17 10:49:52.066168000 Z
sub_total: 7050.0
tax_rate:
rate_cost:
deliver_charges:
discount_code:
discount_rate:
discount_cost:
total_cost: 7050.0
order_dishes_count:
order_events_count:
status: 0
created_at:
updated_at:
OrderEvent
--- !ruby/object:OrderEvent
attributes:
id:
order_id:
event_id: 2
no_of_ppl: 3
event_date: 2015-01-22 00:00:00.000000000 Z
cost: 2350.0
total_cost: 7050.0
status: 0
created_at:
updated_at:
Order
class Order < ActiveRecord::Base
belongs_to :user
belongs_to :host
has_many :order_events
has_many :messages
end
OrderEvent
class OrderEvent < ActiveRecord::Base
belongs_to :event
belongs_to :order
end
If you have set proper associations, following will work for you:
#order = Order.create(...)
#order_event = #order.order_events.create(...) # pass all attrs except id & order_id
EDIT:
If I have the object ready and just wanna save can I use '.save' instead of .create – Harsha M V
In that case, you can directly update #order_event as:
#order_event.update(:order => #order)
Best way is to use Transactions of rails. Reference : Transactions
What you are looking for might be :
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
If you only want to update the OrderEvent just do
class OrderEvent
belongs_to :order
end
And in your controller
#order_event.update_attributes(order: #order)
Edit : update_attributes saves to the database.
See http://apidock.com/rails/ActiveRecord/Persistence/update for more info.
If you have both models ready but none has an id yet then I think you should go for nested attributes :
class Order
accepts_nested_attributes_for :order_events
has_many :order_events
end
class OrderEvent
belongs_to :order
end
I controller, In order to save the Order, with its order_events :
def update
#order.update_attributes(order_events_attributes: [#order_event])
end
Should work like a charm.
Inverse_of and accepts_nested_attributes_for allow you to create two associated objects at the same time.
Your models should be something like:
class Order < ActiveRecord::Base
has_many :order_events, inverse_of: :order
accepts_nested_attributes_for :order_events
end
class OrderEvent < ActiveRecord::Base
belongs_to :order, inverse_of: :order_event
end
In the orders controller:
def new
#order = Order.new
#order.order_events.build
end
def create
#order = Order.new(order_params)
...
end
Allow order_events attributes in the params:
def order_params
params.require(:order).permit(order_event_attributes: [])
end
In the form:
<%= form_for #order do |f| %>
<%= f.fields_for :order_events do |event| %>
<!-- event fields go here -->
<% end %>
Here is an article with more details what invese_of does: http://viget.com/extend/exploring-the-inverse-of-option-on-rails-model-associations
Related
I want to create an Invoice and the regarding InvoiceItems at the same time. While Invoice has_many :invoice_items and an InvoiceItem belongs_to :invoice. How do I perform such action in Rails 7 so that a User can add multiple invoiceItems to their invoice via Turbo? I dont need to know how TurboStreams and stuff work, since I am familiar, but I just cant get the InvoiceItems to be created at the same time as the Invoice.
I already found this post, but could not get any useful information out of it.
Models
Invoice.rb
class Invoice < ApplicationRecord
belongs_to :project
has_many :invoice_items, foreign_key: :invoice_id # not sure if this foreign_key is necessary
accepts_nested_attributes_for :invoice_items
end
invoice_item.rb
class InvoiceItem < ApplicationRecord
belongs_to :invoice
end
Controllers
Invoice_controller.rb
def create
#project = Project.find(params[:project_id])
#client = Client.find(params[:client_id])
#invoice = #project.invoices.new(invoice_params)
#invoice_item = #invoice.invoice_items.new
#invoice.invoice_items_attributes = [:invoice_id, :amount]
#invoice.client_id = #client.id
respond_to do |format|
if #invoice.save
....
def invoice_params
params.require(:invoice).permit(... :invoice_item, invoice_item_attributes: [:id, :invoice_id, :amount, ...])
end
Currently I try using a form_for inside of the Invoice form like:
<%= form.fields_for #invoice.invoice_items.build do |lorem| %>
Which gives me following error in the console (but saves the invoice as expected:
Unpermitted parameter: :invoice_item. Context: { controller: InvoicesController, action: create, request: #<ActionDispatch::Request:0x000000010a0c8d88>, params: {"authenticity_token"=>"[FILTERED]", "invoice"=>{..., "invoice_item"=>{"invoice_id"=>"", "amount"=>"3"}}, "button"=>"", "controller"=>"invoices", "action"=>"create", "user_id"=>"1", "client_id"=>"1", "project_id"=>"1"} }
notice that the invoice_id is not passed to the invoice_item.
Via console something like
#invoice = Invoice.new
#invoice.invoice_items.new(amount: "3", ...)
#invoice.save!
Does work weirdly but it does not translate to my code.
What am I doing wrong here?
# invoice_item_attributes is wrong
def invoice_params
params.require(:invoice).permit(... :invoice_item, invoice_item_attributes: [:id, :invoice_id, :amount, ...])
end
Should be
# invoice_items_attributes is right
def invoice_params
params.require(:invoice).permit(... :invoice_item, invoice_items_attributes: [:id, :invoice_id, :amount, ...])
end
Notice the missing 's'.
https://www.ombulabs.com/blog/learning/rails/nested-forms.html
After following the GoRails screencast on how to properly set nested form attributes in rails, I still came across errors. I eventually could trace them and found this neat post which game the hint to use inverse_of and autosave: true. I am not 100% sure what those do, even though I will read now to find out, but my stuff is working properly now :)
Modified Model
class Invoice < ApplicationRecord
belongs_to :project
has_many :invoice_items, inverse_of: :invoice, autosave: true
accepts_nested_attributes_for :invoice_items
...
I am attempting to update multiple records at once but coming to an issue with my validation for uniqueness scope.
When I create a record on a per-record-basis, it does what the title asks. But with the array, it doesn't.
Why?
Because the params don't pass the ID of the nested attribute to update.
Example of param difference of updating on a per record vs array:
Mutli update:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"==", "shop_product_ids"=>["42"], "shop_product"=>{"shop_product_print_files_attributes"=>{"0"=>{"print_location_id"=>"1", "image_file_id"=>"2"}, "1"=>{"print_location_id"=>"2", "image_file_id"=>"2"}, "2"=>{"print_location_id"=>"3", "image_file_id"=>""}, "3"=>{"print_location_id"=>"4", "image_file_id"=>""}, "4"=>{"print_location_id"=>"5", "image_file_id"=>""}, "5"=>{"print_location_id"=>"6", "image_file_id"=>""}}}, "commit"=>"Edit Checked"}
1 Record Update:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"==", "shop_product"=>{... "shop_product_print_files_attributes"=>{"0"=>{"print_location_id"=>"1", "image_file_id"=>"1", "id"=>"145"}, "1"=>{"print_location_id"=>"2", "image_file_id"=>"", "id"=>"151"}, "2"=>{"print_location_id"=>"3", "image_file_id"=>""}, "3"=>{"print_location_id"=>"4", "image_file_id"=>""}, "4"=>{"print_location_id"=>"5", "image_file_id"=>""}, "5"=>{"print_location_id"=>"6", "image_file_id"=>""}},"id"=>"42"}, "commit"=>"Sync", "id"=>"42"}
On the one record update, it passes the shop_product_print_file ID in order to update. On the multi, it doesn't and shouldn't.
Goal: To update if exists, create if it doesn't destroy if empty. I need to somehow do what I do in the model reject_if in my controller but I am boggled on how to.
Models:
class PrintLocation < ApplicationRecord
has_many :shop_products, through: :shop_product_print_files
has_many :shop_product_print_files
end
class ShopProductPrintFile < ApplicationRecord
validates :shop_product, uniqueness: { scope: :print_location }
belongs_to :shop_product
belongs_to :print_location
belongs_to :image_file
end
class ShopProduct < ApplicationRecord
has_many :shop_product_print_files
has_many :print_locations, through: :shop_product_print_files
accepts_nested_attributes_for :shop_product_print_files
...
accepts_nested_attributes_for :shop_product_print_files, reject_if: :reject_file, :allow_destroy => true
def reject_file(attributes)
if attributes[:image_file_id].blank?
if attributes[:id].present?
attributes.merge!({:_destroy => 1}) && false
else
true
end
end
end
end
class ImageFile < ApplicationRecord
# this is where users upload files to
belongs_to :user
has_many :shop_product_print_files
end
Controller methods handling this:
def edit_multiple
#image_files = ImageFile.where(user_id: current_user.id)
#shop_products = ShopProduct.find(params[:shop_product_ids])
#shop_product = ShopProduct.new
#shop_product.shop_product_print_files.build PrintLocation.all.map { |pl| { print_location: pl } }
end
def update_multiple
#shop_products = ShopProduct.find(params[:shop_product_ids])
#image_files = ImageFile.where(user_id: current_user.id)
#shop_products.reject! do |shop_product|
shop_product.update_attributes!(params[:shop_product].permit(:shop_product, shop_product_print_files_attributes: [ :id, :print_file, :print_location_id, :shop_product_id, :image_file_id ]).reject { |k,v| v.blank? })
end
if #shop_products.empty?
redirect_to '/'
else
#shop_product = ShopProduct.new(params[:shop_product])
render "edit_multiple"
end
end
When updating on a record-by-record basis, the :reject_file method handles my goal with the help of the SPPF passing through. But When doing this with the array, no SPPF ID passes through which gives me the error:
> ActiveRecord::RecordInvalid (Validation failed: Shop product print
> files shop product has already been taken):
Does anyone have any conclusions on how I can accomplish this?
I am omitting the form to update array to keep this short as the parameters should explain it enough since what I need is most likely either a controller conditional statement to achieve my goal .
I'm trying to set up a classic 'like' model for Posts on a blog, where users can create one Like for any Post. I have the following models:
class Post < ApplicationRecord
belongs_to :user
has_many :likes
end
class User < ApplicationRecord
has_many :posts
has_many :likes
end
class Like < ApplicationRecord
belongs_to :user
belongs_to :post, counter_cache: true
end
In my controller I monitor the currently logged in user, current_user.
I would like to add a column to my Posts model that indicates whether or not current_user has liked each Post.
I tried adding a method to the Posts model that looks for likes:
class Post < ApplicationRecord
belongs_to :user
has_many :likes
def user_liked
!likes.empty?
end
end
And using includes in the controller method.
#posts = Post.includes(likes: { user: current_user }).where(safe_params).order(order)
render json: #posts
However I get the following error:
ArgumentError (#<User id: 1, username: "pete", ... > was not recognized for preload):
app/controllers/posts_controller.rb:51:in `index'
I'm using Rails API 5.0.
Update:
To clarify, I'm looking for the Rails equivalent of this SQL statement:
SELECT *
FROM Posts
LEFT OUTER JOIN
(SELECT *
FROM Likes
WHERE Likes.user_id = current_user.id) AS MyLikes
ON Posts.id = MyLikes.post_id
The problem is includes takes the name of associations as parameters (like :user, :likes, :posts) as parameter and does not include any instances(In your case current_user)
You could try the following instead.
#posts = Post.includes(likes: :user).where(safe_params).order(order)
If you want to check if posts belong to current_user or not (in haml view for example)
- #posts.each do |post|
- if post.likes && post.likes.any? { |like| like.user_id == current_user.id }
%span You have commented on this post
In Ruby on Rails 4, how do you create a many-to-many relationship inside a relationship model for a friends list such as Facebook using the has_many :through ... syntax ?? I'm a newbie and currently learning Ruby on Rails 4. I have looked at this link.
But still have a hard time grasping it.
you will need a join table that references both sides of the relations
let us say you have an relation Post and another relation Category with a many to many relationship between them you need a join table to be able to represent the relationship.
migration for a join table would be
class CreateCategoriesPosts < ActiveRecord::Migration
def change
create_table :categories_posts do |t|
t.integer :category_id
t.integer :post_id
t.timestamps
end
add_index :categories_posts, [:category_id, :post_id]
end
end
and in the models/post.rb
Class Post < ActiveRecord::Base
has_and_belongs_to_many :categories
end
and in the models/category.rb
Class Category < ActiveRecord::Base
has_and_belongs_to_many :posts
end
more here:
http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association
I think #RAF pretty much nailed it. But to use the OP's example:
class User < ActiveRecord::Base
has_and_belongs_to_many :users_list
end
class UsersList < ActiveRecord::Base
has_and_belongs_to_many :users
end
Although at first it might seem like a User should have only one list of friends (UsersList), that might not always be the case. Think of types within the UserList model, such as: 'close friends', 'work friends', 'all friends' for example.
My advice: dig into the Rails guides. This is a concept worth learning and truly understanding (which I'm still doing :).
many-to_many relationships are a simple concept, but complex when using the database because of the way databases work. A person could have 1 to N different friends, which means that a single entry for a database would need a dynamic amount of memory for each entry, which in the db world is a no-no. So instead of creating a list of friends you would have to make a table that represents the links between friends, for example:
friendship.rb
class Friendship < ActiveRecord::Base
belongs_to :friend, foreign_key: 'friend_A' # this entry has a field called 'friend_A'
belongs_to :friend, foreign_key: 'friend_B' # this entry has a field called 'friend_B'
end
These links will represent your network of friends. However, as the two previous answers have mentioned, Rails has some nifty magic, "has_and_belongs_to_many", which will do this for you.
NOTICE: The problem here is that in my StatusesController, in the index action, the #relationship object only gets the statuses of all your friends, but does not get your own statuses. Is there a better way of approaching this? I am trying to create a view to view all statuses of users that are your friends, and your own statuses too, and so far, I can't seem to figure out how to order it chronologically, even if in my status model, i included "default_scope -> { order(created_at: :desc) } ". Any advice would be deeply appreciated
class User < ActiveRecord::Base
has_many :relationships
has_many :friends, :through => :relationships
has_many :inverse_relationships, class_name: 'Relationship', foreign_key: 'friend_id'
has_many :inverse_friends, through: 'inverse_relationships', :source => :user end
#
class Relationship < ActiveRecord::Base
# before_save...
belongs_to :user
belongs_to :friend, class_name: 'User'
end
#
class RelationshipsController < ApplicationController
def friend_request
user_id = current_user.id
friend_id = params[:id]
if Relationship.where( user_id: user_id, friend_id: friend_id, accepted: false).blank?
Relationship.create(user_id: user_id, friend_id: friend_id, accepted: false)
redirect_to user_path(params[:id])
else
redirect_to user_path(params[:id])
end
end
def friend_request_accept
# accepting a friend request is done by the recipient of the friend request.
# thus the current user is identified by to_id.
relationship = Relationship.where(user_id: params[:id], friend_id: current_user.id).first
if Relationship.exists?(relationship) and relationship.accepted == false
relationship.update_attributes(accepted: true)
end
redirect_to relationships_path
end
def friend_request_reject
relationship = Relationship.where(user_id: params[:id], friend_id: current_user.id).first
relationship.destroy
redirect_to relationships_path
end
################################
def index
#relationships_pending = Relationship.where(friend_id: current_user.id, accepted: false)
end
end
#
class StatusesController < ApplicationController
def index
#status = Status.new
#relationship = Relationship.where('friend_id = ? OR user_id = ?', current_user.id, current_user.id).
where( accepted: true)
end
def new
#status = Status.new
end
end
#
I read this interesting article about Using Polymorphism to Make a Better Activity Feed in Rails.
We end up with something like
class Activity < ActiveRecord::Base
belongs_to :subject, polymorphic: true
end
Now, if two of those subjects are for example:
class Event < ActiveRecord::Base
has_many :guests
after_create :create_activities
has_one :activity, as: :subject, dependent: :destroy
end
class Image < ActiveRecord::Base
has_many :tags
after_create :create_activities
has_one :activity, as: :subject, dependent: :destroy
end
With create_activities defined as
def create_activities
Activity.create(subject: self)
end
And with guests and tags defined as:
class Guest < ActiveRecord::Base
belongs_to :event
end
class Tag < ActiveRecord::Base
belongs_to :image
end
If we query the last 20 activities logged, we can do:
Activity.order(created_at: :desc).limit(20)
We have a first N+1 query issue that we can solve with:
Activity.includes(:subject).order(created_at: :desc).limit(20)
But then, when we call guests or tags, we have another N+1 query problem.
What's the proper way to solve that in order to be able to use pagination ?
Edit 2: I'm now using rails 4.2 and eager loading polymorphism is now a feature :)
Edit: This seemed to work in the console, but for some reason, my suggestion of use with the partials below still generates N+1 Query Stack warnings with the bullet gem. I need to investigate...
Ok, I found the solution ([edit] or did I ?), but it assumes that you know all subjects types.
class Activity < ActiveRecord::Base
belongs_to :subject, polymorphic: true
belongs_to :event, -> { includes(:activities).where(activities: { subject_type: 'Event' }) }, foreign_key: :subject_id
belongs_to :image, -> { includes(:activities).where(activities: { subject_type: 'Image' }) }, foreign_key: :subject_id
end
And now you can do
Activity.includes(:part, event: :guests, image: :tags).order(created_at: :desc).limit(10)
But for eager loading to work, you must use for example
activity.event.guests.first
and not
activity.part.guests.first
So you can probably define a method to use instead of subject
def eager_loaded_subject
public_send(subject.class.to_s.underscore)
end
So now you can have a view with
render partial: :subject, collection: activity
A partial with
# _activity.html.erb
render :partial => 'activities/' + activity.subject_type.underscore, object: activity.eager_loaded_subject
And two (dummy) partials
# _event.html.erb
<p><%= event.guests.map(&:name).join(', ') %></p>
# _image.html.erb
<p><%= image.tags.first.map(&:name).join(', ') %></p>
This will hopefully be fixed in rails 5.0. There is already an issue and a pull request for it.
https://github.com/rails/rails/pull/17479
https://github.com/rails/rails/issues/8005
I have forked rails and applied the patch to 4.2-stable and it works for me. Feel free to use my fork, even though I cannot guarantee to sync with upstream on a regular basis.
https://github.com/ttosch/rails/tree/4-2-stable
You can use ActiveRecord::Associations::Preloader to preload guests and tags linked, respectively, to each of the event and image objects that are associated as a subject with the collection of activities.
class ActivitiesController < ApplicationController
def index
activities = current_user.activities.page(:page)
#activities = Activities::PreloadForIndex.new(activities).run
end
end
class Activities::PreloadForIndex
def initialize(activities)
#activities = activities
end
def run
preload_for event(activities), subject: :guests
preload_for image(activities), subject: :tags
activities
end
private
def preload_for(activities, associations)
ActiveRecord::Associations::Preloader.new.preload(activities, associations)
end
def event(activities)
activities.select &:event?
end
def image(activities)
activities.select &:image?
end
end
image_activities = Activity.where(:subject_type => 'Image').includes(:subject => :tags).order(created_at: :desc).limit(20)
event_activities = Activity.where(:subject_type => 'Event').includes(:subject => :guests).order(created_at: :desc).limit(20)
activities = (image_activities + event_activities).sort_by(&:created_at).reverse.first(20)
I would suggest adding the polymorphic association to your Event and Guest models.
polymorphic doc
class Event < ActiveRecord::Base
has_many :guests
has_many :subjects
after_create :create_activities
end
class Image < ActiveRecord::Base
has_many :tags
has_many :subjects
after_create :create_activities
end
and then try doing
Activity.includes(:subject => [:event, :guest]).order(created_at: :desc).limit(20)
Does this generate a valid SQL query or does it fail because events can't be JOINed with tags and images can't be JOINed with guests?
class Activity < ActiveRecord::Base
self.per_page = 10
def self.feed
includes(subject: [:guests, :tags]).order(created_at: :desc)
end
end
# in the controller
Activity.feed.paginate(page: params[:page])
This would use will_paginate.