Ruby on Rails -- Navigation Properties :o? - ruby-on-rails

I'm new to Rails and I have two models (Ticket, User).
One Ticket has one user - One User has_many tickets.
I would like to display "the username of a ticket" on an overview page..
I already set my models and assoziations. The following code should show a 3-column table with the username in the middle, but I get an error :(
"undefined method `users' for ..."
I'm coming from C# using Entity Framework.. I thought RoR does understand the expression "article.user.firstname"
<% #tickets.each do |article| %>
<tr>
<td class="cell"><%= article.title %></td> //fine
<td class="cell"><%= article.user.firstname %></td> // le error :(
<td class="cell"><%= article.description %></td> //fine
</tr>
<% end %>
Could someone show me how to archive this ?
User.rb
class User < ActiveRecord::Base
has_many :tickets, dependent: :destroy
end
Ticket.rb
class Ticket < ActiveRecord::Base
belongs_to :user
end
ticket_controller.rb
class TicketsController < ApplicationController
def index
#tickets = Ticket.all.includes(:user)
end
def new
#users = User.all
end
def create
#ticket = Ticket.new(article_params)
#ticket.save
redirect_to #ticket
end
def show
#ticket = Ticket.find(params[:id])
end
private
def article_params
params.require(:ticket).permit(:title, :description)
end
end
UPDATE:
I had to redefine my model with "rails generate model Ticket title:string message:string user_id".
After that I ran "rake db:migrate" (after deleting that table, otherwise I got an error).
Then I set assoziations by writing "belongs_to :user" into ticket.rb and "has_many :tickets" into my user.rb-file.

Can you post your user table, ticket table and ticket controller.
to see if you have a syntax error.
You say that tickets only have 1 user and you have article.users and not article.user
add user to the
def article_params
params.require(:ticket).permit(:title, :description,:user_id)
end
Can you post the form to use if your getting the current user id on the form
on the migration of the ticket table
do you have a
t.integer :user_id
The problem I'm noticing is that when you create the list the user is looking for is at this table tickets and not to Users table.That's why you do not give error when using only article.user
on the Console try User.all and see if you got user and then try the same with Ticket.all

In the comments, you mention you're getting an error along the lines of "undefined method `firstname' for nil:NilClass". This means that your ticket has no user assigned, so the user is nil. You're getting this error because you're trying to treat nil like a User.

Related

Creating multiple unrelated associated model instances in Rails

I have what I believe is fairly simple model setup:
class Booking < ApplicationRecord
has_many :payments
end
class Payment < ApplicationRecord
belongs_to :booking
end
Now, I want to create a form that allows a user to register payments in batch. That is, the form should have a number of input rows, each one representing a payment for some booking (i.e., each row has some fields for the columns of Payment plus a booking_id field). Upon submitting, each row should cause the creation of a corresponding Payment, which should be associated with the Booking indicated by the user for that row.
This seems to be surprisingly tricky, and my Google-Fu is failing me. I've tried the following (inspired by this post describing a solution without associations), which I thought would work, but which, well, doesn't:
class Admin::PaymentController < Admin::Controller
def batch
#payments = []
5.times do
#payments << Payment.new
end
end
def submit
params["payments"].each do |payment|
if payment["booking_id"] != "" || payment["amount"] != ""
Payment.create(payment_params(payment))
end
end
end
private
def payment_params(p)
p.permit(:booking_id, :amount)
end
end
<%= form_tag admin_payment_submit_path do %>
<% #payments.each do |payment| %>
<%= fields_for 'payments[]', payment do |p| %>
<%=p.text_field :booking_id%>
<%=p.number_field :amount%>
<% end %>
<% end %>
<%= submit_tag %>
<% end %>
This renders the form without erroring out, but the HTML names work out such that only a single payment (the last one) is submitted (e.g., name="payments[booking_id]"). Furthermore, upon submitting, I get the error
undefined method `permit' for "booking_id":String Did you mean? print
Which is less than helpful.
I've tried other variations too, but I feel like at this point I'm just feeling my way in the dark. Any guidance would be greatly appreciated!
params in controller is a instance of ActiveController::Parameter that has permit method.
But params["payments"] is a just array as subset of params.
For multiple payment params
def submit
payment_params.each do |payment|
if payment["booking_id"].present? || payment["amount"].present?
Payment.create(payment)
end
end
end
private
def payment_params
params.permit(payments: [:booking_id, :amount])["payments"]
end
For Single payment param
def submit
if payment_param["booking_id"].present? || payment_param["amount"].present?
Payment.create(payment_param)
end
end
private
def payment_param
params.require(:payments).permit(:a, :b)
end

How to get assocated model's attribute values in views ? Rails

I have three models...
models/resident.rb
class Resident < ActiveRecord::Base
belongs_to :hostel
has_many :leaves,dependent: :delete_all
has_one :user,dependent: :delete
end
models/user.rb
class User < ActiveRecord::Base
belongs_to :resident
end
models/leave.rb
class Leave < ActiveRecord::Base
belongs_to :resident
end
Now when I am trying to access the value of leave's attribute in views/leave/show.html.erb
I am getting this:
app/views/leaves/show.html.erb
<%= #leaves %>
out put In Browser :
#<Leave::ActiveRecord_Associations_CollectionProxy:0x007fde611850f0>
My leave controller looks like :
leaves_controller.rb
class LeavesController < ApplicationController
def new
if logged_in?
#leave=Leave.new
else
flash[:info]="Please login to mark a leave"
redirect_to root_path
end
end
def show
#leaves= current_user.resident.leaves
end
def create
#leave=current_user.resident.leaves.create(leave_params)
if #leave.save
flash[:info] = "Leave successfully marked"
redirect_to new_leave_path
else
flash[:danger] = "Something wrong Happened try again"
redirect_to root_path
end
end
private
def leave_params
params.require(:leave).permit(:start_date,:end_date,:destination)
end
end
Am I making correct leaves for resident and related user (create method)?
Is show method correct ?
and How to assess the user's leaves attribute in show.html.erb of leaves views.
A Resident has_many Leaves so current_resident.leaves returns an array of all the current_resident's leaves. You will need to loop through leaves to show individual attributes. Try
#leaves.first.attribute_name
in your view to get an idea of how the data is represented. To show all the leaves you'll need to use a loop in the view
#leaves.each do |leave|
leave.inspect
end
You are doing everything fine, and show method is fine, and the template shows exactly what is was told to show.
#leaves is a collection. You probably want to show it’s elements? This should lead to the proper solution:
<% #leaves.each do |l| %>
<%= l.inspect %>
<% 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

Passing additional information through controllers

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 %>

Rails - Creating a GettingStarted Controller

Im working to create a getting started controller, that guides a new users through uploading a photo, finding friends, inviting people etc.
GettingStarted has no model itself, it just guides users through a wizard. A user could fully bypass this gettingstarted process without breaking the site. It's just a guide...
What I've done so far is:
Create a Route, Controller and Model:
Route:
resources :getting_started
namespace :getting_started do
resource :users, :only => [:edit, :update]
end
Controller:
class GettingStartedController < ApplicationController
def index
#current_step = current_step
end
protected
def current_step
current_step || steps.first
return 1
end
def steps
%w[step1 step2 step3]
end
end
Model
class GettingStarted < ActiveRecord::Base
attr_writer :current_step
attr_accessor :current_step
def current_step
#current_step || steps.first
return 1
end
def steps
%w[step1 step2 step3]
end
def next_step
self.current_step = steps[steps.index(current_step)+1]
end
def previous_step
self.current_step = steps[steps.index(current_step)-1]
end
def first_step?
current_step == steps.first
end
def last_step?
current_step == steps.last
end
end
View:
<%= #current_step.inspect %>
<% form_for #gettingstarted do |f| %>
<table>
<tbody>
<tr>
<td>
<%= link_to image_tag current_user.profile_pic.url(:large), :class => 'getting-started-profile-pic' %>
</td>
<td>
Upload a photo
</td>
</tr>
<table>
<tbody>
<% end %>
Right now I'm stuck on the issue that I need GettingStarted to guide users through existing models, not be a model itself. And I'm getting undefined method `model_name' for NilClass:Class
Suggestions, thoughts on the above?
Thanks
your GettingStarted model doesn't have to inherit from ActiveRecord::Base but it does, you are getting error because Base is expecting a table in your db called GettingStarteds, or something. If you want to keep the content dynamic, meaning saving it in the db so you can change it, then you are pretty close, you could use a natural language model like 'steps' and the steps have an order associated with them, then you can look up the step based on its order in the getting started controller. you can also use a vanilla workflow with a steps controller, and then rename the route in the routes with the :as => option
if the steps are static you might want to explore some of the static page model libraries available like high voltage https://github.com/thoughtbot/high_voltage

Resources