How do I construct the proper before_destroy in model in rails - mysql

I have the following which deletes a bin and everything related to it. It's great and functions;
MODEL BIN
class Bin < ActiveRecord::Base
has_many :savedtweets, :dependent => :destroy
before_destroy :mod_newtweets
def mod_newtweets
Newtweet.where(:tweet_id => #bin.savedtweets.pluck(:tweet_id)).update_all(:status => 'new')
end
end
However, it destroys a bin, deletes everything but doesn't run :mod_newtweets to update my other table and its column.
If I put this in the controller it works fine;
Newtweet.where(:tweet_id => #bin.savedtweets.pluck(:tweet_id)).update_all(:status => 'new')
I thought I've got everything done right.

Replace your method with:
def mod_newtweets
Newtweet.where(:tweet_id => savedtweets.pluck(:tweet_id)).update_all(:status => 'new')
end
You are using #bin.savedtweets in your model while you have not defined #bin anywhere. as it is an instance method you can use either self.savedtweets or only savedtweets to call the savedtweets method on current instance of Bin model.

Related

Nested strong parameters in rails - AssociationTypeMismatch MYMODEL expected, got ActionController::Parameters()

I'm rendering a model and it's children Books in JSON like so:
{"id":2,"complete":false,"private":false, "books" [{ "id":2,"name":"Some Book"},.....
I then come to update this model by passing the same JSON back to my controller and I get the following error:
ActiveRecord::AssociationTypeMismatch (Book (#2245089560) expected, got ActionController::Parameters(#2153445460))
In my controller I'm using the following to update:
#project.update_attributes!(project_params)
private
def project_params
params.permit(:id, { books: [:id] } )
end
No matter which attributes I whitelist in permit I can't seem to save the child model.
Am I missing something obvious?
Update - another example:
Controller:
def create
#model = Model.new(model_params)
end
def model_params
params.fetch(:model, {}).permit(:child_model => [:name, :other])
end
Request:
post 'api.address/model', :model => { :child_model => { :name => "some name" } }
Model:
accepts_nested_attributes_for :child_model
Error:
expected ChildModel, got ActionController::Parameters
Tried this method to no avail: http://www.rubyexperiments.com/using-strong-parameters-with-nested-forms/
Are you using accepts_nested_attributes_for :books on your project model? If so, instead of "books", the key should be "books_attributes".
def project_params
params.permit(:id, :complete, :false, :private, books_attributes: [:id, :name])
end
I'm using Angular.js & Rails & Rails serializer, and this worked for me:
Model:
has_many :features
accepts_nested_attributes_for :features
ModelSerializer:
has_many :features, root: :features_attributes
Controller:
params.permit features_attributes: [:id, :enabled]
AngularJS:
ng-repeat="feature in model.features_attributes track by feature.id
My solution to this using ember.js was setting the books_attributes mannualy.
In controller:
def project_params
params[:project][:books_attributes] = params[:project][:books_or_whatever_name_relationships_have] if params[:project][:books_or_whatever_name_relationships_have]
params.require(:project).permit(:attr1, :attr2,...., books_attributes: [:book_attr1, :book_attr2, ....])
end
So rails checks and filters the nested attributes as it expected them to come
This worked for me. My parent model was an Artist and the child model was a Url.
class ArtistsController < ApplicationController
def update
artist = Artist.find(params[:id].to_i)
artist.update_attributes(artist_params)
render json: artist
end
private
def artist_params
remap_urls(params.permit(:name, :description, urls: [:id, :url, :title, :_destroy]))
end
def remap_urls(hash)
urls = hash[:urls]
return hash unless urls
hash.reject{|k,v| k == 'urls' }.merge(:urls_attributes => urls)
end
end
class Artist < ActiveRecord::Base
has_many :urls, dependent: :destroy
accepts_nested_attributes_for :urls, allow_destroy: true
end
class Url < ActiveRecord::Base
belongs_to :artist
end
... and in coffeescript (to handle deletions):
#ArtistCtrl = ($scope, $routeParams, $location, API) ->
$scope.destroyUrls = []
$scope.update = (artist) ->
artist.urls.push({id: id, _destroy: true}) for id in $scope.destroyUrls
artist.$update(redirectToShow, artistError)
$scope.deleteURL = (artist,url) ->
artist.urls.splice(artist.urls.indexOf(url),1)
$scope.destroyUrls.push(url.id)
Something is missing from all of the answers, which is the inputs for fields_for in the form.
The form works if you do this:
f.fields_for #model.submodel do ..
However, the form is sent as model[submodel], but that's what causes the error others have mentioned in their answers. If you try to do model.update(model_params), Rails will raise an error that it's expecting a Submodel type.
To fix this, make sure you follow the :name, value format:
f.fields_for :submodel, #model.submodel do ...
Then in the controller, make sure you put _attributes on your params:
def model_params
params.require(:model).permit(submodel_attributes: [:field])
end
Now the save, update, etc. will work fine.
Wasted several days trying to figure out how to use accepts_nested_attributes with Angular, and the issue is always the same: Rails whitelist will not allow the variables into the params hash. I've tried every single different whitelisting syntax that everyone said on SO and other blogs, tried using :inverse, tried using habtm and mas_many_through, tried manually rolling my own solution but that wont work if the whitelist wont allow params through, tried doing what http://guides.rubyonrails.org says about 'Outside the Scope of Strong Parameters', tried removing whitelisting all together which isnt really an option but it causes other problems anyways. Not sure why rails 4 strong parameter whitelisting wont allow arbitrary data thru, thats a huge problem especially if accepts_nested_attributes doesn't work either.... I guess we are left to just create/delete all associations on a separate page/form/controller and look like an idiot making my end users use several forms/pages to do something that should be easily doable on 1 page with 1 form. Ya know, usually I expect Angular to screw me, but this time Angular worked quite well and it was actually Rails 4 that screwed me twice on 1 issue that should be very straightforward.

Able to add myself as friend in rails console

I have built a user and friend relationship model but the problem is that with those associations I can friend myself. I have successfully suppressed it in my views and controller, but logically it should be suppressed in the model because I could still create the friendship from the console which I want to avoid.
User model
has_many :user_friendships
has_many :friends, through: :user_friendships,
conditions: { user_friendships: { state: 'accepted' } }
User_friendship model
belongs_to :user
belongs_to :friend, class_name: 'User', foreign_key: 'friend_id'
Everything else is working perfectly like adding, blocking, deleting, requesting a friend the only problem with my model is that I can also friend myself which I want to avoid.
Add a validation to UserFriendship:
validate :cannot_friend_self
def cannot_friend_self
errors.add(:friend_id, "cannot friend self") unless user_id != friend_id
end
This issue is a little problematic because we want to remain RESTful, separate the different tasks (MVC,) and take into account of weird race conditions (Thread Safety.)
Try using validations#exclusions (http://guides.rubyonrails.org/active_record_validations_callbacks.html#exclusion)
class ApplicationController < ActionController::Base
...
before_filter do |c|
User.current_user = User.find(c.session[:user]) unless c.session[:user].nil?
end
...
end
class User < ActiveRecord::Base
...
cattr_accessor :current_user
...
end
class Friends < ActiveRecord::Base
...
validates :friend_id, :exclusion => { :in => %w(User.current_user.id),
:message => "I don't think you really want to friend yourself" }
...
end
If you want to be safe, please refer to (http://nhw.pl/wp/2011/11/30/passing-current-user-id-to-rails-models)
Disclaimer:
I wrote this possible solution without testing it (aka pulled it out of the thin air with little reference)
I have not thread with Ruby on Rails.
You probably want to throw in a validation
Such as
validate :cannot_friend_self
def cannot_friend_self
current_user.id != friend.id
end
This code may not be exactly what you want, but should point you in the right direction.
Full guide here http://guides.rubyonrails.org/active_record_validations_callbacks.html#custom-methods

Rails MySQL query with Single Table Inheritance N+1 issue

I am trying to find all users that signed up during a given period of time to the ActionMovie plan. I am running into an N+1 problem and it's taking me a very long time to get the number of new signups. I was wondering if there was any creative thing I could do with arel_tables or something like that that could help cut down on this process?
My current code looks similar to the below:
#find all UserMovies created during time frame
user_movies = UserMovie.where(:created_at => start_time..end_time)
#find users
users = user_movies.collect {|um| um.user}
#iterate through each users user_movies and see if the their first action movie was during the time frame I am looking for
users.each do |user|
user_movies_array = user.user_movies.map {|um| {um.movie.type => um.created_at}}
user_movies_array.each do |um|
if um["ActionMovie"] > start_time
puts "new user"
end
end
end
Class User
has_many :user_movies
has_many :movies, :through => :user_movies
end
Class Movie
has_many :user_movies, :foreign_key => :movie_id
has_many :users, :through => :user_movies
end
Class UserMovie
belongs_to :user
belongs_to :movie
end
Class ActionMovie < Movies
end
Class SuspenseMovie < Movies
end
Have you tried eager loading the Movie association using the :include option?
Take a look at the API docs for #has_many to see specific implementation and scroll to the top to the section called Eager loading of associations to see a general overview.

Insert Data into Database in Ruby on Rails

I got 2 Models, Main Model and Details Model.
In my Home Controller i want to insert 1.000.000 entries into the database, just for testing issues.
But this is very slow, the inserts are happening very slow.. is it possible to speed this up?
Maybe i did something wrong with the configuration?
Because i want, every time a Main entry is saved, a corresponding detail entry should be created.
Main Model looks like:
class Main < ActiveRecord::Base
has_one :detail, :dependent => :destroy
before_create :build_a_detail
private
def build_a_detail
self.build_detail(:Bezeichnung => 'test', :Koordinaten => 10, :main_id => self.id)
end
end
Detail Model looks like:
class Detail < ActiveRecord::Base
belongs_to :main
end
Home Controller:
def new
for i in 0..1000000
main = Main.new(:Category => 'Krankenhaus', :Latitude => 5, :Longitude => 6)
main.save
end
end
The problem is that a single insert query is executed into the DB for every create and that slows the application.
You can have bulk inserts with Rails too:
Bulk insert in RAILS

validating uniqueness within a has_many through

I have a User that can have many Restaurants. I can also have multiple users.
I'd like to have it so that if User A creates Restaurant A, he should NOT be able to make another restaurant with the same name.
However, if User B goes to create Restaurant A, that should be allowed but still cannot make another Restaurant A afterwards.
I have the following has_many through relationship:
restaurant.rb
has_many :ownerships
has_many :users, :through => :ownerships
# This following ensures uniqueness of the name within the
# Restaurants table regardless of who the User is that created it.
validates :name, presence: true, uniqueness: true
user.rb
has_many :ownerships
has_many :restaurants, :through => :ownerships
ownership.rb
belongs_to :restaurant
belongs_to :user
What I've Tried
1. Adding :uniqu => true
I've tried adding :uniq => true to the restaurant.rb file so it looks like this:
has_many :ownerships
has_many :users, :through => :ownerships, :uniq => true
And removing uniqueness: true from the validation so it looks like this:
validates :name, presence: true
But that doesn't do anything useful.
2. Adding validation within ownership.rb
I've tried adding the validation to the ownership.rb file as such:
validates :restaurant, uniqueness: {:scope => :user}
But I get:
NoMethodError in RestaurantsController#create
undefined method `text?' for nil:NilClass
And I can't seem to tell it to look for the restaurant name within the scope of user either within this validation.
3. Creating before_create callback function
In my restaurant.rb file, I declared the following:
before_create :check_uniqueness
def check_uniqueness?
user = User.find_by_id(self.user_ids)
isUnique = false
user.restaurants.each do |restaurant|
if !Restaurant.find_by_name(self.name).nil? # Restaurant w/ same now found
isUnique = false
else
isUnique = true
end
return isUnique
end
end
My assumption is that before the restaurant record is created, it'll do this check_uniqueness check and if the function returns false, it'll not save.
But I'm getting the following error when I hit the submit button:
NameError in RestaurantsController#create
undefined local variable or method `check_uniqueness' for #<Restaurant:0x007f95a16d10f8>
Working Solution
Thanks to Robert Chuchro's help below, I was able to get the validation to work. Here's what I did:
restaurant.rb
before_create :unique_per_user?
def unique_per_user?
user = User.find_by_id(self.user_ids)
restaurant = user.restaurants.find(:all, :conditions => ["name = ?", self.name])
if restaurant.size > 0
self.errors.add(:name, ": You've already created a restaurant with this name.")
end
return (restaurant.size <= 0)
end
You can try to define a method to do this in your restaurant model
def unique_per_user?
#get user trying to create restaurant, either by paramter or association
#check if any of the user's current restaurant names match this name (return true/false)
end
now whereever you define a new restaurant check if its unique_per_user? before deciding to save it.