Rails upload csv or text file with associations - ruby-on-rails

I am building a rails application that associates serial #'s with software titles. For instance, I have a software title, and need to be able to upload batches of serial #'s(codes) and associate it with that specific software title. It needs to be simple enough for a user(authenticated) to click an upload link, select a software title from a dropdown and hit import. Here is what I have so far... It does not necessarily have to be a csv it could be a text file too. I just need help figuring out the best way to accomplish this.
Code Upload UI
Code Schema
create_table "codes", force: :cascade do |t|
t.integer "software_id"
t.integer "user_id"
t.string "label"
t.string "code"
t.string "in_use"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "codes", ["software_id"], name: "index_codes_on_software_id"
add_index "codes", ["user_id"], name: "index_codes_on_user_id"
Code 'form' for UI
<%= simple_form_for(#code) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.association :software %>
<%= f.input :label %>
<%= f.input :code %>
</div>
<div class="form-actions">
<%= f.file_field :code %>
<br>
<%= f.button :submit, "Upload Codes", class: 'btn btn-warning' %>
</div>
<br>
<% end %>
Code.rb Model
class Code < ActiveRecord::Base
belongs_to :software
belongs_to :user
accepts_nested_attributes_for :software
def self.import(file)
CSV.foreach(file.path, headers: true) do |row|
Code.create! row.to_hash
end
end
end
Software.rb Model
class Software < ActiveRecord::Base
has_many :software_assigns
has_many :products, through: :software_assigns
has_many :software_downloads
has_many :codes
end
Codes Controller
class CodesController < ApplicationController
before_action :authenticate_user!
before_action :verify_admin
before_action :set_code, only: [:show, :edit, :update, :destroy]
# GET /codes
# GET /codes.json
def index
#codes = Code.all
end
# GET /codes/1
# GET /codes/1.json
def show
end
# GET /codes/new
def new
#code = Code.new
end
# GET /codes/1/edit
def edit
end
# POST /codes
# POST /codes.json
def create
#code = Code.new(code_params)
respond_to do |format|
if #code.save
format.html { redirect_to #code, notice: 'Codes were successfully created.' }
else
format.html { render :new }
end
end
end
# PATCH/PUT /codes/1
# PATCH/PUT /codes/1.json
def update
respond_to do |format|
if #code.update(code_params)
format.html { redirect_to #code, notice: 'Codes were successfully updated.' }
else
format.html { render :edit }
end
end
end
# DELETE /codes/1
# DELETE /codes/1.json
def destroy
#code.destroy
respond_to do |format|
format.html { redirect_to codes_url, notice: 'Codes were successfully destroyed.' }
end
end
def import
Code.import(params[:file])
redirect_to codes_path, notice: 'Codes were successfully uploaded!'
end
private
# Use callbacks to share common setup or constraints between actions.
def set_code
#code = Code.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def code_params
params.require(:code).permit(:software_id, :label, :code)
end
end

Ok - doing a lot of communication in the comments and I think I can put together an answer now.
So, as per my edit to pitabas prathal's answer, you need to look for by the :code key in the code_params hash, but since the Code class has no idea that you've got a code_params[:software_id] to refer to, you'll need to pass that along. So your import method becomes:
code.rb
def self.import(file, software_id)
CSV.foreach(file.path, headers: true) do |row|
code = Code.new row.to_hash
code.software_id = software_id
code.save!
end
end
Then, your call to this method with the new argument, from the create action (or your import method on the controller):
Code.import(code_params[:code], code_params[:software_id])
Now, you are giving your Code class all the information it needs to associate the new Code objects with the appropriate Software object.
Edit
In your GoRails post, Chris Oliver's answer would also work, with one edit:
#software = Software.find(params[:software_id])
will not work - params[:software_id] is nil. You can access software_id from the code_params hash:
#software = Software.find(code_params[:software_id])
or by adding the [:code] key in to the params reference like so:
#software = Software.find(params[:code][:software_id])
since [:software_id] is inside the [:code] array in the params hash.

Related

How to associate a subscriber to an event

I'm trying to make that a subscriber, sub to an certain event
with the following url per example:
http://localhost:3001/events/1/subscribers/new
but I don't know how to associate event_id when creating a new subscriber
for the moment i'm getting this error:
Couldn't find Event with 'id'=
in the routes:
resources :events do
resources :subscribers #url/events/:events_id/subscribers/new
end
resources :events
root 'events#index'
in the subscribers controller:
def show
end
# GET /subscribers/new
def new
#puts "Look for me in console\n"
#puts params.inspect
#event = Event.find(params[:events_id])
#subscriber = #event.Subscriber.new
end
# GET /subscribers/1/edit
def edit
end
# POST /subscribers
# POST /subscribers.json
def create
#event = Event.find(params[:order_id])
#subscriber = #event.Subscriber.new order_params
##subscriber = Subscriber.new(subscriber_params)
respond_to do |format|
if #subscriber.save
SubsMailer.new_subscriber(#subscriber).deliver
format.html { redirect_to #subscriber, notice: 'Subscriber was successfully created.' }
format.json { render :show, status: :created, location: #subscriber }
else
format.html { render :new }
format.json { render json: #subscriber.errors, status: :unprocessable_entity }
end
end
end
in the new.html.erb:
<h1>New Subscriber</h1>
<%= render 'form', subscriber: #subscriber %>
<%= link_to 'Back', subscribers_path %>
model association:
event.rb:
class Event < ApplicationRecord
has_many :subscribers, dependent: :destroy
end
subscriber.rb:
class Subscriber < ApplicationRecord
belongs_to :event
validates :email, presence: true,
format: /\A\S+#\S+\z/,
uniqueness: { case_sensitive: false }
end
Well, I think this documentation will help you to understand what you need to do.
If briefly at first you need to change your models. You could have many to many for Event -> Subscriber association or one to many. One to many is the simplest to show so you need to add this to your Subscriber model:
belongs_to :event
And this to your Event model:
has_many :subscribers
Add new migration:
def change
remove_column :subscribers, :events_id
remove_column :subscribers, 'Event_id'
add_column :subscribers, :event_id, :integer
end
Then in your controller, you should change method calls, as Subscriber is a class, not the method.
def new
#event = Event.find(params[:event_id])
#subscriber = #event.subscribers.build
end
And you should be sure that in your database you have Event with this id.
To check it you can try to debug your controller code:
def new
puts "Event ids: " + Event.all.map(&:id).inspect
#event = Event.find(params[:event_id])
#subscriber = #event.subscribers.build
end
In your logs you should have something like:
Event ids: [1]
I think you just have a typo in your new method. You call params[:eventS_id] when it should be params[:event_id]. Also you don't properly reference your association. it should be event.subscribers.new:
def new
#puts "Look for me in console\n"
#puts params.inspect
#event = Event.find(params[:event_id])
#subscriber = #event.subscribers.build
end
Migration:
def up
change_table :subscribers do |t|
t.remove :Event_id
t.references :event
end
end
def down
change_table :subscribers do |t|
t.remove :event_id
t.add :Event_id, :integer
end
end
Keep me posted whether this helps and if you have any additional issues

NoMethodError when I try to access localhost:3000/home

I'm trying to access the 'home' page of my Rails app by going to localhost:3000/home
When I try the link above, however, I get the below error message --- however this doesn't make sense to me because 'category' is just a parameter for an order. Order has a category, store, items and other parameters.
My code is below. What am I doing wrong here?
Showing /Users/fk/tenence_ai/app/views/tenence/home.html.erb where line #49 raised:
undefined method `category' for #<Order:0x007f8ed99c8500>
Extracted source (around line #49):
47 <div>
48 <%= form_for :order, url: orders_path do |f| %>
49 <%= f.text_field :category,:id=> 'category',:style=>'display:none' %>
ORDERS CONTROLLER
class OrdersController < ApplicationController
respond_to :html, :json
def show
#order = Order.find(params[:id])
#idx = Order.last.id
render json: #order
end
def create
#order = Order.new(order_params)
#order.save
end
def edit
#order = Order.find(params[:id])
end
def update
#order = Order.find(params[:id])
#order.update(order_params)
end
respond_to :html, :json
private
def order_params
params.require(:order).permit(:address,:store,:name,:items,:category,:status,:total,{:item => []},{:price => []})
end
end
ORDER MODEL
class Order < ApplicationRecord
end
RELEVANT MIGRATION
class CreateOrders < ActiveRecord::Migration[5.0]
def change
create_table :orders do |t|
t.string :name
t.text :address
t.string :store
t.text :items
t.timestamps
end
end
end
It seems that you want to add category field:
Adding in params.require is good but you also need to add attr_accessor :category in your model.
class Order < ApplicationRecord
attr_accessor :category
end
attr_accessor can be used for values you don't want to store in the database directly and that will only exist for the life of the object.

Rails: Struggling with Associations (nested comments)

First off, I apologize for the amount of code and for what I'm asking. But I need help desperately. I can't wrap my head around this concept whatsoever.
I have a basic CRUD (projects) and I'm trying to nest another CRUD (discussions) to projects so that there can be a discussion for each project. Now, I have been trying to do this for five days straight. I can't figure it out for the life of me. I've read and researched everything there is to be read and researched. I can't figure it out on my own.
So, I've started fresh. I've set up a new project and got the basics, but I have no clue where to go from here. I would be so incredibly appreciative if someone could take the time to write me step by step instructions. Or, if you're able to do it quick, perhaps even finish my code for me? Because I'm going to have to do about 5 more of these, so if I were to have 1 fully completed one I could reference that would be so amazing.
projects_controller.rb
class ProjectsController < ApplicationController
def index
#projects = Project.all
end
def show
end
def new
#projects = Project.new
end
def create #no view
#projects = Project.new(project_params)
if #projects.save
redirect_to projects_path, :notice => "Your project was sent!"
else
render "new"
end
end
def edit
#projects = Project.find(params[:id])
end
def update #no view
#projects = Project.find(params[:id])
if #projects.update_attributes(project_params)
redirect_to projects_path, :notice => "Your project has been updated."
else
render "edit"
end
end
def destroy #no view
#projects = Project.find(params[:id])
#projects.destroy
redirect_to projects_path, :notice => "Your project has been deleted."
end
private
def project_params
params.require(:project).permit(:title, :description)
end
end
discussions_controller.rb
class DiscussionsController < ApplicationController
def index
#discussion = Discussion.all
end
def show
#discussions = Discussion.find(params[:id])
#projects = #discussions.Project.all
end
def new
#discussions = Discussion.new
end
def create #no view
#discussions = Discussion.new(discussion_params)
if #discussions.save
redirect_to discussions_path, :notice => "Your discussion was submitted."
else
render "new"
end
end
def edit
#discussions = Discussion.find(params[:id])
end
def update #no view
#discussions = Discussion.find(params[:id])
if #discussions.update_attributes(discussion_params)
redirect_to discussions_path, :notice => "Your discussion has been updated."
else
render "edit"
end
end
def destroy #no view
#discussions = Discussion.find(params[:id])
#discussions.destroy
redirect_to discussions_path, :notice => "Your discussions has been deleted."
end
private
def discussion_params
params.require(:discussion).permit(:title, :description)
end
end
routes.rb
Rails.application.routes.draw do
resources :homes
resources :projects
resources :discussions
root "homes#index"
Models:
discussion.rb
class Discussion < ActiveRecord::Base
belongs_to :project
end
project.rb
class Project < ActiveRecord::Base
has_many :discussions
end
Migrates:
_create_projects.rb
class CreateProjects < ActiveRecord::Migration
def change
create_table :projects do |t|
t.string :title
t.text :description
t.date :due_date
t.timestamps null: false
end
end
end
_create_discussions.rb
class CreateDiscussions < ActiveRecord::Migration
def change
create_table :discussions do |t|
t.string :title
t.text :description
t.timestamps null: false
end
end
end
_create_nested_discussions
class NestedDiscussion < ActiveRecord::Migration
def change
add_column :discussions, :project_id, :integer
end
end
One thing I noticed is that you don't permit project_id attribute in your strong parameters. So add :project_id in your params in discussions_controller.rb:
private
def discussion_params
params.require(:discussion).permit(:title, :description, :project_id)
end
You probably want your routes to be as such:
resources :projects do
resources :discussions
end

Rails - storing values in join table

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.

Rails associated model id on create

I have 2 models, a sport model and a team model. The team model belongs_to :sport and the sport model has_many :teams.
Sport model:
class Sport < ActiveRecord::Base
has_many :teams
has_many :competitions
has_many :games
end
Team Model:
class Team < ActiveRecord::Base
belongs_to :sport
has_many :competition_teams
has_many :competitions, :through => :competition_teams
has_many :home_games, :foreign_key => "home_team_id", :class_name => "Game"
has_many :visiting_games, :foreign_key => "visiting_team_id", :class_name => "Game"
end
When a new team is created it must always associate with a sport. So for example if Hockey has an ID of 1, the team that is created under hockey must contain the sport ID. Below is the current schema:
create_table "sports", force: true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "teams", force: true do |t|
t.string "name"
t.integer "sport_id"
t.datetime "created_at"
t.datetime "updated_at"
end
This is the teams controller:
class TeamsController < ApplicationController
before_action :set_team, only: [:show, :edit, :update, :destroy]
# GET /games
# GET /games.json
def index
#teams = Team.all
end
# GET /games/1
# GET /games/1.json
def show
end
# GET /games/new
def new
#team = Team.new
end
# GET /games/1/edit
def edit
end
# POST /games
# POST /games.json
def create
#team = Team.new(team_params)
respond_to do |format|
if #team.save
format.html { redirect_to #team, notice: 'team was successfully created.' }
format.json { render action: 'show', status: :created, location: #team }
else
format.html { render action: 'new' }
format.json { render json: #team.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /games/1
# PATCH/PUT /games/1.json
def update
respond_to do |format|
if #team.update(team_params)
format.html { redirect_to #team, notice: 'team was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #team.errors, status: :unprocessable_entity }
end
end
end
# DELETE /games/1
# DELETE /games/1.json
def destroy
#team.destroy
respond_to do |format|
format.html { redirect_to sports_url }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_team
#team = Team.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def team_params
params[:team].permit(:name, :sport_id)
end
end
I tried to do the following in the routes:
resources :sports do
resources :teams
end
But get an error when trying to create a team from the the following URL: /sports/1/teams/new
The error is: undefined method `teams_path' for #<#:0x007fafb4b9b0c0>
app/views/teams/_form.html.erb where line #1 raised:
For your route setup:
resources :sports do
resources :teams
end
You will need to use new_sport_team_path which will map to sports/:sport_id/teams/:id/new.
And in your app/view/teams/_form.html.erb, since your route is sports/:sport_id/teams, your form_for declaration should be:
<%= form_for #comment, url: sport_teams_path ... %>
...
<% end %>
In this case sport_teams_path will route to /sports/:sport_id/teams with post method which will execute the create action in your TeamsController.
The form_for declaration above can also be written as:
<%= form_for([#sport, #team]) ... %>
...
<% end %>
In this case you'd need to define #sport and #team in your controller as follows:
# app/controllers/teams_controller.rb
def new
#sport = Sport.find(params[:sport_id])
#team = #sport.teams.build
...
end
For a list of routes defined in your application, you could run rake routes from within your application directory in the terminal.

Resources