Adding and listing has_many through associations in Active Admin - ruby-on-rails

I have the following setup:
class User < ActiveRecord::Base
has_many :device_ownerships, :dependent => :destroy
has_many :devices, :through => :device_ownerships
end
class device < ActiveRecord::Base
has_one :device_ownership, :dependent => :destroy
has_one :user, :through => :device_ownership
end
class deviceOwnership < ActiveRecord::Base
belongs_to :user
belongs_to :device
validates_uniqueness_of :device_id, :scope => :user_id
validates_uniqueness_of :user_id, :scope => :device_id
end
I am trying to achieve the following in Active Admin:
In Edit screen
1) List all devices that belong to user with an option to delete the device or destroy the deviceOwnership that connects device to user
2) have an option to create a new pairing user-device from existing devices (by creating new DeviceOwnership).
3) have an option to create new device and add it to user via new DeviceOwnership.
I listed my problems with what I have now in comments below:
ActiveAdmin.register User do
permit_params :email, :password, :password_confirmation, :role,
device_ownerships_attributes: [:device_id, :user_id],
devices_attributes: [:device_identifier]
index do |user|
user.column :email
user.column :current_sign_in_at
user.column :last_sign_in_at
user.column :sign_in_count
user.column :role
actions
end
filter :email
form do |f|
f.inputs "User Details" do
f.input :email
f.input :password
f.input :password_confirmation
f.input :role, as: :radio, collection: {Editor: "editor", Moderator: "moderator", Administrator: "administrator"}
#This one allows to create new devices but also lists all existing devices with option to modify their device_identifier column which I don't want
f.has_many :devices, :allow_destroy => true, :heading => 'Themes', :new_record => true do |cf|
cf.input :device_identifier
end
#This one lists all the devices but no option to remove any of them.
f.input :devices
#This one shows dropdownw with existing devices but allows to swap them
f.has_many :devices, :allow_destroy => true do |device_f|
device_f.input :device_identifier, :as => :select, :collection => device.all.map{ |device| [device.device_identifier] }, include_blank: false,
end
f.actions
end
end
end

The line
f.has_many :devices, :allow_destroy => true, :heading => 'Themes', :new_record => true do |cf|
cf.input :device_identifier
end
looks like it might do the job. You could check whether the cf.object is a new record and only let the user change the device_identifier in that case.
cf.input :device_identifier if cf.object.new_record?
or something like cf.input :device_identifier, input_html: { readonly: !cf.object.new_record? }
What do you think?

Related

ActiveAdmin form: multiple nested forms

I have a structure User-> Profile-> Image and want to use one form for editing and recording in ActiveAdmin.
I use accepts_nested_attributes_forin models:
class User < ActiveRecord::Base
has_one :profile, dependent: :destroy;
accepts_nested_attributes_for :profile, allow_destroy: true
end
class Profile < ActiveRecord::Base
belongs_to :image, dependent: :destroy;
accepts_nested_attributes_for :image,:reject_if => proc { |attributes| !attributes['img'].present? }, :allow_destroy => true
end
class Image < ActiveRecord::Base
has_attached_file :img
validates_attachment_content_type :img, :content_type => ["image/jpg", "image/jpeg", "image/png", "image/gif"]
end
And such permit_params in ActiveAdmin.register User:
permit_params do
permitted=[:id,:login, :email, :admin, :password, :password_confirmation, :ip_address];
permitted.append(profile_attributes:[:name,:second_name,:middle_name,:img,:mobile_phone,:country, :city,:region, image_attributes:[:img]]);
permitted
end
Finally, the code itself forms
form do |f|
f.semantic_errors *f.object.errors.keys
f.inputs "User Details" do
f.input :login
f.input :email
f.input :password
f.input :password_confirmation
end
f.inputs "Profile", for: [:profile, f.object.profile || f.object.build_profile] do |pf|
pf.input :name
pf.input :second_name
pf.input :middle_name
pf.input :mobile_phone, :as => :phone
pf.input :country, selected: "RU"
pf.input :city
pf.input :region
pf.inputs "Avatar", for:[:image, pf.object.image || pf.object.build_image] do |fpf|
fpf.input :img, :as => :file
end
end
f.inputs "User Perference" do
f.input :admin, type: :boolean
end
f.actions
end
Unfortunately, this code does not work: the form is correctly displayed with Profile and work, but the form is not visible to the Image. How can I fix this?
Unfortunately, I have not found the right solution for this problem, and it is still relevant (I'm not going to close this question, just in case someday it will answer).
But I modeled the behavior close to the desired one.
So, to the User model I added Avatar-interface for the respective object.
def avatar
if self.profile && self.profile.image
self.profile.image.img
else
nil
end
end
def avatar=(arg)
unless self.profile.image
self.profile.create_image(img:arg)
else
self.profile.image.update_attributes(img:arg)
end
self.profile.image
end
def avatar?
if self.profile && self.profile.image
!self.profile.image.img.nil?
else
nil
end
end
In ActiveAdmin added to the avatar permit_params for User.
permit_params ..., :avatar
Finally, in the form of added file fields:
form do |f|
f.semantic_errors *f.object.errors.keys
f.inputs "User Details" do
#Fields for User
end
f.inputs "profile", for: [:profile, f.object.profile || f.object.build_profile] do |pf|
#Fields for profile
end
f.inputs "Avatar" do
f.input :avatar, as: :file
end
f.actions
end
It is worth noting that if you have observed abnormal behavior when the form is submitted, with empty values, then you should pay attention to the parameters for accepts_nested_attributes_for, in particular update_only and reject_if.
I'm still waiting for the normal solution for multiple nested forms in ActiveAdmin

How to update attributes in many to many relation in rails?

I am new in rails. So i am not comprehending much of it. I have a database with three table. students table, courses table and registrations table.
My Railde models are as follows :
class Student < ActiveRecord::Base
attr_accessible :name
has_many :registrations
has_many :courses, :through => :registrations
validates :name, :presence => true
end
class Course < ActiveRecord::Base
attr_accessible :name
has_many :registrations, :dependent => :destroy
has_many :students, :through => :registrations
validates :name , :presence => true
validates :name, :uniqueness => true
end
class Registration < ActiveRecord::Base
attr_accessible :course_id, :student_id
belongs_to :course
belongs_to :student
validates :course_id, :student_id, :presence => true
validates :course_id, :uniqueness => {:scope => :student_id}
validates :student_id, :uniqueness => {:scope => :course_id}
end
......................
Controller action for updating student :
def update
#student = Student.find(params[:id])
if #student.update_attributes(params[:student])
redirect_to students_path, :notice => "Registration completed"
else
render 'edit'
end
end
................
View :
<%=form_for #student do |f| %>
<p>
<%= f.label :name, "Name" %>
<%= f.text_field :name %>
</p>
<p>
<%= render('course_list') %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
...............
_course_list partial :
Select Courses :<br/>
<p>
<% Course.all.each do |course| %>
<%=check_box_tag "student[course_ids][]", course.id, `enter code here`#student.course_ids.include?(course.id) %>
<%= course.name %> <br/>
<% end %>
</p>
.............................
when i submit the update button, i got an error
Can't mass-assign protected attributes: course_ids
.......
parameters :
{"utf8"=>"✓",
"_method"=>"put",
"authenticity_token"=>"j/lDE5bv1gWkfadQ6Cag6hGjg5nD2Ikad9vHOJTE7Pc=",
"student"=>{"name"=>"Galib",
"course_ids"=>["2",
"3"]},
"commit"=>"Update Student",
"id"=>"6"}
.........................
What i want to do is, if update button is clicked, both students table and registrations table need to be updated. Please help.
In models define the associations
class Student < ActiveRecord::Base
attr_accessible :name
has_many :registrations
has_many :courses, :through => :registrations
validates :name, :presence => true
accepts_nested_attributes_for :courses
attr_accessible :course_ids
end
class Course < ActiveRecord::Base
attr_accessible :name
has_many :registrations, :dependent => :destroy
has_many :students, :through => :registrations
validates :name , :presence => true
validates :name, :uniqueness => true
accepts_nested_attributes_for :students
end
class Registration < ActiveRecord::Base
attr_accessible :course_id, :student_id
belongs_to :course
belongs_to :student
validates :course_id, :student_id, :presence => true
validates :course_id, :uniqueness => {:scope => :student_id}
validates :student_id, :uniqueness => {:scope => :course_id}
accepts_nested_attributes_for :course
end
In Controller
def update
#student = Student.find(params[:id])
if #student.update_attributes(params[:student])
#student.courses.build
redirect_to students_path, :notice => "Registration completed"
else
render 'edit'
end
end
It may help you
The error is telling you that you have a permission error - course_ids can't be posted to from a form. More specifically, you have attr_accessible :name in the Student model which means that is the only attribute that can be saved when you save a Student record using a form (attr_accessible dictates what can be mass assigned).
Try changing your Student model to:
attr_accessible :name, :registrations_attributes
accepts_nested_attributes_for :registrations
You are trying to created a nested model form, so accepts_nested_attributes_for is used for this.
If you want to have the Registrations table updated, as well, you're going to have to tell the partial so that it knows that the partial is updating a different model than Student.
<%= f.fields_for :registrations do |registration| %>
<%= render('course_list') %>
<% end %>
In case of MongoDB, For implementing many to many in rails model you have to use has_and_belongs_to_many instead of simple has_many

Activeadmin formtastic dynamic select

I would like to make a dynamic select option via Activeadmin's formtastic like so:
form do |f|
f.inputs "Exam Registration Details" do
f.input :user_id, :as => :select, :collection => User.where(:admin => 'false')
#selects user from list. WORKING
f.input :student_id, :as => :select, :collection => Student.joins(lessons: :user)
#collection of students will change to students who have lessons with chosen user. NOT WORKING, returns all students who have lessons.
f.input :lesson_id, :as => :select, :collection => Lesson.joins(:student, :user)
#collection of lessons will change to reflect lessons connected by chosen user and student. NOT WORKING, returns all lessons.
end
f.buttons
end
My amateurish code is obviously not working as I intended it to. What changes should I make?
I have 4 models as below:
class Student < ActiveRecord::Base
has_many :lessons
has_many :users, through: :lessons
has_many :exam_registrations, through: :lessons
class Lesson < ActiveRecord::Base
belongs_to :user
belongs_to :student
belongs_to :exam_registration
class User < ActiveRecord::Base
has_many :lessons
has_many :students, through: :lessons
has_many :exam_registrations, through: :lessons
class ExamRegistration < ActiveRecord::Base
has_many :lessons
has_many :users, through: :lessons
has_many :students, through: :lessons
SOLVED
For anyone else wrestling with the same problem, look at this railscast
here's how I implemented multiple dynamic select menus in activeadmin:
config/initializers/active_admin.rb
config.register_javascript 'exam_registrations.js.coffee'
app/admin/exam_registrations.rb
form do |f|
f.inputs "Exam Registration Details" do
f.input :user_id, :label => 'Teacher', :as => :select, :collection => User.where(:admin => 'false', :active => true).order(:name), :include_blank => true
f.input :student_id, :hint => 'Students grouped by teacher names', :as => :select, :collection => option_groups_from_collection_for_select(User.where(:admin => false, :active => true).order(:name), :students, :name, :id, :name)
f.input :lesson_id, :hint => 'Lessons grouped by student names', :as => :select, :collection => option_groups_from_collection_for_select(Student.where(:active => true).order(:name), :lessons, :name, :id, :name)
end
f.buttons
end
app/assets/javascripts/exam_registrations.js.coffee
#first menu
jQuery ->
$('#exam_registration_student_id').parent().hide()
students = $('#exam_registration_student_id').html()
$('#exam_registration_user_id').change ->
user = $('#exam_registration_user_id :selected').text()
escaped_user = user.replace(/([ #;&,.+*~\':"!^$[\]()=>|\/#])/g, '\\$1')
options = $(students).filter("optgroup[label='#{escaped_user}']").html()
if options
$('#exam_registration_student_id').html(options)
$('#exam_registration_student_id').parent().show()
else
$('#exam_registration_student_id').empty()
$('#exam_registration_lesson_id').empty()
# second menu
$('#exam_registration_lesson_id').parent().hide()
lessons = $('#exam_registration_lesson_id').html()
$('#exam_registration_student_id').click ->
student = $('#exam_registration_student_id :selected').text()
escaped_student = student.replace(/([ #;&,.+*~\':"!^$[\]()=>|\/#])/g, '\\$1')
options = $(lessons).filter("optgroup[label='#{escaped_student}']").html()
if options
$('#exam_registration_lesson_id').html(options)
$('#exam_registration_lesson_id').parent().show()
else
$('#exam_registration_lesson_id').empty()
restart the server and the menus work!

Rails: undefined method 'MODELNAME' ? Am I saving the wrong thing on create?

Currently I'm making an app for users to create picture albums. The catch is that there can be multiple owners for each album, so in the form to create an album there is a check box portion where you highlight your friends names and "invite them" to be an owner. However, I am having a hard time getting it to work since it's giving me an error in my AlbumsController#create. The error is :
undefined method 'user' for #<Album:0x007fcd9021dd00>
app/controllers/albums_controller.rb:43:in 'create'
Here's my form
<%= form_for ([#user, #album]), :html => { :id => "uploadform" } do |f| %>
<div class="formholder">
<%= f.label :name %>
<%= f.text_field :name %>
<br>
<label>Hosts</label>
<% #user.friends.each do |friend| %>
<%= friend.name %>
<%= check_box_tag 'album[user_ids][]', friend.id, #album.users.include?(friend) %>
<% end %>
<%= f.label :description %>
<%= f.text_area :description %>
<br>
<%=f.submit %>
</div>
<% end %>
The checkboxes return an array of friend.id values that I want to invite to be owners for the album. The tricky part here is that I have a imaginary pending_album column in my join album_user model. Here's what is sending me errors that I can't figure out how to fix:
albums controller
def create
#user = User.find(params[:user_id])
#album = #user.albums.build(params[:album], :status => 'accepted')
#friends = #user.friends.find(params[:album][:user_ids])
for friend in #friends
params[:album1] = {:user_id => friend.id, :album_id => #album.id, :status => 'pending'}
AlbumUser.create(params[:album1])
end
#the next line is where the error occurs. why???
if #user.save
redirect_to user_album_path(#user, #album), notice: 'Album was successfully created.'
else
render action: "new"
end
end
album model
class Album < ActiveRecord::Base
attr_accessible :name, :description, :user_ids
validates_presence_of :name
validates :user, :uniqueness => true
has_many :album_users
has_many :users, :through => :album_users
has_many :photos
end
user model
class User < ActiveRecord::Base
has_secure_password
attr_accessible :email, :name, :password, :password_confirmation, :profilepic
validates_presence_of :password, :on => :create
validates_format_of :name, :with => /[A-Za-z]+/, :on => :create
validates_format_of :email, :with => /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create
validates_length_of :password, :minimum => 5, :on => :create
# validates :album, :uniqueness => true
has_many :album_users
has_many :albums, :through => :album_users, :conditions => "status = 'accepted'"
has_many :pending_albums, :through => :album_users, :source => :album, :conditions => "status = 'pending'"
accepts_nested_attributes_for :albums
has_many :friendships, :dependent => :destroy
has_many :friends, :through => :friendships, :conditions => "status = 'accepted'"
has_many :requested_friends, :through => :friendships, :source => :friend, :conditions => "status = 'requested'", :order => :created_at
has_many :pending_friends, :through => :friendships, :source => :friend, :conditions => "status = 'pending'", :order => :created_at
has_attached_file :profilepic
before_save { |user| user.email = email.downcase }
def name_with_initial
"#{name}"
end
private
def create_remember_token
self.remember_token = SecureRandom.urlsafe_base64
end
end
album_user join table model
class AlbumUser < ActiveRecord::Base
attr_accessible :user_ids, :album_id, :status, :user_id
belongs_to :album
belongs_to :user
end
parameters (looks a little fishy... especially since album_id is nil):
{"utf8"=>"✓",
"authenticity_token"=>"xkIi6+1vjEk4yQcFs9vI1uvI29+Gyuenyp71vhpX9Hw=",
"album"=>{"name"=>"123123",
"user_ids"=>["27",
"28"],
"description"=>"123123"},
"commit"=>"Create Album",
"user_id"=>"29",
"album1"=>{"user_id"=>28,
"album_id"=>nil,
"status"=>"pending"}}
I'm not too sure what I'm doing wrong. Can someone help please!!
The error message is:
undefined method 'user' for #<Album:0x007fcd9021dd00>
All right, so something's calling Album#user. Line 46 in the controller is #user.save, so how is user getting called?
class Album < ActiveRecord::Base
attr_accessible :name, :description, :user_ids
validates_presence_of :name
validates :user, :uniqueness => true # <-- bam!
has_many :album_users
has_many :users, :through => :album_users
has_many :photos
end
save triggers validations, including that line right there, which validates that user is unique. Since this is exploding, I'm guessing that user is a column that doesn't exist, either because a) it's not defined in a migration or b) you didn't run the migration.
(As an aside, I'm rather confused as to why you would have a unique user attribute on Album, an AlbumUser model with both user_id and user_ids, and a User model. Maybe it makes sense, but I think it's more likely that it all needs a cleanup.)
Your album really has many several users? Then that should return method "user"? If want that one user owned album you must use belongs_to in your model. Then method "user" will return correct user to album

rails activeadmin (multi) nested form

I'm trying to get started with active admin. I have this models:
class Client < ActiveRecord::Base
has_many :direcctions
validates :empresa, :presence => true
validates :fono, :presence => true
validates :giro, :presence => true
accepts_nested_attributes_for :direccionts
end
class Direction < ActiveRecord::Base
belongs_to :client
has_one :city
accepts_nested_attributes_for :city
end
class City < ActiveRecord::Base
belongs_to :direction
end
In my Activeadmin.register block for Client I have:
ActiveAdmin.register Cliente do
form do |f|
f.inputs do
f.input :empresa
f.input :fono
f.input :giro
end
f.inputs "Direcciones" do
f.has_many :directions do |j|
j.input :direction
# j.inputs "Ciudad" do
# j.has_one :ciudads do |r|
# r.input :city
# end
# end
end
end
f.buttons
end
end
With this i cant add multiple directions to one cliente, but i can't show the inputs to add a city to a Direction... how can i do that?? and this don't work to.. i have also this error when i try to create a client:
unknown attribute: client_id
Thanks in advance...
ActiveAdmin uses Justin French's Formtastic gem, so you can use that DSL directly in your forms, for example:
f.inputs "Direcciones" do
f.semantic_fields_for :directions do |j|
j.input :direction
j.inputs "Ciudad" do
j.semantic_fields_for :ciudads do |r|
r.input :city
end
end
end
end

Resources