Form to create relationship between objects - ruby-on-rails

In my app I have User and Language models.
User can have multiple languages and a language can have multiple users.
class Language < ApplicationRecord
has_and_belongs_to_many :users
end
class User < ApplicationRecord
has_and_belongs_to_many :languages
end
I want to create a form that will allow user to add a new language to the profile.
Since both User and Language models already exist, I'm wondering how to create a form that will not create any new model, but just create a relation between existing models.

Both the has_many and HABTM macros create _ids and _ids= setters and getters that make it trivial to associate different records:
<%= form_with(model: #user) do |form| %>
<div class="field">
<%= form.label :language_ids, "Languages" %>
<%= form.collection_select(:language_ids, Language.all, :id, :name, multiple: true) %>
# or if you prefer checkboxes
<%= form.collection_checkboxes(:language_ids, Language.all, :id, :name) %>
</div>
# ...
<% end %>
The form collection helpers are smart enough to iterate accross the collection and will select/check depending on if an assocation already exists.
You whitelist an array parameter by passing a hash key to permit with an empty array as its value:
class UsersController < ApplicationController
def create
#user = User.new(user_params)
# ...
end
private
def user_params
params.require(:user)
.permit(
:foo, :bar, :baz,
langauge_ids: []
)
end
end
I would also seriously consider if you want to use has_and_belongs_to_many in the first place. Since there is no model you can't access any additional columns on the join table like for example how proficient a user is or if its their primary language. There is also no straight forward way to query the join table directly. has_many through: is a actually better solution in most cases.

Related

Filter for Nested forms - Ruby on Rails

In my rails application I've two models called 'user' and 'store'. An user belongs_to store and A store has_many users.An user has many attributes like name,email and role etc.,. In one of the forms for store, I want to use nested forms to create users whose role is packer . The Problem is when I use nested forms, other users whose role is not 'packer' are also listed. I specifically want to use nested forms only for users whose role is 'packer'. Is there any way to filter users with role as 'packer' in nested forms
To start with you may want to setup a "short-cut" relationship:
class Store < ActiveRecord::Base
has_many :users
has_many :packers, -> { where(role: 'packer') }, class_name: 'User'
end
class User < ActiveRecord::Base
belongs_to :store
end
fields_for(record_name, record_object = nil, options = {}, &block) takes an optional record_object argument that lets you set the records used:
<%= form_for #store do |f| %>
...
<%= f.fields_for :users, f.object.packers do |user| %>
<%= user.hidden_field :role %>
<%= user.text_field :name %>
<% end %>
...
<% end %>
Using f.object is not strictly necessary - you could use any variable, but it does make it easier to build reusable partials.

Rails 4+ Edit multiple objects in one form BEST PRACTICE

I have a User, Drinks, Gyms, Foods Model.
class User < ActiveRecord::Base
has_many :drinks
has_many :foods
has_many :gyms
end
I track the number of drinks a user had during the day and save it in the database.I do the same with Foods and Gyms.
I have a User and a Session (for login) controller. So far I haven't needed a controller for my "passiv" Models (Drink, Food, Gym).
Now I have one page with a form on which the User can change the entries of all tables(Drink, Food, Gym) of the previous day.
I think I need to use fields_for in the form to edit objects of multiple Models in one form.
However I don't know how many controllers I need and where I should put in all the business logic... I don't want to do anything quick and dirty, but rather follow certain Best Practices.
My approach so far:
A lot of forms on one page
<%= form_for :running, url: data_update_user_path do |f| %>
<%= form_for :drinks, url: data_update_user_path do |f| %>
<%= form_for :food, url: data_update_user_path do |f|
One DataController who handles all the different updates (It's basically a big if elsif)
class DataController
def update
if params[:drinks]
#update drinks
elsif params[:foods]
#update foods
elsif params[:gyms]
#update gyms
end
end
end
So my question: What is the best practice in such a situation?
Use nested form with only one controller; the users controller, the update action for the user will update it's related models as well when you use accepts_nested_attributes_for so basically your user model will be
class User < ActiveRecord::Base
has_many :foods
has_many :drinks
has_many :gyms
accepts_nested_attributes_for :foods, :drinks, :gyms
end
And the for for user will contain fields_for foods, drinks and gyms
Don't forget in your users controller if you are using strong_parameters to permit the attributes of the nested models
def user_params
params.require(:user).permit(:id, .... , foods_attributes: [:id, :destroy, ....], gyms_attributes: [:id, :_destroy, ....], drinks_attributes: [:id, :_destroy, ....])
end

Rails 4 Tagging users on a model

Whats the best method on tagging users? If you have a team model, and when you create a team, you want to add the members, how would this architecture work?
I was thinking of just using acts as tanggble and use it on the users, but not sure if this would be the best method? Is there another gem out there that would do something like this?
It sounds like you're looking for a has many through relationship. This would require you to have a joining table called team_members to record which users are members of each team, having user_id and team_id columns. So for example your Team model would have a relationship that looks like this:
has_many :users, through: :team_members
This then defines the appropriate method on Team for adding, querying and removing users.
More information is here
To add to #tpbowden's answer, if you just want to "tag" users, you may wish to use has_and_belongs_to_many:
# app/models/user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :teams
end
# join table "teams_users" - team_id | user_id
# app/models/team.rb
class Team < ActiveRecord::Base
has_and_belongs_to_many :users
end
This will allow you to use the singular_collection_ids method, with which you'll be able to define which user is in a "team":
#app/controllers/teams_controller.rb
class TeamsController < ApplicationController
def edit
#team = Team.find params[:id]
end
def update
#team = Team.find params[:id]
#team.update team_params
end
private
def team_params
params.require(:team).permit(user_ids: [])
end
end
#app/views/teams/edit.html.erb
<%= form_for #team do |f| %>
<%= f.collection_select :user_ids, User.all, :id, :name %>
<%= f.submit %>
<% end %>
This is as close to "tagging" as you're going to get without any extra dependencies.

Can you pass params for associated model on create

What i want to do -
I've got 2 models Record and Author. when calling Record.create params i whant to pass params for associated Author model.
Record has column body and Author has column name
When i try to pass as follows
Record.create { body: "some text", author: { name: 'Some name'}}
i get error ActiveRecord::UnknownAttributeError: unknown attribute: author
How can i do what i need ?
UPDATE 1
association - Record has author
Nested Attributes
You'll probably be looking for accepts_nested_attributes_for, or inverse_of - both relying on an association between your two models:
#app/models/record.rb
Class Record < ActiveRecord::Base
has_one :author
accepts_nested_attributes_for :author
end
#app/models/author.rb
Class Author < ActiveRecord::Base
belongs_to :record
end
Essentially, you'll need to build the associative data, allowing you to send the associated attributes through to your other model. I'll explain this further down the page
This is what I would do if I were you:
#app/controllers/records_controller.rb
Class RecordsController < ApplicationController
def new
#record = Record.new
#record.author.build
end
def create
#record = Record.new record_params
#record.save
end
private
def record_params
params.require(:record).permit(:record, :attributes, author_attributes: [:name])
end
end
#app/views/records/new.html.erb
<%= form_for #record do |f| %>
<%= f.text_field :record %>
<%= f.fields_for :author do |a| %>
<%= a.text_field :name %>
<% end %>
<%= f.submit %>
<% end %>
This will allow you to save the author params / attributes upon save
--
inverse
Inverse attributes are also another idea for you.
I'm not sure whether they'll work directly in this instance, but you could use the following:
#app/models/record.rb
Class Record < ActiveRecord::Base
has_one :author, inverse_of: :author
before_create :build_record
end
#app/models/author.rb
Class Author < ActiveRecord::Base
belongs_to :record, inverse_of: :record
before_create :set_options
private
def set_options
self.draft = true unless self.record.draft.present?
end
end
This means you should be able to access the nested attribute data (I'm not sure whether you have to use accepts_nested_attributes_for still in this instance) in your other model
ActiveRecord Objects
Finally, you need to consider the role of ActiveRecord objects in this setup
Please remember you're not just passing single items of data here - you're constructing & passing objects. This means you have to consider how they work & what they mean. I'll give you a brief explanation:
Rails, because its built on Ruby, is an object-orientated framework. This means that every piece of data you create / use in this is an object. Objects are much different than variables - they are deeper & have much more data contained within them, allowing them to be used in a variety of different ways:
Rails makes use of objects in many different ways; the main one being that a lot of the helpers & other methods build themselves around the objects. That's why you get the resources directive in your routes, and can do the following: <%= link_to #user.name, #user %>
The problem many people have is they don't understand the value of object-orientation in a Rails app, and consequently try and think about their logic from the perspective of a disjointed system. Conversely, and this will help you tremendously, you need to consider that every time you create a record, you're building an object, and consequently, you need to ensure you build your app around them.
As noted, you have to ensure you have an association between the objects you wish to create. If you do that, you'll be able to build them both at the same time
Try this hopefully will solve your problem:
class Record < ActiveRecord::Base
has_one :author
accepts_nested_attributes_for :author, :reject_if => lambda { |a| a[:name].blank? }, :allow_destroy => true
end
And for more details see accepts_nested_attributes_for

How can I create dynamic form field to store/update hash sets in Rails?

In my reservations table I have a rooms (text) field to store hash values such (1 => 3) where 1 is roomtype and 3 corresponds to the amount of rooms booked by the same agent.
My Reservation model
serialize reserved_rooms, Hash
Here is my nested resource
resources :hotels do
resources :roomtypes, :reservations
end
RoomType stores a single room type which belongs to Hotel model. Though I can enlist roomtypes within my reservation form I do not know how I can create a dynamic hash via form to create/update this hash.
I have this but I am looking for a way to create a dynamic hash "key, value" set. Meaning, if Hotel model has two RoomType my hash would be {12 = > 5, 15 => 1} (keys corresponds to the roomtype_ids while values are the amount}
<%= f.fields_for ([:roomtypes, #hotel]) do |ff| %>
<% #hotel.roomtypes.each do |roomtype| %>
<%= ff.label roomtype.name %>
<%= f.select :reserved_rooms, ((0..50).map {|i| [i,i] }), :include_blank => "" %>
<% end %>
<% end %>
What I want is what this website has in the availability section (nr. of rooms):
specs: rails 4.1, ruby 2.1
Note: If you think there is a design problem with this approach (storing reserved_room in a serialized field) I can follow another path by creating another table to store the data.
Might need tweaking but i used similar code with check-boxes and it worked!
<% #hotel.roomtypes.each do |roomtype| %>
<%= f.label roomtype.name %>
<%= f.select :"reserved_rooms[roomtype.id]", ((0..50).map {|i| [i,i] }), :include_blank => "" %>
<% end %>
This gets messy enough that I would probably consider going with a separate models as you mentioned. I would simply do:
class Hotel < ActiveRecord::Base
has_many :room_types
has_many :rooms, :through => :room_types
end
class RoomType < ActiveRecord::Base
has_many :rooms
end
class Room < ActiveRecord::Base
has_many :reservations
belongs_to :room_type
end
class Reservation < ActiveRecord::Base
belongs_to :room
belongs_to :agent
end
class Agent < ActiveRecord::Base
has_many :reservations
end
Then just use a generic form to submit the # Rooms integer, and let your controller handle making multiple reservations...? Maybe I'm not understanding your objective well enough...
Rails 4 has a new feature called Store you would love. You can easily use it to store a hash set which is not predefined. You can define an accessor for it and it is recommended you declare the database column used for the serialized store as a text, so there's plenty of room. The original example:
class User < ActiveRecord::Base
store :settings, accessors: [ :color, :homepage ], coder: JSON
end
u = User.new(color: 'black', homepage: '37signals.com')
u.color # Accessor stored attribute
u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
# There is no difference between strings and symbols for accessing custom attributes
u.settings[:country] # => 'Denmark'
u.settings['country'] # => 'Denmark'

Resources