has_many and belongs_ association rails 4 - has-many

I am while leraning how to use association in rails 4 application
I have a user having many opinions and I want to add user opinion in the book show page
This is how i proceed:
my user.rb
class User < ActiveRecord::Base
has_one :panier
has_many :opinions
end
opinion.rb
class Opinion < ActiveRecord::Base
belongs_to :user
end
views/books/show.html.erb
<h2>Votre opinion nous intéresse:</h2>
<%= form_for([#user, #user.opinions.build]) do |f| %>
<p>
<%= f.label :body, 'votre opinion' %><br>
<%= f.text_area :body %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
opinion_controller.rb
class OpinionsController < ApplicationController
def create
#user = current_user
#opinion= #user.opinion.create(opinion_params)
end
private
def opinion_params
params.require(:opinion).permit(:body)
end
end
and in books_controllers this is my show method:
def show
#user= current_user
#books= Book.all
end
my routes:
get 'books/show' => 'books#show' , as: :books_list
resources :users do
resources :opinions
end
what I got as error:
undefined method `opinions' for nil:NilClass
in this line of code:

Most probably #user.opinions in your form causing this issue. check whether current_user returning object or not.
Also in your create method there is typo(#user.opinion), it should be #user.opinions.
Use accept nested attributes for same.

Related

How to update #model.submodel in rails 6

I'm unsuccessfully trying to update a submodel, somehow nested but getting unusual results.
Background (Scroll down for problem):
Update a boolean verified of a reviews model to associate whether a translation is verified or not, with translation entry and user model associated references. Details about creating a verification are here
# routes.rb
resources :entries do
resources :reviews
end
# entry.rb
belongs_to :user
has_one :review
accepts_nested_attributes_for :review
# user.rb
has_many :entries
has_many :reviews
# review.rb
belongs_to :user
belongs_to :entry
From entry index, pass the entry instance to the edit partial, works perfect
# /entries/index.html.erb
<% #entries.each do |entry| %>
...
<% if entry.review %>
<%= render 'reviews/edit', entry: entry %>
<% end %>
...
<% end %>
The _edit.html.erb form seems correct...
# reviews/_edit.html.erb
<span>
<%= form_for([entry, entry.review]) do |f| %>
<div class="form-check form-switch">
<%= f.check_box :verified, class: "form-check-input" %>
</div>
<%= f.submit class: "btn btn-primary"%>
<% end %>
</span>
In the browser console, the model entry is well assigned. And also the association entry.review is well assigned i.e
>> entry.review
=> #<Review id: 4, user_id: 1, entry_id: 19,
verified: false, created_at: "2021-02-18 03:43:27",
updated_at: "2021-02-18 14:31:15">
Even using the Ruby on Rails 6 deprecated method update_attribute works
>> entry.review.update_attribute(:verified, false)
=> true
Problem: The update method in reviews_controller.rb executes successfully when verified is true, but not when it's false
# reviews_controller.rb
def update
#entry.review.update(review_params)
end
private
def review_params
params.require(:review).permit(:verified, user: current_user, entry: #entry)
end
Works
{"_method"=>"patch", "authenticity_token"=>"...",
"review"=>{"verified"=>"1"}, "commit"=>"Update Review"}
but not
{"_method"=>"patch", "authenticity_token"=>"...",
"review"=>{"verified"=>"0"}, "commit"=>"Update Review"}
I think I found the problem after #yzalavin suggestion. Somehow validations were preventing the update. I do not know the association yet.
Simply adding on: :create allowed me o update.
validates_presence_of :entry_id, :user_id, :verified, on: :create

Unpermitted parameters in Rails 5 using has_many, through

I keep getting unpermitted parameters for my appointment model.
Here are my models
class Appointment < ApplicationRecord
belongs_to :client
belongs_to :trainer
end
class Trainer < ApplicationRecord
has_many :appointments
has_many :clients, through: :appointments
end
class Client < ApplicationRecord
has_many :appointments
has_many :trainers, through: :appointments
end
Here's my controller, I just listed my private method for sake of brevity.
def appt_params
params.require(:appointment).permit(:appointment_date, client_id: [],
trainer_id: [])
end
The error says unpermitted parameters for trainer, client.
Am I missing something in my strong parameters method?
Here is my appointments/new view
<%= form_for #appointment do |f| %>
<%= f.datetime_select :appointment_date %>
<%= f.collection_select :trainer, Trainer.all, :id, :first_name %>
<%= f.collection_select :client, Client.all, :id, :name %>
<%= f.submit %>
<% end %>
I added collection to my appt_params method and still getting the same error. I'm still getting the hang of Rails and any help would be appreciated, thanks!
Since you have used associations, only client_id and trainer_id is enough and those should of integer form not array.
So change your strong parameters method code to :
def appt_params
params.require(:appointment).permit(:appointment_date, :client_id,
:trainer_id)
end
And your applications/new view to:
<%= form_for #appointment do |f| %>
<%= f.datetime_select :appointment_date %>
<%= f.collection_select :trainer_id, Trainer.all, :id, :first_name %>
<%= f.collection_select :client_id, Client.all, :id, :name %>
<%= f.submit %>
<% end %>
Was running with the same issue as you and after hours of debugging I was able to solve it:
Was my first time trying associations in Rails with models similar to you, so most documentation online recommends to define the foreign key's as client_ids:[] assuming your ids were arrays, when definitely they are integers as my parameters where
Parameters: {"utf8"=>"✓", "authenticity_token"=>"LEcwQ56xYJGpq2zIs6Cz0YbU7B7mBKRa6rhspVIxo9vEB5/UoFUvHYiN0UC0krTiIp+d0tzhit6DZT1Z8PmYYg==", "califica"=>{"text"=>"hi", "grade"=>"5", "user_id"=>"1", "party_id"=>"1"}, "commit"=>"Create Calification"}
I think this is due to f.collection_select taking as expected one value. So after hours of using the permit of arrays such as :user_id => [] , user_id:[] I was getting always error of Unpermitted parameters.
Tried the answer of #Bharath (which is correct) but it was still not working, that's was when I realized that my old models weren't made with references (ActiveModel::UnknownAttributeError (unknown attribute 'user_id' for Calification.):) so I had to make a references migration to add a foreign key and then it was all working perfectly.

Skip has_many :through Model Creation on No Change

This could be the wrong way to go about this entirely, and I'm very open to alternatives.
I've got the following models, where Users can have many Positions:
class User < ApplicationRecord
has_many :user_positions
has_many :positions, through: :user_positions
accepts_nested_attributes_for :user_positions,
reject_if: :all_blank
end
class UserPosition < ApplicationRecord
belongs_to :user
belongs_to :position
end
class Position < ApplicationRecord
end
On my edit user form, I'd like to allow a User's current position to be updated. I do that in the following way:
<%= form_for #user do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.fields_for :user_positions, #user.user_positions.order(created_at: :desc).first do |ff| %>
<%= ff.hidden_field :user_id, value: #user.id %>
<%= ff.collection_select :position_id, Position.all, :id, :label %>
<% end %>
<%= f.submit "Update User" %>
<% end %>
The issue I'm running into is that a new instance of UserPosition is being created every time I submit the form, even if the Position that's selected hasn't changed. This leads to a bunch of duplicate entries in the join table, when I really only care about "promotions" or "demotions" when the value of position_id has changed.
I don't want to add a custom validator to disallow the creation, because I still want the form to be able to submit with an unchanged position. An example of this is when I only want to change the User's name.
Any advice on how to deal with this use case?
It turns out that you can actually use any method as a Symbol argument the to accepts_nested_attributes_for reject_if option.
I updated my User model as follows, and now the behavior is exactly what I want:
class User < ApplicationRecord
has_many :user_positions
has_many :positions, through: :user_positions
accepts_nested_attributes_for :user_positions,
reject_if: :same_as_previous_position
def same_as_previous_position(attributes)
if self.user_positions.empty?
return false
end
Position.find(attributes[:position_id]) == self.user_positions.order(created_at: :desc).first.position
end
end

associating post and user

Here are the three files associated with the original problem, I think...please excuse my ignorance...the rails generator does a lot of work for you, so I don't understand all the connections yet.
class Post < ActiveRecord::Base
has_many :comments
belongs_to :user
default_scope { order('created_at DESC') }
end
class AddUserToPosts < ActiveRecord::Migration
def change
add_column :posts, :user_id, :integer, :name
add_index :posts, :user_id
end
end
class CreatePosts < ActiveRecord::Migration
def change
create_table :posts do |t|
t.string :title
t.text :body
t.text :name #added this
t.timestamps
end
end
end
where the error is occuring:
<%= link_to post.title, post %>
</h4>
<small>
>> submitted
<%= time_ago_in_words(post.created_at) %> ago by
<%= post.user.name %> <br/>
<!-- <%= post.comments.count %> Comments -->
</small>
Ruby is basically telling you that on line 11 (<%= post.user.name %>) the user association of your post object is nil (i.e. a non-existent value, which in Ruby is a singleton instance of class NilClass).
Thus you're trying to call the #namemethod on nil, that obviously has no such method, hence the NoMethodError.
Just add a check to make sure that user exists before printing its name <%= post.user.name unless post.user.nil? %> or make sure that there is always a user associated with your posts (e.g. through a validation), whatever fits best in your scenario.

How to save to Database with associations in rails protecting mass assignment

After trying for few hours I can not save to the database.
The context is this:
I have two types of users, one for that I only need very basic information [Username, email, password] and another kind of user for who I need a lot of information [age, gender, city and so on]
I did not use STI becouse of the vast quantity of Null values there would be in the table.
So I created this three modes in which a user has a profile (profiles table) or not depending of its type [1 or 2], and a field of this profile is the city this user is living in, that relates to another table in the DB, the cities table
class User < ActiveRecord::Base
has_one :profile
has_one :city, through: :profile
end
class Profile < ActiveRecord::Base
belongs_to :user
belongs_to :city
[...a bunch of fields here]
end
class City < ActiveRecord::Base
has_many :profiles
has_many :users, through: :profiles
end
When I play with them in the rails console everything goes OK:
usr = User.new(name: "roxy", email: "roxy#example.me", password: "roxanna", password_confirmation: "roxanna", utype: 1)
cty = City.new(name: "Bucaramanga")
prf = Profile.new (rname: "Rosa Juliana Diaz del Castillo"...)
prf.city = cty
usr.profile = prf
usr.valid?
=> true
usr.save
=> true
but when I try to save in the app (View an Model)
<%= f.label :city, "En que ciudad te encuentras?"%>
<%= select_tag :city, options_from_collection_for_select(City.all, 'id', "name"),{:prompt => 'Selecciona tu ciudad'}%>
def new
#profile = Profile.new
end
def create
#profile = params[:profile]
#city= City.find_by_id(params[:city].to_i)
#profile.city = #city
end
I get this error:
undefined method `city=' for #<ActiveSupport::HashWithIndifferentAccess:0xa556fe0>
Can someone please help me?
UPDATE
As David suggested I created the Profile object in the first line of the create method, so my controller now look like this:
def create
#profile = Profile.new(params[:profile])
#city= City.find_by_id(params[:city].to_i)
#profile.city = #city
#usr = current_user
if #usr.profile.exists? #profile
#usr.errors.add(:profile, "is already assigned to this user") # or something to that effect
render :new
else
#usr.profile << #profile
redirect_to root_path
end
end
But I'm getting this error now
undefined method `exists?' for nil:NilClass
current_user returns the #current_user
def current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
end
Could you tell me please, what am I doing wrong?
I want to write this to all of you who are beginning as well as I am and are stuck in this step.
I had to create a new project and play with it to realize what I was doing wrong. I figured out that I was validating a last time field I added to the Profiles table and had
# education :string(255) not null
but I had not added it yet to the form so the error launched is:
Failed to save the new associated so_profile.
Now, you know if you got this error, go check your schema and look for NOT_NULL fields you might be missing in the form, also you can comment out all your model validations and after it's working uncomment'em to be sure.
So, my Final Models:
class User < ActiveRecord::Base
has_one :profile
has_one :city, through: :profile
attr_accessible :email, :name
end
class Profile < ActiveRecord::Base
belongs_to :user
belongs_to :city
attr_accessible :age, :fcolor, :gender
end
class City < ActiveRecord::Base
has_many :profiles
has_many :users, through: :profiles
attr_accessible :name
end
My controllers:
class ProfilesController < ApplicationController
def new
#user = User.find_by_id(params[:id])
#profile = Profile.new
end
def create
#profile = Profile.new(params[:profile])
city = City.find_by_id(params[:city])
#profile.city = city
#user = User.find_by_id(params[:userid])
#user.profile = #profile
if #user.save
flash[:success] = "Guardado"
redirect_to profile_path(id: #user.id)
end
end
def show
#user = User.find(params[:id])
end
end
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(params[:user])
if #user.save
flash[:success] = "Registrado!"
redirect_to new_profile_path(id: #user.id)
else
flash[:error] = "No Registrado :("
redirect_to new
end
end
def show
#user = User.find_by_id(params[:id])
end
end
In a real app you have to use Cookies or something else to keep the session alive and therefore the user_token from where you get the user_id, but it works to play with associations.
The views:
profiles/new.html.erb
<%= #user.name %>
<%= form_for #profile, url: {action: :create, userid: #user.id } do |f| %>
<%= f.label :age, "Edad" %>
<%= f.text_field :age%> <br />
<%= label :city, "Ciudad"%>
<%= select_tag :city, options_from_collection_for_select(City.all, 'id', 'name')%>
<%= f.submit %>
<% end %>
profiles/show.html.erb
Hello <%= #user.name %><br />
Tu edad es: <%= #user.profile.age %><br />
Vives en <%= #user.profile.city.name%>
users/new.html.erb
<%= form_for #user do |f|%>
<%= f.label :name, "Nombre"%>
<%= f.text_field :name, size: 20, placeholder: "Escribe tu nombre aqui" %><br />
<%= f.label :email, "Email"%>
<%= f.text_field :email, size: 20, placeholder: "Escribe tu email aqui" %><br />
<%= f.submit "Sign me up!"%>
users/show.html.erb
Name: <%= #user.name %><br />
Email: <%= #user.email %>
And that's it!
Cheers.
Learn to read the error messages. The problem is that #profile is a hash because you didnt't actually create a new Profile object on the first line of the create method.
I think that the correct is
#so_profile.City
not
#so_profile.city
Because the class name is City

Resources