Setting attribute in join model when creating two new parents - ruby-on-rails

I have two models: User and Company. A company can have many users and a user can have many companies. As you might suggest, this is the perfect place to use a join table. I'm actually using a full blown model to join User and Company so that I can specify the role that each user has. The table, companies_users, therefore has the following columns: user_id, company_id and company_role.
The situation I'm trying to negotiate is one in which I'm creating both a Company and a User and would like to specify the company_role while doing so.
My new method is as follows:
def new
#user=User.new
#company=#user.companies.build
end
This creates an entry in the companies_users join table but (obviously) does so in leaving the company_role blank.
How might I add this bit of info?
Thanks in advance!

You can pass the attributes through the build / create methods:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def new
#user = User.new
#user.company_users.build.build_company
end
def create
#user = User.new user_params
#user.save
end
private
def user_params
params.require(:user).permit(company_users_attributes: [company_attributes:[:name]])
end
end
#app/views/users/new.html.erb
<%= form_for #user do |f| %>
<%= f.fields_for :company_users do |cu| %>
<%= cu.text_field :company_role %>
<%= cu.fields_for :company do |c| %>
<%= c.text_field :name %>
<% end %>
<% end %>
<%= f.submit %>
<% end %>
The above looks complicated, I'll explain in a second.
You need the following models:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :company_users
has_many :companies, through: :company_users
accepts_nested_attributes_for :company_users
end
#app/models/company_user.rb
class CompanyUser < ActiveRecord::Base
belongs_to :company
belongs_to :user
accepts_nested_attributes_for :company
end
#app/models/company.rb
class Company < ActiveRecord::Base
has_many :company_users
has_many :users, through: :company_users
end
If you want to create a company and company_user, you'll have to pass params for both. Although it looks messy, all you're doing is passing each nested object to their respective models.
If you want to set the "role", you have to pass the attributes to company_users. If you want to also create a new company (rather than just assigning an existing one), you need to also pass the respective params for that too.

You could explicitly reference the join table...
def new
#user = User.new
#company = Company.new
#companies_user=#user.companies_user.build(company: #company, company_role: 'default role')
end

Related

Rails polymorphic form for message system - user <-> admin

I used this guide as a starting point for creating a messaging system from scratch. The idea is to have conversations between User and AdminUser. I want the AdminUser respond to questions sent it by User. AdminUser can have many conversations with the same User (there can be several topics of conversation, each topic is a separate conversation).
The Admin should be able to see the previous conversations with a reply box.
Like in the article I've created below db structure:
class User < ApplicationRecord
has_many :conversations, as: :sendable
has_many :conversations, as: :recipientable
end
class AdminUser < ApplicationRecord
has_many :conversations, as: :sendable
has_many :conversations, as: :recipientable
end
class Conversation < ApplicationRecord
belongs_to :sendable, polymorphic: true
belongs_to :recipientable, polymorphic: true
has_many :messages, dependent: :destroy
validates :sendable_id, uniqueness: { scope: :recipientable_id }
end
class Message < ApplicationRecord
belongs_to :conversation
belongs_to :messageable, polymorphic: true
validates_presence_of :body
end
Inside the article provided earlier I read that:
We also added “messageable” as polymorphic. This way, both Admin and User can send messages to conversation with their respective references.
What "messageable" is responsible for? because to create new message I need to provide this messageble_id and I don't now what it is.
# app/views/messages/index.html.erb
<% #messages.each do |message| %>
<%= message.body %>
<% end %>
<%= form_for [#conversation, #message] do |f| %>
<%= f.text_area :body %>
<%= f.submit "Send Reply" %>
<% end %>
controller:
class MessagesController < ApplicationController
before_action :fetch_conversation
def index
#messages = #conversation.messages
#message = #conversation.messages.new
end
def new
#message = #conversation.messages.new
end
def create
#message = #conversation.messages.new(message_params)
redirect_to conversation_messages_path(#conversation) if #message.save
end
private
def message_params
params.require(:message).permit(:body, :messageable_id)
end
def fetch_conversation
#conversation = Conversation.find(params[:conversation_id])
end
end
With above code I've got an error:
ActiveRecord::RecordInvalid: Validation failed: Messageable must exist
Which means I need to provide messageable_id and probably messageable_type but what are these values?

Add Record to Existing Collection in a Rails App

I am attempting to develop a model in which a user can add the recipe they are viewing to an existing menu of recipes they have created, similar to adding a song to a custom playlist. I believe I have the models set up correctly (using a many to many through relationship) however I am unsure how to go about the adding of the actual records to a selected collection. Any guidance would be helpful. My code is as below.
Menus Controller
class MenusController < ApplicationController
before_action :set_search
def show
#menu = Menu.find(params[:id])
end
def new
#menu = Menu.new
end
def edit
#menu = Menu.find(params[:id])
end
def create
#menu = current_user.menus.new(menu_params)
if #menu.save
redirect_to #menu
else
render 'new'
end
end
def update
#menu = Menu.find(params[:id])
if #menu.update(menu_params)
redirect_to #menu
else
render 'edit'
end
end
def destroy
#menu = Menu.find(params[:id])
#menu.destroy
redirect_to recipes_path
end
private
def menu_params
params.require(:menu).permit(:title)
end
end
Menu Model
class Menu < ApplicationRecord
belongs_to :user
has_many :menu_recipes
has_many :recipes, through: :menu_recipes
end
menu_recipe Model
class MenuRecipe < ApplicationRecord
belongs_to :menu
belongs_to :recipe
end
Recipe Model
class Recipe < ApplicationRecord
belongs_to :user
has_one_attached :cover
has_many :menu_recipes
has_many :menus, through: :menu_recipes
end
User Model
class User < ApplicationRecord
has_secure_password
has_one_attached :profile_image
has_many :recipes
has_many :menus
end
You can do something like :
def add_recipe_to_menu
menu = current_user.menus.find params[:id]
recipe = current_user.recipes.find params[:recipe_id]
menu.recipes << recipe
end
It will add a viewing recipe to existing menu of recipes.
First make sure you build the new record off the user:
class MenusController < ApplicationController
# make sure you authenticate the user first
before_action :authenticate_user!, except: [:show, :index]
def new
#menu = current_user.menus.new
end
def create
#menu = current_user.menus.new(menu_attributes)
# ...
end
end
Then we can just add a select to the form where the user can select from his recipes:
# use form_with in Rails 5.1+
<%= form_for(#menu) do |f| %>
... other fields
<div class="field">
<%= f.label :recipe_ids %>
<%= f.collection_select :recipe_ids, f.object.user.recipies, :id, :name, multiple: true %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
f.object accesses the model instance wrapped by the form builder.
recipe_ids is a special setter/getter created by ActiveRecord for has_many associations. As you may have guesses it returns an array of ids and lets the association be set with an array of ids - automatically inserting/deleting rows in the join table in the process.
You then just need to whitelist the recipe_ids param:
def menu_attributes
params.require(:menu)
.permit(:foo, :bar, recipe_ids: [])
end
recipe_ids: [] whitelists an array of permitted scalar types. Since this is a hash option it must be listed after any positional arguments to be syntactically valid.
rb(main):003:0> params.require(:menu).permit(:foo, recipe_ids: [], :bar)
SyntaxError: (irb):3: syntax error, unexpected ')', expecting =>

Update form not working at all in many-to-many relationship

In my app there is a many-to-many relationship between recipe and ingredient, everything is working fine but update.
When I update a recipe, i can update any value associated to recipe table in my database but ingredients are not modified
Here is the recipe model
class Recipe < ActiveRecord::Base
after_create :save_implementos
after_create :save_ingredientes
has_many :HasImplemento, dependent: :destroy
has_many :HasIngrediente, dependent: :destroy
has_many :ingredientes, through: :HasIngrediente
has_many :implementos, through: :HasImplemento
#CUSTOM SETTER#
def ingredientes=(value)
#ingredientes = value
end
def implementos=(value)
#implementos = value
#raise #implementos.to_yaml
end
private
#Guarda los implemenos de una receta
def save_implementos
#raise self.id.to_yaml
#implementos.each do |implemento_id|
HasImplemento.create(implemento_id: implemento_id, recipe_id: self.id)
end
end
def save_ingredientes
#raise #ingredientes.to_yaml
#ingredientes.each do |ingrediente_id|
HasIngrediente.create(ingrediente_id: ingrediente_id, recipe_id: self.id)
end
end
Here is the ingredient model
class Ingrediente < ActiveRecord::Base
has_many :has_ingredientes
has_many :recipes, through: :HasIngrediente
end
and Here is the join table
class HasIngrediente < ActiveRecord::Base
belongs_to :recipe
belongs_to :ingrediente
end
I think you just forgot the accepts_nested_attributes_for in your Recipe model:
accepts_nested_attributes_for :ingredients
Your models have a lot of non-conventional code, which is likely the reason for them not working as required:
Use snake_case to define your associations (not CamelCase)
If you're saving nested/associated data, use accepts_nested_attributes_for
Only use callbacks to deal with the model directly; not to deal with other models
#app/models/recipe.rb
class Recipe < ActiveRecord::Base
has_and_belongs_to_many :ingredientes
has_and_belongs_to_many :implementos
accepts_nested_attributes_for :ingredientes, :implementos #-> if you wanted to create new ones
end
Because you're only creating join table records with your callbacks, you can get away with using << or populating the collection_singular_ids value (if you're using existing records):
#app/controllers/recipes_controller.rb
class RecipesController < ApplicationController
def new
#recipe = Recipe.new recipe_params
#recipe.save
end
private
def recipe_params
params.require(:recipe).permit(:implementos_ids, :ingredientes_ids)
end
end
This will allow you to use:
#app/views/recipes/new.html.erb
<%= form_for #recipe do |f| %>
<%= f.collection_select :implementos_ids, Implementos.all, :id, :name %>
<%= f.collection_select :ingredientes_ids, Ingrediente.all, :id, :name %>
<%= f.submit %>
<% end %>
-
I recommend has_and_belongs_to_many because it does not appear that you're populating your join table with any more data than the two model references.
The difference between has_many :through and has_and_belongs_to_many is has_many :through gives you the ability to store extra data in the join table. If you don't need this, has_and_belongs_to_many is far simpler to maintain.
Thus, to answer your question directly, to update your recipe, you can use the following (with my updated code):
#app/views/recipes/edit.html.erb
<%= form_for #recipe do |f| %>
<%= f.collection_select :implementos_ids, Implementos.all, :id, :name %>
<%= f.collection_select :ingredientes_ids, Ingrediente.all, :id, :name %>
<%= f.submit %>
<% end %>
#app/controllers/recipes_controller.rb
class RecipesController < ApplicationController
def edit
#recipe = Recipe.find params[:id]
end
def update
#recipe = Recipe.find params[:id]
#recipe.update recipe_params
end
private
def recipe_params
params.require(:recipe).permit(:implementos_ids, :ingredientes_ids)
end
end
This will set the implementos_ids & ingredientes_ids values for your recipe, which will update the associations automatically.

Rails 4: First argument in form cannot contain nil or be empty

In our Rails 4 app, there are four models:
class User < ActiveRecord::Base
has_many :administrations, dependent: :destroy
has_many :calendars, through: :administrations
end
class Administration < ActiveRecord::Base
belongs_to :user
belongs_to :calendar
end
class Calendar < ActiveRecord::Base
has_many :administrations, dependent: :destroy
has_many :users, through: :administrations
end
class Post < ActiveRecord::Base
belongs_to :calendar
end
We have routed the corresponding resources with Routing Concerns, as follows:
concern :administratable do
resources :administrations
end
resources :users, concerns: :administratable
resources :calendars, concerns: :administratable
To create a new #calendar, we use the following form:
<h2>Create a new calendar</h2>
<%= form_for(#calendar) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_field :name, placeholder: "Your new calendar name" %>
</div>
<%= f.submit "Create", class: "btn btn-primary" %>
<% end %>
When we embed this form into the user's show.html.erb (so that a user can create a new calendar from his profile), everything works fine.
However, we would like to have a special page, call dashboard, where a user could see all his calendars and create a new one.
We believe the logical url should be /users/:id/administrations.
So, we embedded the above form in the Administration's index.html.erb.
And as soon as we do that and visit for instance /users/1/administrations, we get the following error:
ArgumentError in AdministrationsController#index
First argument in form cannot contain nil or be empty
Extracted source (around line #432):
else
object = record.is_a?(Array) ? record.last : record
raise ArgumentError, "First argument in form cannot contain nil or be empty" unless object
object_name = options[:as] || model_name_from_record_or_class(object).param_key
apply_form_for_options!(record, object, options)
end
EDIT: here is also our Administrations#Controller:
class AdministrationsController < ApplicationController
def to_s
role
end
def index
#user = current_user
#administrations = #user.administrations
end
def show
#administration = Administration.find(params[:id])
end
end
Any idea of what we are doing wrong?
First argument in form cannot contain nil or be empty
The reason is #calendar is not initialised in your controller action, so obviously #calendar is nil. So is the error.
To get your form to work in administrations/index.html.erb, you should be having the below code in your index action of your administrations_controller.
def index
#user = current_user
#administrations = #user.administrations
#calendar = Calendar.new # this one.
end

Rails 4 Attendance system

Hi I'm actually struggling with building a Rails 4 application implementing an attendance model on rails 4, I found maybe two question on stackoverflow but they're posted in 2012 and it failed when i tried following them.
This is the closest one i got on stackoverflow
Edit: I already have a view of classroom and list out the students. And could assign students into the classroom but the problem would be to GET the students into a new attsheet and saving them into attendance
Here's what I currently have now
# Attendance
# take student as a single entry for :attsheet and
# has a :attended (boolean) and remarks as well
class Attendance < ActiveRecord::Base
belongs_to :student
belongs_to :attsheet
end
#Attsheet which means attendance sheet
#has :post_date and :remark
class Attsheet < ActiveRecord::Base
belongs_to :classroom
has_many :attendances
accepts_nested_attributes_for :attendances
end
class Student < ActiveRecord::Base
belongs_to :school
has_and_belongs_to_many :classrooms
has_many :attendances
end
class Classroom < ActiveRecord::Base
belongs_to :school
has_and_belongs_to_many :students
has_many :attsheets
validates :class_name, presence: true
end
I want the classroom to be able to create a new attendance or view attendance archives for each student.
I am able to do this in classroom right now but I'm stuck at what to do next for the controller and view
$ = link_to "New Attendance", new_school_classroom_attsheet_path(#school, #classroom, #attsheet)
In attandances_controller,
class AttendancesController < ApplicationController
before_filter :set_parents
def new
#attendance= #classroom.attendances.new
end
def create
#attendance= #classroom.attendances.new(params[:milestone])
if #attendance.save
redirect_to ....
else
render :action=>:new
end
end
def set_parents
#school= School.find(params[:school_id])
#classroom= #school.classrooms.find(params[:classroom_id])
end
end
and in _form.html.erb of attendacen,
<%= form_for(#school, #classroom, #attendance]) do |f|%>
<% if #attendance.errors.present? %>
<ul class="warning">
<% #attendance.errors.full_messages.each do |message| %>
<li><%= message%></li>
<% end %>
</ul>
<% end %>
<h2>Attendance</h2>
.........
<%= f.submit button %>
<% end %>
this will submit fotm to create action of attendance
I found the solution by doing
I changed attsheet to attendance_list
def new
#attendance_list = #classroom.attendance_lists.new
#attendance_list.attendances = #classroom.student_ids.map do |student_id|
#attendance_list.attendances.build(student_id: student_id)
end
end
def create
#attendance_list = #classroom.attendance_lists.new(attendance_list_params)
#attendance_list.classroom_id = params[:classroom_id]
respond_to do |format|
if #attendance_list.save
format.html {redirect_to school_classroom_path(#school, #classroom), notice: "You added the attendance!" }
else
redirect_to new_school_attendance_list_path(attendance_list_params)
end
end
end
with simple fields
= f.simple_fields_for :attendances do |g|
= g.input :student_id, as: :hidden
...... more fields ...

Resources