rails create page from - ruby-on-rails

I have Categories and Products. A product has a relation
belongs_to :category
In the categories show page I have a button to add a new product. This button goes to a page where I create the new product, but I need to give the category to the new product.
How can I pass the id from the category page where I was to the new Product? So, if I am in the category Electronic I click 'Add product' and this product automaticaly is associated with Eletronic category.
Hope you can understand what I want.
Thanks

You need to pass the category_id in your link, e.g. new_product_path(category_id: #category.id).
You will also need to have a field in your product form to save the category's ID, e.g <%= f.hidden_field :category_id, params[:category_id] %>

First, I would decide whether each product is contained within a category, or whether it's simply associated with a category. Hints it is contained would be:
You expect each product to have exactly one 'parent' category.
You expect each product will always appear in the context of its parent category.
If and only if you believe this to be the case, I would be tempted to nest the product resource within the category.
# routes.rb
resources :categories do
resources :products
end
# products_controller.rb (SIMPLIFIED!)
class ProductController < ApplicationController
before_filter :get_category
def new
#product = #category.products.build
end
def create
#product = #category.products.build(params[:product])
if #product.save
redirect_to #product
else
render template: "new"
end
end
def get_category
#category = Category.find(params[:category_id])
end
end
If you do this, rails will ensure your product is associated with the right category. The magic happens in #category.products.build, which automatically sets the category_id based on the relationship.
If you'd rather keep categories and products as simple associations, I'd just use a query parameter as per Eric Andres answer, although I'd be tempted to handle it in a slightly different way:
# link:
new_product_path(category_id: #category.id) # So far, so similar.
# products_controller.rb
class ProductsController < ApplicationController
def new
#product = Product.new
#product.category_id = params[:category_id].to_i if params[:category_id]
end
end
# new.erb
<%= f.hidden_field :category_id %>
This is mostly just a stylistic difference. Eric's answer will work too - I just prefer to set the value on the model itself rather than have the view worry about parameters etc.

Related

How to update existing model when another is created?

I have two models, User, and Product. Product belongs to User, User has many Products.
When a Product is created I also want to update multiple fields in the User model. I've been developing with Ruby for like 2 years now and still don't understand forms fully when it comes to this stuff. I'm still getting permitted: false. Now I know that for instance if I was creating a user while also creating a product I would just do #product.user.build but in this case I just want to update an already existing record.
I also realize that I probably can't call f.fields_for :user as #product doesn't know about user yet. In my head I believe I should be able to just pass additional params to the form, grab the current_user in the product#create action and then update the attributes manually by calling update_attributes on user.
product.rb
accepts_nested_attributes_for :user
product controller
def new
#product = Product.new
end
params.require(:product).permit(:product_name, user_attributes: [:phone_number, :email_address])
product view
form_for #product do |f|
f.fields_for :user do |c|
c.text_field :phone_number
c.text_field :email_address
f.text_field :product_name
end
I also realize that I probably can't call f.fields_for :user as #product doesn't know about user yet.
You can assign attributes to #product without saving it.
def new
#product = Product.new(
user: current_user
)
end
Now #product.user works.

How could I add to a table from from a different model HTML page?

I'm new to rails. I have a table TeamMemberships that has a foreign key to Students and a foreign key to Teams. I want to be able to have a link in the show.html.erb page for a specific team that redirects to a form that lets me add a row to TeamMemberships in which the Team foreign key is the team whose show.html.erb page the link was clicked on and the Student foreign key would be determined by the form. Is this possible?
Thanks!
Add a new resources in your routes.rb file like so:
Rails.application.routes.draw do
resources :teams do
resources :team_memberships
end
end
Which will create the CRUD routes:
team_team_memberships GET /teams/:team_id/team_memberships(.:format) team_memberships#index
POST /teams/:team_id/team_memberships(.:format) team_memberships#create
new_team_team_membership GET /teams/:team_id/team_memberships/new(.:format) team_memberships#new
edit_team_team_membership GET /teams/:team_id/team_memberships/:id/edit(.:format) team_memberships#edit
team_team_membership GET /teams/:team_id/team_memberships/:id(.:format) team_memberships#show
PATCH /teams/:team_id/team_memberships/:id(.:format) team_memberships#update
PUT /teams/:team_id/team_memberships/:id(.:format) team_memberships#update
DELETE /teams/:team_id/team_memberships/:id(.:format) team_memberships#destroy
What is interesting you here is the new_team_team_membership GET /teams/:team_id/team_memberships/new(.:format) that you can call using the named route new_team_team_membership.
You will be able to pass the team ID for which you'd like to create the TeamMembership in the route with new_team_team_membership_path(#team.id).\
Regarding the student_id you will pass it in the HTTP POST request body.
Here is how to create the link from the team show page to the TeamMembership form:
<%= link_to 'Add a student', new_team_team_membership_path(#team.id) %>
You will have to create the TeamMembershipsController and the new action. The action should initialise the #new_team_membership variable that will be used by the form and load the student list like so:
class TeamMembershipsController < ApplicationController
def new
#new_team_membership = TeamMembership.new(team_id: params[:team_id])
#students = Student.all
end
end
Then you have to create the app/views/team_memberships/ folder and add the new.html.erb file with the following form:
<%= form_for #new_team_membership do |f| %>
<%= f.select :student_id, options_for_select(#students.collect{ |student| [student.name, student.id]) %>
<%= f.submit %>
<% end %>
Then in your TeamMembershipsController controller, you have to define the create action that will receive in parameter the team_id, and from the form the selected student_id. You will then be able to create the TeamMembership instance.
Update
Regarding the create action of the TeamMembershipsController controller, here is how it should be done:
class TeamMembershipsController < ApplicationController
def new
# ...
end
def create
# The form should have embedded all the params within a key with the model's name
# like `:team_membership`.
student = Student.find(params[:team_membership][:student_id])
#team_membership = TeamMembership.new(
team_id: params[:team_id],
student: student
)
if #team_membership.save
redirect_to :team_path(params[:team_id])
else
# Reload again the student as we will show errors.
# The student dropdown would be empty.
#students = Student.all
# Render again the new form
render :new
end
end
end

How to add user_id in form params in Rails controller

I have this model, called products, that has an id, user_id and product_name.
I have a simple form_for to pass to my product_controller the product_name param. In this same controller, I have access to the current_user, which I have to pass in order to create a new Product.
The problem is that the user_id always has to be the current user logged in, so I can't allow the user to send me this param in my form_for, thus I can't permit it as well.
What I'm doing right now is to create a new hash with the user_id param, and merge the params that comes from the form.
products_controller.rb:
def create
product_user_id_param = { "user_id": current_user.id.to_s }
#product = Product.new(product_user_id_param.merge(params[:product_name]))
...
end
Is there a more convenient or Rails way to do this?
You can create you product with you current_user reference, this is the more convenient way in my opinion:
current_user.produtcs.create(name: params[:product_name])
But, to above code works, you have to construct you relations correctly, like the following:
class User < ActiveRecord::Base
has_many :products
end
class Product < ActiveRecord::Base
belogs_to :user
end
Now, you can do it!
You can read more about it, in https://guides.rubyonrails.org/v3.2/association_basics.html, the docs recommeds this way!
Solution #1
Product has user_id which means product belongs_to :user and user has_many :products
Hence it can be written as
current_user.products.new(product_name: params[:product_name])
Solution#2
Set current_user explicitly
#product = Product.new(product_name: params[:product_name])
#product.user_id = current_user.id
##product.user = current_user
#product.save

Nested Forms: Copy fields_for from another object

Implemented nested_form for a has_may relationship between Person and Post models.
class Person
has_many :posts
accepts_nested_attributes_for :posts
end
It works well. Now there is button "Copy Person" that redirects to new person page by per populating all attributes of previous person. All the fields of person is populated except the posts of that person. Here is the view code of rendering posts:
<%= f.fields_for :posts do |ff| %>
<%= render 'post_fields', ff: ff %>
<% end %>
controller:
def copy_person
#person = Person.new
#previous_person = Person.find(params[:id])
redirect_to new_person_url(#person)
end
If a person has 3 posts and it should populate all fields of person and should display 3 posts as well while copying it. The previous person object is available on view as #previous_person
What change I need in view to render those posts?
Please consider to create new person and theirs posts according to previous_person's attributes
def copy_person
previous_person = Person.find(params[:id])
#person = Person.create!(previous_person.attributes.except("id"))
previous_person.posts.each do |old_posts|
#person.posts.create!(old_posts.attributes.except("id"))
end
end
Now you have newly created person copied from the previous one with all posts. Also, you can use dup method to create a copy of person
def copy_person
previous_person = Person.find(params[:id])
#person = previous_person.dup
#person.save!
previous_person.posts.each do |old_posts|
#person.posts.create!(old_posts.attributes.except("id"))
end
end

How can I capture an instance generically?

I'm using Rails 3.2.19 and Ruby 2.1.2. I've been googling around trying to figure this out, but perhaps I'm not searching for the right thing. Anyway, I'll try and be as concise as possible.
I have a few different models that all have a name attribute. In my views I want to somehow be able to access that name attribute regardless of the instance name passed into the view. Currently my various controllers create instances of their respective models. For instance:
class PagesController < ApplicationController
def show
#page = Page.find(params[:id])
respond_to do |format|
format.html
end
end
end
-
class ProductsController < ApplicationController
def show
#product = Product.find(params[:id])
respond_to do |format|
format.html
end
end
end
While I understand I could simply re-name the instances something generic, I was wondering if there was some way of accessing any/all instances while maintaining unambiguous instance names.
Basically something like this:
page.html.haml
%h1= resources[0].name #equates to #page.name
%h2= #page.some_other_attribute
or
product.html.haml
%h1= resources[0].name #equates to #product.name
%h2= #product.price
Where in each of the above resources[0] would be either #page or #product
You will have to define a route with an additional resource_type parameter to a generic controller or otherwise just include the resource_type into the url query parameter
/resources/product/17
or
/resources/17?resource_type=product
This will allow you to do the following in the controller
class ResourcesController < ApplicationController
def show
#resource = find_resource(params)
respond_to do |format|
format.html
end
end
private
def find_resource(params)
resource_klass = {
product: Product,
page: Page
}[params[:resource_type]]
resource_klass.find(params[:id])
end
end
Another Option would be to introduce another ResourceType Entity and define a polymorphic :has_one :belongs_to association to the actual resource entity (product, page). Then always search for ResourceTypes and load the polymorphic resource entity
class ResourceType < ActiveRecord::Base
belongs_to :resource, polymorphic: true
end
class Product < ActiveRecord::Base
has_one :resource_type, as: :resource
end
class Page < ActiveRecord::Base
has_one :resource_type, as: :resource
end
product_resource_type = ResourceType.create(...)
product = Product.create(resource_type: product_resource_type)
page_resource_type = ResourceType.create(...)
page = Page.create(resource_type: page_resource_type)
ResourceType.find(product_resource_type.id).resource
=> product
ResourceType.find(page_resource_type.id).resource
=> page
I figured this out after discovering instance_variables and instance_variables_get
Those methods will return all instance variables being passed into the view. From there I discovered that the :#_assigns instance variable contained the instances that I was looking for. So I iterated over them to find if any had the name attribute.
- instance_variable_get(:#_assigns).each do |var|
- if var[1].respond_to?("name")
%h1= var[1].name
There is probably a better way of accomplishing this, so if anyone has any opinions, they are welcome.

Resources