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.
Related
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.
I am using Rails 4.2.4. I know I have set my relations correctly but I'm getting undefined method "city" for #
support.rb:
belongs_to :user
user.rb:
has_many :supports (should the be plural?)
views/users/show.html.erb:
<%= #user.supports.city %>
In my supports table:
t.string :city
t.integer :user_id
I have a form for support in which I have filled out the city field and I can see in entry with Support.all in the rails console so Im sure the value for :city is saved in the db.
I have used rails g scaffold support for this process where a user can create many supports. Am I missed something?
has_many :supports should be plural
#user.supports returns all supports but it can return an empty array. So you have to use:
if support = #user.supports.first
# use support.city
end
or
<% #user.supports.each do |support| %>
<h1><%= support.city %></h1>
<% end %>
If you're trying to access associative data, you'll need to understand that pluralized relations (IE has_many) will return collections of data:
#app/models/support.rb
class Support < ActiveRecord::Base
belongs_to :user #-> #support.user
end
#app/models/user.rb
class User < ActiveRecord::Base
has_many :supports #-> #user.supports
end
To answer your question about the :plural, no, you don't need to call it a plural. However, as per Rails convention, it builds the entire relationship (and queries) off the back of the name:
belongs_to associations must use the singular term. If you used the
pluralized form in the above example for the customer association in
the Order model, you would be told that there was an "uninitialized
constant Order::Customers". This is because Rails automatically infers
the class name from the association name. If the association name is
wrongly pluralized, then the inferred class will be wrongly pluralized
too.
If you wanted to use singular names for your associations with has_many, you'll have to define your class etc:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :support, class_name: "Support", foreign_key: "support_id"
end
--
When you get your returned data from a has_many collection, you need to cycle through the data. Since it's a collection (as opposed to a "member" -- single record), you will need to something like the following:
<% #user.supports.each do |support| %>
<%= support.city %>
<% end %>
I have a Product model with the following columns:
name:string
price:integer
I also have a cart model, line_item model, an orders controller: pretty basic but you get the idea.
What I need to do (and have done) is add size (since it's for tshirts) and color to the Product model. Fair enough,
rails g migration AddSizeToProducts size:string
works just fine and similarly for color.
The way the buying process is set up is as follows:
select shirt (one page)
after selecting shirt, takes you to next page
on this page, you will select both the color and size for the shirt.
From what I've gathered, using select will not be tied to the database, so I suppose collection_select would be more appropriate here? Or would something like this be OK?
<%= select #product, :id, [ ["Small",1], ["Medium",2], ["Large",3]] %>
Also, how would I "prepopulate" these fields with sizes ie small/med/large and colors ie black/blue/white without having them associated to a particular product before a product is selected (from step 1 above)?
Any input on the matter is appreciated.
without having them associated to a particular product
This confused me - surely you'd want to associate the select box with a particular product's options?
I would personally make sure the "buy process" is set up to pass the product ID throughout. I'd do this by using a nested resource in the routes:
#config/routes.rb
resources :products do
get :step2 #-> /products/1/step2
end
This will pass params[:product_id] through the process, allowing you to use it on your next step:
#app/controllers/products_controller.rb
def step2
#product = Product.find(params[:product_id])
end
#app/views/products/step2.html.erb
<%= collection_select(:product, :size_id, #product.size, :id, :name, prompt: true) %>
Having looked at it, I think your real problem is storing your size as a string inside your Product model. I'd make a separate model called sizes, and then use a join model called products_sizes to allow you to associate as many sizes as you need with each product. You'd then be able to call the associative data
Why not do this:
#app/models/product.rb
Class Product < ActiveRecord::Base
has_many :product_sizes
has_many :sizes, through: :product_sizes
end
#app/models/size.rb
Class Size < ActiveRecord::Base
has_many :product_sizes
has_many :products, through: :product_sizes
end
#app/models/product_size.rb
Class ProductSize < ActiveRecord::Base
belongs_to :product
belongs_to :size
end
This allows you to store sizes for many different products, allowing you to pick ID's from the ProductSize model, referencing them directly in your cart model:
#app/models/cart.rb
Class Cart < ActiveRecord::Base
belongs_to :user
belongs_to :product
belongs_to :product_size
end
#carts
id | user_id | product_id | product_size_id | created_at | updated_at
This means if you create a new cart item, you'll be able to assign a product's size to the record directly:
<%= collection_select(:product, :product_size_id, #product.sizes, :id, :name, prompt: true) %>
I hope to get some great help here. I am a noob in many areas of Rails 3.2 but am getting much better.
I have a collection select that has retrieved the correct records. I have multiple properties, that then has multiple users. The collection select correctly uses a UserProperty table to filter only the property users (I want the collect to display each user related to that property). So I have the user ID and not the name.
So I tried to create an object by querying the parent table (users) to pull the parents properties and pass through. Unfortunately, the second query is only passing a single record. So scrapped that.
The easiest would be to use the 1st query in the collection_select but then display the parent field based on the reference user id. Is there a syntax that allow me to display text back to the parent (USER) table within the collection select and queried object "pool"?
The goal would be to query UserProperties by the nested route(#property) - Working.
(#pool contains the correct results)
Then display the the name field from User. (User.name)
Here is my code:
Controller:
def find_property
#property = Property.find(params[:property_id])
#pool = UserProperty.find_all_by_property_id(#property)
end
View:
<%= fields_for (:assigned_to_user_id) do |pool| %>
<%= pool.collection_select(:assigned_to_user_id, #pool, :id, :id ) %>
<!-- Need to change last field from id to name field of Users Table -->
Model Design: (need name from Users)
# Table name: users
#
# id :integer not null, primary key
# name :string(255)
# email :string(255)
class User < ActiveRecord::Base
attr_accessible :name, :email
has_many :properties, through: :user_properties
end
# Table name: user_properties
#
# id :integer not null, primary key
# user_id :integer
# property_id :integer
class UserProperty < ActiveRecord::Base
attr_accessible :property_id, :user_id
belongs_to :user
belongs_to :property
end
Issue Solved. Rails has built in filters based on nested routing.
So a friend helped me solve this issue using a simpler method and along RoyTheBoy's suggestion.
Although there was a many through relation, there was a need just for a has many statement.
Models as follows:
has_many :users, through: :user_properties
has_many :user_properties (new line)
Then remove the filter from the #pool
#pool = #property.users.all (rails automagically pulls from the nested route)
FYI - existing before_filter for loading nested properties
#property = Property.find(params[:property_id])
This allowed a simpler collection_select
<%= fields_for (:assigned_to_user_id) do |pool| %>
<%= collection_select(:assigned_to_user_id, :user_id, #pool , :id, :name, prompt: true) %>
Nested routing for the property, handles the filtering automagically! Gotta love rails, especially a newbie like me!
Thanks to Robert for the Help!
from your table definitions, in your models you need
class UserPropery < ActiveRecord::Base
belongs_to :user
class User < ActiveRecord::Base
has_many :user_properties
you can then use
UserProperty.where(...).user.name
however you say
"I have multiple properties, that then has multiple users."
this implies to me that a property can have many users, In this case you should have a user_propery_id field in your User table and in you models
class UserPropery < ActiveRecord::Base
has_many :user
class User < ActiveRecord::Base
belongs_to :user_properties
you can then use something like
UserProperty.where(...).users.collect {|user| [ user.id, user.name ]}
this is a good explanation of how the associations work
http://guides.rubyonrails.org/association_basics.html
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.