Creating multiple unrelated associated model instances in Rails - ruby-on-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

Related

Can I have two different input fields and one column in the database?

I want to have two input fields, but only one column in the database.
The first input is stored data in numbers and the other one is stored data in numbers divided by 24.
You can put data only in one field.
Is there any possible way to do this?
UPD:
Migration:
def change
add_column :employees, :hourly_payment , :integer
end
View:
employees/_form.html.erb
<%= simple_form_for #employee do |form| %>
<%= form.input :name %>
<%= form.input :hourly_payment %>
<%= form.input :monthly_payment %>
<%= form.button :submit, class: "btn btn-success" %>
<% end %>
Your model and database tables are the internals of your application and are not actually tied to the view by anything except how easy ActiveRecord makes it to use convention over configuration to link the two*.
In Rails style MVC the controller is responsible for passing user input to the model. Usually you would just do this with simple mass assignment:
class UsersController < ApplicationController
def create
#user = User.new(user_params)
# ...
end
private
def user_params
params.require(:user)
.permit(:email, :salary)
end
end
This is basically just passing a whitelisted hash of parameters straight to the model as is and it all gets passed to the setters that ActiveRecord magically created for you by reading the database schema.
But there is nothing stopping you from assigning attributes manually:
class UsersController < ApplicationController
def create
#user = User.new(user_params) do |user|
user.salary = calculated_salary
end
# ...
end
private
def user_params
params.require(:user)
.permit(:email)
end
def calculated_salary
if params[:user][:hourly_payment].present?
params[:user][:hourly_payment]
elsif params[:user][:monthly_payment].present?
params[:user][:monthly_payment].to_i / 168
else
0 # sorry no cookies for you
end
end
end
Or monkeying with the parameters object:
def user_params
params.require(:user)
.permit(:email)
.merge(salary: calculated_salary)
end
It is after all just a hash on steroids. The only thing that Rails will prevent you from is passing a parameters object that has not been whitelisted.
There is no stone tablet for what you can do in a controller. The only thing to bear in mind is that controllers are notoriously hard to test and fat controllers are a recipe for disaster.
If you're doing anything more complicated there are better solutions such as form objects, decorators or service objects.
You'll need to create a view for that. Here is an example of migration:
def up
sql = %(CREATE OR REPLACE VIEW my_view_models AS
SELECT m.*,m.int_field*12 as my_new_value from my_models m
)
self.connection.execute(sql)
end
def down
self.connection.execute('DROP VIEW IF EXISTS my_view_models')
end
Then, you can access your value with method my_new_value on your model. You'll need to change the name of the default table matching your model.
class MyModel
self.table_name = 'my_view_models'
end
And access it via
MyModel.first.my_new_value

Rails, how to change automatic generated link

I have a RoR app. And in app users can create posts. I've connected Posts table in my routes.rb via resources :posts. And right now - link to created post look like: http://mysitename.com/posts/1 (where 1 is post number).
What i want to do, is to make rails generate link to post. So users didn't see how much posts I have in my DB. And as result it must look like http://mysitename.com/post/generatedlink. It must generate, for example post theme.
For start, we must create link column in Posts table. And make it to generate something like that:
#post.link = #post.theme.parameterize.underscore
But I don't understand, where to put this code.
And the next problem is: "How to replace post/1 for #post.link?"
Hope, I make my self clear. If you'll say I can provide information, what is needed to resolve my question.
UPDATE
What I did after #SteveTurczyn advise.
I've created new column, called random_link as a string.
I didn't touch my routes.rb:
resources :posts
My post.rb (post model) look like this:
after_validation :add_link
def add_link
self.random_link = self.theme.to_slug_param
# to_slug_param it's a gem for translating from other language into english
end
def to_param
random_link
end
I don't have find method. My posts_controller.rb look like this:
def show
#post = Post.find_by_random_link(params[:id])
right_menu_posts
random_link_to_other_post(#post)
end
private
def random_link_to_other_post(post)
random_post = Post.where.not(id: post.id)
#random_post = random_post.sort_by {rand}.first
end
def right_menu_posts
#posts_for_video_in_right_menu = Post.where(video: true)
end
And html.erb:
<%= #post.theme %>
<%= #post.content %>
<% for post in #random_post %>
<%= link_to post %>
<% end %>
<% for post in #posts_for_video_in_right_menu %>
<%= link_to post %>
<% end %>
And on a main page (where i have a list of posts) a keep getting an error: NoMethodError in Home#index private method 'to_param' called for #<Post:0x007fae3096bf78>.
The technique is referred to as slugifying and you need to do three things...
(1) create a new field called slug in your posts table.
(2) add this code to your Post model...
after_validation :generate_slug
private
def generate_slug
self.slug = theme.parameterize.underscore
end
public
def to_param
slug
end
(3) finally, in your controllers where you have find_post methods, rewrite it to be...
def find_post
Post.find_by_slug(params[:id])
end
The to_param method in the model is how things like post_path(#post) build the url... the to_param if not replaced substituted the id field but by writing your own to_param method you can ensure that the slug field is substituted instead.
Ensure that 'to_param' is a public method! Don't put it in the private part of your model. You can do that by putting public immediately before the to_param method. You should then put private after the method definition if subsequent methods are to be private.

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

rails 4 post multiple child objects under multiple parent objects on the same form

I have nested resources with a parent model called main and a child model called temperature. Each main can have many temperatures (1 to many). Is there any way to post multiple temperatures, with each temperature going to a different main object, on one single form using form_for and fields_for (I would like to have just one submit button)? The following is my main's index view and main & temperature controllers. Thanks!
<% #mains.each do |main| %>
<tr>
<td>stuff.....</td>
<td><%= form_for main do |f| %>
<%= f.fields_for :temperature do |t| %>
temp: <%= t.text_field :temp %>
<% end %>
<% end %></td>
</tr>
class MainsController < ApplicationController
#stuff
def index
#mains = Main.all
end
private
def main_params
params.require(:main).permit(:freezer_id, :freezer_name, temperatures_attributes: [:id, :main_id, :temp, :date])
end
end
class TemperaturesController < ApplicationController
#stuff
def create
#main = Main.find(params[:main_id])
logger.debug 'testing 1'
#temp = #main.temperatures.create(temperature_params)
if #temp.save
redirect_to main_path(#main)
else
render 'mains/show'
end
end
private
def temperature_params
params.require(:temperature).permit(:temp, :date)
end
end
What you are trying to do is a bulk update/create on the database for diferent mains. Rails can't handle this on his convention as long as the require(param) is acting. You will need to create a bulk handler in the controller to create individual hashes from each entitiy in the array and then create a handler for the response of each model validations.
Not easy to do, but possible.
Some references, for APIS, nothing for forms, but as long as the view create the appropiate hash, you can reuse it.
http://www.l1ghtm4n.com/post/53259404576/patterns-for-rest-api-bulk-operations
https://github.com/arsduo/batch_api
http://forrst.com/posts/Run_any_rails_RESTful_action_more_than_once_bul-Yb5

Resources