How to ensure a model is saved with the right account in rails? - validation

If I have the following models setup:
class Member < ActiveRecord::Base
has_many :children
end
class Child < ActiveRecord::Base
belongs_to :member
has_many :photos
end
class Photo < ActiveRecord::Base
belongs_to :child
end
When a new Photo is created, what is the best way to ensure that it is associated with a child in the member's account?
I have login working properly, and a current_member helper method, which doesn't seem to be available in the models

So, from what I gather, the "Rails Way™" of doing this would be to put the conditions in the controllers.
ex:
unless current_member.children.collect { |child| child.id.to_s }.include?(#photo.child_id)
#photo.errors.add :child_id "this is not your child"
end

Related

Calling a Associated Controller Method Not Working

I have 3 models of User, Role and UserRole with their respective controllers as UsersController, RolesController and UserRolesController.
I have a method in the UserRoles controller which I would want to access through the Users controller but I keep having errors as explained below.
I have tried various means of even moving the method def self.add_roles_to_user(user, role) from the UsersRoles controller into the UserRole model and call it but I keep having the same error.
I have gone through lots of similar questions and various blogs, including those on this platform such as Calling a method from controller, and others but to no good results.
class UserRole < ApplicationRecord
# many-to-many association using join table with roles and user
belongs_to :user, inverse_of: :user_roles
belongs_to :role, optional: true, inverse_of: :user_roles
end
class User < ApplicationRecord
has_many :user_roles, inverse_of: :user
has_many :roles, through: :user_roles
end
class Role < ApplicationRecord
# table associations between role and user
has_many :user_roles, inverse_of: :role
has_many :users, through: :user_roles
end
class UserRolesController < ApplicationController
def self.add_roles_to_user(user, role)
if ! user.nil?
if role.length > 0
role.each do |sel_role|
#u_role = UserRole.new
#u_role.user_id = user_id
#u_role.role_id = sel_role.role_id
#u_role.save
end
end
end
end
end
class Users::RegistrationsController < Devise::RegistrationsController
def create_user
respond_to do |format|
if #user.save
# add roles
UserRoles.add_user_roles(params[:user], params[:role])
end
end
end
end
I am calling the add_uer_to_role method in the User controller when I am adding or creating a new user.
What I have noticed is that I keep getting different errors based on how I call the method.
For example, I expect to have no errors when I call the method like; UserRoles.add_roles_to_user(params[:user], params[:role]) but it gives the error NameError (uninitialized constant Users::RegistrationsController::UserRoles):
Hoping a good samaritan will come to my aid as soon as possible. Thanks in advance
If it is a common function, you can define it in application controller and call it. Else you can define in helper.
Please verify Calling a method from another controller
You can use that function as a module and use it:
# lib/common_stuff.rb
module CommonStuff
def common_thing
# code
end
end
# app/controllers/my_controller.rb
require 'common_stuff'
class MyController < ApplicationController
include CommonStuff
# has access to common_thing
end

class_name foreign_key in Rails model

I recently come across this code. User has many Answer. What is the purpose of the :class_name and :foreign_key ?
class Answer < ApplicationRecord
belongs_to :user, :class_name => 'Question", :foreign_key => 'question_id'
end
The naming here is kind of strange, but the purpose of :class_name is to allow you to use a class that is different from the one Rails expects. When you have a belongs_to :user on a model, Rails would expect that to point to a parent class called User. In your example, Rails skips looking for a User class and instead looks to the Question model.
The most common usage of this, though, is when a non-default association name makes more sense than the default. So a more apt example is when you have a User model and Competition model wherein each competition has one user as a winner. You could have each Competition belong to a User, but that wouldn't be as expressive. Instead you may want to have the relationship be referred to as winner:
class User < ActiveRecord::Base
has_many :winners, class_name: "Competition", foreign_key: "competition_id"
end
class Competition < ActiveRecord::Base
belongs_to :winner, class_name: "User", foreign_key: "winner_id"
end
This allows you to refer to users as winners:
competition = Competition.first
competition.winner
This is a lot more expressive than if you were to write competition.user.

Modifying a Lib Model

I would like to override a Lib Model in my Models and add a relation.
What is the best way to do it ?
Example of a model in rpush lib:
https://github.com/rpush/rpush/blob/f82cc6a25861612ce118b2661f5a47bceb7ebd86/lib/rpush/client/active_record/app.rb
module Rpush
module Client
module ActiveRecord
class App < ::ActiveRecord::Base
self.table_name = 'rpush_apps'
if Rpush.attr_accessible_available?
attr_accessible :name, :environment, :certificate, :password, :connections, :auth_key, :client_id, :client_secret
end
has_many :notifications, class_name: 'Rpush::Client::ActiveRecord::Notification', dependent: :destroy
validates :name, presence: true, uniqueness: { scope: [:type, :environment] }
end
end
end
end
I would like to add a has_many relation without editing the gem
So I thought creating a models/app.rb with this would be a start:
class Rpush::Client::ActiveRecord::App
has_many :rel_group_apps
has_many :groups, :through => :rel_group_apps
end
I tried this but nothing changed. Maybe my models/app.rb is not called ?:
module Rpush
module Client
module ActiveRecord
module App
def self.included(includer)
includer.class_eval do
has_many :rel_group_apps
has_many :groups, :through => :rel_group_apps
end
end
end
end
end
end
How should I do it ? Is there a way to extend a lib model without removing the original behavior ?
Thanks !
EDIT
I Made it work but only by putting this code directly in config/initializers/rpush.rb
It wasn't working in models/app.rb
class Rpush::Client::ActiveRecord::App
has_many :rel_group_apps
has_many :groups, :through => :rel_group_apps
end
If someone has a nicer idea, I'll take it !
Extend the class with class << self
class Rpush::Client::ActiveRecord::App
class << self
[your methods here]
end
end

Rails STI - custom association in subclass

class Upload < ActiveRecord::Base
has_many :comments
end
class Gallery < Upload
has_many :images
end
class MusicAlbum < Upload
has_many :audio_tracks
end
Should this work as expected? Will Gallery and MusicAlubm models inherit :comments association from their parent (Upload) and add their own?
Yes, the models are just classes, and when inherited they get all the methods from parent class. So, as both Gallery and MusicAlbum are descendants from Upload model, they will have the has_many :comments association, and both will get data from uploads db table (which needs to have a type column to support STI for this model)
A nice simple STI example can be found here

Rails multiple belongs_to assignment

Given
User:
class User < ActiveRecord::Base
has_many :discussions
has_many :posts
end
Discussions:
class Discussion < ActiveRecord::Base
belongs_to :user
has_many :posts
end
Posts:
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :discussion
end
I am currently initializing Posts in the controller via
#post = current_user.posts.build(params[:post])
My question is, how do I set/save/edit the #post model such that the relationship between the post and the discussion is also set?
Save and edit discussions along with post
Existing Discussion
To associate the post you're building with an existing discussion, just merge the id into the post params
#post = current_user.posts.build(
params[:post].merge(
:discussion_id => existing_discussion.id
)
You will have to have a hidden input for discussion id in the form for #post so the association gets saved.
New Discussion
If you want to build a new discussion along with every post and manage its attributes via the form, use accepts_nested_attributes
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :discussion
accepts_nested_attributes_for :discussion
end
You then have to build the discussion in the controller with build_discussion after you built the post
#post.build_discussion
And in your form, you can include nested fields for discussions
form_for #post do |f|
f.fields_for :discussion do |df|
...etc
This will create a discussion along with the post. For more on nested attributes, watch this excellent railscast
Better Relations
Furthermore, you can use the :through option of the has_many association for a more consistent relational setup:
class User < ActiveRecord::Base
has_many :posts
has_many :discussions, :through => :posts, :source => :discussion
end
class Discussion < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :discussion
end
Like this, the relation of the user to the discussion is maintained only in the Post model, and not in two places.

Resources