Passing additional information through controllers - ruby-on-rails

I have a comment model and when a comment is created it makes a note of the profile_name of the user and saves it. So it is basically saving #user.profile_name as comment.profile_name.
If I now want to show additional information from the user who has that profile_name such as #user.avatar - how would I query it without having to add extra fields to be saved when a comment is created?
I Imagined I could do something like
#user = User.all
#comment_user = User.where(:profile_name => #user.profile_name)
And then run
<% #comment_user.each do |user| %>
<%= user.first_name %>
<% end %>
In the view but I get an error
undefined method `profile_name' for #<ActiveRecord::Relation::ActiveRecord_Relation_User:0x007fc2081c7290>
I'm not even sure if that is the correct way to proceed even if I didn't get the error.

The error is because you are trying to call profile_name on User.all, but what you want is a single user. Try:
#user = User.first
#comment_user = User.where(:profile_name => #user.profile_name)
That being said, you should be using ActiveRecord associations instead. Instead of storing the user's profile_name in the comment record, you should store the user_id . Create a belongs_to :user association in the Comment model and then you can access the other user attributes directly.
First add a :user_id integer column to the the comments table, and then define your associations:
class Comment < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :comments
end
When creating a new comment, you can do something like:
#comment = #user.comments.create(text: 'my awesome comment')
Then in your view:
<%= #comment.text %>
<%= #comment.user.profile_name %>

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 to save a nested resource in ActiveRecord using a single form (Ruby on Rails 5)

I have two entities with a many-to-one relationship. User has many Addresses. When creating a User I want the form to also create a single Address. The entities are nested.
Approach 1:
The code below works, but only saves the User, no associated Address.
Reading around, I thought that the accepts_nested_attributes_for would automatically save the address. I'm not sure, but it may be that this isn't working because the parameters I'm getting into the Controller don't actually appear to be nested, ie. they look like:
"user"=>{"name"=>"test"}, "address"=>{"address"=>"test"}
Rather than being nested like this:
"user"=>{"name"=>"test", "address"=>{"address"=>"test"} }
I assume this could be due to something wrong in my form, but I don't know what the problem is...
Approach 2:
I have also tried changing the controller - implementing a second private method, address_params, which looked like params.require(:address).permit(:address), and then explicitly creating the address with #user.address.build(address_params) in the create method.
When tracing through this approach with a debugger the Address entity did indeed get created successfully, however the respond_to do raised an ArgumentError for reasons I don't understand ("respond_to takes either types or a block, never both"), and this rolls everything back before hitting the save method...
[EDIT] - The respond_to do raising an error was a red herring - I was misinterpreting the debugger. However, the transaction is rolled back for reasons I don't understand.
Questions:
Is one or the other approach more standard for Rails? (or maybe neither are and I'm fundamentally misunderstanding something)
What am I doing wrong in either / both of these approaches, and how to fix them so both User and Address are saved?
Relevant code below (which implements Approach 1 above, and generates the non-nested params as noted):
user.rb
class User < ApplicationRecord
has_many :address
accepts_nested_attributes_for :address
end
address.rb
class Address < ApplicationRecord
belongs_to :user
end
users_controller.rb
class UsersController < ApplicationController
# GET /users/new
def new
#user = User.new
end
# POST /users
# POST /users.json
def create
#user = User.new(user_params)
respond_to do |format|
if #user.save
format.html { redirect_to #user, notice: 'User was successfully created.' }
format.json { render :show, status: :created, location: #user}
else
format.html { render :new }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
private
def user_params
params.require(:user).permit(:name, address_attributes: [:address])
end
end
_form.html.erb
<%= form_for(user) do |f| %>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<%= fields_for(user.address.build) do |u| %>
<div class="field">
<%= u.label :address %>
<%= u.text_field :address %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
UPDATE 1:
After making the changes suggested by #Ren, I can see that the parameters look more like what I would've expected for nested resources:
"user"=>{"name"=>"test", "addresses_attributes"=>{"0"=>{"address"=>"test"}}}
However, when trying to save the user, the transaction is still rolled back for reasons I don't understand. The output I get from the users.new page is:
2 error prohibited this user from being saved:
Addresses user must exist
Addresses user can't be blank
However, using byebug, after the #user = User.new(user_params) call, things look as I would expect them:
(byebug) #user
#<User id: nil, name: "test", created_at: nil, updated_at: nil>
(byebug) #user.addresses
#<ActiveRecord::Associations::CollectionProxy [#<Address id: nil, user_id: nil, address: "test", created_at: nil, updated_at: nil>]>
Obviously the user.id field is not set until the record is written to the DB, so equally the address.user_id field cannot be set until user is saved, so maybe this is caused by some sort of incorrect ordering when ActiveRecord is saving to the database? I will continue to try to understand what's going on by debugging with byebug...
UPDATE 2:
Using rails console to test, saving User first and then adding the Address works (both records get written to the DB, although obviously in 2 separate transactions):
> user = User.new(name: "consoleTest")
> user.save
> user.addresses.build(address: "consoleTest")
> user.save
Saving only once at the end results in the same issues I'm seeing when running my program, ie. the transaction is rolled back for some reason:
> user = User.new(name: "consoleTest")
> user.addresses.build(address: "consoleTest")
> user.save
As far as I can tell from debugging with rails console, the only difference between the state of user.addresses in these two approaches is that in the first address.user_id is already set, since the user.id is already known, while as in the second, it is not. So this may be the problem, but from what I understand, the save method should ensure entities are saved in the correct order such that this is not a problem. Ideally it would be nice to be able to see which entities save is trying to write to the DB and in which order, but debugging this with byebug takes me down an ActiveRecord rabbit-hold I don't understand at all!
UPDATE: As opposed to previous versions, Rails 5 now makes it required that in a parent-child belongs_to relationship, the associated id of the parent must be present by default upon saving the child. Otherwise, there will be a validation error. And apparently it isn't allowing you to save the parent and child all in one step... So for the below solution to work, a fix would be to add optional: true to the belongs_to association in the Address model:
class Address < ApplicationRecord
belongs_to :user, optional: true
end
See my answer in a question that branched off from this one:
https://stackoverflow.com/a/39688720/5531936
It seems to me that you are mixing up the singular and plural of your address object in such a way that is not in accordance with Rails. If a User has many addresses, then your Model should show has_many :addresses and accepts_nested_attributes_for should have addresses:
class User < ApplicationRecord
has_many :addresses
accepts_nested_attributes_for :addresses
end
and your strong params in your controller should have addresses_attributes:
def user_params
params.require(:user).permit(:name, addresses_attributes: [:id, :address])
end
Now if you want the User to just save One Address, then in your form you should have available just one instance of a nested address:
def new
#user = User.new
#user.addresses.build
end
By the way it seems like your form has fields_for when it should be f.fields_for:
<%= f.fields_for :addresses do |u| %>
<div class="field">
<%= u.label :address %>
<%= u.text_field :address %>
</div>
<% end %>
I highly recommend that you take a look at the Rails guide documentation on Nested Forms, section 9.2. It has a similar example where a Person has_many Addresses. To quote that source:
When an association accepts nested attributes fields_for renders its
block once for every element of the association. In particular, if a
person has no addresses it renders nothing. A common pattern is for
the controller to build one or more empty children so that at least
one set of fields is shown to the user. The example below would result
in 2 sets of address fields being rendered on the new person form.
def new
#person = Person.new
2.times { #person.addresses.build}
end

Rails: How to only allow User to apply to job only once?

I am creating a job board, and I don't want to allow the users the option to apply for the same job twice. How can I limit this?
app/views/jobs/job.html.erb
<% if applied_to_this_job? %>
<div class="alert" role="alert">You have already applied to this job!</div>
<% else %>
<%= link_to 'Apply', new_job_application_path(#job) %>
<% end %>
app/helpers/jobs_helper.rb
def applied_to_this_job?
JobApplication.exists? user_id: current_user.id
end
Obviously this doesn't work because it checks if this user has applied to any job. How Can I check to see if the current user has applied to the job being viewed.
Also, how can I limit this at the controller level so that the user can't go to job_application/new and get to the form.
You would use a before_filter in the controller action.
class JobsController < ActionController::Base
before_filter :has_applied?, only: [new, create]
....
private
def has_applied?
if JobApplication.where(user_id: :current_user.id, job_id: params[:job_id]).any?
redirect_to :index, alert: "You have already applied"
end
end
end
This would allow the user to visit /jobs/new and post the application to /jobs/create unless they have applied. If they have applied, they will be redirected to the index in the sample code.
Also as another answer has noted, it would be wise to pass in the job id as well. Updated sample code above to reflect.
You need to check and see if the JobApplication object is for this #job try:
JobApplication.where( user_id: current_user.id, job_id: #job.id ).exists?
Although what you've accepted will work, I think it's somewhat of a surface-level fix.
You'll be much better using validators to determine if the user can actually create another job application. This will protect against any problems with the business logic in your "front-end" views
Here's how I'd handle it:
--
Uniq
#app/models/user.rb
class User < ActiveRecord::Base
has_one :job_application
end
#app/models/job_application.rb
class JobApplication < ActiveRecord::Base
belongs_to :user
validates :user_id, uniquness: true
end
You may also wish to give your database a uniq index for your user_id column:
> $ rails g migration AddUniqueIndex
#config/db/add_unique_index.rb
class AddUniqueIndex < ActiveRecord::Migration
def change
add_index :job_applications, [:job_id, :user_id], unique: true
end
end
This will give you a highly efficient DB-level uniqueness index - meaning that if you try and add any more applications than is permitted, it will either fail silently, or come back with an error.
Controller
The structure of the controller would allow you to be less stringent about the accessibility of the job_application functionality:
#app/views/jobs/job.html.erb
<% if current_user.has_applied?(params[:job_id]) %>
<div class="alert" role="alert">You have already applied to this job!</div>
<% else %>
<%= link_to 'Apply', new_job_application_path(#job) %>
<% end %>
#app/models/user.rb
class User < ActiveRecord::Base
has_many :job_applications
def has_applied?(job_id)
job_applications.find job_id
end
end

Committing Into the Database Trying to Change the Form a Different Way

I have a classified model that is hook up to the student model in a way that it looks like this
classified.rb
belongs_to :student
in my student model it
has_many :classifieds
The classifieds table contain a column for student_id
when I head to the rails console
I can create a new classified for that particular student record by doing
Student.find(19).classifieds.create(:ad_title => "blah", :ad_content => "blah", :location => "blah")
The record automatically gets a student_id generated because it was created in the student standpoint.
Now the problem is I have a classifieds controller with a new and a create method and a new form in the view
I am creating a new ad in the classifieds standpoint here is the form
<div>
新广告: <br><br>
<%= simple_form_for #advertisement do |f| %>
<%= f.input :ad_title, label: "Title" %><br>
<%= f.input :ad_content, label: "Content"%><br>
<%= f.input :location, label: "Location"%><br>
<%= f.input :student_id, label: "Your Student ID"%><br>
<%= f.button :submit, "Add an Advertisement" %>
<% end %>
</div>
The problem is the student_id must exist in order for that particular record to show up in index and my users don't know their id.
How do I create a classified in the student standpoint using forms
I consider going to my students controller and adding a method like this
def create_classified_ad
#student = Student.find(params[:id])
#classified = #student.classified.create(params[:classified])
end
I am using devise so there is user session? I don't know how sessions work entirely I want that particular student to be found after logged in and have that student create a classified ad with the inputs from the form so the record will be created correctly?
The question is more like how do you insert data using forms to a hooked up table correctly? (and not merely inserting data into a simple no relationship table)
You should not be passing the student_id from the form. If you have a devise session, you should have access to current_user:
def new
#classified = current_user.classifieds.build
end
def create
#classified = current_user.classifieds.build(params[:classified])
if #classified.save
# do something
else
# handle failure
end
end
In the index, you can do this:
def index
#classifieds = current_user.classifieds.all
end
All of this assumes you have a devise session. If not, just fetch the student like so:
#student = Student.find(params[:student_id])
#classified = #student.classifieds.build
Set up a nested route for the classifieds under the user:
resources :users do
resources :classifieds
end
This allows you to fetch the student from a student_id param. Your route would like this: new_student_classified_path(#student).

Rails - set default for nested model attribute

I'm building a nested model form that will allow a client to register (client model) and create a login (user model) at the same time. However, for new registrants, I would like to set the user.role_code (say to "A" for admin), but I've been struggling with this. From a data model perspective, and client has multiple users, since after registration, the client can access the app to add other users. From that perpective, I don't think I can default user_role in the model, since for added users, their role_code may be different. I would like to set the default in the controller.
Also, after form submission, users are sent as an array, so I would like to ensure only ONE user array object is sent upon registration (clearly for vulnerability reasons).
Here's my code
#client registration form new.html
<%= form_for #client do |client_form| %>
.....
<%= client_form.fields_for :users do |user_form| %>
<div class="field">
<%= user_form.label :first_name %>
<%= user_form.text_field :first_name %>
</div>
.....
#client Controller
def create
logger.info params.inspect
#client = Client.new(params[:client])
if #client.save
......
#client model
class Client < ActiveRecord::Base
attr_accessible :business_name, ... #and more
attr_accessible :users_attributes
has_many :users
has_many :items
accepts_nested_attributes_for :users, allow_destroy: true
#logger.info params.inspect
"...users_attributes"=>{"0"=>{"first_name"=>..."
Found it!!! After much researching, Googling, pulling out my hair, and lastly coming across Get nested params, the answer is as follows:
#client.users.first.role_code = "A"
and to test the number of user array objects, use the following
if #client.users.size != 1
...
end

Resources