Get value from text field and store it as array - ruby-on-rails

I have a text field in my database called departments where i want to store the list of departments. The user will enter the name of departments with comma separation. For example:
department1, deaprtment2, department3
I want this value to be stored as array when the user submits the form. Also, i want the list of departments to show as a drop-down. Finally, while updating the table , the department field should also be editable as before(update by entering texts separated by commas).
EDIT:
I have added this to my model:
class Org < ActiveRecord::Base
serialize :department, Array
attr_accessible :name, :department
before_validation :update_department
validates :name, presence: true
def update_department
if department_changed? and department.is_a?(String)
self.department = self.department.split(',').collect(&:strip)
end
end
end
and the view:
<%= f.text_area :department, :cols => "10", :rows => "10" %>
now Whenever i try to sign up, the department field already has [] present and when i try to update the department is already ["[department1", "department2]"].
I want [] to be removed while signing up and only department1, department2 to show up when updating.
Please Help.

The best way to do this would be via your models. I am assuming that you have a model called Org and another called Department and that you have defined a has many relationship between the two. All you then need to do is in your Org model add the following code:
def department_list
departments.collect { |d| d.department_name }.join(', ')
end
def department_list=(text)
if id && text
departments.destroy_all
text.split(',').each do |d|
departments.create(department_name: d.strip.capitalize)
end
end
end
Then in your view add a text box using #org.department_list.
EDIT:
Based on your expanded question, you have department field in an org model that you want to store and show as an array and but edit as a simple text field. My thoughts on this was that I don't like the idea of storing department data a field in org, it is a one to many relationship so department should be a separate model. I would remove the department field from org. Then create a migration to create a departments table. It should look something like this:
class CreateDeparments < ActiveRecord::Migration
def change
create_table :departments do |t|
t.integer :org_id
t.string :department_name
t.timestamps
end
end
end
Next in the Department model add this line of code:
belongs_to :org
In the org model add the following:
has_many :departments, dependent: :destroy
def department_list
departments.collect { |d| d.department_name }.join(', ')
end
def department_list=(text)
if id && text
departments.destroy_all
text.split(',').each do |d|
departments.create(department_name: d.strip.capitalize)
end
end
end
In your controllers and views you now have the following:
#org = Org.first
# List of departments as an array for a select
#org.departments
# A comma separated string for text boxes
#org.department_list
The department_list method can now be used to display the list in a text box and also be used to post and changes back. So you your view code just becomes this:
<%= f.text_area :department_list, :cols => "10", :rows => "10" %>
You will probably need to amend your org controller by changing the create to something like this:
def create
#org = Org.new(params[:org])
respond_to do |format|
if #org.save
#org.department_list = params[:org][:department_list]
format.html { redirect_to org_url,
notice: "#{#org.name} was successfully created" }
format.json { render json: #org,
status: :created, location: #org }
else
format.html { render action: "new" }
format.json { render json: #org.errors, status: :unprocessable_entity }
end
end
end
If you are still stuck I have a complete webiste on github that you can look through. For you it is orgs and departments and on mysite it is people and skills or people and credits. This is the link:
https://github.com/davesexton/CKCASTING

Related

Forbidden Attributes Error when assigning nested attributes in Rails 4 using Postman

An AssessmentItem has many ItemLevels and one ItemLevel belongs to an AssessmentItem.
In my model I have
has_many :item_levels
accepts_nested_attributes_for :item_levels
When updating an Item, you should be able to specify what levels should be associated with that Item. The update action should receive the parameters specified for levels and create new ItemLevel objects that are associated with the Item being updated, and delete any levels that we previously associated and not specified when updating. However, when I try to create new levels, I get an ActiveModel::ForbiddenAttributes error.
Controller:
def update
#item = AssessmentItem.find(params[:id])
old_levels = #item.item_levels #active record collection
#item.update_attributes(update_params)
convert_levels = old_levels.map {|l| l.attributes} #puts into array
(items - convert_levels).each{|l| ItemLevel.create(l)} ##causes error
(convert_levels - level_params).each { |l| ItemLevel.find(l["id"]).destroy }
end
end
private
def level_params
params.require(:assessment_item).permit(:item_levels => [:descriptor, :level])
end
def update_params
params.require(:assessment_item).permit(:slug, :description, :name)
end
This is my json request in Postman:
{
"assessment_item": {
"slug" : "newSlug",
"description" : "NewDescriptiong",
"name" : "different name",
"item_level_attributes":[
{
"descriptor":"this should be new",
"level":"excellent"
}
]}
}
How can I get my action to allow the parameters? How can I effectively pass them to the factory? Thanks.
I think you should also permit item_level_attributes in update_params like this:
def update_params
params.require(:assessment_item).permit(:slug, :description, :name, :item_levels => [:descriptor, :level])
end
or
def update_params
params.require(:assessment_item).permit(:slug, :description, :name, :item_level_attributes => [:descriptor, :level])
end

Rails has_many :through with uniqueness

I asked this question differently, but deleted it to attempt more clarity.
I have an Article model. It has_many contacts, through: :article_contacts.
I have a Contact model. It has_many articles, through: :article_contacts.
What I need is each Contact object to be unique, but be able to be associated with different articles. My last question led to people showing me how to display only unique contacts, or to validate the join model, but that's not what I need.
I need, for example, tables like the following:
Article
id: 1, name: "Whatever", content: "Whatever"
id: 2, name: "Again, whatever", content: "Whateva"
Contact
id: 1, email: "email#email.com"
id: 2, email: "secondemail#email.com"
ArticleContact
id: 1, article_id: 1, contact_id: 1
id: 2, article_id: 1, contact_id: 2
id: 3, article_id: 2, contact_id: 1
So, when I build the association in my Article controller in the new action and I call #article.save in the create action, I get an insert of the new article, an insert of the contact and an insert of the article_relationship. Great.
BUT, on Article 2, if I add the same email to the contact form, I do not want to create another Contact with the same email. But I do want (as you see with the third ID in ArticleContact above) to create. But each call to #article.save does it. I've tried first_or_initialize and << into the collection, but it always creates multiple times and if I have a validation, it means I can't create the ArticleContact relationship because contact is unique.
I may eventually have a drop down with contacts, but I suspect that will be long, so I'd rather simply enter an email into a form and have the code check that it is unique and if so, just create the join relationship using the existing ID.
This must be easier than I am conceiving, but I can't find a single article that demonstrates this anywhere.
Updated with code per request, though, again, I don't have the code to only create a unique contact and associate the relationship with an existing one. That's what I'm missing so this code will only show you what I already know works and not how to get to what I want :).
articlecontroller:
def new
#article = #business.articles.build
authorize #business
#article.attachments.build
#article.contacts.build
end
def create
#article = #business.articles.new(article_params)
authorize #business
respond_to do |format|
if #article.save
format.html { redirect_to business_article_path(#business, #article), notice: "Knowledge created." }
else
format.html { render 'new' }
end
end
end
Models have the standard has_many :thruogh and belongs to. I can show it, but they are the right way. View is just a standard simple_form building the contact:
<%= f.simple_fields_for :contacts, class: "form-inline" do |contact| %>
<%= contact.input :first_name, label: "Contact First Name" %>
<%= contact.input :last_name, label: "Contact Last Name" %>
<%= contact.input :email, label: "Contact Email" %>
<% end %>
You should enforce uniqueness, just in case:
# contact.rb
class Contact
...
validates_uniqueness_of :email
...
end
But in your controller you could do:
# articles_controller.rb
def create
...
# we can rely on it's uniqueness
contacts << Contact.find_or_create_by(email: param[:email)
...
end
Or whatever best fits your needs.
Hope this helps!
give your ArticleContact a validation with a scope.
validates :article_id, unique {scope: :contact_id}
this will prevent you from having ArticleContacts that are exactly the same.
when you create a contact, try:
contact = Contact.where(email: "e#mail.com:").first_or_create
in contact.rb
validates :email, uniqueness: true
You could try this in the create method
verified_contacts = []
#article.contacts.each do |contact|
existing_contact = Contact.find_by(email: contact.email)
verified_contacts << (existing_contact || contact)
end
#article.contacts = verified_contacts

Default ActiveRecord Associations in Rails 4

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.

mutliple select dropdown company and save perk and respective company

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

Correct way to handle multiparameter attributes corresponding to virtual attributes

I have a Rails app with a model containing a birthdate attribute. This corresponds to a column in my database defined using the ActiveRecord date type. With this I am able to use the date_select form helper method to render this as a three-select input in my view. The form parameters corresponding to this field are then serialized back to the controller as birthdate(1i), birthdate(2i) and birthdate(3i). Consequently, I can use the standard update_attributes method within my controller on my model to update all fields on my model.
I'm now experimenting with encrypting this field using the attr_encrypted gem. While the gem supports marshalling (this is nice), there is no longer a real column of name birthdate of type date - instead, attr_encrypted exposes the value as a virtual attribute birthdate backed by a real encrypted_birthdate column. This means that update_attributes is unable to perform the previous multiparameter attribute assignment to populate and save this column. Instead, I get a MultiparameterAssignmentErrors error resulting from the call to the internal column_for_attribute method returning nil for this column (from somewhere within execute_callstack_for_multiparameter_attributes).
I'm currently working around this as follows:
My model in app/models/person.rb:
class Person < ActiveRecord::Base
attr_encrypted :birthdate
end
My controller in app/controllers/people_controller.rb:
class PeopleController < ApplicationController
def update
# This is the bit I would like to avoid having to do.
params[:person] = munge_params(params[:person])
respond_to do |format|
if #person.update_attributes(params[:person])
format.html { redirect_to #person, notice: 'Person was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #person.errors, status: :unprocessable_entity }
end
end
end
private
def munge_params(params)
# This separates the "birthdate" parameters from the other parameters in the request.
birthdate_params, munged_params = extract_multiparameter_attribute(params, :birthdate)
# Now we place a scalar "birthdate" where the multiparameter attribute used to be.
munged_params['birthdate'] = Date.new(
birthdate_params[1],
birthdate_params[2],
birthdate_params[3]
)
munged_params
end
def extract_multiparameter_attribute(params, name)
# This is sample code for demonstration purposes only and currently
# only handles the integer "i" type.
regex = /^#{Regexp.quote(name.to_s)}\((\d+)i)\)$/
attribute_params, other_params = params.segment { |k, v| k =~ regex }
attribute_params2 = Hash[attribute_params.collect do |key, value|
key =~ regex or raise RuntimeError.new("Invalid key \"#{key}\"")
index = Integer($1)
[index, Integer(value)]
end]
[attribute_params2, other_params]
end
def segment(hash, &discriminator)
hash.to_a.partition(&discriminator).map do |a|
a.each_with_object(Hash.new) { |e, h| h[e.first] = e.last }
end
end
end
And my view app/views/people/_form.html.erb:
<%= form_for #person do |f| %>
<%= f.label :birthdate %>
<%= f.date_select :birthdate %>
<% f.submit %>
<% end %>
What's the proper way to handle this type of attribute without having to introduce ad hoc munging of the params array like this?
Update:
Looks like this might refer to a related problem. And this too.
Another update:
Here is my current solution, based on Chris Heald's answer. This code should be added to the Person model class:
class EncryptedAttributeClassWrapper
attr_reader :klass
def initialize(klass); #klass = klass; end
end
# TODO: Modify attr_encrypted to take a :class option in order
# to populate this hash.
ENCRYPTED_ATTRIBUTE_CLASS_WRAPPERS = {
:birthdate => EncryptedAttributeClassWrapper.new(Date)
}
def column_for_attribute(attribute)
attribute_sym = attribute.to_sym
if encrypted = self.class.encrypted_attributes[attribute_sym]
column_info = ENCRYPTED_ATTRIBUTE_CLASS_WRAPPERS[attribute_sym]
column_info ||= super encrypted[:attribute]
column_info
else
super
end
end
This solution works as is, but would be even better if attr_encrypted were to take a :class option that would construct the ENCRYPTED_ATTRIBUTE_CLASS_WRAPPERS hash dynamically. I'm going to look at ways I can extend/monkeypatch attr_encrypted to do this. Gist available here: https://gist.github.com/rcook/5992293.
You can monkeypatch your model to pass through column_for_attribute calls. I haven't tested this, but it should cause reflection on the birthday field to instead return the reflection for the encrypted_birthday field, which should cause multiparam attributes to properly assign (as AR will then be able to infer the field type):
def column_for_attribute(attribute)
if encrypted = encrypted_attributes[attribute.to_sym]
super encrypted[:attribute]
else
super
end
end
We're patching column_for_attribute per this line so that AR can infer the proper type for the column. It needs to figure out that parameters for "birthday" should be a DateTime type of whatnot, and can't infer that from a virtual attribute. Mapping the reflection onto the actual column should resolve that.

Resources