here is my code:
Perk not save on multiple select,when multiple true/false. perk save and habtm working.
class Perk < ActiveRecord::Base
has_and_belongs_to_many :companies
end
class Company < ActiveRecord::Base
has_and_belongs_to_many :perks
end
view perk/new.html.erb
<%= select_tag "company_id", options_from_collection_for_select(Company.all, 'id', 'name',#perk.companies.map{ |j| j.id }), :multiple => true %>
<%= f.text_field :name %>
Controller's code:
def new
#perk = Perk.new
respond_with(#perk)
end
def create
#perk = Perk.new(perk_params)
#companies = Company.where(:id => params[:company_id])
#perk << #companies
respond_with(#perk)
end
Your select_tag should return an array of company_ids:
<%= select_tag "company_ids[]", options_from_collection_for_select(Company.all, 'id', 'name',#perk.companies.map{ |j| j.id }), :multiple => true %>
http://apidock.com/rails/ActionView/Helpers/FormTagHelper/select_tag#691-sending-an-array-of-multiple-options
Then, in your controller, reference the company_ids param:
#companies = Company.where(:id => params[:company_ids])
(I assume that you've intentionally left out the #perk.save call in your create action... Otherwise, that should be included as well. Model.new doesn't store the record.)
It sounds like you may not have included company_id in the perk_params method in your controller. Rails four uses strong pramas this means you need to state the params you are allowing to be set.However it is difficult to say for sure without seeing more of the code.
In your controller you should see a method like this (there may be more options that just :name):
def perk_params
params.require(:perk).permit(:name)
end
You should try adding :company_id to it so it looks something like this:
def perk_params
params.require(:perk).permit(:name, :company_id)
end
if there are other params int your method leave them in and just added :company_id
EDIT to original answer
The above will only work on a one-to-many or one-to-one because you are using has_and_belongs_to_many you will need to add companies: [] to the end of your params list like this
def perk_params
params.require(:perk).permit(:name, companies: [] )
end
or like this
def perk_params
params.require(:perk).permit(:name, companies_ids: [] )
end
See these links for more details:
http://edgeapi.rubyonrails.org/classes/ActionController/StrongParameters.html
http://edgeguides.rubyonrails.org/action_controller_overview.html#strong-parameters
Related
I have a model A that can have up to 10 associated models B in a one-to-many relationship. These nested models have just a string attribute representing a word.
I want to display a form to create/edit the parent model and all the nested children, displaying fields for the 10 possible models. Then, if I only fill up two of them, two models will be created.
Finally, when editing model A I need to display 10 fields, two of them filled up with the model B associated with A data, and the rest blank ready to fill.
Tried fields_forwith an array, but it only displays fields for the already existing model B instances.
View:
= form_for #a, remote: true do |f|
= f.text_field :title, placeholder: true
= f.fields_for :bs, #a.bs do |ff|
/ Here, for the edit action, N text fields appear, being N equals to #soup.soup_words.size
/ and I need to display 10 fields everytime, because a Soup can have up to 10 SoupWord
/ For the new action, it should display 10 empty text fields.
/ Finally, if you fill three of the 10 fields,
/ model A should have only 3 instances of model B associated. i.e if there were 4 filled and
/ I set one of them blank, the model B instance should be destroyed.
= ff.text_field :word, placeholder: true
= f.submit
Controller:
class Bs < ApplicationController
def edit
respond_to :js
#soup = Soup.find params[:id]
end
def update
respond_to :js
puts params
end
end
Update
Create and edit actions now work, just put a reject_if parameter in model A,
accepts_nested_attributes_for :bs, reject_if: proc { |attrs| attrs[:word].blank? }
and set the build on the controller.
def new
respond_to :js
#a = A.new
10.times { #a.bs.build }
end
def edit
respond_to :js
#a = Soup.find params[:id]
#a.bs.size.upto(9) do |sw|
#a.bs.build
end
end
Now I need to destroy instances of model B if I set them blank in the edit action.
Normally you would delete nested records by using the allow_destroy: true option and by passing the _destroy param:
class Soup
accepts_nested_attributes_for :soup_words,
reject_if: proc { |attrs| attrs[:word].blank? },
allow_destroy: true
end
To get the behavior you want you can use javascript with a hidden input:
= form_for #soup, remote: true do |f|
= f.text_field :title, placeholder: true
= f.fields_for :soup_words, #soup.soup_words do |ff|
= ff.text_field :word, class: 'soup_word', placeholder: true
= ff.hidden_input :_destroy
= f.submit
$(document).on('change', '.soup_word', function(){
var $obj = $(this);
if (!this.value || !this.value.length) {
// set soup_word to be destroyed
$obj.siblings('input[name~=_destroy]').val('1');
}
$obj.fadeOut(50);
});
Make sure you have whitelisted the _destroy and id params.
def update_params
params.require(:soup).permit(:soup_words_attributes: [:word, :id, :_destroy])
end
I'm trying to implement a persitent model Setting storage in Rails, using the Active Record. I've already saw other gems like ledermann/rails-settings, but I don't want other dependency, because I'll use it only for one model and need the ability to customize it.
I've created 3 models, "Company", "Setting", "CompanySetting". For the association, I done the follow:
company.rb
has_many :company_settings
setting.rb
has_many :company_settings
has_many :company, through: :company_settings
company_setting.rb
belongs_to :company
belongs_to :setting
But I've a problem, for example, I seed the Settings table with N settings, and I need to have these Settings built when I try to access the Company settings, since they don't have an CompanySetting entry for that Setting.
My attempt was the follow:
company.rb
has_many :company_settings
accepts_nested_attributes_for :company_settings
def load_company_settings
Setting.all.collect { |setting|
company_settings.find_by( setting: setting ) || company_settings.build( { setting: setting, value: '' } )
}
end
And then, in my form (using Simple Form):
= f.simple_fields_for :company_settings, #company.load_company_settings do |s|
= s.input :value
It renders the correctly number of fields (the N fields in my Setting table), and return they values if exist, otherwise, returns an empty string as value. But when I do a POST, it doesn't saves.
I believe that I'm doing the right thing in Rails 4 Strong Params, so, my companies_controller look like that:
class Company::CompaniesController < Company::BaseController
def show
#company = current_company
end
def edit
#company = current_company
end
def update
#company = current_company
if #company.update(company_params)
redirect_to company_path
else
render 'edit'
end
end
private
def company_params
params.require(:company).permit(:name, company_settings_attributes: [:id, :value, :setting])
end
end
Table Structure - Company:
id
Table Structure - Setting:
title (value to show to user)
key (value used in application)
Table Structure - CompanySetting:
company_id
setting_id
value
Thanks (:
I fixed that, with these steps:
.1 Customize the Model adding the methods for return the list of all possible settings from Setting model, and build new ones with CompanySetting. You need to create an assign method in the class, because when the Strong Params try to save, it will try to find this method.
def settings
Setting.all.collect { |setting|
company_settings.find_by( setting: setting ) || company_settings.build( { setting: setting, value: '' } )
}
end
def set_setting(key, value)
company_settings.find_or_create_by( setting: Setting.find_by(key: key) ).update(value: value)
end
def settings=(attributes)
attributes.map { |key, value|
set_setting(key, value)
}
end
asd
.2 Update the Form to use this new method (Here I added two types of fields, booleans and not booleans. This is based on is_boolean property in Setting
= f.simple_fields_for :settings do |s|
- for setting in #company.settings
.form-group
- if setting.setting.is_boolean
.checkbox
%label
= s.input_field setting.setting.key.to_sym, as: :boolean, boolean_style: :inline, checked: setting.value == "1"
= setting.setting.title
%span.help-block= setting.setting.description
- else
= s.label setting.setting.key.to_sym, setting.setting.title, class: 'control-label'
= s.input_field setting.setting.key.to_sym, class: 'form-control', value: setting.value
%span.help-block= setting.setting.description
.3 Fix your Strong Params
def company_params
params.require(:company).permit(:name,
settings: [
:setting_key_1,
:setting_key_2,
...,
:my_other_n_setting
]
)
end
Done.
I am using several dropdowns to create a search and they are submitting '' if the user doesn't select one. I need a catchall, something like * in SQL as the default value. ie if I have 5 brands in a dropdown, I want the default query to be all 5 brands. Something like Brand.where(brand: ALL). Thanks in advance.
<%= select_tag(:brand, options_for_select(["Brand 1","Brand 2","Brand 3","Brand 4","Other"].map{ |num| [num,num] }),id: 'brand', prompt: 'Brand', class: "table") %>
How about something like:
product.rb
class Product < ActiveRecord::Base
scope :by_brand, -> (brand) { where brand: brand }
# put more scopes for the other drop-down boxes
end
product_controller.rb
class ProductsController < ApplicationController
def search
#products = Product.all
#products = #products.by_brand(params[:brand]) unless params[:brand].blank?
# more filtering here
end
end
You probably want include_blank and a prompt in your selects.
select_tag(..., include_blank: true, prompt: 'All')
Now the first entry in the dropdown will have a blank value, and display "All" for its label.
You'll then need to just make sure you don't use that criteria when you query, something like this (you didn't post any code, so I don't know your model):
class MyController ...
def show
#items = Item.all
#items = #items.where(brand: params[:brand]) if params[:brand].present?
#items = #items.where(size: params[:size]) if params[:size].present?
#items = #items.where(year: params[:year]) if params[:year].present?
#items = #items.where(color: params[:color]) if params[:color].present?
end
end
So I am trying to implement multiple autocomplete using this gem and simple_form and am getting an error.
I tried this:
<%= f.input_field :neighborhood_id, collection: Neighborhood.order(:name), :url => autocomplete_neighborhood_name_searches_path, :as => :autocomplete, 'data-delimiter' => ',', :multiple => true, :class => "span8" %>
This is the error I get:
undefined method `to_i' for ["Alley Park, Madison"]:Array
In my params, it is sending this in neighborhood_id:
"search"=>{"neighborhood_id"=>["Alley Park, Madison"],
So it isn't even using the IDs for those values.
Does anyone have any ideas?
Edit 1:
In response to #jvnill's question, I am not explicitly doing anything with params[:search] in the controller. A search creates a new record, and is searching listings.
In my Searches Controller, create action, I am simply doing this:
#search = Search.create!(params[:search])
Then my search.rb (i.e. search model) has this:
def listings
#listings ||= find_listings
end
private
def find_listings
key = "%#{keywords}%"
listings = Listing.order(:headline)
listings = listings.includes(:neighborhood).where("listings.headline like ? or neighborhoods.name like ?", key, key) if keywords.present?
listings = listings.where(neighborhood_id: neighborhood_id) if neighborhood_id.present?
#truncated for brevity
listings
end
First of all, this would be easier if the form is returning the ids instead of the name of the neighborhood. I haven't used the gem yet so I'm not familiar how it works. Reading on the readme says that it will return ids but i don't know why you're only getting names. I'm sure once you figure out how to return the ids, you'll be able to change the code below to suit that.
You need to create a join table between a neighborhood and a search. Let's call that search_neighborhoods.
rails g model search_neighborhood neighborhood_id:integer search_id:integer
# dont forget to add indexes in the migration
After that, you'd want to setup your models.
# search.rb
has_many :search_neighborhoods
has_many :neighborhoods, through: :search_neighborhoods
# search_neighborhood.rb
belongs_to :search
belongs_to :neighborhood
# neighborhood.rb
has_many :search_neighborhoods
has_many :searches, through: :search_neighborhoods
Now that we've setup the associations, we need to setup the setters and the attributes
# search.rb
attr_accessible :neighborhood_names
# this will return a list of neighborhood names which is usefull with prepopulating
def neighborhood_names
neighborhoods.map(&:name).join(',')
end
# we will use this to find the ids of the neighborhoods given their names
# this will be called when you call create!
def neighborhood_names=(names)
names.split(',').each do |name|
next if name.blank?
if neighborhood = Neighborhood.find_by_name(name)
search_neighborhoods.build neighborhood_id: neighborhood.id
end
end
end
# view
# you need to change your autocomplete to use the getter method
<%= f.input :neighborhood_names, url: autocomplete_neighborhood_name_searches_path, as: :autocomplete, input_html: { data: { delimiter: ',', multiple: true, class: "span8" } %>
last but not the least is to update find_listings
def find_listings
key = "%#{keywords}%"
listings = Listing.order(:headline).includes(:neighborhood)
if keywords.present?
listings = listings.where("listings.headline LIKE :key OR neighborhoods.name LIKE :key", { key: "#{keywords}")
end
if neighborhoods.exists?
listings = listings.where(neighborhood_id: neighborhood_ids)
end
listings
end
And that's it :)
UPDATE: using f.input_field
# view
<%= f.input_field :neighborhood_names, url: autocomplete_neighborhood_name_searches_path, as: :autocomplete, data: { delimiter: ',' }, multiple: true, class: "span8" %>
# model
# we need to put [0] because it returns an array with a single element containing
# the string of comma separated neighborhoods
def neighborhood_names=(names)
names[0].split(',').each do |name|
next if name.blank?
if neighborhood = Neighborhood.find_by_name(name)
search_neighborhoods.build neighborhood_id: neighborhood.id
end
end
end
Your problem is how you're collecting values from the neighborhood Model
Neighborhood.order(:name)
will return an array of names, you need to also collect the id, but just display the names
use collect and pass a block, I beleive this might owrk for you
Neighborhood.collect {|n| [n.name, n.id]}
Declare a scope on the Neighborhood class to order it by name if you like to get theat functionality back, as that behavior also belongs in the model anyhow.
edit>
To add a scope/class method to neighborhood model, you'd typically do soemthing like this
scope :desc, where("name DESC")
Than you can write something like:
Neighborhood.desc.all
which will return an array, thus allowing the .collect but there are other way to get those name and id attributes recognized by the select option.
I am using this named_scope to search for products that have a description matching any word the user inputs.
E.g., Product.description_like_any("choc pret")
Will return products with names like
"Chocolate Bar"
"Chocolate Covered Pretzels"
"Miniature Chocolate Ponies"
Here's the named_scope I've written (which works)
named_scope :description_like_any, (lambda do |query|
return {} unless query
conditions = []
values = []
for q in query.split(/\s+/)
conditions << "(`products`.description LIKE ?)"
values << "%#{q}%"
end
{ :conditions => [conditions.join(' AND '), *values] }
end)
Is there a better way to write this? Perhaps I'm missing a Rubyism/Railism or two?
Solution
Using scope_procedure in conjunction with Searchlogic, this can be done in an even easier way. Note, the solution before even leverages Searchlogic's _or_ syntax for connecting two scopes together. The :keywords scope_procedure finds products matching product.description, or product.vendor.name; All with one text field!
Model
# app/models/product.rb
class Product < ActiveRecord::Base
scope_procedure :keywords, lambda |query|
description_like_any_or_vendor_name_like_any(query.split(/\s+/))
end
end
Controller
# app/controllers/products_controller.rb
class ProductsController < ApplicationController
def index
#search = Product.search(params[:search])
#products = #search.all
end
end
Views
# app/views/products/index.html.erb
<% form_for #search do |f| %>
<%= f.label :keywords, "Quick Search" %>
<%= f.input :keywords %>
<%= f.submit, "Go" %>
<% end %>
The most Railsy thing to do is to not write this yourself. :-) Use the excellent Searchlogic gem which will create the description_like_any scope for you.
Edit: If you want your user to be able to enter search terms in a free text field like this, you can define your own scope:
class Product < ActiveRecord::Base
# ...
scope_procedure :description_like_any_term, lambda { |terms|
name_like_any(terms.split(/\s+/))
}
# ...
end