rails 4 cancancan polymorphic - ruby-on-rails-4

For whatever reason code that use to work regarding following/follower no longer works, I suspect I need a cancancan rule, but I am not sure. Hopefully someone can see my mistake...
Profile model
class Profile < ActiveRecord::Base
...
has_many :follower_relationships, foreign_key: :following_id, class_name: 'Follow', dependent: :destroy
has_many :followers, through: :follower_relationships, source: :follower
has_many :following_relationships, foreign_key: :follower_id, class_name: 'Follow', dependent: :destroy
has_many :following, through: :following_relationships, source: :following
...
end
Follow model
class Follow < ActiveRecord::Base
...
belongs_to :follower, foreign_key: 'follower_id', class_name: 'Profile'
belongs_to :following, foreign_key: 'following_id', class_name: 'Profile'
...
end
Profiles Controller
class ProfilesController < ApplicationController
...
load_and_authorize_resource
...
def follow
if current_user.profile.follow(#profile.id)
SystemMailer.following_email(#profile.user, current_user).deliver_later
redirect_to request.referrer
end
end
def unfollow
redirect_to request.referrer if current_user.profile.unfollow(#profile.id)
end
current ability.rb
class Ability
include CanCan::Ability
...
can :edit, Profile, user_id: user.id
can :read, Profile
can :update, Profile, user_id: user.id
can :show, Profile
can :manage, Profile, id: user.profile.id
I have tried various can abilities, but I feel like I am just hacking around. If there is no errors with the lines above, can someone point out the can rule that I should put in place?
Thanks!

I figured it out, it actually wasn't being blocked because of the model organization, the actions were not being permitted in the ability class.
adding:
can :follow, Profile
can :unfollow, Profile
Solved the issues. Sorry to waste everyone's time.

Related

How to update nested resources Rails 4

I have models - Test, Question, and TeacherAnswers.
test.rb
class Test < ActiveRecord::Base
has_many :questions, dependent: :destroy
accepts_nested_attributes_for :questions
end
question.rb
class Question < ActiveRecord::Base
belongs_to :test
has_many :teacher_answers, dependent: :destroy
accepts_nested_attributes_for :teacher_answers
end
teacher_answer.rb
class TeacherAnswer < ActiveRecord::Base
belongs_to :question
end
and controllers
test_controller.rb
---
standard scaffold code
---
def test_params
params.require(:test).permit(:title,
questions_attributes: [:question_text, :test_id, teacher_answers_attributes: [:teacher_answer_text, :correct, :question_id]],)
end
question_controller.rb
---
standard scaffold code
---
def question_params
params.require(:question).permit(:question_text, :test_id,
teacher_answers_attributes: [:teacher_answer_text, :correct, :question_id])
end
When creating new test with questions and answers it creates everything correctly, but when updating:
can't remove questions
when saving it doesn't update the questions and answers, but saves new ones with already old ones i.e. when updating test with 2 questions, after saving it will have 4 questions.
You should permit :id and :_destroy in the test_params like below for the update and delete to work correctly.
def test_params
params.require(:test).permit(:title, questions_attributes: [:id, :question_text, :test_id, :_destroy, teacher_answers_attributes: [:id, :teacher_answer_text, :correct, :question_id, :_destroy]])
end
Update
You should also add allow_destroy: true for test.rb and question.rb
class Test < ActiveRecord::Base
has_many :questions, dependent: :destroy
accepts_nested_attributes_for :questions, allow_destroy: true
end
class Question < ActiveRecord::Base
belongs_to :test
has_many :teacher_answers, dependent: :destroy
accepts_nested_attributes_for :teacher_answers, allow_destroy: true
end
accepts_nested_attributes_for by default does not allow deletion, use delete: true
Pass id to form, via hidden tag

rails touch chain with has_many :through associations

Using a join model, with a has_many :through on this example setup
class Collage
has_many :arrangements
has_many :photos, through: :arrangements
class Photo
has_many :arragements
has_many :collages, through: :arragements
end
class Arragement
belongs_to :photo
belongs_to _collage
end
The photo may change it size, which will cause the collage to change
Using the touch: true, doesn't work this way, because the chain isn't "one way up", as arragement points to Photo and Collage
How can I work this so a Photo change (ie touch) would also touch its Collages?
Here's an even shorter version:
class Photo
has_many :arragements
has_many :collages, through: :arragements
after_save { collages.find_each(&:touch) }
end
You could just manually touch them all.
class Photo
has_many :arragements
has_many :collages, through: :arragements
after_save do
collages.update_all updated_at: Time.now
end
end

How to do a Polymorphic model association with another model

I have two basic models:
class Case < ActiveRecord::Base
has_many :contacts
end
class Contact < ActiveRecord::Base
belongs_to :case
belongs_to :contactable, :polymorphic => true, :foreign_key => :contactable_id
end
I also have quite a few models that are "sub types" of the Contact model:
class Attorney < ActiveRecord::Base
has_one :contact, as: :contactable, dependent: :destroy
accepts_nested_attributes_for :contact
end
class Client < ActiveRecord::Base
has_one :contact, as: :contactable, dependent: :destroy
accepts_nested_attributes_for :contact
end
I used polymorphic associations instead of STI because I don't want one table to store all the fields, which can get a little slow and overwhelming.
I am wondering if the has_many :contacts line from the Case model is correct. I try to use it in my console, but it doesn't really work the way I expected. Am I missing something?
I basically want an association where a Case can have many Contacts and a contact can belong to many cases. The contact can be of any type (Attorney or Client or Contact by itself). I was hoping it was as easy as case has_many :contacts

Add items to a has_many relation on creation

I'm trying to make a has_many relation work for object to be created.
It is a simple case and despite many efforts and researches through the web, I cannot find why my code is not working.
I have the following classes (note: some variables use French names):
class Comptes::Category < ActiveRecord::Base
has_many :categorizations, dependent: :destroy
accepts_nested_attributes_for :categorizations
has_many :transactions, through: :categorizations
validates :nom, presence: true, uniqueness: true
end
class Comptes::Transaction < ActiveRecord::Base
has_many :categorizations, dependent: :destroy
accepts_nested_attributes_for :categorizations
has_many :categories, through: :categorizations
... # some validations
end
class Comptes::Categorization < ActiveRecord::Base
belongs_to :transaction
belongs_to :category
validates :transaction, presence: true
validates :category, presence: true
end
Category and Transaction are the basic models and Categorization is dedicated to the association (this is a basic account - transaction system).
What I can do is create a transaction and a category then fill transaction.categories with the category (transaction has thus an id).
What I cannot do is:
transaction = Comptes::Transaction.new ...
category = Comptes::Category.first
transaction.categories << category
# OR
transaction.categorizations.build category: category
# OR
# use categorizations_attributes in and accepts_nested_attributes_for.
Thank you very much for any help
Edit: this is done in rails 4.0.0
And I found that the issue was coming from the validation in Comptes::Categorization.
This prevents creation of new categorizations if the transaction or category does not exist yet.
Update (18/08/2014): the issue is coming from the validation in Categorizations, which prevent from creating the association without existing transaction and category. This may be an issue in rails 4.0.0. To see...
Transaction class is not under the module Comptes. Therefore, when you do has_many :categorizations or has_many :categories in it, the corresponding models are inferred as Categorization and Category instead of Comptes::Categorization and Comptes::Category.
To resolve this, you need to specify the class_name option of the association because the name of the model can't be inferred from the association name.
Update the class Transaction as below:
class Transaction < ActiveRecord::Base
has_many :categorizations, class_name: "Comptes::Categorization" , dependent: :destroy
accepts_nested_attributes_for :categorizations
has_many :categories, through: :categorizations, class_name: "Comptes::Category"
end

Rails-style polymorphic model in ember-data

If I have the following models in Rails, how would I represent this in Ember/Ember Data?
class Attachment < ActiveRecord::Base
belongs_to :user
belongs_to :attachable, polymorphic: true
end
class Profile < ActiveRecord::Base
belongs_to :user
has_one :photo, class_name: 'Attachment', as: :attachable
end
class Post < ActiveRecord::Base
belongs_to :user
has_many :attachments, as: :attachable
end
References I've found are the relevant ember-data pull request, the ember-data tests for polymorphic relationships, and this related question but it's difficult to work out a canonical example from them.
I now use two different ways of working with rails-style "polymorphic" models. I have updated the code below to show both uses.
Attachment model: This is "polymorphic" on the Rails side but is always "embedded" on the Ember side. The reason for this is that I currently only need to save/update attachments along with their associated model.
Comment model: This is polymorphic on both the Rails side and the Ember side.
I have also included code for the Post model as it can have multiple attachments and multiple comments.
Rails code:
class Attachment < ActiveRecord::Base
belongs_to :user
belongs_to :attachable, polymorphic: true
end
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :commentable, polymorphic: true
end
class Post < ActiveRecord::Base
belongs_to :user
has_many :attachments, as: :attachable
has_many :comments, as: :commentable
end
class ApplicationSerializer < ActiveModel::Serializer
embed :ids, include: true
end
class AttachmentSerializer < ApplicationSerializer
attributes :id, :url, :errors
has_many :comments
end
class CommentSerializer < ApplicationSerializer
attributes :id, :body, :created_at, :commentable_id, :commentable_type
has_one :user
end
class PostSerializer < ApplicationSerializer
attributes :id, :title, :body, :posted_at, :errors
has_one :user
has_many :attachments, embed: :objects, include: true
has_many :comments
end
class Api::V1::PostsController < Api::V1::BaseController
before_filter :auth_only!, only: :create
def create
# clean / capture ember-data supplied arguments
params[:post].delete(:user_id)
attachments_params = params[:post].delete(:attachments)
#post = current_user.posts.new(params[:post])
process_attachments(attachments_params)
if #post.save
render json: #post, status: 201
else
warden.custom_failure!
render json: #post, status: 422
end
end
protected
def process_attachments(attachments_params)
return unless attachments_params.present?
attachments_params.each do |attachment_params|
# ignore ember-data's additional keys
attachment_params.delete(:created_at)
attachment_params.delete(:user_id)
attachment = #post.attachments.new(attachment_params)
attachment.user = current_user
end
end
end
Ember code:
DS.RESTAdapter.configure 'App.Post',
alias: 'Post'
DS.RESTAdapter.map 'App.Post',
attachments: { embedded: 'always' }
App.Store = DS.Store.extend
adapter: DS.RESTAdapter.create
namespace: 'api/v1'
App.Comment = App.Model.extend
user: DS.belongsTo('App.User')
commentable: DS.belongsTo('App.Commentable', { polymorphic: true })
body: DS.attr('string')
createdAt: DS.attr('date')
App.Commentable = App.Model.extend
comments: DS.hasMany('App.Comment')
App.Attachment = App.Commentable.extend
user: DS.belongsTo('App.User')
url: DS.attr('string')
App.Post = App.Commentable.extend
user: DS.belongsTo('App.User')
attachments: DS.hasMany('App.Attachment')
title: DS.attr('string')
body: DS.attr('string')
postedAt: DS.attr('date')
App.PostFormOverlayController = App.OverlayController.extend
# 'files' attribute is set by a widget that wraps the filepicker.io JS
updateAttachments: (->
attachments = #get('attachments')
attachments.clear()
#get('files').forEach (file) =>
attachment = App.Attachment.createRecord({fpFile: file})
attachments.addObject(attachment)
).observes('files')
App.CommentNewController = App.ObjectController.extend
# this should be instantiated with it's model set to the commentable
# item. eg. `{{render 'comment/new' content}}`
save: ->
transaction = #get('store').transaction()
comment = transaction.createRecord App.Comment,
body: #get('body')
commentable: #get('model')
comment.one 'didCreate', #, ->
#set('body', null)
transaction.commit()
Unfortunately my Rails code has become a little polluted with ember-data specific oddities as it tries to send back all the attributes that are defined on the models. (Note: There is an open proposal for read-only attributes that would solve the params pollution issue)
If anyone knows a better way to approach any of the above please let me know!
NB: I'm a little concerned that having models extend from App.Commentable will prevent me from having multiple polymorphic attachments on a model, I may need to look for a different way of handling that.
The Comment part of Kevin Ansfield's answer (didn't use the Post part) works as of Ember 1.3.0-beta / Ember Data 1.0.0-beta.4 except for the rails serializer, which should now be:
class CommentSerializer < ActiveModel::Serializer
attributes :id, :body, :created_at, :commentable_id, :commentable_type
has_one :user
def attributes
data = super
data[:commentable] = {id: data[:commentable_id], type: data[:commentable_type]}
data.delete(:commentable_type)
data.delete(:commentable_id)
data
end