Delete a Post without the associated Task - sql

I would like to be able to destroy my Post without the Task which is associated. But I'm currently facing a SQL error which say:
ActiveRecord::InvalidForeignKey (SQLite3::ConstraintException: FOREIGN KEY constraint failed: DELETE FROM "posts" WHERE "posts"."id" = ?):
After a few search, I saw that is coming from associations and Foreign Key. But I cannot solve the problem for the moment.
I have tested to put (optional: true) into my model.
I also have tried to change the foreign key into (, on_delete: :cascade) & (, on_delete: :nullify) but it's still not working.
My code =
//Post Model
class Post < ApplicationRecord
has_one :task
end
//Task Model
class Task < ApplicationRecord
belongs_to :post, optional: true
end
To destroy :
//Destroy_one into the Post Controller
def destoy_one
#post.destroy
end
Migration File : (also tried with on_delete: :nullify)
class EditForeightKeys < ActiveRecord::Migration[5.1]
def change
# remove the old foreign_key
remove_foreign_key :tasks, :post
# add the new foreign_key
add_foreign_key :tasks, :post, on_delete: :cascade
end
end
Do you have any other solution for that ?

I solve this issue by implement a deleted_state into Posts
def destroy_one
#post.update(deleted_state: true)
end
And after you can put a default scope into your Post model like this :
default_scope { where(deleted_state: false) }
Like this all will work without problems !!

You can use callbacks on destroy action for User as a variant:
class Post
before_destroy :update_tasks
private
def update_tasks
self.tasks.update_all(post_id: nil)
end
end

Related

Rails: Scope parent model by attribute of child

I'm having a tough time figuring something out in Rails. It probably has to do with my very limited knowledge of SQL, since I know Rails pretty well. I'm using Rails 5.
I have two models: Applicant and Application.
class Applicant < ApplicationRecord
has_one :application
has_many :skills
accepts_nested_attributes_for :application
accepts_nested_attributes_for :skills,
reject_if: ->(skill) { skill[:name].empty? || skill[:experience].empty? }
validates_with ApplicantValidator
end
class Application < ApplicationRecord
belongs_to :applicant
has_many :notes
VALID_STATUSES = ["in review", "accepted", "declined", "closed"]
validates_length_of :why_interested, minimum: 25
validates :accept_terms, acceptance: true
validates :status, inclusion: { in: VALID_STATUSES }
before_validation :set_status
private
def set_status
self.status ||= "in review"
end
end
I'd like to add a scope, :active, to the Applicant model that returns only applicants who have an application whose status is "in review". However, I can't find a way to access the application within a scope proc.
I've seen other suggestions for cases where there is a has_many relationship with the child, but they didn't work in my case.
I doubt it makes a difference, but I'm using Postgres. The closest I've come to a solution is to add this, but when I run RSpec it says there needs to be a FROM-clause for the applications table. I don't know how to effect that.
scope :active, -> { joins(:application).where('"application"."status" = "in review"') }
scope :in_review_applicants, -> { joins(:application).where('application.status = ?', :in_review) }
I think is something like that..

How to solve N + 1 issue in select box?

In my Rails app I have people which can have many projects and vice versa. The two tables are linked by a join table jobs.
class Project < ActiveRecord::Base
belongs_to :user
has_many :people, :through => :jobs
def self.names_as_options
order(:name).map{ |p| [ p.name, p.id, :'data-people_count' => p.people.count ] }
end
end
In one of my forms I have this select box:
<%= f.select :project_id, Project.names_as_options %>
The problem is that the count on people gives me an N + 1 query for each project.
What is the best way to overcome this?
Thanks for any help.
Try use scope whit lambda, is for that, here is one example how this works:
scope :top, lambda { order('views DESC').limit(20) }
in controller just call
Project.top
this is the best way to filter results in Ruby on Rails.
If you use counts, you might better use also counter caches, they are automatically used when needed. http://guides.rubyonrails.org/association_basics.html
You could add a "counter cache" of the people count for each project. Ordinarily you would add a field to the projects table via a migration
add_column :projects, :people_count, :integer, :default => 0
and then declare to use :counter_cache in the Person model
class Person < ActiveRecord::Base
belongs_to :projects, :counter_cache => true
end
This probably won't do what you want as it stands, as you are going through a Job join. So, the Person#projects declaration is just a convenient finder, and not used in any callback. But, you get the idea.
You could add a column as suggested above, and then make use of some callback methods in the Job class.
class Job
def update_project_counter
project.update_people_counter
end
after_create :update_project_counter
after_destroy :update_project_counter
end
class Project
def update_people_counter
self.update_attribute :people_count, people.count
end
end
Or something similar thats appropriate. You should then only need the one query.
class Project < ActiveRecord::Base
def self.names_as_options
order(:name).map do |p|
[p.name, p.id, :'data-people_count' => p.people_count]
end
end
end
Eager loading will solve this issue, use 'includes' as follows.
Example,
class LineItem < ActiveRecord::Base
belongs_to :order, -> { includes :customer }
end
class Order < ActiveRecord::Base
belongs_to :customer
has_many :line_items
end
class Customer < ActiveRecord::Base
has_many :orders
end
Ref: http://guides.rubyonrails.org/association_basics.html

saving embeds_many in mongoid

I have a embeds_many association and when I save the parent document the children are not being saved.
class User
include Mongoid::Document
embeds_many :user_missions
attr_accessible :user_missions_attributes
accepts_nested_attributes_for :user_missions, allow_destroy: true
end
class UserMission
include Mongoid::Document
embedded_in :user, :inverse_of => :user_missions
has_one :mission, autosave: true
validates_presence_of :mission
attr_accessible :mission_title
def mission_title
mission.try(:title)
end
def mission_title=(title)
self.mission = Mission.find_or_create_by(:title => title) if title.present?
end
end
Here is the spec I'm failing:
it "should save mission to user_mission when created" do
user_mission = UserMission.new
user = create(:user)
user.user_missions << user_mission
user_mission.mission_title = "Created Title"
user.save!
#user_mission.save!
User.first.user_missions[0].mission.title.should == "Created Title"
end
I get:
undefined method `title' for nil:NilClass
When I comment in the line #user_mission.save! it works. The problem is I need this to work in a form and I thought Mongoid automatically saved embedded document's fields.
How do I get the parent User document to save the embedded UserMission's data?
Thanks
UPDATE
Here is the Mission model I forgot to add (wasn't sure if it was important):
class Mission
include Mongoid::Document
belongs_to :user_mission, :inverse_of => :mission
attr_accessible :title
field :title, type: String
validates_presence_of :title
field :lowercase_title
before_create :lower_title_case
field :description, type: String
private
def lower_title_case
self.lowercase_title = self.title.downcase
end
end
AFAIK, embedded documents can't have referenced relations. So the calls to embedded_in and has_one are contradictory.
Other than that, have you tried reordering the lines in your test so that the user gets created first, and then you create user_misison through the user?
user = create(:user)
user_mission = user.user_missions.new
user_mission.mission_title = "Created Title"
user.save!
It looks like what you're trying to do is similar to an SQL JOIN table. If this is what you're trying to achieve, it would be better to take advantage of Mongoid's awesome N-N referenced mapping (unless you need to store extra data in the 'join' collection). I'd do something like:
class User
include Mongoid::Document
has_and_belongs_to_many :missions
end
class Mission
include Mongoid::Document
has_and_belongs_to_many :users
end
If you want to create missions through users, then turn on autosave on the Mission side of the relation.
EDIT: After seeing your Mission model, I realize you're trying to directly reference UserMission (belongs_to :user_mission), an embedded document, directly from your Mission model. Like I said earlier, not doable. You need some relation between the top level docs, User and Mission. You could probably get things to work by changing that line to:
has_many :users
and then changing has_one :mission from the User model to:
belongs_to :mission
You won't be able to use autosave from the User side though.
EDIT: Corrected for proper way to show right way to do 1-n relation from users to missions.

Generate primary keys without creating the record

I want to be able to do something like
#foo = MyClass.new
5.times do
#foo.things.build
end
But my #foo needs to have a primary key for this to work, Soo what is the best way to generate primary keys without creating the object?
The purpose for this is to be able to use nested forms more easely
form_builder.fields_for :things do ...
I believe the OP is asking for how to initialize a view action property for use in new action for a standard Rails resource. At this point, there is no ID for the main parent. The solution is simple:
The model:
class ParentObject < ActiveRecord::Base
# the child model in this example is called child_objects
has_many :child_objects, :dependent => :destroy
accepts_nested_attributes_for :child_objects
The controller action for new:
#object = Object.new :example_field => "my field"
#object.child_objects.build :name => "value_1" # pretending that name is a field
#object.child_objects.build :name => "value_2"
Then, in the view:
= form_for(#object) do |f| # top level Object
= f.label :example_field
= f.text_field :example_field
=# the next line loops twice in this example
= f.fields_for :child_objects do |child|
= child.label :name
= child.text_field :name
There is also a good gem called nested_form written by Ryan Bates (https://github.com/ryanb/nested_form) which may help you with the rest of the CRUD operations.
What you probable want is NestedAttributes
Nested attributes allow you to save attributes on associated records through the parent. By default nested attribute updating is turned off, you can enable it using the accepts_nested_attributes_for class method. When you enable nested attributes an attribute writer is defined on the model.
The implementation is different between each ORM, here is for sequel and ActiveRecord
NOTE: Full tutorial also available at Nerdgem
Sequel impementation
Imagine there is a Project class that has many tasks
class Project < Sequel::Model
one_to_many :tasks
end
class Task < Sequel::Model
many_to_one :project
end
To enable the nested attributes you will need include two plugins for the Project class
Sequel::Plugins::NestedAttributes: allows you to create, update, and delete associated objects directly by calling a method on the current object. Nested attributes are defined using the nested_attributes class method:
Sequel::Plugins::InstanceHooks: which is a dependency of NestedAttributes
You can find really good doc on the plugin site
Project.plugin :instance_hooks
Project.plugin :nested_attributes
After that is done you can call the nested_attributes method on the desired class
Project.nested_attributes :tasks
Now you can do this
p = Project.new(:title=>'Project')
p.tasks_attributes = [{:title=>'First Task'}, {:title=>'Second Task'}]
puts p.tasks.inspect
# It will output this
# [#<Task #values={:title=>"First Task"}>, #<Task #values={:title=>"Second Task"}>]
When you save the project it will save both the project and the tasks.
If you can even to edit many tasks at the same.
ActiveRecord implementation
Here is how to use it.
Imagine there is a Project class that has many tasks
Project.rb
class Project < ActiveRecord::Base
attr_accessible :title
has_many :tasks
accepts_nested_attributes_for :tasks
end
Task.rb
class Tasks < ActiveRecord::Base
attr_accessible :title, :project_id
belongs_to :project
end
Now you can do this.
p = Project.new
p.tasks_attributes=[{title: "First Task"}]
p.things
# Would output this
#=> [#<Thing id: nil, title: "First Task", created_at: nil, updated_at: nil, bar_id: nil>]
p.save
When you save the project it will save both the project and the tasks.
If you want to edit many project tasks at the same time you can to this
p.tasks_attributes=[{title: "First Task"},{title: "Second Task"}]
NOTE: there is also a Railscasts that can help you out with nested forms. Orginal Railscast, Revised Railscast

Problems with validations

I have 2 models
class Variant < ActiveRecord::Base
belongs_to :product
with_options :if => :is_active? do |p_active|
p_active.validates :avatar, :presence => true
end
with_options :if => :isnt_diavoleria? do |p_active|
p_active.validates :color, :presence => true
end
def is_active?
self.product.active
end
def isnt_diavoleria?
a = (self.is_active? and self.product.section_id != 5)
a
end
end
class Product < ActiveRecord::Base
has_many :variants, :autosave => true
accepts_nested_attributes_for :variants
end
If i change the attribute section_id or active of a product and save, the validations of the model variant are executed with the old values of section_id and active.
Why?
How can i do the validations with the new values?
The problem is that by default a pair of has_many and belongs_to associations don't know that they are the inverse of each other. So when you
product.section_id = 23
product.save
then inside your validation, the variant goes
self.product
and actually fetches that from the database again, which obviously doesn't have your unsaved change.
You should be able to fix this by adding the :inverse_of flag to your associations, i.e.
class Variant < AR::Base
belongs_to :product, :inverse_of => :variants
end
class Product < AR::Base
has_many :variants, :inverse_of => :products
end
One day rails will have an identity map which should make this sort of stuff less error prone (it is in rails 3.1 but disabled because of subtle associated bugs if i remember correctly)
You probably need to do what #thoferon is suggesting (assuming you aren't doing taking nested attributes for products or something) or make sure all changes to the product are happening through the association object so it is up-to-date.
Maybe you are modifying a product through another Ruby object. The product referenced by the variant is still holding the old values. I don't know if this is what you're doing but it could be the case.
A solution could be to reload the product before validation.
class Variant
before_validation do
self.product.reload
end
end