I have a User model which can have many child accounts. I have set up the model as below
class User < ApplicationRecord
has_many :child_accounts, class_name: "User", foreign_key: "parent_account_id"
belongs_to :parent_account, class_name: "User", optional: true
end
I have created a ChildAccountsController to handle the child accounts creation etc. and defined routes as below.
resources :users do
resource :child_accounts
end
But I can get form_with to work in this situation. as
form_with(model: [current_user, #child_account], local: true) do
#...
end
form_with infers the url from the model class since both of them are User. the path it infers user_user_path instead of user_child_accounts_path.
So, is there a rails way to create forms with self joins? Or do I have manually handle this case?
To start with you have a pluralization error:
resources :users do
resources :child_accounts
end
resource is used to declare singular resources.
But the polymorphic route helpers will not be able route to that path automatically anyways, when you pass model instances to form_for, form_with, link_to and button_to they deduce the name of route helper method by calling #model_name and using the methods of ActiveModel::Naming. Since #child_account is an instance of User you get user_users_path. The polymorphic routing helpers are not aware of your associations.
It does not matter at all here if you use form_for or form_with as both use the exact same methods to figure out a path from a model or array of models.
You either need to explicitly pass the url:
form_with(model: #child_account, url: user_child_accounts_path(current_user), local: true) do
#...
end
Or use single table inheritance:
class AddTypeToUsers < ActiveRecord::Migration[6.0]
def change
change_table :users do |t|
t.string :type
end
end
end
class User < ApplicationRecord
has_many :child_accounts,
foreign_key: "parent_account_id",
inverse_of: :parent_account
end
class ChildAccount < User
belongs_to :parent_account,
class_name: "User",
inverse_of: :child_accounts
end
class ChildAccountsController < ApplicationController
def new
#child_account = current_user.child_accounts.new
end
def create
#child_account = current_user.child_accounts.new(child_account_params)
# ...
end
private
def child_account_params
params.require(:child_account)
.permit(:foo, :bar, :baz)
end
end
Related
I'm having trouble creating the correct form_with syntax to create a record for a has_many through association and cannot find an example to copy.
My model has a Factory. Factories can have Equipment. Equipment can have many EquipmentVariations. Variations are specific to a particular type of equipment, but are different for each specific piece of equipment (so the VariationType is associated with the EquipmentType)
I can create the factory (e.g. localhost/factories/1)
I can create the equipment at the factory (e.g. localhost/factories/1/equipment/1)
But I cannot manage to make a form that creates EquipmentVariations. That is, when I navigate to localhost/factories/1/equipment/1 I want a form to add EquipmentVariations to that Equipment entry.
Here's my code:
routes.rb
resources :factories do
resources :equipment do
resources :equipment_variations
end
end
resources :equipment_types do
resources :variation_types
end
Models
class Factory < ApplicationRecord
has_many :equipment
end
class Equipment < ApplicationRecord
belongs_to :factory
belongs_to :equipment_type
has_many :equipment_variations
has_many :variation_types, through: :equipment_variations
end
class EquipmentVariation < ApplicationRecord
belongs_to :equipment
belongs_to :variation_type
end
class VariationType < ApplicationRecord
belongs_to :equipment_type
has_many :equipment_variations
has_many :equipment, through: :equipment_variations
end
And the view in app/views/equipment/show.html.erb
<h1><%= #equipment.equipment_type.name %></h1>
<h3>Add Variation</h3>
<%= form_with(model: [#equipment, VariationType.new], url: factory_equipment_equipment_variations_path, local: true) do |form| %>
<%= form.submit %>
<% end %>
This is as close as I have been able to manage, but gives the error: No route matches {:action=>"index", :controller=>"equipment_variations", :factory_id=>"2", :id=>"3"}, missing required keys: [:equipment_id]
Basically, I need to be able to post to the URL /factories/1/equipment/1/equipment_variations from the page at /factories/1/equipment/1. The route given for that is factory_equipment_equipment_variations which is why I specified that in the url parameter, but I feel there must be a simpler way to acheive this. What should the form_with parameters look like?
Ok, so I figured this out. I was close, but the correct syntax is as follows
<%= form_with(model: [#factory, #equipment, EquipmentVariation.new], local: true) do |form| %>
So you need to give the model: parameter all the models for the route (factory, equipment) and it will figure out the correct path by itself and submit the parameters appropriately.
Also, my controller for EquipmentVariation called by above looks like:
def create
#equipment = Equipment.find(params[:equipment_id])
#equipment.variation_types << VariationType.find(params[:variation_type][:variation_type_id])
end
Hope this helps someone in future!
I'm setting up my rails association for an app and I'm not sure if my associations are correct for my use case. The use case is: A product can be added once by a user. Once created other users can then add the same product to their own "feed" within the app. I want to be able to do User.products to list all of a users products. And for products I want to be able to do something like Product.where(id: 2).users to list all of the users that have added the product. I'm currently using a has_and_belongs_to_many association but I think that this is incorrect for what I am trying to achieve?
User model: has_and_belongs_to_many :products
Product model: has_and_belongs_to_many :users
add_index "products_users", ["product_id"], name: "index_products_users_on_product_id"
add_index "products_users", ["user_id"], name: "index_products_users_on_user_id"
Do this:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :created_products, class_name: "Product", foreign_key: :user_id #-> created product
has_and_belongs_to_many :products #-> list of products
end
#app/models/product.rb
class Product < ActiveRecord::Base
belongs_to :user #-> created the product
has_and_belongs_to_many :users #-> list of users
end
You'll need to add the appropriate foreign_key to your User model (user_id in the Product model for the belongs_to :user association) --
--
If your has_and_belongs_to_many relationship is working already, the above should be sufficient.
If not, you need to look up this documentation to see how it works, and then create a join table called products_users (which is populated with the appropriate data):
$ rails g migration CreateProductsUsers
#db/migrate/create_products_users______.rb
class CreateProductsUsers < ActiveRecord::Migration
def change
create_table :products_users, id: false do |t|
t.references :product
t.references :user
end
end
end
$ rake db:migrate
It will allow you to create a single product for a user (IE the Product object will have a direct association with the user who created it). The Product and User models will also be joined with the habtm relationship.
In your controllers, you could use the following:
#config/routes.rb
resources :products #-> url.com/products
scope "profile" do
resources :products, only: :index #-> url.com/profile/products
end
This will allow you to use the following:
#app/controllers/products_controller.rb
class ProductsController < ApplicationController
before_action :product, only: :edit
def index
#products = current_user.products #-> if you're using Devise
end
def edit
#product = current_user.created_products.find params[:id]
end
def new
#product = current_user.created_products.new
end
def create
#product = current_user.created_products.new product_params
#product.save
end
private
def product
redirect_to root_path, notice: "This is not your product" unless current_user.products.exists? params[:id]
end
def product_params
params.require(:product).permit(:x, :y, :z)
end
end
To be able using has_and_belongs_to_many create association, you must create one temperator table container 2 column are product_id, user_id
you can refer
http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_and_belongs_to_many
http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association
I am still new to rails and I am trying to figure out how to implement a polymorphic association without using a nested route or form. I tried searching but everything seemed to be about nesting forms or adding comments, which is not what I am trying to do.
Here are my models
Article.rb
class Article < ActiveRecord::Base
belongs_to :articable, polymorphic: true
end
Organization.rb
class Organization < ActiveRecord::Base
has_many :articles, as: :articable
end
People.rb
class People < ActiveRecord::Base
has_many :articles, as: :articable
end
I want to implement a 'New Article' link from a Organization or People show page and have the correct article_id and article_type entered. What would the correct syntax be to generate this link?
Thanks!
Routes:
resource :people do
resource :articles
end
resource :organizations do
resource :articles
end
ArticlesController:
def create
article = Article.new(params[:article])
if params[:people_id]
people = People.find(params[:people_id])
people.articles << article
else
organization = Organization.find(params[:organization_id])
organization.articles << article
end
article.save
end
Organizations view:
link_to new_organization_article_path(#organization)...
In Rails 3.2 I have been looking for a way to traverse the associations of an object within the before_add callback.
So basically my use case is:
class User < ActiveRecord::Base
has_and_belongs_to_many :meetings
end
class Meeting < ActiveRecord::Base
has_and_belongs_to_many :users
has_many :comments, :before_add => :set_owner_id
end
class Comment < ActiveRecord::Base
belongs_to :meeting
end
def set_owner_id(child)
child.owner_id = <<<THE USER ID for #user >>>
end
and I am creating a comment within the context of a user:
#user.meetings.first.comments.create
How do I traverse the associations from within the before_add callback to discover the id of #user? I want to set this at model level. I have been looking at proxy_association, but I may be missing something. Any ideas?
You should probably create the comment in the context of the meeting, no? Either way, you should handle this in the controller since you'll have no access to #user in your model.
#comment = Meeting.find(id).comments.create(owner_id: #user, ... )
But if you insist on your way, do this:
#comment = #user.meetings.first.comments.create(owner_id: #user.id)
I have namespaced model Equipment::Feature and namespaced controller in my admin part Admin::Equipment::FeaturesController. Model is generic and is used as from within :admin namespace and for public website. I've set up the routing for :admin and :equipment namespaces
namespace :admin do
namespace :equipment do
resources :features
end
end
Which gives me following routes:
admin_equipment_features GET /admin/equipment/features(.:format) admin/equipment/features#index
POST /admin/equipment/features(.:format) admin/equipment/features#create
new_admin_equipment_feature GET /admin/equipment/features/new(.:format) admin/equipment/features#new
edit_admin_equipment_feature GET /admin/equipment/features/:id/edit(.:format) admin/equipment/features#edit
admin_equipment_feature GET /admin/equipment/features/:id(.:format) admin/equipment/features#show
PUT /admin/equipment/features/:id(.:format) admin/equipment/features#update
DELETE /admin/equipment/features/:id(.:format) admin/equipment/features#destroy
Pretty standard stuff. But when I address /admin/equipment/features it throws uninitialized constant Admin::Equipment::FeaturesController::Equipment exception
#index action in my Admin::Equipment::FeaturesController looks like
def index
#features = Equipment::Feature.all
end
It did seem to work, until I declared Admin::Equipment namespace. Before it was like Admin::EquipmentFeaturesController
I guess this is some sort of namespace collision, but I don't get it - where does it come from?
Thanks in advance!
UPDATE Feature model (uses STI pattern)
class Equipment::Feature < ActiveRecord::Base
attr_accessible :category_id, :name_en, :name_ru, :type
belongs_to :category, :class_name => 'Equipment::Category'
has_many :item_features, :class_name => 'Equipment::ItemFeature'
has_many :items, :through => :item_features
translates :name
end
class FeatureBoolean < Equipment::Feature
end
class FeatureNumeric < Equipment::Feature
end
class FeatureString < Equipment::Feature
end
class FeatureRange < Equipment::Feature
end
UPDATE2
Fixing #index action as per answer below resolved the issue. New code:
def index
#features = ::Equipment::Feature.all
end
I think it's now looking for Feature in Admin::Equipment, rather than in ::Equipment
Try specifying that there is no namespace, i.e.
def index
#features = ::Equipment::Feature.all
end
Please create folder like this app/controllers/admin/equipment/features.rb
And then edit your controller name to Admin::Equipment::FeaturesController
class Admin::Equipment::FeaturesController < ActiveRecord::Base
end