I want to destroy data from relationships but I can't - ruby-on-rails

I want user to remove group that user attending groups by removing groupings.
So, I tried write below the code , but when run leave action , it happen error that
Unknown key: group_id.But I don't know how to deal with it . Please some help.
Thanks in advance.
GroupingsController.rb
def leave
#user = current_user
#group = Group.find(params[:id])
#user.remove(#group)
redirect_to :back , notice: "Destroy!"
end
User.rb
has_many :groups, :through => :groupings,:source => :group
def remove(group)
groupings.find_by_group_id(:group_id => group).destroy
end
# attend method is work correctly.
def attend(group)
groupings.create(:group_id => group)
end

You've already specified that you're using group_id (by saying find_by_group_id) - so you don't need to specify it again by passing it as a 'key' (eg :group_id =>)
So your code should just be
def remove(group)
groupings.find_by_group_id(group.id).destroy
end

Related

How to implement retweet functionality in RoR?

I'm trying to implement retweet functionality on my app.
So I have my retweet_id in my tweets model
tweets schema
| user_id | content | created_at | updated_at | retweet_id
tweets.rb
belongs_to :user
has_many :retweets, class_name: 'Tweet', foreign_key: 'retweet_id'
user.rb
has_many :tweets
And in my tweets controller
tweets_controller.rb
...
def retweet
#retweet = Tweet.new(retweet_params)
if #retweet.save
redirect_to tweet_path, alert: 'Retweeted!'
else
redirect_to root_path, alert: 'Can not retweet'
end
end
Private
...
def retweet_params
params.require(:retweet).permit(:retweet_id, :content).merge(user_id: current_user.id)
end
In my view
tweets/show.html.erb
<%= link_to 'Retweet', retweet_tweet_path(#tweet.id), method: :post %>
My routes
resources :tweets do
resources :comments
resources :likes
member do
post :retweet
end
end
So when I try this I get an error
param is missing or the value is empty: retweet
So I remove .require from 'retweet_params' and that removes that error (though i'm unsure of how wise that is)
Then the link works but won't retweet - reverting to the fallback root_path specified in my action instead.
Unpermitted parameters: :_method, :authenticity_token, :id
Redirected to http://localhost:3000/
I'm not sure what i'm doing wrong. How can I get my retweets working? ty
The reason retweet_params raises an error is because your link link_to 'Retweet', retweet_tweet_path(#tweet.id), method: :post doesn't contain parameters like a new or edit form does. Instead you should create a new tweet that reference to tweet you want to retweet.
before_action :set_tweet, only: %i[show edit update destroy retweet]
def retweet
retweet = #tweet.retweets.build(user: current_user)
if retweet.save
redirect_to retweet, notice: 'Retweeted!'
else
redirect_to root_path, alert: 'Can not retweet'
end
end
private
def set_tweet
#tweet = Tweet.find(params[:id])
end
The above should automatically link the new tweet to the "parent". If this doesn't work for some reason you could manually set it by changing the above to:
retrweet = Tweet.new(retweet_id: #tweet.id, user: current_user)
The above approach doesn't save any content, since this is a retweet.
If you don't want to allow multiple retweets of the same tweet by the same user, make sure you have the appropriate constraints and validations set.
# migration
add_index :tweets, %i[user_id retweet_id], unique: true
# model
validates :retweet_id, uniqueness: { scope: :user_id }
How do we access the content of a retweet? The answer is we get the content form the parent or source (however you want to call it).
There is currently no association that lets you access the parent or source tweet. You currently already have:
has_many :retweets, class_name: 'Tweet', foreign_key: 'retweet_id'
To easily access the source content let's first add an additional association.
belongs_to :source_tweet, optional: true, inverse_of: :retweets, class_name: 'Tweet', foreign_key: 'retweet_id'
has_many :retweets, inverse_of: :source_tweet, class_name: 'Tweet', foreign_key: 'retweet_id'
With the above associations being set we can override the content getter and setter of the Tweet model.
def content
if source_tweet
source_tweet.content
else
super
end
end
def content=(content)
if source_tweet
raise 'retweets cannot have content'
else
super
end
end
# depending on preference the setter could also be written as validation
validates :content, absence: true, if: :source_tweet
Note that the above is not efficient when talking about query speed, but it's the easiest most clear solution. Solving parent/child queries is sufficiently difficult that it should get its own question, if speed becomes an issue.
If you are wondering why I set the inverse_of option. I would recommend you to check out the section Active Record Associations - 3.5 Bi-directional Associations.
Right now the error you're seeing is the one for strong params in Rails. If you can check your debugger or the HTTP post request that's being sent, you'd find that you don't have the params that you're "requiring" in retweet_params
def retweet_params
params.require(:retweet).permit(:retweet_id, :content).merge(user_id: current_user.id)
end
This is essentially saying that you expect a nested hash for the params like so
params = { retweet: { id: 1, content: 'Tweet' } }
This won't work since you're only sending the ID. How about something like this instead?
TweetsController.rb
class TweetsController < ApplicationController
def retweet
original_tweet = Tweet.find(params[:id])
#retweet = Tweet.new(
user_id: current_user.id,
content: original_tweet.content
)
if #retweet.save
redirect_to tweet_path, alert: 'Retweeted!'
else
redirect_to root_path, alert: 'Can not retweet'
end
end
end

Rails: corret way of validate number of records

In my case, user has many categories, and I want to prevent user to delete his last category, this is what I did in Categories#destroy:
def destroy
if current_user.categories.count > 1
#category.destroy
redirect_to categories_url
else
flash[:category_notice] = "Cannot destroy the last category"
redirect_to categories_url
end
end
Obviously, this solves the problem. But I want to know if there is a corret way of doing it? Or this is just fine?
I would prefer to do it in the model (thatway you can prevent destroying the last record when you are working on the console too).
For ex you can use callback inside your Category-model:
before_destroy: check_if_last
def check_if_last
raise "Can't destroy last category!" if self.user.categories.count == 1
end
EDIT: To avoid race condition on user object, locking should do.
I would add a counter onto the user. So a user has a categories_count field, with a default of 0.
add_column :users, :categories_count, :integer, default: 0
then
class User
has_many :categories
end
class Category
belongs_to :user, counter_cache: true
end
You might want to use reset_counters:
User.find_each do |u|
User.reset_counters(u.id, :categories)
end
To set the counters to the appropriate values.
Then you can do
if current_user.categories_count > 1
#category.destroy
redirect_to categories_url
else
flash[:category_notice] = "Cannot destroy the last category"
redirect_to categories_url
end
This way the count is cached and it saves you a query. It's not much of a difference and your way is fine.

Before_destroy filter now calling given method

My User's have one Group assigned each. If admin removes they group they are in i need to move them down to most basic gorup "Member" that cannot be removed.
Now i know that the method works and routing and controller however because at first i called the method moveUsers from controller in order:
user = User.find(id)
user.moveUsers
user.delete
However i want to seperate this and keep controller minimalistic, the: Rails3 way?
Controller method:
def destroy
group = Group.find(params[:id])
if group.delete
redirect_to groups_url, :notice => "Group deleted."
else
redirect_to :back, :notice => "Cannot delete group."
end
end
Model:
class Group < ActiveRecord::Base
before_destroy :moveUsers
validates :title, :presence => :true, :uniqueness => true
has_many :user
# Get the number of users associated with the group & title
def getUserCount
return User.where(:group_id => self.id).length
end
# If group is deleted move all of its users
# to core group: Members (id = 1)
private
def moveUsers
users = User.where(:group_id => self.id)
users.each do |user|
user.group_id = 1
user.save
end
end
end
The delete method ignores callback methods
You need to use group.destroy in order to fire the before_destroy callback
http://guides.rubyonrails.org/active_record_validations_callbacks.html#skipping-callbacks

Rails 3: alias_method_chain to set specific attribute first

When user's create a post I'd like to set the user_id attribute first. I'm trying to do this using alias_method_chain on the arrtibutes method. But I'm not sure if this is right as the problem I thought this would fix is still occurring. Is this correct?
Edit:
When my users create a post they assign 'artist(s)' to belong to each post, using a virtual attribute called 'artist_tokens'. I store the relationships in an artist model and a joined table of artist_ids and post_ids called artisanships.
I'd like to to also store the user_id of whomever created the artist that belongs to their post (and I want it inside the artist model itself), so I have a user_id column on the artist model.
The problem is when I create the artist for each post and try to insert the user_id of the post creator, the user_id keeps showing as NULL. Which is highly likely because the post's user_id attribute hasn't been set yet.
I figured to get around this I needed to set the user_id attribute of the post first, then let the rest of the attributes be set as they normally are. This is where I found alias_method_chain.
post.rb
attr_reader :artist_tokens
def artist_tokens=(ids)
ids.gsub!(/CREATE_(.+?)_END/) do
Artist.create!(:name => $1, :user_id => self.user_id).id
end
self.artist_ids = ids.split(",")
end
def attributes_with_user_id_first=(attributes = {})
if attributes.include?(:user_id)
self.user_id = attributes.delete(:user_id)
end
self.attributes_without_user_id_first = attributes
end
alias_method_chain :attributes=, :user_id_first
EDIT:
class ArtistsController < ApplicationController
def index
#artists = Artist.where("name like ?", "%#{params[:q]}%")
results = #artists.map(&:attributes)
results << {:name => "Add: #{params[:q]}", :id => "CREATE_#{params[:q]}_END"}
respond_to do |format|
format.html
format.json { render :json => results }
end
end
In your controller, why not just do this:
def create
#post = Post.new :user_id => params[:post][:user_id]
#post.update_attributes params[:post]
...
end
But it seems to me that it would be much better to create the artist records after you've done validation on the post rather than when you first assign the attribute.
EDIT
I would change this to a callback like this:
class Post < ActiveRecord::Base
attr_accessor :author_tokens
def artist_tokens=(tokens)
#artist_tokens = tokens.split(',')
end
after_save :create_artists
def create_artists
#artist_tokens.each do |token|
...
end
end
end

How to create a full Audit log in Rails for every table?

We recently began a compliance push at our company and are required to keep a full history of changes to our data which is currently managed in a Rails application. We've been given the OK to simply push something descriptive for every action to a log file, which is a fairly unobtrusive way to go.
My inclination is to do something like this in ApplicationController:
around_filter :set_logger_username
def set_logger_username
Thread.current["username"] = current_user.login || "guest"
yield
Thread.current["username"] = nil
end
Then create an observer that looks something like this:
class AuditObserver < ActiveRecord::Observer
observe ... #all models that need to be observed
def after_create(auditable)
AUDIT_LOG.info "[#{username}][ADD][#{auditable.class.name}][#{auditable.id}]:#{auditable.inspect}"
end
def before_update(auditable)
AUDIT_LOG.info "[#{username}][MOD][#{auditable.class.name}][#{auditable.id}]:#{auditable.changed.inspect}"
end
def before_destroy(auditable)
AUDIT_LOG.info "[#{username}][DEL][#{auditable.class.name}][#{auditable.id}]:#{auditable.inspect}"
end
def username
(Thread.current['username'] || "UNKNOWN").ljust(30)
end
end
and in general this works great, but it fails when using the "magic" <association>_ids method that is tacked to has_many :through => associations.
For instance:
# model
class MyModel
has_many :runway_models, :dependent => :destroy
has_many :runways, :through => :runway_models
end
#controller
class MyModelController < ApplicationController
# ...
# params => {:my_model => {:runways_ids => ['1', '2', '3', '5', '8']}}
def update
respond_to do |format|
if #my_model.update_attributes(params[:my_model])
flash[:notice] = 'My Model was successfully updated.'
format.html { redirect_to(#my_model) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #my_model.errors, :status => :unprocessable_entity }
end
end
end
# ...
end
This will end up triggering the after_create when new Runway records are associated, but will not trigger the before_destroy when a RunwayModel is deleted.
My question is...
Is there a way to make it work so that it will observe those changes (and/or potentially other deletes)?
Is there a better solution that is still relatively unobtrusive?
I had a similar requirement on a recent project. I ended using the acts_as_audited gem, and it worked great for us.
In my application controller I have line like the following
audit RunWay,RunWayModel,OtherModelName
and it takes care of all the magic, it also keeps a log of all the changes that were made and who made them-- its pretty slick.
Hope it helps
Use the Vestal versions plugin for this:
Refer to this screen cast for more details. Look at the similar question answered here recently.
Vestal versions plugin is the most active plugin and it only stores delta. The delta belonging to different models are stored in one table.
class User < ActiveRecord::Base
versioned
end
# following lines of code is from the readme
>> u = User.create(:first_name => "Steve", :last_name => "Richert")
=> #<User first_name: "Steve", last_name: "Richert">
>> u.version
=> 1
>> u.update_attribute(:first_name, "Stephen")
=> true
>> u.name
=> "Stephen Richert"
>> u.version
=> 2
>> u.revert_to(10.seconds.ago)
=> 1
>> u.name
=> "Steve Richert"
>> u.version
=> 1
>> u.save
=> true
>> u.version
=> 3
Added this monkey-patch to our lib/core_extensions.rb
ActiveRecord::Associations::HasManyThroughAssociation.class_eval do
def delete_records(records)
klass = #reflection.through_reflection.klass
records.each do |associate|
klass.destroy_all(construct_join_attributes(associate))
end
end
end
It is a performance hit(!), but satisfies the requirement and considering the fact that this destroy_all doesn't get called often, it works for our needs--though I am going to check out acts_as_versioned and acts_as_audited
You could also use something like acts_as_versioned http://github.com/technoweenie/acts_as_versioned
It versions your table records and creates a copy every time something changes (like in a wiki for instance)
This would be easier to audit (show diffs in an interface etc) than a log file

Resources