Rails - storing values in join table - ruby-on-rails

I am trying to store client_id in join table: clients_orders after submitting the form below.
I set the tables in this way so I can look up all the orders a client has made.
I am using rails 4 with devise and simple form.
models
class Order < ActiveRecord::Base
belongs_to :user
#has_and_belongs_to_many :clients
belongs_to :clients #solution
end
class Client < ActiveRecord::Base
#has_and_belongs_to_many :orders
as_many :orders, dependent: :destroy #solution
end
orders form
<%= simple_form_for(#order) do |f| %>
<%= f.error_notification %>
<%= f.association :client, collection: Client.all, label_method: :name, value_method: :id, prompt: "Choose a Client" } %>
<%= etc... %>
<%= f.submit %>
<% end %>
with the current code above, the join table clients_orders does not update
create_table "clients_orders", id: false, force: true do |t|
t.integer "client_id"
t.integer "order_id"
end
order controller
class OrdersController < ApplicationController
# GET /orders/new
def new
#order = Order.new
end
# POST /orders
# POST /orders.json
def create
#order = Order.new(order_params)
#order.user_id = current_user.id
respond_to do |format|
if #order.save
format.html { redirect_to #order, notice: 'Order was successfully created.' }
format.json { render :show, status: :created, location: #order }
else
format.html { render :new }
format.json { render json: #order.errors, status: :unprocessable_entity }
end
end
end
# Never trust parameters from the scary internet, only allow the white list through.
def order_params
params.require(:order).permit(:code, :client_id, :user_id, :memo, :status, items_attributes: [:id, :name, :_destroy])
end
end

For future reference:
this is a simple one-to-many relationship. All you have to do to access a client's orders is to set up the has_many :orders in the User model and belongs_to :user in Order model. Then you can use collection methods like current_user.orders and it will get all of that specific user's orders for you. Just assign it to the user with #order = current_user.orders.build(:order_params)

You aren't whitelisting the correct parameters in your create action.
When dealing with has_and_belongs_to_many associations, you're dealing with multiple objects on each side, so the attributes you're whitelisting are plural, not singular.
You need to be whitelisting client_ids, not client_id.
Also, I'm pretty sure your form is wrong. You have it setup as though client is a has_one relationship. I think you want the plural version there as well.
<%= f.association :clients, #...
# ^----- add an 's'
If you really intended for the form to model a singular relationship, then you'll need to massage the data somewhere before saving your model. Here's one way to do it:
def create
#order = Order.new(order_params)
#order.client_ids << params[:order][:client_id]
#order.user_id = current_user.id
# save and respond...
end
If you go this route, then just remove :client_id from your parameters whitelist rather than pluralizing it.

Related

how update has_and_belongs_to_many table on update activeRecord

I'm having trouble doing a has_and_belongs_to_many, my data is structured as follows
# app/models/student.rb
class Student < ApplicationRecord
belongs_to :city
belongs_to :degree
has_and_belongs_to_many :services, optional: true
end
# app/models/service.rb
class Service < ApplicationRecord
has_and_belongs_to_many :student, optional: true
belongs_to :vendor
belongs_to :degree, optional: true
end
in my database schema i have this table and the tables of student and service.
create_table "services_students", id: false, force: :cascade do |t|
t.integer "service_id", null: false
t.integer "student_id", null: false
end
what happens is for a Student to be associated with a Service, I needed to add some lines in services_controller.create()
# POST /services or /services.json
def create
#service = Service.new(service_params)
for student in params[:service][:student]
if student != ""
#service.student << Student.find(student)
end
end
respond_to do |format|
if #service.save
format.html { redirect_to service_url(#service), notice: "Service was successfully created." }
format.json { render :show, status: :created, location: #service }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: #service.errors, status: :unprocessable_entity }
end
end
end
this way the tables save the information correctly. however, in the update() function, when saving the record, the service_students table records are duplicated instead of being updated.
in services_controller i have set the permit params as this:
# service_controller.rb
# Only allow a list of trusted parameters through.
def service_params
params.require(:service).permit(:student, :vendor_id, :degree_id, :category, :description)
end
and in my form is like this:
# .... begin of form ...
<%= form.label :student, "Aluno(s)" %>
<%= form.collection_check_boxes :student, Student.all, :id, :name do |b|
b.label(class:"w-full ") { b.check_box(class:"rounded", checked: #service.student.ids.include?(b.object.id)) + b.text }
# .... more fields of form ....
I'm not understanding what I'm doing wrong. shouldn't the update function, and even create(), handle this without having to add more code?
I tried to follow some tutorials on the internet, and I realized that the activeRecord should generate the attribute students_ids or students to register the association correctly, but I didn't understand exactly why this happens or why in my model it is referred to as student. (maybe something is wrong)
>> rails console
service = Service.find(1) # get the Service with id: 1
student1 = Student.find(1) # get the Student with id: 1
student2 = Student.find(2) # get the Student with id: 2
service.students << student1 # error
service.student << student1 # works
service.students #error #method missing
service.student # display all students relationship
is this supposed to happen?
The assocation should have a plural name:
class Service < ApplicationRecord
has_and_belongs_to_many :students # this should be plural!
belongs_to :vendor
belongs_to :degree, optional: true
end
Use the students_ids method generated by has_and_belongs_to_many :students with your input:
<%= form.collection_check_boxes :student_ids, Student.all, :id, :name do |b| %>
<%= b.label(class:"w-full") { b.check_box(class:"rounded") + b.text } %>
<% end %>
And you need to whitelist an array of ids:
def service_params
params.require(:service)
.permit(
:vendor_id, :degree_id,
:category, :description,
student_ids: [] # permits an array of values
)
end
And just get rid of the cruft:
def create
#service = Service.new(service_params)
respond_to do |format|
if #service.save
format.html { redirect_to #service, notice: "Service was successfully created." }
format.json { render :show, status: :created, location: #service }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: #service.errors, status: :unprocessable_entity }
end
end
end
The students_ids= method which is also created by has_and_belongs_to_many :students will automatically add/delete the rows in the join table.

Why is a join record being created for this model?

This is a web app for scheduling our company's various site deployments among the developers and product owners on our Scrum team, when we launch updates or improvements. I have two models, User and Release. A Release is a single deployment event. Then I have a join table, called release_users. A user has and belongs to many releases. A release has many users, under the name product_owners, and belongs to one user, under the name developer. The Release table has a developer_id column which equals the User's ID, so it doesn't involve the ReleaseUser association at all (at least, that's what I want).
Here's the problem: when I create a new Release record (through the form), it automatically creates/saves a ReleaseUser record for the developer in addition to the product owners. When I look at #release.product_owners, I see the developer listed under there when it shouldn't be. It should only create ReleaseUser records for product owners. #release.developer works as expected, it selects the user from the User table based on developer_id and doesn't go through the ReleaseUser table.
So something is wrong in my model configurations I think? Rails is performing some behind-the-scenes magic that I can't pinpoint.
Code
release.rb:
class Release < ActiveRecord::Base
has_many :release_users, dependent: :destroy
has_many :product_owners, :through => :release_users, :source => :user
belongs_to :developer, :class_name => "User"
end
user.rb:
class User < ActiveRecord::Base
has_many :release_users, dependent: :destroy
has_many :releases, :through => :release_users
end
release_user.rb:
class ReleaseUser < ActiveRecord::Base
belongs_to :release
belongs_to :user
end
_form.html.erb:
<div class="field">
<%= f.label "Developer" %><br>
<%= f.select :developer_id, options_for_select(User.select_options, selected: #release.selected_option), { include_blank: 'None' } %>
</div>
<div class="field">
<%= f.label "Product Owners" %><br>
<%= f.collection_check_boxes :product_owner_ids, User.all, :id, :full_name do |b| %>
<label class="checkbox">
<%= b.check_box %> <%= b.label %> <br/>
</label>
<% end %>
</div>
releases_controller.rb:
# POST /releases
# POST /releases.json
def create
#release = Release.new(release_params)
# Release belongs to one developer at most
unless release_params[:developer_id].empty?
#developer = User.find(release_params[:developer_id])
# User has many releases, add to array
#developer.releases << #release
#developer.save
end
respond_to do |format|
if #release.save
format.html { redirect_to releases_url, notice: 'Release was successfully created.' }
format.json { render :show, status: :created, location: #release }
else
format.html { render :new }
format.json { render json: #release.errors, status: :unprocessable_entity }
end
end
end
(Rails 4)
This line:
#developer.releases << #release
is adding a record to ReleaseUsers because you stated that:
class User
has_many :releases, :through => :release_users
end
So by using #developer.releases << #release you are telling rails to create that association in the release_users table.
It seems you need to isolate the scope for something like developer_releases on User and then none of the unless block is needed.
Something like
class User
# existing relationships
has_many :developer_releases, class_name: 'Release', foreign_key: :developer_id
end
Then the controller code can be slimmed down:
def create
#release = Release.new(release_params)
respond_to do |format|
if #release.save
format.html { redirect_to releases_url, notice: 'Release was successfully created.' }
format.json { render :show, status: :created, location: #release }
else
format.html { render :new }
format.json { render json: #release.errors, status: :unprocessable_entity }
end
end
end
this is creating the join table
#developer.releases << #release
There's no reason to do that. Just take it out.

Ruby on Rails : Updating multiple models in a single form

I have 3 models User, House and Order.
Order Model
class Order < ActiveRecord::Base
belongs_to :from_house, :class_name => "House"
belongs_to :to_house, :class_name => "House"
belongs_to :user
accepts_nested_attributes_for :from_house, :to_house, :user
end
My House Model.
class House < ActiveRecord::Base
belongs_to :user
belongs_to :place
belongs_to :city
end
My user model.
class User < ActiveRecord::Base
has_many :orders
has_many :houses
end
In my order form I have something like this
<%= form_for #order do |f| %>
... # order fields
<%= f.fields_for :user do |i| %>
... # your from user forms
<% end %>
<%= f.fields_for :from_house do |i| %>
... # your from house forms
<% end %>
<%= f.fields_for :to_house do |i| %>
... # your to house forms
<% end %>
...
<% end %>
I haven't changed much in controller from the default. The controller code
def create
#order = Order.new(order_params)
respond_to do |format|
if #order.save
format.html { redirect_to #order, notice: 'Order was successfully created.' }
format.json { render action: 'show', status: :created, location: #order }
else
format.html { render action: 'new' }
format.json { render json: #order.errors, status: :unprocessable_entity }
end
end
def order_params
params.require(:order).permit( :shift_date, user_attributes: [:name, :email, :ph_no], from_house_attributes: [:place_id, :floor, :elevator, :size], to_house_attributes: [:place_id, :floor, :elevator])
end
When I submit the form, as expected a Order gets created with a new from_house and to_house along with a new user. But however my user_id in house table remains NULL. How can I make the houses(both from and to) reference the user created after submit.
The User is not logged in, So there is no current_user. We have to create a new user based on the details given. That user has to be associated with the houses (from and to).
I hope I'm clear. If not please let me know.
P.S: This question is an extension to this Ruby on rails: Adding 2 references of a single model to another model
I think this change in app/models/order.rb should do the trick:
class Order < ActiveRecord::Base
belongs_to :user
belongs_to :from_house, class_name: 'House'
belongs_to :to_house, class_name: 'House'
accepts_nested_attributes_for :user, :from_house, :to_house
validates :user, :from_house, :to_house, presence: true
def from_house_attributes=(attributes)
fh = build_from_house(attributes)
fh.user = self.user
end
def to_house_attributes=(attributes)
th = build_to_house(attributes)
th.user = self.user
end
end
Now, try this in your Rails console:
params = { user_attributes: { name: 'New name', email: 'name#example.com' }, from_house_attributes: { name: 'From house name' }, to_house_attributes: { name: 'to house name' } }
o = Order.new(params)
o.save
o.from_house
o.to_house
Cheers!

How to limit current scope of nested form to first item

I'm creating a simple discussion board in Rails. Every new Topic creates also a first Reply that includes the content. This is my current schema.
Topic
> title:string
> user_id: integer
has_many :replies
accepts_nested_attributes_for :replies
Reply
> topic_id: integer
> user_id: integer
> content: text
belongs_to :topic
The current topics/_form.html.haml is something like this
= form_for #topic fo |f|
= f.text_field :title
= f.fields_for :replies
= reply.text_area :content
The problem is when trying to edit a Topic, I see all the list of replies as editable since it's iterating the fields_for :replies field in the form partial. I should only see the first one.
What would be a convenient way to limit this iteration to its current first available reply only while also building a new one if a topic is new?
I ended up with something like this that works but I guess there should be a better way.
# Topic model
has_one :owner_reply, class_name: 'Reply'
accepts_nested_attributes_for :owner_reply
# Form partial view
= form_for #topic fo |f|
- reply_resource = (#topic.new_record? ? :replies : :owner_reply)
= f.text_field :title
= f.fields_for :replies
= reply.text_area :content
These are the full TopicsController#create and update actions.
def create
#board = Board.find(params[:board_id])
#topic = #board.topics.new(topic_params)
#topic.user_id = current_user.id
#topic.replies.each { |reply| reply.user_id = current_user.id }
if #topic.save
respond_to do |format|
format.html { redirect_to topic_path(#topic) }
end
else
render :new
end
end
def update
#topic = Topic.find(params[:id])
if #topic.update_attributes(topic_params)
respond_to do |format|
format.html { redirect_to topic_path(#topic) }
end
else
render :edit
end
end
I would use a scoped association, the same way you are using :owner_reply but adding a scope to limit to the first record, you can also add a order to it if you need
class Topic
has_many :replies
has_many :first_replies, -> { first }, class_name: 'Reply'
accepts_nested_attributes_for :replies
accepts_nested_attributes_for :first_replies
And in your view
= form_for #topic fo |f|
...
= f.fields_for :first_replies
= reply.text_area :content
Create a class method on Topic that returns the first Reply:
class Topic
accepts_nested_attributes_for :first_reply
def self.first_reply
self.replies.first
end
# ...
end
Then call the class method in fields_for.

HABTM relationships and accepts_nested_attributes_for

I have a form that lets me create new blog posts and I'd like to be able to create new categories from the same form.
I have a habtm relationship between posts and categories, which is why I'm having trouble with this.
I have the following 2 models:
class Post < ActiveRecord::Base
has_and_belongs_to_many :categories
attr_accessible :title, :body, :category_ids
accepts_nested_attributes_for :categories # should this be singular?
end
class Category < ActiveRecord::Base
has_and_belongs_to_many :posts
attr_accessible :name
end
My form lets me pick from a bunch of existing categories or create a brand new one. My form is as follows.
# using simple_form gem
.inputs
= f.input :title
= f.input :body
# the line below lets me choose from existing categories
= f.association :categories, :label => 'Filed Under'
# I was hoping that the code below would let me create new categories
= f.fields_for :category do |builder|
= builder.label :content, "Name"
= builder.text_field :content
When I submit my form, it gets processed but the new category is not created. My command prompt output tells me:
WARNING: Can't mass-assign protected attributes: category
But, if I add attr_accessible :category, I get a big fat crash with error message "unknown attribute: category".
If I change the fields_for target to :categories (instead of category) then my form doesn't even display.
I've spent a while trying to figure this out, and watched the recent railscasts on nested_models and simple_form but couldn't get my problem fixed.
Would this be easier if I was using a has_many :through relationship (with a join model) instead of a habtm?
Thanks to everyone who answered. After much trial and error, I managed to come up with a fix.
First of all, I switched from a HABTM to a has_many :through relationship, calling my join model categorization.rb (instead of categorizations_posts.rb) - NB: the fix detailed below will likely work with a HABTM too:
Step 1: I changed my models to look like this:
# post.rb
class Post < ActiveRecord::Base
has_many :categorizations
has_many :categories, :through => :categorizations
attr_accessible :title, :body, :category_ids
accepts_nested_attributes_for :categories
end
#category.rb
class Category < ActiveRecord::Base
has_many :categorizations
has_many :posts, :through => :categorizations
attr_accessible :name, :post_ids
end
#categorization.rb
class Categorization < ActiveRecord::Base
belongs_to :post
belongs_to :category
end
From the post model above: obviously, the accessor named :category_ids must be present if you want to enable selecting multiple existing categories, but you do not need an accessor method for creating new categories... I didn't know that.
Step 2: I changed my view to look like this:
-# just showing the relevent parts
= fields_for :category do |builder|
= builder.label :name, "Name"
= builder.text_field :name
From the view code above, it's important to note the use of fields_for :category as opposed to the somewhat unintuitive fields_for :categories_attributes
Step 3
Finally, I added some code to my controller:
# POST /posts
# POST /posts.xml
def create
#post = Post.new(params[:post])
#category = #post.categories.build(params[:category]) unless params[:category][:name].blank?
# stuff removed
end
def update
#post = Post.find(params[:id])
#category = #post.categories.build(params[:category]) unless params[:category][:name].blank?
# stuff removed
end
Now, when I create a new post, I can simultaneously choose multiple existing categories from the select menu and create a brand new category at the same time - it's not a case of one-or-the-other
There is one tiny bug which only occurs when editing and updating existing posts; in this case it won't let me simultaneously create a new category and select multiple existing categories - if I try to do both at the same time, then only the existing categories are associated with the post, and the brand-new one is rejected (with no error message). But I can get round this by editing the post twice, once to create the new category (which automagically associates it with the post) and then a second time to select some additional existing categories from the menu - like I said this is not a big deal because it all works really well otherwise and my users can adapt to these limits
Anyway, I hope this helps someone.
Amen.
In your form you probably should render the fields_for once per category (you can have multiple categories per post, hence the habtm relation). Try something like:
- for category in #post.categories
= fields_for "post[categories_attributes][#{category.new_record? ? category.object_id : category.id}]", category do |builder|
= builder.hidden_field :id unless category.new_record?
= builder.label :content, "Name"
= builder.text_field :content
I have made my application and my nested form works with HABTM.
My model is :
class UserProfile < ActiveRecord::Base
attr_accessible :name, :profession
has_and_belongs_to_many :cities
belongs_to :user
attr_accessible :city_ids, :cities
def self.check_city(user,city)
user.cities.find_by_id(city.id).present?
end
end
class City < ActiveRecord::Base
attr_accessible :city_name
has_and_belongs_to_many :user_profiles
end
In my form I have:
-# just showing the relevent parts
= f.fields_for :cities do|city|
= city.text_field :city_name
And at my controller:
def create
params[:user_profile][:city_ids] ||= []
if params[:user_profile][:cities][:city_name].present?
#city= City.create(:city_name=>params[:user_profile][:cities][:city_name])
#city.save
params[:user_profile][:city_ids] << #city.id
end
#user=current_user
params[:user_profile].delete(:cities)
#user_profile = #user.build_user_profile(params[:user_profile])
respond_to do |format|
if #user_profile.save
format.html { redirect_to #user_profile, notice: 'User profile was successfully created.' }
format.json { render json: #user_profile, status: :created, location: #user_profile }
else
format.html { render action: "new" }
format.json { render json: #user_profile.errors, status: :unprocessable_entity }
end
end
end
def update
params[:user_profile][:city_ids] ||= []
if params[:user_profile][:cities][:city_name].present?
#city= City.create(:city_name=>params[:user_profile][:cities][:city_name])
#city.save
params[:user_profile][:city_ids] << #city.id
end
#user=current_user
params[:user_profile].delete(:cities)
#user_profile = #user.user_profile
respond_to do |format|
if #user_profile.update_attributes(params[:user_profile])
format.html { redirect_to #user_profile, notice: 'User profile was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #user_profile.errors, status: :unprocessable_entity }
end
end
end
This code works.
Maybe you should try it with (not testet):
attr_accessible :category_attributes
And HBTM relations arent really recommened... But I use them on my own :P

Resources