Not sure what to put for strong parameters in nested resource - ruby-on-rails

I'm trying to debug a controller that just won't play ball. I've narrowed it down to the strong parameters not working properly and so I've tried it out on the rails console.
params = ActionController::Parameters.new({
"user"=> {
"login"=>"username",
"password"=>"[FILTERED]"
},
"staff_id"=>"1"
})
This returns, as you'd expect:
=> {"user"=>{"login"=>"username", "password"=>"[FILTERED]"}, "staff_id"=>"1"}
So, I attempted to filter the parameters, like so...
params.require(:staff_id)
=> "1"
> params.require(:user).permit(:password,:login)
=> {"password"=>"[FILTERED]", "login"=>"username"}
That looks ok.
In my controller, I have:
def create
#staff=Staff.find(params[:staff_id])
#user = #staff.create_user(reg_params[:user])
DISASTER
Now, at the point of the disaster, the user object #user should have a login and password set but It doesn't. I've tried logging the #user object at that point, it's properties are nil.
Why isn't my user object being created properly? If I remove the DISASTER, it creates a database record with blank fields, except for the timestamps.
Models:
class Staff < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_one :staff
end
Schema:
ActiveRecord::Schema.define(version: 20150909102012) do
create_table "staff", force: :cascade do |t|
t.string "qualifications"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
end
add_index "staff", ["users_id"], name: "index_staff_on_users_id"
create_table "users", force: :cascade do |t|
t.string "login"
t.string "username"
t.string "password"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
It routes to the custom controller ok. Here's the route:
Rails.application.routes.draw do
resources :staff do
resource :user, shallow: true, controller: 'staff_register'
end
resources :users
There's nothing else in the app because it's just an experiment to help me work on a bigger problem I've been struggling with.
Basically, I want to be able to link a staff model to a user model. I think I've done that ok, I just need help figuring out the strong parameters bit.
EDIT: here's the form for the nested resource:
<%= form_for #user, :url => staff_user_path(#staff) do |f| %>
<% if #user.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#user.errors.count, "error") %> prohibited this user from being saved:</h2>
<ul>
<% #user.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :login %><br>
<%= f.text_field :login %>
</div>
<div class="field">
<%= f.label :password %><br>
<%= f.text_field :password %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>

In this line:
#user = #staff.create_user(reg_params[:user])
reg_params already gives you hash with all the permitted parameters:
{"password"=>"[FILTERED]", "login"=>"username"}
There is no user key there, hence reg_params[:user] is just nil. Instead you need to do:
#user = #staff.create_user(reg_params)
Now, you do not need to worry about staff_id here, as you are executing create_user method on already existing #staff model. This method will take care of the association.

Related

Undefined method `street' for #<Profile:0x00007fea78589ac0>

I have a User who has a Profile (2 models). Here is the relevant part of my schema:
create_table "profiles", force: :cascade do |t|
t.text "about"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "provider"
t.string "uid"
t.string "first_name"
t.string "last_name"
t.string "street"
t.integer "house_number"
t.string "city"
t.integer "zip_code"
t.string "image"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
The reason I have a Profile as a separate model, as because I thought it was easier to assign roles later, for certain manipulations. So, now I am wondering, if it is possible to ask for
user.first_name , user.last_name, user.email and user.password
in the registration form and for
user.street, user.house_number, user.city and user.zip_code
in the Profile#new _form. Like this:
<%= form_for([#user, #profile], url: user_profiles_path, method: :post) do |form| %>
<div class="field">
<%= form.label :about %>
<%= form.text_area :about %>
</div>
<div class="field">
<%= form.file_field :avatar %>
<% form.label "Profile photo" %>
</div>
<div class="field">
<%= form.label :street %><br />
<%= form.text_field :street, class: 'form-control' %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
So here you can see, that avatar and about refer to a Profile, while street if from User table. But somehow this form, doesn't undertand this. I allow nested_attributes for :profile, but I guess, this doesn't matter for this form. I know, that maybe the easier way would be, to rearrange my table, so that all the adress attributes are stored in Profile. But as I am new to Rails and I really wish to learn more, I would love to know, if there is a way of saving to both #user and #profile in one form? Thank you!
You're touching on two somewhat different concepts here that most beginners get stumped on.
The first is nested resources. A nested resource has its path nested under another resource.
# config/routes.rb
resources :magazines do
resources :ads
end
So now instead of /ads we have /magazines/:magazine_id/ads. So the routes themselves describe the relation between the two resources in a RESTful way - awesome.
class AdsController < ApplicationController
before_action :set_magazine
# GET /magazines/:magazine_id/ads/new
def new
#ad = #magazine.ads.new
end
# POST /magazines/:magazine_id/ads/new
def create
#ad = #magazine.ads.new(ad_params)
if #ad.save
redirect_to #ad
else
render :new
end
end
def set_magazine
#magazine = Magazine.find(params[:magazine_id])
end
# ...
end
<%= form_for([#ad, #magazine]) do |f| >
# ...
<% end %>
This will let you create ads that belong to a magazine. It will not magically let you create a magazine at the same time as an add in the same form.
That's where nested attributes comes in. It creates a super-powered setter in the model which lets it accept attributes for an associated model and creates / updates the associated records in the same request as the parent.
This for example would let us create a user and a profile in the same form:
class User < ApplicationRecord
has_one :profile
accepts_nested_attributes_for :profile
end
class Profile < ApplicationRecord
belongs_to :user
end
<%= form_for(#user) do |f|>
<div class="field">
<%= f.label :email %>
<%= f.email_field :street, class: 'form-control' %>
</div>
# ...
<%= f.fields_for(:profile) do |profile_fields| %>
<div class="field">
<%= profile_fields.label :about %>
<%= profile_fields.text_area :about %>
</div>
<% end %>
# ...
<% end %>
class UsersController < ApplicationRecord
POST /users
def create
#user = User.new(user_params)
if #user.save
redirect_to :user
else
render :new
end
end
# ...
private
def user_params
params.require(:user)
.permit(:email, ..., profile_attributes: [:about])
end
end
accepts_nested_attributes_for is one of the most misused, misunderstood and hardest concepts to grasp in rails though. If you're just starting out you should consider bypassing this and circling back around once you have a better understanding of rails.

how to show user name with associated posts?

I was building a rails app. I want to all post on a page. I am able to show posts title and body but i am not able users name(to whom post belongs -owner of post).
Post-Model
class Post < ApplicationRecord
belongs_to :user
end
User-Model
class User < ApplicationRecord
has_many :posts
end
Posts-Controller
class PostsController < ApplicationController
def index
#posts = Post.all
end
end
Posts-View index.html.erb
<h1> All article </h1>
<% #posts.each do |post| %>
<ul>
<div class="row">
<div class="col-md-offset-3 col-md-5">
<h5><b><%= post.title %></b> by <%= %></h5>
<p><%= post.body %></p>
</div>
</div>
</ul>
<% end %>
Schema looks like
create_table "posts", force: :cascade do |t|
t.string "title"
t.text "body"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "user_id"
end
create_table "users", force: :cascade do |t|
t.string "name"
t.string "email"
t.string "password_digest"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
Display name of user who posted that post
Since you have a relation to the User in the Post as belongs_to :user, So, you can simply use post.user.name to get the name of the user like.
<h1> All article </h1>
<% #posts.each do |post| %>
<ul>
<div class="row">
<div class="col-md-offset-3 col-md-5">
<h5><b><%= post.title %></b> by <%= post.user.name %></h5>
<p><%= post.body %></p>
</div>
</div>
</ul>
<% end %>
bonus
If you want to eager load the user in a single query you might wanna use. Post.includes(:user).all, its better if you always use the user, saves you extra query. Mind that it has some downsides to it as well.
Try post.user to get user object. Then you can print any attribute from the user object.
<p><%= post.user.name %></p>
And use includes method to avoid N + 1 queries problem.
def index
#posts = Post.includes(:user)
end
Reference: https://guides.rubyonrails.org/active_record_querying.html
It's quite easy to access. Try post.user.name and you will get name of associated user.
Read here about all standard methods available for belongs_to association
https://guides.rubyonrails.org/association_basics.html#methods-added-by-belongs-to

Creating form for an object which has an association

I have two models: project and todo. Project has many todos.
So I wanna create a form, where I select project category from the combobox and then I add a todo to it.
For instance:
I have following categories: family, work, study.
In form in the combobox I select 'study', and then in textfield I spell a todo like 'make homework for monday' and press submit button.
project.rb
class Project < ActiveRecord::Base
has_many :todos
end
todo.rb
class Todo < ActiveRecord::Base
belongs_to :project
end
my data schema:
create_table "projects", force: :cascade do |t|
t.string "title"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "todos", force: :cascade do |t|
t.string "text"
t.boolean "isCompleted"
t.integer "project_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
_form.html.erb
<%= form_for #project do |f| %>
<div class="form_control">
<%= f.select :title, options_for_select([["Work", "w"],
["Family", "f"],
["Study", "f"],
["TheRest", "t"]]) %>
</div>
<div class="form_control">
*** HERE I NEED TO FIGURE OUT HOW TO ADD SOME DATA TO todo.text ***
</div>
<div class="form_control">
<%= f.submit 'Add' %>
</div>
<% end %>
this is how I show all the projects with their todos:
<% #projects.each do |project| %>
<h2> <%= project.title %> </h2>
<% project.todos.all.each do |todo| %>
<p><%= todo.text %> <%= check_box('tag', todo.__id__, {checked: todo.isCompleted}) %></p>
<% end %>
<% end %>
GitHub link : https://github.com/NanoBreaker/taskmanager
In your todo form, you could have a select box to choose the project the todo belongs to:
# todos/_todo_form.html.erb
<%= select_tag "project_id", options_for_select(Project.pluck(:title, :id)) %>
And in your todos_controller create action:
def create
#project = Project.find(params[:project_id])
#todo = #project.todos.new(todo_params)
if #todo.save
# success
else
# error
end
end
finally, permit the project_id in todo_params:
def todo_params
params.require(:todo).permit(:text, :project_id) # add any other attributes you want
end

Importing CSV data into Rails app, using something other then the association "id"

I am trying to import a few CSV files into my rails app. I learned and managed to import tables into Models without association.
Now i have managed to import the data into a table that has associations, but only by entering the actual "id" number on the CSV column. Although functional, this isn't really an option because i have many tables with thousands of IDs.
My main goal is to be able to use the column in the CSV and type in the actual value (that exists in the other model it is associated with), instead of the id number.
I have a Country model and a Ports model. The Ports model is associated with country_id
Port Model
class Port < ApplicationRecord
def self.import(file)
#code
CSV.foreach(file.path, headers: true) do |row|
port = find_by_id(row["id"])
Port.create! row.to_hash
end
end
belongs_to :shipment_type
belongs_to :country
has_many :origins, :class_name => 'Rate'
has_many :destinations, :class_name => 'Rate'
end
Country Model
class Country < ApplicationRecord
def self.import(file)
#code
CSV.foreach(file.path, headers: true) do |row|
Country.create! row.to_hash
end
end
has_many :ports, dependent: :destroy
end
schema.db
create_table "ports", force: :cascade do |t|
t.string "name"
t.string "port_code"
t.integer "shipment_type_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "country_id"
t.index ["country_id"], name: "index_ports_on_country_id", using: :btree
t.index ["shipment_type_id"], name: "index_ports_on_shipment_type_id", using: :btree
end
create_table "countries", force: :cascade do |t|
t.string "name"
t.string "country_code"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "shipment_types", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
The associations are working because i am able to manually add them view my forms i create just fine.
<%= form_for(port) do |f| %>
<% if port.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(port.errors.count, "error") %> prohibited this port from being saved:</h2>
<ul>
<% port.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :port_code %>
<%= f.text_field :port_code %>
</div>
<div class="field">
<%= f.label :shipment_type_id %>
<%= f.collection_select :shipment_type_id, ShipmentType.all, :id, :name %>
</div>
<div class="field">
<%= f.label :country_code %>
<%= f.collection_select :country_id, Country.all, :id, :country_code %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Any guidance or help would be greatly appreciated. I have been going in circles with this for days now.
ADDING SAMPLE TABLE FROM CSV FILE.
A shipment_type is a ruby object, you want to send a string.
If you are needing to import relationships, add methods on the Port model like so
class Port < ApplicationRecord
def shipment_type_name
shipment_type.try(:name)
end
def shipment_type_name=(name)
self.shipment_type = ShipmentType.where(:name => name).first_or_create
end
def country_country_code
country.try(:country_code)
end
def country_country_code=(code)
self.country = Country.where(:country_code => code).first
end
end
Then in the CSV you'd send a shipment_type_name and country_country_code attributes.
You would do something similar to other relationships.
You may want to use this gem for importing CSV:
https://github.com/michaelnera/active_record_importer
It's easy to use.
Thank you everyone for the help. Below is what ended up working for me. The biggest issue i was getting was Origin and Destination. There is only one Port table, which includes a list of the Ports. Ports are used for both Origin and Destination.
class Rate < ApplicationRecord
def self.import(file)
CSV.foreach(file.path, headers: true) do |row|
rate = find_by_id(row["id"])
Rate.create! row.to_hash
end
end
belongs_to :origin, :class_name => 'Port'
belongs_to :destination, :class_name => 'Port'
belongs_to :carrier
belongs_to :shipment_category
belongs_to :unit_of_measure
has_many :additional_items
# associatiing Origin and Destination Port Code
def origin_port_code
origin.try(:port_code)
end
def origin_port_code=(port_code)
self.origin = Port.where(:port_code => port_code).first
end
def destination_port_code
destination.try(:port_code)
end
def destination_port_code=(port_code)
self.destination = Port.where(:port_code => port_code).first
end
# associating carrier name
def carrier_name
carrier_name.try(:name)
#code
end
def carrier_name=(name)
self.carrier = Carrier.where(:name => name).first
#code
end
# associating Shipment Category Name
def shipment_category_name
shipment_category.try(:name)
end
def shipment_category_name=(name)
self.shipment_category = ShipmentCategory.where(:name => name).first
end
# associating unit_of_measure name
def unit_of_measure_name
unit_of_measure.try(:name)
#code
end
def unit_of_measure_name=(name)
self.unit_of_measure = UnitOfMeasure.where(:name => name).first
#code
end
end

Ruby Rails update two different table columns using form

Here is my newbie story,
my table looks like this:
create_table "books", :force => true do |t|
t.string "title"
t.integer "count"
t.integer "id"
end
create_table "users", :force => true do |t|
t.string "name"
t.decimal "money"
end
user can create many books, and user using these forms to update it:
to update money:
<%= form_for(#user) do |f| %>
<%= f.number_field :money, :value => #user.money %>
<% end %>
to update book title, count etc., for each books:
<% #book.each do |book| %>
<%= form_for(book) do |f| %>
<%= f.number_field :count %>
<%= f.text_field :title %>
<% end %>
<% end %>
and im trying to do is i want update both of them. (lets say user need to update money and book title) currently it only able to update money or book info separately
model:
class Book < ActiveRecord::Base
attr_accessible :count, :title, :id
belongs_to :user
end
any idea? Thanks!
It's not that simple to explain in a single answer. I suggest you to have a look at the following Railscasts to get an idea.
http://railscasts.com/episodes/198-edit-multiple-individually
http://railscasts.com/episodes/165-edit-multiple-revised

Resources