I have an order form and when an order is created, a new customer is created as well. For this I have the following models:
class Customer < ActiveRecord::Base
has_many :orders
has_many :subscriptions, through: orders
end
class Order < ActiveRecord::Base
belongs_to :customer
has_many :subscriptions
accepts_nested_attributes_for :customer
accepts_nested_attributes_for :subscriptions
end
class Subscription< ActiveRecord::Base
belongs_to :order
belongs_to :customer
end
On my order page I have this form:
= simple_form_for(#order) do |f|
= render 'order_fields', f: f
= f.simple_fields_for :subscriptions do |subscription|
= render 'subscription_fields', subscription: subscription
= f.simple_fields_for :customer do |customer|
= render 'customer_fields', customer: customer
= f.button :submit
In my OrderController I have:
def new
#order = Order.new
#order.build_customer
#order.subscriptions.build
end
def create
#order = Order.new(order_params)
if #order.save
(.... etc ...)
end
private
def order_params
params.require(:order).permit(
:amount,
customer_attributes: [ :id, :email, :password, :password_confirmation],
subscriptions_attributes: [ :id, :product_id, :customer_id])
end
Almost everything goes well:
- User is created
- Order is created and has customer_id = User.id
- Subscription is created and has order_id = Order.id
But somehow it wont associate the subscription to the customer :(
I keep having Subscription.customer_id = nil
Can someone please point me in the right direction? Is there something wrong in the models? or in the controllers? I have no idea anymore where to look.
Your relationships are set up a little different. Instead of creating a customer_id field on Subscription, I'd expect you'd just have a has_one :customer, through: :order.
If you do this you won't have need for a customer_id attribute on your Subscription model anymore. And if you want the id of the customer from the world-view of a subscription you'd call subscription.customer.id.
You may also want to add inverse_of designations for your relationships in your models (always a good practice to minimize reloading of models from the database).
So, in total, I'd recommend:
class Customer < ActiveRecord::Base
has_many :orders, inverse_of: :customer
has_many :subscriptions, through: orders
end
class Order < ActiveRecord::Base
belongs_to :customer, inverse_of: :orders
has_many :subscriptions, inverse_of: :order
accepts_nested_attributes_for :customer
accepts_nested_attributes_for :subscriptions
end
class Subscription< ActiveRecord::Base
belongs_to :order, inverse_of: :subscriptions
has_one :customer, through: :order # THIS IS THE KEY CHANGE
end
Oh, and then you can remove the customer_id from the permitted attributes for subscriptions_attributes.
UPDATE
Given that Subscription#customer_id is meant to be disjointed from the Customer -> Order -> Subscription relationship... Ignore the above (except for perhaps the inverse_of stuff) and... You should be able to do:
class Customer < ActiveRecord::Base
has_many :subscriptions, through: :orders, after_add: :cache_customer_id
private
def cache_customer_id(subscription)
subscription.update_column(:customer_id, self.id)
end
end
Thanks pdobb! I got it working now with adding in the order.controller:
def create
#order = Order.new(order_params)
if #order.save
#order.subscriptions.each { subscription| subscription.update_column(:customer_id, #order.customer.id) }
end
Related
I'm trying to figure out how to automatically set up an invoice with invoice_rows, once a reservation is saved.
Attempts
Before even including the order_rows, I tried generating an invoice for order:
I tried including #order.invoices.create(order_contact_id: #order.order_contact_id) after saving the order in create, but this resulted in an empty array:
Order.last.invoice => []
Afterwards I probably should iterate over all products belonging to a order and include them as invoice_rows in invoice. But not sure how.
Note
The actual structure is more complex and consequently I need all my tables.
Code
models
class Order < ApplicationRecord
has_many :invoices
has_many :order_products, dependent: :destroy
end
class OrderProduct < ApplicationRecord
belongs_to :product
belongs_to :order
accepts_nested_attributes_for :product
end
class Product < ApplicationRecord
has_many :orders, through: :order_products
has_many :product_prices, dependent: :destroy, inverse_of: :product
accepts_nested_attributes_for :product_prices, allow_destroy: true
end
class ProductPrice < ApplicationRecord
belongs_to :product, inverse_of: :product_prices
end
orders_controller
class OrdersController < ApplicationController
def create
#order = #shop.orders.new(order_params)
authorize #order
if #order.save
authorize #order
# #order.invoices.create(order_contact_id: #order.order_contact_id)
redirect_to new_second_part_shop_order_path(#shop, #order)
end
end
private
def order_params
params.require(:order).permit(:order_contact_id,
order_products_attributes: [:id, :product_id, :product_quantity, :_destroy,
products_attributes: [:id, :name, :description]])
end
end
As suggested in the comments, I found the error message by using #order.invoices.create!.
Afterwards I iterated over each product and created an invoice_row for the created invoice.
#invoice = #order.invoices.create!(order_contact_id: #order.order_contact_id)
#order.order_products.each do |o_product|
#invoice.invoice_rows.create!(
description: o_product.product.name,
total_price: #reservation.total_product_price(#reservation, o_product)
)
end
**Updated
I have the following models:
product.rb
class Product
belongs_to :user
has_many :line_items
has_many :orders, :through => :order_products
has_many :order_products
lineitem.rb
class LineItem
belongs_to :product
belongs_to :cart
belongs_to: order
order.rb
class Order
belongs_to :user
belongs_to :product
has_many :purchases
has_many :line_items, :dependent => :destroy
has_many :orders, :through => :order_products
has_many :order_products
accepts_nested_attributes_for :order_products
order_product.rb
class OrderProduct < ActiveRecord::Base
belongs_to :order
belongs_to :product
end
order_controller.rb
if #order.save
if #order.purchase
Cart.destroy(session[:cart_id])
session[:cart_id] = nil
The above are my association for the models. I have a big problem in storing multiple product_id from line_items after the cart has been purchased.
I believe there should be codes after if#order.purchase for it to work. How am I suppose to store the order_id and product_id into order_products table.
Can anyone assist me on this?
Appreciate any help here. Thanks
It looks like you might benefit from learning about accepts_nested_attributes_for, as well as how foreign_keys work with ActiveRecord
Foreign Keys
If you set up your ActiveRecord associations correctly, you'll be able to call something like #product.line_items from a single call
ActiveRecord (& relational databsaes in general) work off foreign_keys, which are basically a reference to a model's id in another table. When you say you want to input order_id in another table, what you really need to look at is how to get ActiveRecord to put the correct foreign key into the record
The reason why I'm writing this is because if you can appreciate how ActiveRecord works, you'll be in a much stronger position to fix your problem
Accepts_Nested_Attributes_For
I believe the function that will help you is accepts_nested_attributes_for - which basically allows you to save another model's data through your current model
It works like this:
#/app/models/order.rb
class Order
belongs_to :user
belongs_to :product
has_many :purchases
has_many :line_items, :dependent => :destroy
has_many :orders, :through => :order_products
has_many :order_products
accepts_nested_attributes_for :order_products
This means that if you send the appropriate nested attributes to this model, it will process :order_products for you
To do this, you need to add this to your form & controller:
#app/controllers/orders_controller.rb
def new
#order = Order.new
#order.order_products.build
end
private
def strong_params
params.require(:order).permit(:standard_params, order_products_attributes: [:name] )
end
#app/views/orders/new.html.erb
<%= form_for #order do |f| %>
<%= f.fields_for :order_products do |product| %>
<%= product.text_field :name %>
<% end %>
<% end %>
i have three models, all for a has_many :through relationship. They look like this:
class Company < ActiveRecord::Base
has_many :company_users, dependent: :destroy
has_many :users, through: :company_users
accepts_nested_attributes_for :company_users, :users
end
class CompanyUser < ActiveRecord::Base
self.table_name = :companies_users #this is because this was originally a habtm relationship
belongs_to :company
belongs_to :user
end
class User < ActiveRecord::Base
# this is a devise model, if that matters
has_many :company_users, dependent: :destroy
has_many :companies, through: :company_users
accepts_nested_attributes_for :company_users, :companies
end
this loads fine, and the joins are built fine for queries. However, whenever i do something like
#company = Company.last
#user = #company.users.build(params[:user])
#user.save #=> true
#company.save #=> true
both the User record and the CompanyUser records get created, but the company_id field in the CompanyUser record is set to NULL
INSERT INTO `companies_users` (`company_id`, `created_at`,`updated_at`, `user_id`)
VALUES (NULL, '2012-02-19 02:09:04', '2012-02-19 02:09:04', 18)
it does the same thing when you #company.users << #user
I'm sure that I'm doing something stupid here, I just don't know what.
You can't use a has_many :through like that, you have to do it like this:
#company = Company.last
#user = User.create( params[:user] )
#company.company_users.create( :user_id => #user.id )
Then you will have the association defined correctly.
update
In the case of the comment below, as you already have accepts_nested_attributes_for, your parameters would have to come like this:
{ :company =>
{ :company_users_attributes =>
[
{ :company_id => 1, :user_id => 1 } ,
{ :company_id => 1, :user_id => 2 },
{ :company_id => 1, :user_id => 3 }
]
}
}
And you would have users being added to companies automatically for you.
If you have a has_many :through association and you want to save an association using build you can accomplish this using the :inverse_of option on the belongs_to association in the Join Model
Here's a modified example from the rails docs where tags has a has_many :through association with posts and the developer is attempting to save tags through the join model (PostTag) using the build method:
#post = Post.first
#tag = #post.tags.build name: "ruby"
#tag.save
The common expectation is that the last line should save the "through" record in the join table (post_tags). However, this will not work by default. This will only work if the :inverse_of is set:
class PostTag < ActiveRecord::Base
belongs_to :post
belongs_to :tag, inverse_of: :post_tags # add inverse_of option
end
class Post < ActiveRecord::Base
has_many :post_tags
has_many :tags, through: :post_tags
end
class Tag < ActiveRecord::Base
has_many :post_tags
has_many :posts, through: :post_tags
end
So for the question above, setting the :inverse_of option on the belongs_to :user association in the Join Model (CompanyUser) like this:
class CompanyUser < ActiveRecord::Base
belongs_to :company
belongs_to :user, inverse_of: :company_users
end
will result in the following code correctly creating a record in the join table (company_users)
company = Company.first
company.users.build(name: "James")
company.save
Source: here & here
I suspect your params[:user] parameter, otherwise your code seems clean. We can use build method with 1..n and n..n associations too, see here.
I suggest you to first make sure that your model associations works fine, for that open the console and try the following,
> company = Company.last
=> #<Tcompany id: 1....>
> company.users
=> []
> company.users.build(:name => "Jake")
=> > #<User id: nil, name: "Jake">
> company.save
=> true
Now if the records are being saved fine, debug the parameters you pass to build method.
Happy debugging :)
My models look something like this:
class User < ActiveRecord::Base
attr_accessible: :name
has_many :reviews
end
class Product < ActiveRecord::Base
attr_accessible: :name
has_many :reviews
end
class Review < ActiveRecord::Base
attr_accessible: :comment
belongs_to :user
belongs_to :product
validates :user_id, :presence => true
validates :product_id, :presence => true
end
I am trying to figure out what the best way is to create a new Review, given that :user_id and :product_id are not attr_accessible. Normally, I would just create the review through the association ( #user.reviews.create ) to set the :user_id automatically, but in this case I am unsure how to also set the product_id.
My understanding is that if I do #user.reviews.create(params), all non attr_accessible params will be ignored.
You can do:
#user.reviews.create(params[:new_review])
...or similar. You can also use nested attributes:
class User < ActiveRecord::Base
has_many :reviews
accepts_nested_attributes_for :reviews
...
See "Nested Attributes Examples" on http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html.
It seems you would like to implement a many-to-many relationship between a User and Product model, with a Review model serving as a join table to connect the two with an added comment string. This can be accomplished with a has many through association in Rails. Start by reading the Rails Guides on Associations.
When setting up your Review model, add foreign keys for the User and Product:
rails generate model review user_id:integer product_id:integer
And set up your associations as follows:
class User < ActiveRecord::Base
has_many :reviews
has_many :products, through: :reviews
end
class Product < ActiveRecord::Base
has_many :reviews
has_many :users, through: :reviews
end
class Review < ActiveRecord::Base
# has comment string attribute
belongs_to :user
belongs_to :product
end
This will allow you to make calls such as:
user.products << Product.first
user.reviews.first.comment = 'My first comment!'
Here's how you would create a review:
#user = current_user
product = Product.find(params[:id])
#user.reviews.create(product: product)
I have many-to-many relationship between Game and Account models like below:
class Account < ActiveRecord::Base
has_many :account_games, :dependent => :destroy
has_many :games, :through => :account_games
end
class Game < ActiveRecord::Base
has_many :account_games, :dependent => :destroy
has_many :accounts, :through => :account_games
end
class AccountGame < ActiveRecord::Base
belongs_to :account
belongs_to :game
end
Now I know let's say I want to create a record something like:
#account = Account.new(params[:user])
#account.games << Game.first
#account.save
But how am I supposed to update some of the attributes in AccountGame while I'm doing that? Lets say that AccountGame has some field called score, how would I update this attribute? Can you tell me about the best way to do that? To add any field in the through table while I'm saving the object.
#account = Account.new(params[:user])
#accountgame = #account.account_games.build(:game => Game.first, :score => 100)
#accountgame.save
Though I'd strongly recommend that if you start adding columns to your join-model that you call it something different eg "subscription" or "membership" or something similar. Once you add columns it stops being a join model and starts just being a regular model.
This should work:
class AccountGame < ActiveRecord::Base
belongs_to :account
belongs_to :game
attr_accessible :account_id, :game_id #<======= Notice this line
end