No slug being added with friendly id rails - ruby-on-rails

When i create a new category_item_key in the db the slug isn't being added to the slug column.
here is my coding
migration file
class AddSlugToCategoryItemKeys < ActiveRecord::Migration
def change
add_column :category_item_keys, :slug, :string
add_index :category_item_keys, :slug, unique: true
end
end
category_item_key controller
def new
#guide = Guide.friendly.find(params[:guide_id])
#category = Category.friendly.find(params[:category_id])
#key = Category.friendly.find(params[:category_id]).category_item_keys.new
end
def create
#guide = Guide.friendly.find(params[:guide_id])
#category = Category.friendly.find(params[:category_id])
#key = Category.friendly.find(params[:category_id]).category_item_keys.new(key_params)
if #key.save
flash[:info] = "Key added succesfully!"
redirect_to #guide
else
render 'new'
end
end
private
def key_params
params.require(:category_item_key).permit(:name, :slug)
end
new.html.erb
<%= form_for([#category, #key], url: category_item_keys_create_path) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :name, "Key name" %>
<%= f.text_field :name %>
<%= f.submit "Next" %>
<% end %>
When creating a new guide or category friendly id works just fine, with the slug being added. But for category_item_key something is going wrong.
Maybe i'm missing something. But i cant find the problem.

def new
#guide = Guide.find params[:guide_id]
#category = Category.find params[:category_id]
#key = #category.category_item_keys.new
end
def create
#guide = Guide.find params[:guide_id]
#category = Category.find params[:category_id]
#key = #category.category_item_keys.new key_params
if #key.save
redirect_to #guide, notice: "Key added succesfully!"
else
render 'new'
end
end
private
def key_params
params.require(:category_item_key).permit(:name)
end
--
<%= form_for [#guide, #category, #key] do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :name, "Key name" %>
<%= f.text_field :name %>
<%= f.submit "Next" %>
<% end %>
If you've ran your migration (have the columns in the db), the above should work. If it doesn't, you need to post the sent params, and the response you're receiving from the server.
--
As an aside, you'll also want to look at how many levels you're nesting your routes...
Resources should never be nested more than 1 level deep.
Because you've got guides and then categories before you get to keys, you'll probably want to remove the category or guide param:
# config/routes.rb
resources :categories do
resources :keys
end
I think the issue you have is likely that you're only passing [#category, #key] to your form_for. Instead, you'll need to include the #guide as well.

Forgot to add extend FriendlyId
friendly_id :name, use: :slugged into my model. once it was added my problem was solved

Just for your notice
You can rewrite to_params method in your model to generate your uniq slug
to_params
#for instance
your_column + your_column
end

Related

Why is my form not saving? form_for question

I have this form. I am new to rails and I am trying to write a simple ecommerce site. This is the only part not working. (It worked 2 days ago I sear)
<%= form_tag line_items_path do%>
<%binding.pry%>
<%= hidden_field_tag :lite_item, :order_id, #order.id%>
<%= hidden_field_tag :line_item, :menu_item_id, #menu_item.id%>
<%= number_field_tag :line_item, :quantity, 1 %>
<%= submit_tag "Add to Cart"%>
<% end %>
It gives params that look like:
#<ActionController::Parameters {"authenticity_token"=>"VECKnS5SBot1rCyekepPXZa7TyTYkfFi0KdNRTB617ZnelmQo8Lkz_cJmQ8nAmCHUdDlPu1mpkhrPvMKysfjew", "order_id"=>"1", "menu_item_id"=>"1", "quantity"=>"1", "commit"=>"Add to Cart", "controller"=>"line_items", "action"=>"create"} permitted: false>
The controller for the view looks like this:
class MenusController < ApplicationController
def index
#menu_items = MenuItem.all
end
def show
#menu_item = MenuItem.find(params[:id])
#line_items = current_order.line_items.build
end
end
The form is really going through the line_items controller
def create
binding.pry
#line_item = LineItem.create(line_item_params)
if #line_item.save
#order.line_item_id = #line_item.id
#order.save
redirect_to cart_path(#current_cart), notice: "Item added to cart."
else
redirect_to menu_path(#menu_item), alert: "Item did not add to cart."
end
end
With strong params like this
def line_item_params
params.require(:line_item).permit(:menu_item_id, :quantity, :order_id)
end
It should use the line_items_path POST>
If anything else is needed just ask. Thanks in advance.
There are a lot of problems here.
The signature is hidden_field_tag(name, value = nil, options = {}). So the parameters you would actually be creating with that form is:
{
"lite_item" => "order_id", # check your spelling...
"line_item" => "quantity"
}
Oops. And that not even going to happen as <%= number_field_tag :line_item, :quantity, 1 %> will raise since you're passing an integer where the method expects a hash.
If you really have to create the inputs manually you would want:
<%= hidden_field_tag "line_item[order_id]", #order.id %>
But since you actually have a model there is no reason why you should be using form_tag instead of form_for(#line_item) or form_with(model: #line_item).
<%= form_for(#line_item) do |form| %>
<%= form.hidden_field :order_id %>
<%= form.hidden_field :menu_item_id %>
<%= form.number_field :quantity %>
<%= form.submit_tag "Add to Cart"%>
<% end %>
The controller should also use the correct pluralization for the instance variable:
def show
#menu_item = MenuItem.find(params[:id])
#line_item = current_order.line_items.build
end
Your create method is also pretty questionable. All you should need is:
def create
# use .new not .create
#line_item = LineItem.new(line_item_params)
if #line_item.save
redirect_to cart_path(#current_cart), notice: "Item added to cart."
else
redirect_to menu_path(#menu_item), alert: "Item did not add to cart."
end
end
I have no idea why you think you need to update #order here. Your controller should just really be adding a row to what is essentially a join table.

Saving a list of emails from a form-text input into Models email_list attribute (array)

My goal is to when adding a new product with the new product form, to have an input where one can add a list of emails separated by a space. The list of emails in this string field would be saved as an array of emails in the email_list array attribute of the Product model. This way each product has many emails. (later an email will be sent to these users to fill out questionaire, once a user fills it out there name will be taken off this list and put on completed_email_list array.
I am relatively new to rails, and have a few questions regarding implementing this. I am using postgresql, which from my understanding I do not need to serialize the model for array format because of this. Below is what I have tried so far to implement this. These may show fundamental flaws in my thinking of how everything works.
My first thinking was that I can in my controllers create action first take params[:email].split and save that directly into the email_list attribute (#product.email_list = params[:email].split. It turns out that params[:email] is always nil. Why is this? (this is a basic misunderstanding I have)(I put :email as accepted param).
After spending a long time trying to figure this out, I tried the following which it seems works, but I feel this is probably not the best way to do it (in the code below), which involves creating ANOTHER attribute of string called email, and then splitting it and saving it in the email_list array :
#product.email_list = #product.email.split
What is the best way to actually implement this? someone can clear my thinking on this I would be very grateful.
Cheers
Products.new View
<%= simple_form_for #product do |f| %>
<%= f.input :title, label:"Product title" %>
<%= f.input :description %>
<%= f.input :email %>
<%= f.button :submit %>
<%end %>
Products Controller
class ProductsController < ApplicationController
before_action :find_product, only: [:show, :edit, :update, :destroy]
def index
if params[:category].blank?
#products= Product.all.order("created_at DESC")
else
#category_id=Category.find_by(name: params[:category]).id
#products= Product.where(:category_id => #category_id).order("created_at DESC")
end
end
def new
#product=current_user.products.build
#categories= Category.all.map{|c| [c.name, c.id]}
end
def show
end
def edit
#categories= Category.all.map{|c| [c.name, c.id]}
end
def update
#product.category_id = params[:category_id]
if #product.update(product_params)
redirect_to product_path(#product)
else
render 'new'
end
end
def destroy
#product.destroy
redirect_to root_path
end
def create
#product=current_user.products.build(product_params)
#product.category_id = params[:category_id]
#product.email_list = #product.email.split
if #product.save
redirect_to root_path
else
render 'new'
end
end
private
def product_params
params.require(:product).permit(:title, :description, :category_id, :video, :thumbnail,:email, :email_list)
end
def find_product
#product = Product.find(params[:id])
end
end
To solve your original issue
#product.email_list = params[:email].split. It turns out that params[:email] is always nil
:email is a sub key of :product hash, so it should be:
#product.email_list = params[:product][:email].split
Demo:
params = ActionController::Parameters.new(product: { email: "first#email.com last#email.com" })
params[:email] # => nil
params[:product][:email] # => "first#email.com last#email.com"
I'd say that what you have is perfectly fine, except for the additional dance that you're doing in #product.email_list=#product.email.split, which seems weird.
Instead, I'd have an emails param in the form and an #emails= method in the model (rather than email and #email=):
def emails=(val)
self.email_list = val.split
end
Alternatively, you could do that in the controller rather than having the above convenience #emails= method, similar to the way you're handling the category_id:
#product = current_user.products.build(product_params)
#product.category_id = params[:category_id]
#product.email_list = product_params[:emails].split
Because you need validations on your emails and to make it cleaner I would create an email table, make Product table accept Email attribues and use cocoon gem to have a nice dynamic nested form with multiple emails inputs.
1) models
class Product < ActiveRecord::Base
has_many :emails, dependent: :destroy
accepts_nested_attributes_for :emails, reject_if: :all_blank, allow_destroy: true
end
class Email < ActiveRecord::Base
belong_to :product
validates :address, presence: true
end
2) Controller
class ProductsController < ApplicationController
def new
#product = current_user.products.build
end
def create
#product = current_user.products.build(product_params)
if #product.save
redirect_to root_path
else
render 'new'
end
end
private
def product_params
params.require(:project).permit(:title, :description, :category_id, :video, :thumbnail, emails_attributes: [:id, :address, :_destroy])
end
end
3) View
<%= simple_form_for #product do |f| %>
<%= f.input :title, label:"Product title" %>
<%= f.input :description %>
<%= f.association :category %>
<div id="emails">
<%= f.simple_fields_for :emails do |email| %>
<%= render 'emails_fields', f: email %>
<div class="links">
<%= link_to_add_association 'add email', f, :emails %>
</div>
<%= end %>
</div>
<%= f.button :submit %>
<% end %>
In your _emails_fields partial:
<div class="nested-fields">
<%= f.input :address %>
<%= link_to_remove_association "Remove email", f %>
</div>
Then setup cocoon's gem and javascript and you'll be good.
Reference: https://github.com/nathanvda/cocoon

Edit page to display all category names with edit inputs on each

I have categories nested inside of guides. I'm building an app to learn rails better and I'm trying to make a page that will display all categories that belong to a guide and have edit inputs under them and a save button next to it so the user can edit the names of the categories they want to change.
Bit stuck on how exactly how get this done.
here is the category_item_keys controller
def edit
#guide = Guide.friendly.find(params[:guide_id])
#category = Category.friendly.find(params[:category_id])
#key = #category.category_item_keys
end
def update
#guide = Guide.friendly.find(params[:guide_id])
#category = Category.friendly.find(params[:category_id])
#key = #category.category_item_keys.friendly.find(key_params) # no idea what to make this equal because it isn't one set key being edited on the page
if #key = #category.category_item_keys.update_attributes(key_params)
flash[:success] = "Key updated"
redirect_to #guide
else
render 'edit'
end
end
private
def key_params
params.require(:category_item_key).permit(:key, :slug)
end
routes
match '/guides/:guide_id/:category_id/keys/edit' => 'category_item_keys#edit', :via => :get
match '/guides/:guide_id/:category_id/keys/' => 'category_item_keys#update', :via => :post, as: :category_item_keys_update
edit.html.erb
<ul>
<% #key.each do |key| %>
<li><%= key.key #just shows key name %><br>
<%= form_for([#category, #keys], url: category_item_keys_create_path) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :key, "Key name" %>
<%= f.text_field :key %>
<%= f.submit "Save" %>
<% end %>
</li>
<% end %>
</ul>
This just gives me an error of:
undefined method `to_key' for #<CategoryItemKey::ActiveRecord_Associations_CollectionProxy:0x007fe20a86b480>
Later I plan on using an in-place editor gem but i would like to learn how this can be done fist.
EDIT:
Fixed the error ( changed form_for([#category, #keys] to form_for([#category, key] and turns out this way works for displaying and allowing all categories to be edited... to an extent.
I get another error when i submit a form
undefined method 'update_attribute'
EDIT 2
slowly getting there. I Changed the update #key variable to #key = #category.category_item_keys.all to fix the error. But this line is now giving me problems
if #key = #category.category_item_keys.update_attributes(key_params)'
THIRD EDIT
ERROR
Couldn't find CategoryItemKey without an ID
on line
#key = #category.category_item_keys.find params[:id]
paramaters:
{"utf8"=>"✓", "_method"=>"patch", "authenticity_token"=>"egj/OebdSbxxaoaTkr46WVIOIIu4Ezijzu45kqxLT0krjFWHqi67SRJDSgV7bcL6SeoGpUSYsrolspylCXBu9g==",
"category_item_key"=>{"name"=>"def1111"},
"commit"=>"Save",
"guide_id"=>"dbz",
"category_id"=>"characters"}
Here's how to clean up the code:
#config/routes.rb
resources :guides do
resources :categories, path: "" do
resources :category_item_keys, path: "keys", only: [:update] do
get :edit, on: :collection #-> url.com/guides/:guide_id/:category_id/keys/edit
end
end
end
#app/controllers/keys_controller.rb
class KeysController < ApplicationController
def edit
#guide = Guide.find params[:guide_id]
#category = Category.find params[:category_id]
#keys = #category.category_item_keys
end
def update
#guide = Guide.find params[:guide_id]
#category = Category.find params[:category_id]
#key = #category.category_item_keys.find params[:id]
if #key.update key_params
redirect_to #guide, success: "Key Updated"
else
render 'edit'
end
end
private
def key_params
params.require(:category_item_key).permit(:key)
end
end
#app/views/keys/edit.html.erb
<% #keys.each do |key| %>
<%= form_for [#guide, #category, key] do |f| %>
<%= f.text_field :key %>
<%= f.submit %>
<% end %>
<% end %>
If you wanted to use an in-place editor gem, I'd recommend looking at X-Editable, as we've applied it here (its only a demo app, just sign up for free and go to profile):
Looks like you are trying to do update_attributes on a collection instead of an object. Try to first fetch the key object
#key = #category.category_item_keys.friendly.find(params[:id])
and then try to update its attributes
if #key.update_attributes(key_params)
...
end
use nested forms available in rails 4.

Cannot enter simply form information into SQLite DB (Rails)

So, I'm running into a fairly simple problem, where I cannot enter some simple form values into my SQLite DB (Rails).
Interestingly, the code doesn't fail - I submit the form, and am redirected successfully to the correct URL - and a record IS inserted into the DB, but the columns "name, type and user_id" are not filled with anything. To clarify, the columns are blank, for that new record.
If I comment out the code in the "create" and simply spit out the params (render plain: params[:plan].inspect) I do see all the correct parameters filled out, so I have a feeling there must be something wrong in the line:
#plan = Plan.new(params[:plan])
I'm really stuck here, any guidance would be much appreciated!
The create form
<h1> Create a new plan </h1>
<%= form_for :plan, url: plans_path do |f| %>
<p>
<%= f.label :name %><br>
<%= f.text_field :name %>
</p>
<p>
<%= f.label :type %><br>
<%= f.text_field :type %>
</p>
<%= f.hidden_field :user_id, :value => current_user.id %>
<p>
<%= f.submit %>
</p>
<% end %>
plans_controller.rb
class PlansController < ApplicationController
def index
#plans = Plan.all
end
def show
#plan = Plan.find(params[:id])
end
def new
#plan = Plan.new
end
def create
#render plain: params[:plan].inspect
params.permit!
#plan = Plan.new(params[:plan])
if #plan.save
redirect_to #plan
else
redirect_to dashboard_path, :notice => "Plan NOT Created!"
end
end
end
The Model
class Plan < ActiveRecord::Base
end
Edit the plans_controller.rb:-
def create
#render plain: params[:plan].inspect
#plan = Plan.new(plan_params)
if #plan.save
redirect_to #plan
else
redirect_to dashboard_path, :notice => "Plan NOT Created!"
end
end
private
def plan_params
params.require(:plan).permit(:name,:type,:user_id)
end
Change the field name type as :-
rails g migration rename_fields_in_plans
class RenameFieldsInPlans < ActiveRecord::Migration
def change
rename_column :plans, :type, :plan_type
end
end
Then run the command:-
rake db:migrate

"One-to-Many" Rails Form Creation

I have an association of One Classroom has Many Students. I want to create a form where I can create a student and assign him a classroom. And I am having problems creating the form.
model/classroom.rb
class Classroom < ActiveRecord::Base
has_many :students
end
model/student.rb
class Student < ActiveRecord::Base
belongs_to :classroom
end
I want to create a new student and assign it to a certain classroom.
<%= form_for(#student) do |f|%>
<%= f.label :name %>
<%= f.text_field :name %>
<br />
<br />
<%= f.label :student.classroom.number %> #Is this correct?
<%= f.text_field :student.classroom.number %> #Is this correct?
<%= f.submit %>
<%end%>
The attributes for each model are
1.9.3-p448 :026 > Classroom
=> Classroom(id: integer, number: string, created_at: datetime, updated_at: datetime)
1.9.3-p448 :027 > Student
=> Student(id: integer, name: string, created_at: datetime, updated_at: datetime, classroom_id: string)
students_controller
class StudentsController < ApplicationController
def index
#students = Student.all
end
def show
#student = Student.find(params[:id])
end
def new
#student = Student.new
end
def create
#student = Student.new(article_params)
respond_to do |format|
if #student.save
format.html {redirect_to(#student, notice: 'Student was successfully created.')}
else
format.html {render action: "new"}
end
end
end
private
def article_params
params.require(:student).permit(:name, :classroom_id)
end
end
classroom_controller
class ClassroomsController < ApplicationController
def index
#classrooms = Classroom.all
end
def show
#classroom = Classroom.find(params[:id])
end
def new
#classroom = Classroom.new
end
def create
#classroom = Classroom.new(article_params)
respond_to do |format|
if #classroom.save
format.html {redirect_to(#classroom, notice: 'Classroom was successfully created.')}
else
format.html {render action: "new"}
end
end
end
private
def article_params
params.require(:classroom).permit(:number)
end
end
You can set a hidden field setting it to the classroom itself, if you already know which classroom you want to add him in:
<%= f.hidden_field, :classroom_id, value: here_you_put_the_classroom_id $>
And don't forget to add :classroom_id in the permitted params in your controller.
Another way you can do if you want the option to select the classroom you are putting the student in, you can create a select field passing all the classrooms.
<% classroom_array = Classroom.all.map { |classroom| [classroom.name, classroom.id] } %>
<%= options_for_select(classroom_array) %>
Don't forget to add the permitted params again.
Hope it helps.
***UPDATE***
The options_for_select should go inside de select tag, like this:
<% classroom_array = Classroom.all.map { |classroom| [classroom.number, classroom.id] } %>
<%= f.label :classroom %>
<%= f.select(:classroom_id, options_for_select(classroom_array)) %>
***UPDATE 2***
Pluck could also be an option, as long as you pass the classroom id as param. So, the code can be refactored to:
<%= f.label :classroom %>
<%= f.select :classroom_id, Classroom.all.pluck(:name, :id) %>
Assuming you want to assign the Student to an existing Classroom:
First, ensure the Student model has the following in the attr_accessible:
attr_accessible :classroom_id
In your form, instead of your second label/text_field, you should then be able to do:
<%= f.label :classroom %>
<%= f.select(:classroom_id, Classroom.all.pluck(:number)) %>
Note that for the f.select method you must pass the attribute you are setting, not the association name (i.e., classroom_id not classroom)
Also note that best practice would be to move logic associated with collecting information from a model (i.e. Classroom.all.pluck(:number)) into an instance variable in the controller,
e.g. #classrooms = Classroom.all.pluck(:number)
and using that #classrooms instance variable in your view instead.
Aside from the above, you should also read some more about symbols. What you've tried there with :student.classroom.number isn't going to work how you thought it might. There's a good SO question about it here: When to use symbols instead of strings in Ruby?

Resources