How do I make an attendance sheet in rails? - ruby-on-rails

I have a Meeting model, view, and controller. Every Meeting, I would like to take attendance of the Members of the club. Each Meeting is also associated with a Tournament.
It seems ideal to me, that each Meeting Entry would store the Tournament_id and attendence for each Member_id. However, if you were to do this, you would need a dynamic column length for the member_ids (one column for each member), which can vary.
Is that possible?
If that is not the best way, what is the best way to store the data?
Currently, I have it where each Meeting.id stores 1 Member's Attendance and the form rolls out all Members for 1 given tournament and a check box for their attendance. However, I don't fully understand how that is working, and I can't figure out how to easily Edit the member's attendance that way.
I got part of the solution of my current functionality from here, but was trying to find out if there is another way to get it fully working.
Thanks

It seems ideal to me, that each Meeting Entry would store the
Tournament_id and attendence for each Member_id. However, if you were
to do this, you would need a dynamic column length for the member_ids
(one column for each member), which can vary.
Big nope. That's not a tenable solution for modeling data in a relational database as the schema would be completely unmaintable and you would end up with a huge amount of columns that almost entirely contain nulls. The SQL queries would also be very crazy.
You instead need to think in terms of each row in a table containing the unique data for each member. In database design this corresponds to the concept First Normal Form.
This is a simplefied example of how you would keep track of attendence*:
class Member
has_many :attendences
has_many :meetings, through: :attendences
end
class Meeting
has_many :attendences
has_many :members, through: :attendences
end
# rails g model attendence member:belongs_to meeting:belong
class Attendence < ApplicationRecord
belongs_to :member
belongs_to :meeting
end
Here attendences is a join table containing the member and meeting ids and we have a many to many assocation between Member and Meeting.
You can then just use the rails form helpers for creating checkboxes from collections together with the member_ids setter / getter created by has_many :members, through: :attendences:
<%= form_with(model: #meeting) do |form| %>
<div class="field">
<%= form.label :member_ids, 'Members in attendence' %>
<%= form.collection_checkboxes :member_ids, #available_members, :id, :name %>
</div>
# ...
<% end %>
In your controller you would then whitelist the member_ids column with a hash key with an empty array as the value:
class MeetingsController < ApplicationController
def new
#meeting = Meeting.new
#available_members = Member.all
end
def create
#meeting = Meeting.new(meeting_params)
if #meeting.save
redirect_to #meeting
else
#available_members = Member.all
render :new
end
end
private
def meeting_params
params.require(:meeting)
.permit(:foo, :bar, :baz, member_ids: [])
end
end
This will permit an array of permitted scalar values such as a list of ids.

Related

Rails 4+, ordering list of parent records by children

I have a page that displays a lot of data and I'm working on optimizing it and I'm struggling with balancing reducing DB queries with getting the proper data in the order I need.
A Transaction is the top-level element, and each transaction has multiple workflows and each workflow has many milestones. I order the workflows and milestones by a position:integer attribute.
# Top-level element
class Transaction < ApplicationRecord
has_many :workflows
has_many :team_members
end
# Parent element for milestones
class Workflow < ApplicationRecord
belongs_to :transaction
has_many :milestones
end
# Unique individual
class Person < ApplicationRecord
has_many :team_members
end
# Acts as a join between Transactions and Persons
class TeamMember < ApplicationRecord
belongs_to :transaction
belongs_to :person
belongs_to :team_member_type
end
class TeamMemberType < ApplicationRecord
has_many :team_members
end
In my controller this is what I have:
def show
#transaction = Transaction.includes(
team_members: [:person, :team_member_type],
workflows: [:milestones]
).find(params[id])
end
This gathers all the data I need in 1 DB query, but the problem is when I iterate over the milestones for a workflow, I need it to be sorted by the position attribute.
I can grab the data from the DB and then sort it with Ruby, but that seems inefficient and I would prefer to get it all done in the DB properly (if possible).
If I were to write it with an N+1 issue, it would look like this:
<% #workflows.order(:position).each do |workflow| %>
<%= workflow.name %>
<% workflow.milestones.order(:position).each do |milestone| %>
<%= milestone.name %>
<% end %>
<% end %>
I think everyone agrees that's a bad idea, but I'm struggling to figure out how to sort the data and optimize my DB calls at the same time.
I also sort the team_members by the team_member_type.value attribute, but that's the same question as above.
<% #team_members.includes(:team_member_type, :person).order("team_member_types.value").each do |team_member| %>
...
<% end %>
My main objective is efficiency. If I need to use some Query objects to clean it up and raw SQL, I'm ok with that, I'm just not extremely proficient at SQL and would prefer using ActiveRecord if reasonable.
You can order by those two attributes in the controller method:
def show
#transaction = Transaction.includes(
team_members: [:person, :team_member_type],
workflows: [:milestones]
).order('milestones.position, team_member_types.value').find(params[id])
end
Let me know if this helps.
You could create associations that include the order_by, but my experience has been that that can silently fail when eager loading, leaving you with an unordered result.
Although you believe, probably correctly, that ordering in Ruby would be less efficient, I would at least benchmark it. The major performance problem is in the n + 1 query situation, and I would be optimistic that you'll find sorting in Ruby to be performant enough.

Rails: Polymorphic Associations - Create Child In Form And Choose Parent

To illustrate my question, i'll use the following association as listed on ruby guides for polymorphic associations:
class Picture < ApplicationRecord
belongs_to :imageable, polymorphic: true
end
class Employee < ApplicationRecord
has_many :pictures, as: :imageable
end
class Product < ApplicationRecord
has_many :pictures, as: :imageable
end
Now in our :pictures table, we will have 2 different columns, namely imageable_id and imageable_type.
The first thing that I am unclear of is, what exactly goes into imageable_type ? Is that something that the "rails magic" would automatically fill up when imageable_id is declared?
Moving on to my next point (which will probably indirectly answer my uncertainty above), how do I assign either a Product or Employee to my Picture.new form?
#create new picture form
#assume pre_created_array holds the :id for both Employee & Product
<%= form_for #picture do |f| %>
#relevant fields ontop
#choosing parent field is the one im unsure of
#this is what i assumed might be correct(?)
<%= f.select :imageable_id, options_for_select(pre_created_array) %>
<% end %>
Now does this form actually work? Is the building of the association something that has to be handled in the controller action instead? I am actually not very sure because usually in a regular association the parent can be declared before .save, such as doing #post.employee = #find an employee. So are we suppose to read the :imageable_id?
#pictures controller
def create
#picture = Picture.new(picture_params)
#manipulate :imageable_id here???
if #picture.save
#blahblah other code
end
So i am actually quite unsure about this, whether it is supposed to be the form or controller handling the building of association. Which is why i'm bringing up this 2 "uncertainties".
A polymorphic association is cool because it allows a single table to belong_to multiple, as you know. So given a picture, we don't have to know whether it belongs to an employee or a product, we can just call picture.imageable and get the parent.
So if we don't have to know where the parent is, that must means Rails has to know. How does it know? imageable_type! imageable_type is the name of the class which it belongs to. In this case, either 'Employee' or 'Product'. That way, given the imageable_id, it knows what table in which to search.
image.imageable actually calls image.imageable_type.constantize.find(image.imageable_id)
Rails will do this "magic" for you if you simply assign objects instead of IDs. image.update(imageable: Product.first) will assign both for you.
So in your form, you should be able work with a collection of objects and let Rails do the rest for you.

Setting a relationship between Users to access a resource

I'm teaching myself Rails and I'm trying to setup a collaboration relationship kind of like Github adds collaborators to projects. My models look like this:
class Restaurant < ActiveRecord::Base
has_many :employees
has_many :users, through: :employees
end
class User < ActiveRecord::Base
has_many :employees
has_many :restaurants, through: :employees
end
class Employee < ActiveRecord::Base
belongs_to :restaurant
belongs_to :user
end
The employee table also has a user_type column to handle permissions within the project (restaurant). I can't figure out how to make my employee_controller set this relationship. Users primary key is :email so I'm guessing a form should be able to receive the :email parameter, check if such a user with the inputed email exists, and add the relationship to the employees table.
I'm looking to be able to do something like this:
Restaurant_A = Restaurant.create(restaurant_params)
User_A.restaurants = Restaurant_A
Restaurant_A.employees = User_B
I think my models might be wrong but essentially I'd like to be able to have users with the ability to create a restaurant as well as be added as employees of another restaurant/their own restaurants.
Your model is all right - no problem with that.
What you are trying to accomplish, you can accomplish that by following:
restaurant_a = Restaurant.create(restaurant_params)
# Remember to name it 'restaurant_a', it is convention in Ruby
user_a.restaurants << restaurant_a
<< is an operator that inserts left hand side thing into its right hand thing. So in our case, it will insert restaurant_a into the list of restaurants that are associated with user_a, and then you call save operation on your user_a like user_a.save.
Same case is on the other side:
restaurant_a.employees << user_b
# According to Ruby convention, you shouldn't start your variable
# name with an upper case letter, and you should user a convention
# called 'snake_type' naming convention. So instead of naming
# your variable like 'firstDifferentUser', name it 'first_different_user'
# instead.
restaurant_a.save # To successfully save the record in db
Edit:
For creating a form:
<%= form_for(#restaurant, #employee) do |f| %>
<%= f.label :email %>
<%= f.text_field :email %>
<% end %>
And you need to define #restaurant and #employee in your employee's controller new action, because you are gonna create a new employee for a particular restaurant.

Get value from other model without any association

I have 3 models and tables Products, Customers, Buyers and there are has_and_belongs_to_many relationship among them. And I have another model and table sells. I need to get value from all of the above 3 tables in sells/new page. Do I have to use any association among them? How can I get the values?
I want product_id, product_name, customer_id, customer_name in views/sells/new.html.erb file I don't understand how can I get that
First of all it should be sales table and the Sale model. But anyway, from the view (or helper), you can do:
Product.all # gives you all products
# or fetch just the columns you want:
Product.select( [:id, :name] )
Same goes for customers (i.e. Customer.all etc.).
It's not an orthodox way to do it but it will work. With Erb you'll need <% ... %> or <%= ... %> of course.
Add user_id and product_id to sells table
Class User
has_many :sells
end
Class Product
has_many :sells
end
Class Sells
belongs_to :user
belongs_to :user
end
Then do on sell show page
sell.user_id
sell.user.name
sell.product_id
sell.product.name
Hope this is what you need or least give an idea :)

Add variable number of items of one type when creating item of another type

I want to create a form that lets you create an item of type A. In that form, you can add multiple items of type B to be associated to the item of type A that you are creating.
I think I understand how to do this in the models- just have a has_many and belongs to relationship.
I'm not really sure how to do this in the UI. Right, now I have the scaffolding and it only has fields for attributes of the item I'm creating. Is there a way to have it show fields for adding items of types defined in has_many that we see in the model file?
EDIT
ClassA
has_many :ClassB
ClassB
belongs_to :ClassA
Let's assume your Item A is a Person and Item B is a Project. So, for what you said, a Person has_many Projects. (I decided to use "real world" resources, instead of Item A and Item B for the clarify of the example).
Here is how you would define your models associations:
class Person < ActiveRecord::Base
has_many :projects
accepts_nested_attributes_for :projects
end
class Project < ActiveRecord::Base
belongs_to :person
end
Then, the form of your Person would look like this:
<%= form_for #person do |person_form| %>
...
<%= person_form.fields_for :projects do |project_builder| %>
# Here goes the fields of the projects
<%= project_builder.text_field :name %>
<% end %>
...
<% end %>
The key here is fields_for method, which will let you put into your Person form, form builders for the Projects associated with that Person.
It is important to know that if you are creating a new Person, there are obviously no Projects associated, and therefore the fields_for part would be empty. You might want to start by just basically doing this in the #new action:
def new
#person = Person.new
5.times { #person.projects.build }
end
Later, and once you get the feeling you know what's going on, you might want to add some buttons in the front end side that would let you "dynamically" add new Projects.

Resources