I got a variable in a nested model (account) belonging the user model in which there is a column for :skills. in my edit_user_path, i would like to be able to save multiple skills into that column via checkboxes. For doing that I permitted the skills in the controller as array, even though it is saved as string into the database.
My controller:
def user_params
params.require(:user).permit(:email, :password, :password_confirmation, :account,
account_attributes:[:id, :username, {:skills => []}, :description, :location, :avatar, :tags, :tag_list])
end
If i save multiple values via checkboxes into this variable inside of a nested form, i do it like this:
<% skills = ["Coding", "Design", "Petting Cats"] %>
<% skills.each do |skill| %>
<div class="form-check form-check-inline">
<div class="custom-control custom-checkbox">
<%= form.check_box :skills, { multiple: true, class:"custom-control-input",id: skill }, skill, false %>
<%= form.label skill, class:"custom-control-label", for: skill %>
</div>
</div>
<% end %>
This works and the values are getting saved as an array, but oddly the array once saved into the database looks like this:
"[\"Artist\", \"Mixing\", \"Mastering\"]"
instead of this:
["Artist", "Mixing", "Mastering"]
Which leads to troubles, since i would like to iterate through all users later "filtering" for certain skills, like User.account.where(skills: "Petting Cats") if a user has Petting Cats saved somewhere inside of the array.
For Development i am using SQLite, for production PostgresQL.
How do i save multiple strings into a string variable as clean array without mess, and how to iterate through the array later with a where query method?
You're falling into the common double whammy noob trap of using an array column and serialize.
The reason you are getting "[\"Artist\", \"Mixing\", \"Mastering\"]" stored in is that you are using serialize with a native array column. serialize is an old hack to store stuff in varchar/text columns and the driver actually natively converts it to an array. When you use serialize with a native array/json/hstore column your actually casting the value into a string before the driver casts it. The result is:
["[\"Artist\", \"Mixing\", \"Mastering\"]"]
Removing serialize will fix the immediate problem but you're still left with a substandard solution compared to actually doing the job right:
# rails g model skill name:string:uniq
class Skill < ApplicationRecord
validates :name, uniqueness: true, presence: true
has_many :user_skills, dependent: :destroy
has_many :users, through: :user_skills
end
# rails g model user_skill user:belongs_to skill:belongs_to
# use `add_index :user_skills, [:user_id, :skill_id], unique: true` to ensure uniqueness
class UserSkill < ApplicationRecord
validates_uniqueness_of :user_id, scope: :skill_id
belongs_to :user
belongs_to :skill
end
class User < ApplicationRecord
has_many :user_skills, dependent: :destroy
has_many :skills, through: :user_skills
end
<%= form_with(model: #user) do |form| %>
<div class="field">
<%= form.label :skill_ids %>
<%= form.collection_select :skill_ids, Skill.all, :id, :name %>
</div>
<% end %>
def user_params
params.require(:user).permit(
:email, :password, :password_confirmation, :account,
account_attributes: [
:id, :username, :description, :location, :avatar, :tags, :tag_list,
],
skill_ids: []
)
end
This gives you:
Queryable data
Referential integrity
Normalization
Encapsulation
ActiveRecord Assocations!
The pleasure of not looking like an idiot in a review/audit
Related
This is a new error to me, and struggling to resolve it. It also states: Roaster(#70130698993440) expected, got "1" which is an instance of String(#70130675908140)
It's highlighting my create method in my Roasts Controller:
def create
#roast = Roast.new(roast_params)
The scenario is that I'm trying to create a triple nested form. for three models Roasts Countries and Regions where roasts has many countries and countries has many regions.
I'm assuming there is something wrong with the roast params, but I can see what it is. I have added the associations there for the nested models
def roast_params
params.require(:roast).permit(:roaster, :name, :bestfor, :beans, :roast, :tastingnotes, :notes, :slug, :avatar, :countries_attributes => [:country_name, :regions_attributes => [:region_name]])
end
my form
<div class="form-group">
<%= form.fields_for :countries do |countries_form| %>
<%= countries_form.label :country %>
<%= countries_form.text_field :name, class: "form-control" %>
</div>
<div class="form-group">
<%= form.fields_for :regions do |regions_form| %>
<%= regions_form.label :region %>
<%= regions_form.text_field :region_name, class: "form-control" %>
<% end %>
<% end %>
</div>
Roast Controller
...
def new
#roast = Roast.new
#roast.countries.build.regions.build
end
...
roast model
class Roast < ApplicationRecord
has_many :tastings
has_many :countries
has_many :notes, through: :tastings
has_many :comments, as: :commentable
belongs_to :roaster
accepts_nested_attributes_for :countries
country model
class Country < ApplicationRecord
has_many :regions, inverse_of: :country
accepts_nested_attributes_for :regions
belongs_to :roasts
region model
class Region < ApplicationRecord
belongs_to :country
I've nested the regions params in the country params, is that correct? I also saw on SO other issues with suggestions for setting config.cache_classes to true in development.rb but that didn't help here.
Update
So looking at this further, I believe it's not related to the nested forms, but rather a collection_select I'm using.
<%= form.label :roaster, class: 'control-label' %>
<%= form.collection_select(:roaster, Roaster.order(:roaster_name).all, :id, :roaster_name, prompt: true, class: "form-control") %>
So this select is pulling the roaster_name from a model called Roaster.
My params now look like the below:
params.require(:roast).permit(:roaster_name, :roaster, :name, :bestfor, :beans, :roast, :tastingnotes, :notes, :slug, :avatar, :countries_attributes => [:country_id, :country_name, :regions_attributes => [:region_id, :region_name]])
And looking at the console when submitting the form, it seems that just the :id of Roaster is getting passed, rather than the value of :roaster_name.
{"utf8"=>"✓",
"authenticity_token"=>"EG+zty85IiVsgipm1pjSAEZ7M66ELWefLq8Znux+cf89sSnVXxielRr1IaSS9+cJvdQD8g1D4+v2KqtKEwh6gw==",
"roast"=>{"roaster"=>"1", "name"=>"Espress", "countries_attributes"=>{"0"=>{"country_name"=>"UK"}}, "regions"=>{"region_name"=>"Highlands"}, "bestfor"=>"", "roast"=>"", "tastingnotes"=>""},
"commit"=>"Create Roast"}
Can't work this out
ActiveRecord::AssociationTypeMismatch is raised when an association-setter (Roast#roaster= in this case) is called with a value that is not an instance of the expected class. Roaster was expected, got String.
The issue seems to be with passing roaster in as a param, which is "1" (String) in your example. I'm guessing this is actually an ID of a Roaster, the form code in the question does not show it.
Perhaps you meant to permit and pass a roaster_id param?
def roast_params
params.require(:roast).permit(:roaster_id, # ...
end
I'm trying to make e-commerce shop with RoR. Most of the required functionality I did without any problems, but now I really need somebody's help here.
I want to make product attributes, like a "Size", "Weight", "Color", etc.
Easiest way is to define this attributes in model migration, but now I want to make attributes dynamic. The main problem is that I can't get all params with attributes from forms when trying to create product.
products/new.html.erb
<%= form_for #product, url: admin_products_path(#product) do |f| %>
<%= f.label :name, 'Name' %>
<%= f.text_field :name, class: "form-control" %>
<%= text_field_tag "product[product_feature][]" %>
<%= text_field_tag "product[product_feature][]" %>
<%= f.submit "Submit" %>
<% end %>
So, I want to generate many fields with attribute name and value, fill them and use these params in controller to interate them and finally create product attributes.
Like
params[:product_features].each do |k, v|
ProductFeature.create(name: k, value: v, product_id: product_id)
end
All gems, that can manipulate with dynamic attributes aren't working with Rails 5+, so I need to find solution for this problem.
I even have working simple db solution for this, but it's uncomfortable to create params. Here it is.
Product.rb
class Product < ApplicationRecord
has_many :product_features
has_many :features, :through => :product_features
end
ProductFeature.rb
class ProductFeature < ApplicationRecord
belongs_to :product
belongs_to :feature
end
Feature.rb
class Feature < ApplicationRecord
end
Make a new model, a child of product called ProductAttribute with two attributes.
Class ProductAttribute < ApplicationRecord
belongs_to :product
validates :name, presence: true
validates :value, presence: true
end
Then use cocoon, or just accepts_nested_attributes
class Product < ApplicationRecord
has_many :product_attributes, as: :attributes
accepts_nested_attributes_for :attributes, allow_destroy: true
end
class ProductsController < ApplicationController
.
.
.
private
.
.
def product_params
params.require(:product).permit(. . . attributes_attributes: [:id, :name, :value])
end
end
Cocoon is definitely what you're looking for.
Heres a quick example i found
class Product
belongs_to :collection
end
class Collection
has_many :products
end
and then in your view something like this
<%= collection_select(:product, :collection_id, Collection.all, :id, :name) %>
I'm creating an admin interface where the admin (of a company) can add custom fields to their employees.
Example:
Models:
Employee: Basic info like name, contact info, etc (has_many employee_field_values)
EmployeeFields: These are the dynamic ones the admin can add (every company has different needs, it could be anything), lets say favorite_food
EmployeeFieldValues: The actual values based on the fields above, say pizza (belongs_to both models above)
What's a smart way of adding the EmployeeFieldValues fields while editing an employee?
I'm trying something simple like this, but not sure if I like it
# Controller
#custom_fields = EmployeeFields.all
# View
<%= form_for(#employee) do |f| %>
<%= f.text_field :first_name %>
<% #custom_fields.each do |custom_field| %>
<%= custom_field.name %>
<%= text_field_tag "employee_field_values[#{custom_field.name}]" %>
<% end %>
<%= f.submit :save %>
<% end %>
And then when updating, params[:employee_field_values] gives this:
<ActionController::Parameters {"favorite_food"=>"pizza"}>
So, not sure if this is a good direction, also I'm not sure how to handle future edits to an employee's custom_fields if they change.
I think it will be better to use EmployeeField as nested model and EmployeeFieldValue for select field.
For example:
Models
class Employee < ActiveRecord::Base
validates :name, presence: true
has_many :employee_field_values
accepts_nested_attributes_for :employee_field_values, reject_if: ->(x) { x[:value].blank? }
end
class EmployeeFieldValue < ActiveRecord::Base
belongs_to :employee
belongs_to :employee_field
end
class EmployeeField < ActiveRecord::Base
has_many :employee_field_values, inverse_of: :employee_field, dependent: :destroy
validates :title, presence: true, uniqueness: true
end
Controller
class EmployeesController < ApplicationController
def new
#employee = Employee.new
#employee.employee_field_values.build
end
end
View
= simple_form_for #employee, url: '/' do |f|
= f.input :name
= f.simple_fields_for :employee_field_values do |ff|
= ff.input :value
= ff.input :employee_field_id, collection: EmployeeField.all.map{|x| [x.title, x.id]}
Also you need to make buttons for adding/removing :employee_field_value, and you can do it with gem cocoon for example
OR you can build all objects in controller(for each EmployeeField) and do without select box
I have this problem. I need to validate the attributes of two models in the same form in Rails. One is the parent of the other.
The form is like this:
<%= semantic_form_for #professional do |pro| %>
<%= pro.inputs :id => "information" do %>
<%= pro.input :name, label: t("Artistic Name") %>
<%= pro.semantic_fields_for #user do |user| %>
<%= user.inputs :id => "register" do %>
<%= user.input :email, :placeholder=>"email#example.com" %>
<%= user.input :password, label: t('Password') %>
<%end%>
<% end %>
<% end %>
<% end %>
The models I am using are like this:
User:
class User < ActiveRecord::Base
belongs_to :role, polymorphic: true
validates :email, :password, presence: true
end
Professionals:
class Professional < ActiveRecord::Base
has_one :user, as: :role, dependent: :destroy
accepts_nested_attributes_for :user
validates :date_birthday, :gender, :height, :name, :description, :Weight, :address, :languages,:services, :category, :phonenumber, :fullname, :hair_color, :age, :orientation, presence: true
end
So, what is the problem?
When I clicked in the submit button the professional attributes are marked but not the users attributes.
Like this:
The fields marked in red belongs to the professional model but the fields email and password belongs to the user model aren't marked in red when it should be because they are empty.
What can i do? I need the warning message for the user is attributes too
Thanks in advances.
We've achieved what you need before.
We had to use inverse_of so that the object was a singular piece of data (rather than multiple pieces as is the case by default):
#app/models/user.rb
class User < ActiveRecord::Base
belongs_to :role, polymorphic: true, inverse_of: :user
validates :email, :password, presence: true
end
#app/models/professional.rb
class Professional < ActiveRecord::Base
has_one :user, as: :role, dependent: :destroy, inverse_of: :role
accepts_nested_attributes_for :user
end
This will help.
You also need to make sure you're passing these objects correctly (I see so many people not doing this).
You need to tell Professional to validate the associated User:
class Professional < ActiveRecord::Base
...
validates_associated :user
I am having trouble rendering a model's attributes in foreign model's view. I think it is a nested attributes problem because f.bike works but f.bike.biketype gives an undefined method error. Below is my error and code. Any help would be great. Thanks!
Error from Browser:
NoMethodError in Carts#show
Showing /Users/willdennis/rails_projects/spinlister/app/views/carts/show.html.erb where line #4 raised:
undefined method `biketype' for nil:NilClass
Extracted source (around line #4):
1: <h2>Your Cart</h2>
2: <ul>
3: <% #cart.line_items.each do |f| %>
4: <li><%= f.bike.biketype %></li>
5: <% end %>
6: </ul>
views/carts/show.html.erb
<h2>Your Cart</h2>
<ul>
<% #cart.line_items.each do |f| %>
<li><%= f.bike.biketype %></li>
<% end %>
</ul>
cart.rb
class Cart < ActiveRecord::Base
has_many :line_items, :dependent => :destroy
belongs_to :user
accepts_nested_attributes_for :line_items
attr_accessible :bike_id, :name, :description, :size, :biketype, :price, :photo, :id, :address, :city, :state, :zip, :latitude, :longitude, :neighborhood
end
line_item.rb
class LineItem < ActiveRecord::Base
belongs_to :bike
belongs_to :cart
accepts_nested_attributes_for :bike, :cart
attr_accessible :name, :description, :size, :biketype, :price, :photo, :id, :address, :city, :state, :zip, :latitude, :longitude, :neighborhood
end
bike.rb
class Bike < ActiveRecord::Base
has_many :line_items
attr_accessible :name, :description, :size, :biketype, :price, :photo, :id, :address, :city, :state, :zip, :latitude, :longitude, :neighborhood
end
carts_controller.rb
def show
#cart = Cart.find(params[:id])
end
Your error is telling you that the object f.bike is of class nil. You would have been expecting it to be an instance of class Bike. Obviously you have a line item in your view that doesn't have a bike record attached to it.
To take care of the error so your view will display just add a check for blank?
li><%= f.bike.biketype unless f.bike.blank? || f.bike.biketype.blank? %></li>
As to why you have a line item with no bike? Well that's an entirely different question and there is not enough info here to answer that.
UPDATE Based on comments below
Your button_to looks a little wrong. Try
<%= button_to "Rent this Bicycle!", {line_items_path(:bike_id => #bike)}, {:id => "rentthisbike"} %>
Using the curly braces ensures that Rails knows the second param is a css style not a param to be passed into the controllers action
To check if your bike id is getting into the controller then check the params that are actually getting into the controller action that add the bike to the line item. You will be able to find this in your development.log file, find the post request for the action and one of the first lines in that action will list all the params. If you see the bike_id in that list then either one of 2 possible situations is occurring.
1) You are not accessing the params hash properly to get the bike id from it
or
2) You are not setting the bike ID on the line item before saving the record.
You should be able to access the id using params[:bike_id]
If you don't see the bike id then you need to look again at the button_to code to see why it's not passing the bike ID
End of update
Hope that helps