Fairly new to Rails and have tried a number of things here without success.
My problem is when I post to the database with this nested form, one of my tables (apartment_images) posts with the wrong foreign key (apartment_id).
I have a fairly complex model relationship: I have a Building has_many_through another table that associates it with (among others) an Apartments table. The problematic apartment_images table belongs to Apartments.
A summarized version is below:
Building Model
has_many :building_relationships
has_many :apartments, :through => :building_relationships
accepts_nested_attributes_for :apartments, allow_destroy: true
Apartment Model
belongs_to :building
has_many :apartment_images, -> { order(position: :asc) }, dependent: :destroy
has_many :building_relationships
has_many :buildings, :through => :building_relationships
ApartmentImage Model
belongs_to :apartment
buildings_controller (excluded new method)
def createNewBuilding
#building = Building.new(building_params)
#apartment = Apartment.where(building_id: #building.id)
#also tried this but results in no id being save:
##apartment = #building.apartments.build(apartment_params)
if #building.save
redirect_to newBuilding_path, notice: "Successfully created building"
else
render 'newBuilding'#, notice: "ERROR"
end
if apartment_image_params
apartment_image_params[:image].each do |value|
#apartment.apartment_images.build({image: value}).save
end
end
end
def apartment_image_params
#also tried adding :apartment_id. didn't work.
params.require(:apartment_image).permit(:id, image: []) if params[:apartment_image]
end
so I finally got it to work. Don't fully understand why this worked vs what I was doing, but will be reading up on it (but I'm also all ears if it's obvious to you guys :) ).
The answer was to switch from using a where / find / findby statement to using Rails magic:
def createNewBuilding
#building = Building.new(building_params)
#apartments = #building.apartments
#apartments.each do |apartments|
Related
I'm having trouble accessing validation messages for a related model when saving. The setup is that a "Record" can link to many other records via a "RecordRelation" which has a label stating what that relation is, e.g. that a record "refers_to" or "replaces" another:
class Record < ApplicationRecord
has_many :record_associations
has_many :linked_records, through: :record_associations
has_many :references, foreign_key: :linked_record_id, class_name: 'Record'
has_many :linking_records, through: :references, source: :record
...
end
class RecordAssociation < ApplicationRecord
belongs_to :record
belongs_to :linked_record, :class_name => 'Record'
validates :label, presence: true
...
end
Creating the record in the controller looks like this:
def create
# Record associations must be added separately due to the through model, and so are extracted first for separate
# processing once the record has been created.
associations = record_params.extract! :record_associations
#record = Record.new(record_params.except :record_associations)
#record.add_associations(associations)
if #record.save
render json: #record, status: :created
else
render json: #record.errors, status: :unprocessable_entity
end
end
And in the model:
def add_associations(associations)
return if associations.empty? or associations.nil?
associations[:record_associations].each do |assoc|
new_association = RecordAssociation.new(
record: self,
linked_record: Record.find(assoc[:linked_record_id]),
label: assoc[:label],
)
record_associations << new_association
end
end
The only problem with this is if the created association is somehow incorrect. Rather than seeing the actual reason, the error I get back is a validation for the Record, i.e.
{"record_associations":["is invalid"]}
Can anyone suggest a means that I might get record_association's validation back? This would be useful information for a user.
For your example, I would rather go with nested_attributes. Then you should easily get access to associated record errors. An additional benefit of using it is removing custom logic you have written for such behavior.
For more information check documentation - https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
In Ruby on Rails 4, how do you create a many-to-many relationship inside a relationship model for a friends list such as Facebook using the has_many :through ... syntax ?? I'm a newbie and currently learning Ruby on Rails 4. I have looked at this link.
But still have a hard time grasping it.
you will need a join table that references both sides of the relations
let us say you have an relation Post and another relation Category with a many to many relationship between them you need a join table to be able to represent the relationship.
migration for a join table would be
class CreateCategoriesPosts < ActiveRecord::Migration
def change
create_table :categories_posts do |t|
t.integer :category_id
t.integer :post_id
t.timestamps
end
add_index :categories_posts, [:category_id, :post_id]
end
end
and in the models/post.rb
Class Post < ActiveRecord::Base
has_and_belongs_to_many :categories
end
and in the models/category.rb
Class Category < ActiveRecord::Base
has_and_belongs_to_many :posts
end
more here:
http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association
I think #RAF pretty much nailed it. But to use the OP's example:
class User < ActiveRecord::Base
has_and_belongs_to_many :users_list
end
class UsersList < ActiveRecord::Base
has_and_belongs_to_many :users
end
Although at first it might seem like a User should have only one list of friends (UsersList), that might not always be the case. Think of types within the UserList model, such as: 'close friends', 'work friends', 'all friends' for example.
My advice: dig into the Rails guides. This is a concept worth learning and truly understanding (which I'm still doing :).
many-to_many relationships are a simple concept, but complex when using the database because of the way databases work. A person could have 1 to N different friends, which means that a single entry for a database would need a dynamic amount of memory for each entry, which in the db world is a no-no. So instead of creating a list of friends you would have to make a table that represents the links between friends, for example:
friendship.rb
class Friendship < ActiveRecord::Base
belongs_to :friend, foreign_key: 'friend_A' # this entry has a field called 'friend_A'
belongs_to :friend, foreign_key: 'friend_B' # this entry has a field called 'friend_B'
end
These links will represent your network of friends. However, as the two previous answers have mentioned, Rails has some nifty magic, "has_and_belongs_to_many", which will do this for you.
NOTICE: The problem here is that in my StatusesController, in the index action, the #relationship object only gets the statuses of all your friends, but does not get your own statuses. Is there a better way of approaching this? I am trying to create a view to view all statuses of users that are your friends, and your own statuses too, and so far, I can't seem to figure out how to order it chronologically, even if in my status model, i included "default_scope -> { order(created_at: :desc) } ". Any advice would be deeply appreciated
class User < ActiveRecord::Base
has_many :relationships
has_many :friends, :through => :relationships
has_many :inverse_relationships, class_name: 'Relationship', foreign_key: 'friend_id'
has_many :inverse_friends, through: 'inverse_relationships', :source => :user end
#
class Relationship < ActiveRecord::Base
# before_save...
belongs_to :user
belongs_to :friend, class_name: 'User'
end
#
class RelationshipsController < ApplicationController
def friend_request
user_id = current_user.id
friend_id = params[:id]
if Relationship.where( user_id: user_id, friend_id: friend_id, accepted: false).blank?
Relationship.create(user_id: user_id, friend_id: friend_id, accepted: false)
redirect_to user_path(params[:id])
else
redirect_to user_path(params[:id])
end
end
def friend_request_accept
# accepting a friend request is done by the recipient of the friend request.
# thus the current user is identified by to_id.
relationship = Relationship.where(user_id: params[:id], friend_id: current_user.id).first
if Relationship.exists?(relationship) and relationship.accepted == false
relationship.update_attributes(accepted: true)
end
redirect_to relationships_path
end
def friend_request_reject
relationship = Relationship.where(user_id: params[:id], friend_id: current_user.id).first
relationship.destroy
redirect_to relationships_path
end
################################
def index
#relationships_pending = Relationship.where(friend_id: current_user.id, accepted: false)
end
end
#
class StatusesController < ApplicationController
def index
#status = Status.new
#relationship = Relationship.where('friend_id = ? OR user_id = ?', current_user.id, current_user.id).
where( accepted: true)
end
def new
#status = Status.new
end
end
#
Given the following models:
class Company
include Mongoid::Document
has_many :workers, autosave: true
accepts_nested_attributes_for :workers
attr_accessible :workers_attributes
end
class Worker
include Mongoid::Document
field :hours
attr_accessible :hours
belongs_to :company
end
class Manager < Worker
field :order
has_many :contributors, :class_name => "Worker"
attr_accessible :order, :contributors
end
class Contributor < Worker
field :task
belongs_to :manager, :class_name => "Worker"
attr_accessible :task
end
How does one create a manager in a company in the controller and view using nested attributes?
Here's my guess:
def new
#company = Company.new
#company.workers = [Manager.new]
end
def create
#company = Company.new params[:user]
if #company.save
redirect_to root_url, :notice => "Company with manager created."
else
render :new
end
end
= semantic_form_for #company do |f|
= f.semantic_fields_for :workers do |worker_fields|
= worker_fields.inputs do
= worker_fields.input :hours
= worker_fields.input :order
problem is the order field which specifically belongs to the manager is not persisting after the create. Also when the data is improperly filled there is an error:
undefined method `order' for #<Worker:0x0000000646f018> (ActionView::Template::Error)
So is there a way for nested attributes to handle inheritance in the models from mongoid?
The question is related to Can nested attributes be used in combination with inheritance? except instead of active record using mongoid.
Honestly, this is a paraphrasing of my code... the real code is more complex situation although i believe these are all of the relevant parts. If you have more questions ask.
UPDATE:
I changed the view to the following:
= semantic_form_for #company do |f|
- #company.workers.each do |worker|
- if worker._type == "Manager"
= f.semantic_fields_for :workers, worker do |worker_fields|
= worker_fields.inputs do
= worker_fields.input :hours
= worker_fields.input :order
I do not get the error anymore, however the nested attributes do not update the company object properly. The params are the following:
{"company"=> {"workers_attributes"=>{"0"=>{"hours"=>"30", "order" => "fish", "id"=>"4e8aa6851d41c87a63000060"}}}}
Again edited for brevity. So the key part is that there is a hash between "0" => {data for manager}. The workers data seems to be held in a hash. I would expect the data to look more like the following:
params = { company => {
workers_attributes => [
{ hours => "30", "order" => "fish" }
]}}
This is different because the workers data is held in an array instead of a hash. Is there another step to get the nested attributes to save properly?
Thanks
what version of Mongoid are you using? Because I don't think the use of refereneces_many is encouraged -- Not that that's related to your problem here, just wanted to probe what version you're using. In the doc on the gorgeous Mongoid.org, get this, I had to learn it the hard way, they say for Updating your records, you need to the autossave set to true. That's NOT accurate. You need it for even creating
so:
class Company
include Mongoid::Document
has_many :workers, :autossave => true # your money shot
accepts_nested_attributes_for :workers
attr_accessible :workers_attributes
end
ADDED:
I was re-reading your code, I spotted the following that might be the problem: Your Company model is set to has_many :workers and is set to accept nested attribbutes for Worker when changes come in, correct? And there is a field named Order in your Manager model which is subclassed from Worker. Yet you're having a form whose nested fields part is pointed at Worker not at Manager, the model that actually has the Order field. And that's obviously not enough, because Company isn't having_many :managers yet, you may need to set it to has_many :managers in the Company model as well.
Currently I'm building a system where users can apply to workshops... the only problem is that a user can apply multiple times.
This is the code for applying
#apply to workshop
def apply
#workshop = Workshop.find(params[:id])
#workshop.users << #current_user
if #workshop.save
#workshop.activities.create!({:user_id => #current_user.id, :text => "applied to workshop"})
flash[:success] = "You successfully applied for the workshop"
redirect_to workshop_path(#workshop)
else
flash[:error] = "You can't apply multiple times for the same workshop"
redirect_to workshop_path(#workshop)
end
end
The Workshop model does the following validation:
has_and_belongs_to_many :users #relationship with users...
validate :unique_apply
protected
def unique_apply
if self.users.index(self.users.last) != self.users.length - 1
errors.add(:users, "User can't apply multiple times to a workshop")
end
end
And the save fails because the message "You can't apply multiple times for the same workshop" shows up.
But the user is still added to the workshop as an attendee?
I think the problem is that the user is already added to the array before the save applies, then the save fails but the user isn't removed from the array.
How can I fix this issue?
Thanks!
Marcel
UPDATE
Added this in the migration so there are no duplicates in the database only ruby on rails doesn't catch the sql error, so it crashes ugly.
add_index(:users_workshops, [:user_id, :workshop_id], :unique => true)
UPDATE SOLUTION
Fixed the problem by doing the following:
Create a join model instead of a has_and_belongs_to_many relation
This is the join model:
class UserWorkshop < ActiveRecord::Base
belongs_to :user
belongs_to :workshop
validates_uniqueness_of :user_id, :scope => :workshop_id
end
This is the relationship definition in the other models:
In User:
has_many :workshops, :through => :user_workshops
has_many :user_workshops
In Workshop:
has_many :users, :through => :user_workshops, :uniq => true
has_many :user_workshops
Because you can only do a uniqueness validation on the current model you can't validate uniqueness on a has_and_belongs_to_many relation. Now we have a join model where we join users and workshops through, so the relationship in user and workshop stays the same the only BIG difference is that you can do validation in the join model. This is exactly what we want, we want to verify that there is only one :user_id per :workshop_id and therefore we use validates_uniqueness_of :user_id, :scope => :workshop_id
Case solved!
P.S. Watch carefully that you mention the through relation (:user_workshops) as a separate has_many relation otherwise the model can't find the association!!
According to "The Rails 3 Way", the "has_and belongs_to_man" is practically obsolete.
You should use has_many :through with an intermediate table.
I'm not exactly sure what my problem is, so this question may require some more clarification, but here's what seems to be most relevant:
I have a has_many :through and the join model has some fields that aren't foreign keys. When I build the models up and try to save I get a validation error on the non-foreign key fields from the join model.
My files look like:
Person.rb
has_many :wedding_assignments, :dependent => :destroy
has_many :weddings, :through=>:wedding_assignments
accepts_nested_attributes_for :weddings
accepts_nested_attributes_for :wedding_assignments
Wedding.rb
has_many :wedding_assignments, :dependent => :destroy
has_many :people, :through=>:wedding_assignments
accepts_nested_attributes_for :people
accepts_nested_attributes_for :wedding_assignments
WeddingAssignment.rb
belongs_to :person
belongs_to :wedding
validates_presence_of :role, :person, :wedding
(role is a string)
people_controller.rb
def new
#person = Person.new
1.times do
wedding = #person.weddings.build
1.times do
assignment = wedding.wedding_assignments.build
assignment.person = #person
assignment.wedding = wedding
end
end
end
def create
#person = Person.new(params[:person])
#person.weddings.each do |wedding|
wedding.wedding_assignments.each do |assignment|
assignment.person = #person #i don't think I should need to set person and wedding manually, but I get a validation error if I don't
assignment.wedding = wedding
end
end
end
the params that come back look like:
{"first_name"=>"", "last_name"=>"", "weddings_attributes"=>{"0"=>{"wedding_assignments_attributes"=>{"0"=>{"role"=>"Bride's Maid", "budget"=>""}}, "date"=>"", "ceremony_notes"=>""}}}
And the exact error is:
ActiveRecord::RecordInvalid in PeopleController#create
Validation failed: Role can't be blank
Which is clearly not correct, since you can see it in params[]
What am I doing wrong?
This is rails 3.0.0
Right, this is a bit of a guess, so apologies if I wind up wasting your time here...
It looks to me like in your create method, you're creating the 'wedding' relationship (which is only a 'pretend' relationship really, has it's using :through => :wedding_assignments), and then returning this. You're then asking rails to re-create these objects in your call to Person.new. My guess is that rails is getting confused by trying to create an object at the far side of a has_many :through without the intermediate object being present.
I would be tempted to restructure this a little (untested code!):
def new
#person = Person.new
#wedding = Wedding.new
#wedding_assignment = WeddingAssignment.new
end
def create
#person = Person.new(params[:person])
#wedding = Wedding.new(params[:person])
#assignment = WeddingAssignment.new(params[:wedding_assignment].merge({:person => #person}))
end
I've got a feeling this'll work until the last line. I suspect to get that to work you might need to use transactions:
def create
#person = Person.new(params[:person])
#wedding = Wedding.new(params[:person])
ActiveRecord::Base.transaction do
if #person.valid? && #wedding.valid?
[#person,#wedding].each.save!
#assignment = WeddingAssignment.new(params[:wedding_assignment].merge({:person => #person}))
#assignment.save!
end
end
end
This ought to ensure that everything is created in the right order and IDs are available at the right times etc. Unfortunately though, it's a bit more complicated than your example, and does mean that you'll struggle to support multiple weddings.
Hope this helps, and doesn't wind up being a blind alley.
Try changing "Person.new" to "Person.create", maybe creating the record in the db right away will help with the associations.