Rails 4 create new Tag - ruby-on-rails

I have a few questions about how to add a Tag to a users account:
Here is the User Model in relation to Tags
has_many :tags, through: :taggings
Here is the Tag Model:
class Tag < ActiveRecord::Base
attr_accessor :unread_count, :user_feeds
has_many :taggings
has_many :feeds, through: :taggings
end
And the Tagging Model:
class Tagging < ActiveRecord::Base
belongs_to :tag
belongs_to :feed
belongs_to :user
end
I'm at a point in the script where I have the current user object #user and I need to simply create a tag named "Mailbox" if it does not exist. I have tried a few create methods, and got expected object errors.
If anyone can help explain how to work with these models I would appreciate it.

A legit way to do this is to
#user.tags.create(name: "Mailbox")
If you want to check if it exists first, the rails 4 way is to:
#user.tags.find_or_create_by(name: "Mailbox")
Comment if you have any more question.

You can simply use the << method which the has_many association automatically adds:
#user.tags << Tag.find_or_create_by(name:'Mailbox')
Or, the rails 3 way:
#user.tags << Tag.where(name:'Mailbox').first_or_create!(name:'Mailbox')
# Or, as the << method automatically saves new objects
#user.tags << Tag.where(name:'Mailbox').first_or_initialize(name:'Mailbox')

Related

Create multiple submodels into the main model's form

Here is the situation :
class Activity < ApplicationRecord
has_many :activity_amenities
end
class Amenity < ApplicationRecord
has_many :activity_amenities
has_many :activities, through: :activity_amenities
end
class ActivityAmenity < ApplicationRecord
belongs_to :activity
belongs_to :amenity
end
So basically i have Activities that have Amenities. The reason for me to use a has_many :through association is because I want to create basic Amenities and each Activity' amenities will have a description proper to the Activity.
So, I want to create new Amenities straight in the creation/edition of an Activity. This image should illustrate it very well :
So when I click on Add amenity button, it should add new select/input group.
Then when I click on create, it should create all the ActivityAmenities and associate them with the Activity model.
Any idea How this should be done (in the Controller and in the View) ? Couldn't find anything really...
ps : note that I'm using simple-form gem for the forms
For all your has_many associations, include the inverse_of option. Then when adding the ActivityAmenities by ID, pass them all in at once as an array. e.g when you make activity_params, pass in activity_amenity_ids[]. Rails will use the automatically generated #activity_amenity_ids= method to handle the creation for you.
Model:
class Activity < ApplicationRecord
has_many :activity_amenities, inverse_of: activity
end
class Amenity < ApplicationRecord
has_many :activity_amenities, inverse_of: amenity
has_many :activities, through: :activity_amenities
end
class ActivityAmenity < ApplicationRecord
belongs_to :activity
belongs_to :amenity
end
Controller:
params.require(:activity).permit(activity_amenity_ids[], :whatever_else)
View: Whatever the name of your input for your form
name="activity[activity_amenity_ids][]"
If you want an explanation of why you you pass in activity_amenity_ids, is basically tl;dr it creates the object if it doesn't exist, and does nothing if it does.
E.g try
Activity.create(name: "Running or whatever", activity_amenity_ids: [1, 2, 3])
And see the result. Notice how a new activity is created along with its associated activity amenities in a single transaction?

Rails has_many :through #new doesn't set associations on new record

As per the Rails docs, one can use has_many :through as a shortcut:
The has_many :through association is also useful for setting up
"shortcuts" through nested has_many associations. For example, if a
document has many sections, and a section has many paragraphs, you may
sometimes want to get a simple collection of all paragraphs in the
document.
So let's say we have this code:
class User < ApplicationRecord
has_many :sub_users
has_many :settings
end
class SubUser < ApplicationRecord
belongs_to :user
has_many :settings, through: :user
end
class Setting < ApplicationRecord
belongs_to :user
end
Based on this, if I run user.settings.new, I get a new Setting instance with user_id set to user.id.
That's great. But if I run sub_user.settings.new, I get a new Setting instance that doesn't have user_id set to sub_user.user.id.
Is this expected behavior?
I wouldn't use has_many through: for that, delegate looks like the best idea https://apidock.com/rails/Module/delegate
class SubUser < ApplicationRecord
belongs_to :user
delegate :settings, to: :user
end
Your current code is not what has_many through is for, check the docks, the relations are different https://guides.rubyonrails.org/association_basics.html#the-has-many-through-association

rails: deleting objects in has_many through only if no longer referenced

A posts model:
class Post < ActiveRecord::Base
has_many :taggings, dependent: :destroy
has_many :tags, through: :taggings
and a tags model:
class Tag < ActiveRecord::Base
has_many :taggings
has_many :posts, through: :taggings
and the associated join table:
class Tagging < ActiveRecord::Base
belongs_to :tag, dependent: :destroy
belongs_to :post
so, the dependent: :destroy in Post makes the db entries in Tagging to be destroyed when doing for instance Post.last.destroy, and the dependent: :destroy in Tagging makes the associated Tag object to be destroyed as well. Fine.
My problem is, 2 posts may share the same Tag, and I would like to destroy that tag -only- if the post being deleted is the last one referencing it. I don't allow duplicate entries in the Tag table. The tags for a post are submitted as a a string with the tags separated by commas, and upon creation of a post I do the following:
def tag_names=(names)
self.tags = names.split(",").map{ |tag| Tag.where(name: tag.squish).first_or_create! }
end
Any idea on how I could achieve this? Thanks!
Are you using a plugin? act_as_taggable or something like that?
If so, take a look to the docs because probably is already implemented.
If not, you can always implement the after_destroy callback to count the elements associated to a tag and delete the tag in the case that you have "empty" tags.
For instance, in your Post model:
class Post
before_destroy :clean_up_tags
protected
def clean_up_tags
tags_to_delete = Tagging.where(id: self.tag_ids).group(:tag_id).having("count(distinct taggable_id) = 1").pluck(:id)
Tag.find(tags_to_delete).map(&:destroy)
end
end
This method is assuming that you have a method tag_ids that returns the tags associated to an specific Post and that your taggings model is polymorphic).
As you probably have more than one model with the tags feature, a good approach would be packing this method into a module and include it in all the models, in this way you keep the things DRY.

.build method not creating association in the join table

I have three Models setup with the following associations
class User < ActiveRecord::Base
has_many :faculties
has_many :schools, :through => :faculties
end
class School < ActiveRecord::Base
has_many :faculties
has_many :users, :through => :faculties
end
class Faculty < ActiveRecord::Base
belongs_to :user
belongs_to :school
end
and in my controller i go to create a school and assign the user
class SchoolsController < ApplicationController
def create
#school = current_user.schools.build(params[:school])
...
end
end
When I login and submit the form the flash displays success, but the association doesn't build on the join table.
I tried it inside the apps console and it builds the association just fine.
I've been stuck on this for a couple days now and I just cannot figure out what I am missing. Thank in advance for any and all advice
The build method does not save the object. You need to explicitly call #school.save.
Two things: If the schools association is :through a has_many association, you will have to select which parent the School exists through.
So, for instance, if you were to nest School resources under users as in /users/:id/faculties/:id you could create a school via current_user.faculties.find(params[:faculty_id]).schools.build(params[:school]).save
Based on the example code, it looks like the fundamental problem is that the has_many xxx, :through syntax is being used without specifying the id of the faculties record. Remember two things: 1) ActiveRecord doesn't natively support composite primary keys, and 2) you must call #save on associated records created using #build. If you remember these, you should be fine.

Can't update rails has_many :through relationship

I try:
#item.associations.update_attributes(:tag_id=>params[:tag])
and
#item.associations.tag_id=params[:tag]
Both give me undefined method errors for update_attributes and tag_id=, respectively. Here's my setup:
class Item < ActiveRecord::Base
has_many :associations,:foreign_key=>"item_id",:dependent=>:destroy
has_many :reverse_associations,:foreign_key=>"tag_id",:class_name=>"Association"
has_many :tags,:through=>:associations
end
class Tag < ActiveRecord::Base
has_many :associations,:foreign_key=>"tag_id",:dependent=>:destroy
has_many :reverse_associations,:foreign_key=>"item_id",:class_name=>"Association"
has_many :items,:through=>:associations
attr_accessible :name
end
class Association < ActiveRecord::Base
belongs_to :item
belongs_to :tag
end
What am I doing wrong?
You're trying to update tag_id on the entire #item.associations collection instead of updating a single Assocation instance.
The proper way to solve this depends on what you're trying to accomplish. To update the tag_id for all associations in #item.association, try:
#item.associations.each do |association|
association.update_attributes(:tag_id => params[:tag])
end
If you want to update the tag id for a specific Association, then you somehow need to get that association first:
# Just picking the first association for the item as an example.
# You should make sure to retrieve the association that you actually
# want to update.
retagged_association = #item.associations.first
# Now, retag the association
retagged_association.update_attributes(:tag_id => params[:tag])

Resources