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
Related
Ruby on Rails model logic conflicts with the relational database logic.
In Ruby on Rails, the model possessing the belongs_to, will have the foreign key in the database. So, the model:
class Student < ApplicationRecord
belongs_to :university
end
class University < ApplicationRecord
has_many :university
end
Will have the migration file:
class CreateStudents < ActiveRecord::Migration[6.0]
t.string :name
t.references :university, foreign_key: true
end
class CreateUniversities < ActiveRecord::Migration[6.0]
t.string :name
end
And, logically, the student will have the University foreign_key in the database, like so:
Student
-id int (PK)
-name varchar
-University_id int (FK)
University
-id int (PK)
-name varchar
So good so far
The problem arises when I want accepts_nested_attributes_for in the student model for referencing a University in the _form.html.erb. In order to use it, the host model, in this case Student, must have a has_one instead of a belongs_to for the referencing table. So it would become:
class Student < ApplicationRecord
has_one :university
accepts_nested_attributes_for :university
end
Notice how, in order to use accepts_nested_attributes_for for referencing the university, the Student model MUST replace its belongs_to :university for a has_one :university, and University would have a belongs_to :student, instead of a has_many :student.
So in the migration file, Student would lose its reference to University, and the latter would have a reference to Student instead, like so:
class CreateStudents < ActiveRecord::Migration[6.0]
t.string :name
end
class CreateUniversities < ActiveRecord::Migration[6.0]
t.string :name
t.references :student, foreign_key: true
end
And the database would forcefully be:
Student
-id int (PK)
-name varchar
University
-id int (PK)
-name varchar
-Student_id int (FK)
Which is wrong, because there should be a one to many relationship from University to Student, not the other way around.
So, is there a way to use accepts_nested_attributes_for, without messing up the relational database logic by having to forcefully inverse the relations in the models ?
accepts_nested_attributes_forNested attributes allow you to save attributes on associated records through the parent.
So you might be having a slight mix up about which one you want to be the parent. If you make the student the parent with nested attributes for the university you would not be referencing the university but essentially creating a new university record or updating an existing one with the nested attributes you used in your student form.
One-to-one
Consider a Member model that has one Avatar:
class Member < ActiveRecord::Base
has_one :avatar
accepts_nested_attributes_for :avatar
end
Enabling nested attributes on a one-to-one association allows you to create the member and avatar in one go:
params = { member: { name: 'Jack', avatar_attributes: { icon: 'smiling' } } }
member = Member.create(params[:member])
member.avatar.id # => 2
member.avatar.icon # => 'smiling'
So you would only be updating/creating new University records for each student you used those nested attributes on.
Here is for one-to-many:
One-to-many
Consider a member that has a number of posts:
class Member < ActiveRecord::Base
has_many :posts
accepts_nested_attributes_for :posts
end
You can now set or update attributes on the associated posts through an attribute hash for a member: include the key :posts_attributes with an array of hashes of post attributes as a value.
For each hash that does not have an id key a new record will be instantiated, unless the hash also contains a _destroy key that evaluates to true.
params = { member: {
name: 'joe', posts_attributes: [
{ title: 'Kari, the awesome Ruby documentation browser!' },
{ title: 'The egalitarian assumption of the modern citizen' },
{ title: '', _destroy: '1' } # this will be ignored
]
}}
member = Member.create(params[:member])
member.posts.length # => 2
member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
You can also see that in the one to many example above. Are you looking to change/create a University record when you post a Student record?
If you are just looking for a reference you essentially have that in your first example and could just change your routes up a bit to have the student referenced by the university.
Then once you have the routes nested you can just do a little work to the form partial. Here is a guy doing just that
So maybe if I am way off here describe why when you create/update a Student you want to create/update a University. If that is what you want to do.
This would helps others with your context a little more and might help others understand your intent with the nested attributes.
Add your form partial for example and explain your goal.
EDIT:
As a punt you can maybe look at has and belongs to many association.
This tutorial talks about nested attributes with a many to many association. But since I'm not certain exactly what your after it may or may not help.
Another punt sounds like you want maybe a student's university record to change when you update them. So you would have the student belong to the university and the university to has many students but also has many university records and the student to has many/has one university record and the record to belong to both.
aka:
this
Then you could have the student have accepts nested attributes for the university record/s.
I see that I have REALLY made a blunder trying to explain my doubts (it is my first question, I have learned from my mistake).
The ultimate goal was to know:
Can I use accepts_nested_attributes_for when there is a belongs_to instead of a has_one or has_many ?
So, the answer is yes:
class Student < ApplicationRecord
belongs_to :university
accepts_nested_attributes_for :university
end
In the student controller, there should be a build_:
def new
#student = Student.new
#student.build_university
end
I REALLY, REALLY, REALLY mistyped my question, and I apologize for it.
Thanks for all your answers.
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'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.
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 am using Ruby on Rails 3.1.0. I am trying to save a nested model having an attribute that is intended to store the foreign key of the parent model. At the creation time of the parent model I would like to set that attribute value to the parent model id value.
In my model I have:
class Article < ActiveRecord::Base
has_many :article_category_relationships
has_many :categories,
:through => :article_category_relationships
# Accept nested model attributes
accepts_nested_attributes_for :articles_category_relationships
end
class Category < ActiveRecord::Base
has_many :article_category_relationships
has_many :articles,
:through => :article_category_relationships
end
# The join model:
class ArticleCategoryRelationship < ActiveRecord::Base
# Table columns are:
# - article_id
# - category_id
# - user_id
# - ...
belongs_to :category
belongs_to :article
end
In my view I have the following:
...
<% #current_user.article_categories.each do |article_category| %>
<%= check_box_tag 'article[articles_category_relationships_attributes][][category_id]', article_category.id, false %>
<% end %>
In my controller I have:
def create
#article = Article.new(params[:article])
...
end
In my case, the article_id (related to the ArticleCategoryRelationship nested model) should be set to the #article.id value after the Article creation and the problem is that the Ruby on Rails framework seems do not set that value at the creation time. In few words, considering my case, I would like to attach the foreign key automatically.
Just to know, the generated params when the form is submitted is:
"article"=>{"title"=>"Sample title", "articles_category_relationships_attributes"=>[{"category_id"=>"8"}, {"category_id"=>"9"}, {"category_id"=>"10"}] }
Is it possible to "auto"-set the foreign key (article_id) of the nested model? If so, how can I do that?
try using :
#a_particular_article.article_category_relationships.build(params[:something])
you can see here for more info, and might want to have a look at nested attributes and at validates_associated
In my opinion, you just CAN'T do that.
If you want to save articles_category_relationships, you need a article_id for each of them.
And, when you save article, rails will first validate article and all sub-objects. This means
article.valid? must be true before you can save any record.
Since article does not have an id before save to db, article_id in any of articles_category_relationships is empty.
Therefore, article.valid? will always be false as long as you need to CREATE new article and its sub-objects at the same time
To summarize, here is the steps of rails:
validate article itself, success!(NOTE: note saved)
validate articles_category_relationship for each of article.articles_category_relationships, article_id not provided, fail!
What you can do
create article first
assign params to this created article
update with params