Rails 4 Multipleselect - mysql

I'm trying to create a form using form_for which will add Employees. For an employee i want to assign multiple specializations such as c#, asp, etc as the values dynamically. I'm using the following code in the form
<%= f.select :specilization, Specialization.all.collect{|p| [p.name, p.id]}, {}, :multiple => true %>
I've also made HABTM between employees and specialization like
Employee.rb
class Employee < ActiveRecord::Base
has_and_belongs_to_many :specializations
end
Specialization.rb
class Specialization < ActiveRecord::Base
has_and_belongs_to_many :employees
end
with these done, im not able to save the selected values in db(MySQl). Would appreciate if anyone could solve my problem or guide me on how to get this right?
Thanks in advance.

I usually solve this by using has_many :through and then in my form the select is a field_for on the join model. Like this:
class Employee < ActiveRecord::Base
has_many :employees_specializations
has_many :specializations, through: :employees_specializations
# we will be creating these join models on the employee form
accepts_nested_attributes_for :employees_specializations
end
class Specialization < ActiveRecord::Base
has_many :employees_specializations
has_many :employees, through: :employees_specializations
end
class EmployeesSpecialization < ActiveRecord::Base
belongs_to :employee
belongs_to :specializations
end
Now it's important to note that with this simplified approach I'm assuming Specializations already exist in the database and we're just selecting them and joining them to the employee we're creating/editing:
# in your controller make sure to build at least 1 new instance, the fields_for needs this
#employee.employees_specializations.build
# remember to add to your strong parameters the new join attributes
params.require(:employee).permit(
# ... other attributes ...
employees_specializations_attributes: [:id, :specialization_id]
)
You need to declare :id, :specialization_id as sub fields since employees_specializations_attributes will be a nested hash with those keys inside.
# now in your form use fields_for
<%= f.fields_for :employees_specializations do |ef| %>
<%= ef.select :specialization_id, Specialization.all.map{|p| [p.name, p.id]}, {}, multiple: true %>
<% end %>
That f.fields_for :employees_specializations will create form fields named employee[employees_specializations_attributes][][specialization_id]. Which basically says we are creating a nested association employees_specializations and setting that nested association's specialization_id (remember employees_specialization belongs_to :specialization) to the selected specialization. Note the [] in the middle of the field name, this means its an array of nested employees_specializations.
Post that and barring any validation errors you should be able to create/edit an employee and set it's specializations by selecting from a list of existing specializations and creating a join model between them.
Further reading:
http://apidock.com/rails/v4.0.2/ActionView/Helpers/FormHelper/fields_for
http://apidock.com/rails/v4.0.2/ActiveRecord/NestedAttributes/ClassMethods/accepts_nested_attributes_for
http://laptite.github.io/blog/2014/02/26/deep-nesting-with-has-many-through-and-a-join-model/
Rails nested form with has_many :through, how to edit attributes of join model?

Related

How to save selected values from one table column to another table column

I am new to Rails, and trying to learn. In my simple form I have created a drop down select with generated data from a table called professions. This part is working fine, and I can select multiple values. I'm using a mysql database.
When I click the submit button, I need it to save the chosen values to a column called my_professions in another table called users. I can’t figure out how to do this.
I get this error
My form
<%= simple_form_for #user, url: wizard_path, method: :put do |f| %>
<%= collection_select(:f, :professions_id, Profession.where.not(name: nil), :id, :name, {:multiple => true}, {:class=>'js-example-basic-multiple', :id=>'jsmultipleddd'}) %>
<%= f.submit "Save", :class => 'btn blue' %>
<% end %>
I have tried to add this to the user model
user.rb
class User < ApplicationRecord
has_many :professions
accepts_nested_attributes_for :professions
serialize :my_professions, Array
end
And this to the profession model
profession.rb
class Profession < ApplicationRecord
belongs_to :user
end
my params look like this
registration_steps_controller.rb
def user_params
params.require(:user).permit(:gender,:practitioner_website, :public_health_insurance, clinic_images: [], professions: [])
end
application_controller.rb
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:gender, :practitioner_website, :public_health_insurance, clinic_images: [], professions: []])
devise_parameter_sanitizer.permit(:account_update, keys: [:gender, :practitioner_website, :public_health_insurance, clinic_images: [], professions: []])
end
Start by getting rid of accepts_nested_attributes_for :professions. You don't need nested attributes for this.
Then get rid of serialize :my_professions, Array. Serialize is a legacy hack to store complex data in string columns. You don't need or want this (ever), since associations should be stored in join tables in ActiveRecord - not array columns. That's how AR was designed to work and that's how relational databases where designed to work.
Instead what you want is a join model. Which you can generate with:
rails g model user_profession user:belongs_to profession:belongs_to
Run the migration. You then setup the associations between users and professions:
class User < ApplicationRecord
# ...
has_many :user_professions
has_many :professions, through: :user_professions
end
class Profession < ApplicationRecord
# ...
has_many :user_professions
has_many :users, through: :user_professions
end
Now this lets us associate users with professions by passing profession_ids.
In a normal Rails form you would create the input with:
<%= f.collection_select :profession_ids, Profession.all, :id, :name, multiple: true ... %>
In SimpleForm use the association helper:
<%= f.association :professions, ... %>
Then whitelist the correct param:
def user_params
# don't jam this into one super long unreadable line
params.require(:user)
.permit(
:gender, :practitioner_website, :public_health_insurance,
clinic_images: [], profession_ids: []
)
end

RAILS: saving categories/subjects to user with checkboxes and categories table

I want to make a table called subject_types and populate it with different subjects such as english, spanish, history. And assign the subjects table to each user. I want the user to be able to select multiple subjects with a checkbox and it will save those specific ones and keep checked. each user can save their own preferences/services. Im trying to figure out the best way to approach this.
here is how it would look
in a way the user is editing their settings. I want to be able to take this information and use it later to find results with only those subject types that they select. is there a tutorial or similar reference to this?
-Should I make the subject_types model and populate with these subjects or is there a better approach?
possible solution
could I have an array in a column in my user table? the array would be the ids of the subjects table. How can i do that??
Thank you
Updated:
<%= form_for(#user) do |f| %>
<%= f.collection_check_boxes(:writing_type_ids, WritingType.all, :id, :name) %>
<%= f.submit 'submit' %>
<% end %>
class WritingType < ActiveRecord::Base
has_many :user_writing_types
has_many :users, through: :user_writing_types
end
class UserWritingType < ApplicationRecord
belongs_to :user
belongs_to :writing_type
end
class User < ActiveRecord::Base
has_many :user_writing_types
has_many :writing_types, through: :user_writing_types
end
My migration for the join
create_table :user_writing_types do |t|
t.belongs_to :user, index: true
t.belongs_to :writing_type, index: true
t.boolean :active, default: false
t.timestamps
end
latest update
Im getting my last of errors! when i click submit on that page i get No route matches [PATCH] "/users/51".
I added this <%= f.collection_check_boxes(:writing_type_ids, WritingType.all, :id, :name) %> to my edit form in devise edit.html.erb. The writing_type names populate on the checkboxes, but nothing gets submitted to the database on the user_writing_type table.
Start by creating a many to many relation with a join model by using has_many :through associations:
class User < ApplicationRecord
has_many :user_subjects
has_many :subjects, through: :user_subjects
end
class Subject < ApplicationRecord
has_many :user_subjects
has_many :users, through: :user_subjects
end
# this is the join model
class UserSubject < ApplicationRecord
belongs_to :user
belongs_to :subject
end
You can then create a check box to add subjects to a user with collection_checkboxes:
<%= form_for(#user) do |f| %>
<%= f.collection_checkboxes(:subject_ids, Subject.all, :id, :name) %>
# ...
<% end %>
could I have an array in a column in my user table? the array would be
the ids of the subjects table. How can i do that??
You don't.
Even though Postgres for example allows you to create array type columns
it is not a good solution as thats not how associations work in ActiveRecord.
Its also a crappy relational database design as its messy to write queries with a join though an array column and it does not let you enforce referential integrity with foreign keys or have good indices for performance.

How to create automated association in ruby on rails with mysql

i got 2 tables connected with each other.
device and push information are my models.
class Device < ActiveRecord::Base
has_one :pushinformation
end
class Pushinformation < ActiveRecord::Base
belongs_to :device
end
these are my 2 model files and their relationships.
and these are my migration files
class CreateDevices < ActiveRecord::Migration
def change
create_table :devices do |t|
#t.integer :id
t.string "token"
t.string "carrier"
t.string "segment"
#t.datetime :created_at
#t.datetime :updated_at
t.timestamps
end
end
end
class CreatePushinformations < ActiveRecord::Migration
def change
create_table :pushinformations do |t|
t.integer "device_id"
#t.string "token"
t.string "first_name"
t.string "last_name"
t.string "nickname"
t.timestamps
end
end
end
now the thing is , i was able to create a relationship successfully in rails console by saying
device.pushinformation=push
and they were associated.
How can i make this process automated, like when i add one device- it will have a push information table filled aswell,
i thought about having the same attribute and relating them might be the solution. In this case its token and its completely unique.
How can i create this relationships? I need to be able to know which device has what kind of first_name
i m a beginner in ruby and this is a newbie question sorry guys :)
I am not sure I understand completely what you ask but my guess is that you could use a callback on create
class Pushinformation < ActiveRecord::Base
belongs_to :device
after_create :create_push_notification
private
def create_push_notification
...
end
end
check the docs
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
xlembouras's answer is right (to a degree), but as you're new, let me explain it for you:
--
Associations
ActiveRecord associations are nothing magical, they're just a way to associate two "objects" using a relational database setup.
ActiveRecord is an ORM -- "object relationship mapper" -- which basically means it just provides a level of abstraction for your ActiveRecord objects to associate with each other
Herein lies the crux of the matter - you need to apprciate how and why your associations will work, and more importantly, how to populate them correctly:
--
Models
Firstly, you need to appreciate the object-orientated nature of Ruby (& by virtue of running on Ruby, Rails). This is where the Models of Rails play such a vital role -- they allow you to build & manage objects from your database
The ActiveRecord associations give you the ability to manage the associations those objects have - maning if you build one, you should be able to build the other:
#app/models/device.rb
Class Device < ActiveRecord::Base
has_one :push_information
before_create :build_push_information #-> creates associative object before creation
end
#app/models/push_information.rb
Class PushInformation < ActiveRecord::Base
belongs_to :device
end
You need to consider the importance of what I've written above.
What you need is to create a push_information object with the same foreign_key as the device object, which can be achieved by using the build method
This will essentially create a "blank" version of your associative object, saving it with the correct foreign key etc
--
Nesting
Further to this, you have to appreciate the idea of "nesting", especially the method accepts_nested_attributes_for
This allows you to create associative / dependent objects based on your "parent" object:
#app/models/device.rb
Class Device < ActiveRecord::Base
has_one :push_information
accepts_nested_attributes_for :push_information
end
#app/models/push_informtion.rb
Class PushInformation < ActiveRecord::Base
belongs_to :device
end
This gives you the ability to do the following:
#app/controllers/devices_controller.rb
Class DevicesController < ApplicationController
def new
#device = Device.new
#device.build_push_information
end
def create
#device = Device.new(device_params)
#device.save
end
private
def device_params
params.require(:device).permit(:your, :device, :params, push_information_attributes: [:push, :information, :attributes])
end
end
This gives you the ability to populate the devices#new form like so:
#app/views/devices/new.html.erb
<%= form_for #device do |f| %>
<%= f.text_field :your_device_attributes %>
<%= f.fields_for :push_information do |p| %>
<%= p.text_field :your_field %>
<% end %>
<%= f.submit %>
<% end %>
Add a create
method to your Devise class. Something like:
def self.create(token, carrier, segment)
device = Device.new(:token => token, :carrier => carrier, :segment => segment)
pi = Pushinformation.create(device.id,..) # other params
end

Storing Location Data in the Database

I need to store data about specific Objects which include information about Location(Country, City, District) of an object. I'm trying to figure out the best way to store described above location data in the MySQL database and how it should look like in the rails active model.
My dummy idea is to create 3 separate tables like below:
Location_countries
id
name
Location_cities
id
country_id
name
Location_districts
id
city_id
name
Create a separate table for an object and call it the item_location and include Ids of object its country... so on
Next is to create 3 Rails models (Country, City, Districts).. , but I think it's a very dirty way and it can be combined into the single Model like Item_location
Any Ideas?
Thank you in advance!
I don't entirely understand what you're looking to do, but either way, here's how I'd tackle it:
#app/models/object.rb
Class Object < ActiveRecord::Base
has_one :location
end
#app/models/location.rb
Class Location < ActiveRecord::Base
belongs_to :object
belongs_to :city
belongs_to :district
delegate :name, to: :city, prefix: true
delegate :name, to: :district, prefix: true
end
#app/models/city.rb
Class City < ActiveRecord::Base
has_many :locations
belongs_to :country
end
#app/models/district.rb
Class District < ActiveRecord::Base
has_many :locations
end
#app/models/country.rb
Class Country < ActiveRecord::Base
has_many :cities
end
This will give you the ability to call:
#object = Object.find params[:id]
#object.location.city_name
#object.location.district_name

Joining 3 models together in Rails to display in a view iterating over one model

I have a fairly standard 'has_many :x, :through => :y' relationship with a user, a problem, and a completed_problem which acts as the association between the two:
/models/user.rb
class User < ActiveRecord::Base
has_many :completed_problems
has_many :problems, :through => :completed_problems
end
/models/problem.rb
class Problem < ActiveRecord::Base
belongs_to :wall
has_many :completed_problems
has_many :users, :through => :completed_problems
end
/models/completed_problem.rb
class CompletedProblem < ActiveRecord::Base
belongs_to :user
belongs_to :problem
validates_presence_of :user
validates_presence_of :problem
end
My complication is that data in each of these models impacts the display. I'm looking to display a list of problems on each wall, and for each problem in that list to use or show:
problem.id
problem.name
time since the current user completed the problem last
if no logged in user, some text
if user hasn't completed that problem, some other text
A (very ugly) first pass at the view is as follows:
/views/walls/show.html.erb
<% #wall.problems.each do |problem| %>
<a id=<%= "problem_#{problem.id}" %>>
<h3><%= problem.name %></h3>
<p><%= "#{time_ago_in_words(problem.last_complete_by_user(current_user))} ago" if current_user && problem.last_complete_by_user(current_user) %></p>
</a>
</li>
<% end %>
I've since overwritten it, but problem.last_complete_by_user (seen in the above snippet) was an attempt to use the problem object to find all the related completed_problems, with the user as an argument, in order to identify the 'updated_at' value for the most recently updated completed_problem for that particular problem and user.
Of course this isn't ideal because it'll be a separate query for each item in the list - I assume the preferred solution would be a method in the wall controller or model that joins across all 3 tables and returns a new array for the view to iterate over. Unfortunately I've spent too long bouncing between :join, :include and :find_by_sql without a solution.
Can someone at least lead me in the right direction for how to get this view working properly?
This is how I would solve the problem. It may not be the most efficient solution, but it's clean and easy to refactor when the time comes. I haven't tried the code, but it's probably not too far off. If you go this route and run into performance problems, I would look into fragment caching before adding a bunch of crazy SQL.
Models:
class User < ActiveRecord::Base
has_many :completed_problems
has_many :problems, :through => :completed_problems
# Finds the last completed problem
def last_completed_problem(problem)
problems.order('created_at DESC').where(:problems => {:id => problem}).limit(1).first
end
end
# No Changes
class Problem < ActiveRecord::Base
belongs_to :wall
has_many :completed_problems
has_many :users, :through => :completed_problems
end
# No changes
class CompletedProblem < ActiveRecord::Base
belongs_to :user
belongs_to :problem
validates_presence_of :user
validates_presence_of :problem
end
app/controllers/walls_controller.rb:
class WallsController < Application::Controller
def show
#wall = Wall.find(params[:id]).includes(:problems)
end
end
app/helpers/wall_helper.rb:
module WallHelper
def show_last_completed_problem_for_user(user, problem)
return "You are not logged in" if current_user.nil?
completed = user.last_completed_problem(problem)
return "You have not completed this problem" if completed.nil?
time_ago_in_words(completed.created_at)
end
end
app/views/walls/show.html.erb:
<%= render :partial => 'problem', :collection => #wall.problems %>
app/views/walls/_problem.html.erb:
<li>
<a id=<%= "problem_#{problem.id}" %>>
<h3><%= problem.name %></h3>
<p><%= show_last_completed_problem_for_user(current_user, problem) %></p>
</a>
</li>
So you want the LATEST completed problem, of which there may be many... or none.
We can do that by left-joining to completed_problems (in case there's not one there) then grouping on user/problem. The grouping means you get only one record per user/project. One final trick -- you need to specify your columns in 'select' so that we get the normal project fields and another for the last_attempted.
#problems = Problem.join("LEFT OUTER JOIN problems ON completed_problems.problem_id = problems.id").
group(:user_id, :problem_id).
select("projects.*", "MAX(completed_problems.updated_at) as last_attempted").
where(:wall_id => params[:wall_id])