I have the following model structure:
Composition has many Score (Score belongs to Composition)
Composition has and belongs to many Countries (and viceversa)
score.rb:
class Score < ApplicationRecord
belongs_to :composition
end
composition.rb:
class Composition < ApplicationRecord
has_many :scores
has_and_belongs_to_many :countries, join_table: :rights_countries
end
country.rb:
class Country < ApplicationRecord
has_and_belongs_to_many :compositions, join_table: :rights_countries
end
In activeadmin, I want to be able to edit the countries of a composition, but in the edit form of its scores.
Of course, the form will import this data from composition, and default inputs will be equal for all the scores (children) of a composition.
I found no way to implement this in activeadmin up to now.
Is this even possible? If yes, is the solution easy or cumbersome?
Following this link, I added an inputs within an inputs and updated the corresponding params. I also added accepts_nested_attributes_for :composition in the score model.
app/models/score.rb
...
accepts_nested_attributes_for :composition
...
app/admin/score.rb
...
permit_params ...,
composition_attributes: [:id, country_ids: []]
...
form do |f|
f.inputs do
...
f.inputs "", for: [:composition, score.composition] do |c|
c.input :countries, as: :select, collection: Country.order_by_name.uniq.map { |p| [p.name, p.id] }
end
end
end
Let me know if there's a cleaner solution.
Related
I may be missing something fundamental here, but I can't seem to get ActiveAdmin to work with a sortable has_many through relationship, with the ability to create new records.
So given the following models
class User < ActiveRecord::Base
has_many :user_videos
has_many :videos, through: :user_videos
accepts_nested_attributes_for :user_videos
accepts_nested_attributes_for :videos
...
end
class UserVideo < ActiveRecord::Base
belongs_to :user
belongs_to :video
accepts_nested_attributes_for :video
end
class Video < ActiveRecord::Base
has_many :user_videos
has_many :users, through: :user_videos
...
end
(I admit I'm throwing accepts_nested_attributes_for around somewhat in the hopes that something may work)
And Active Admin setup goes something like this (WIP of course):
f.inputs "User" do
f.has_many :user_videos, heading: 'Videos', sortable: :order, allow_destroy: true, new_record: 'New Record' do |v|
v.inputs for: :video do |video|
video.input :video_url
end
end
f.has_many :videos, heading: 'Videos', new_record: 'New Video' do |v|
v.input :video_url
end
end
f.actions
The first has_many on the :user_videos association does not seem to render any inputs. If there are records there, I can see that video.input :video_url is actually returning an li tag with label and input, however nothing gets rendered to the page. For new records the whole v.inputs bit does not get run (do I need to create the child records somehow there first?).
The second has_many will work in that you'll be able to add records, and update existing records, however it's impossible to sort as the order column is on the UserVideos model. I include this more as illustration than anything.
If anyone has any pointers for this, they would be most appreciated. :)
WHOA! I know I am late to the party, but this is a perfect opportunity to utilize the :delegate method!
Your UserVideo class would look something like this
class UserVideo < ActiveRecord::Base
belongs_to :user
belongs_to :video
validates_with VideoValidator
delegate :video_url, :video_url=, to: :video
end
Best of luck!
Since nobody seemed interested in tackling this, I took another approach - rather than trying to get ActiveAdmin / Formtastic to work with the existing model structure, I added getters and setters for the necessary field on the intersection model.
class UserVideo < ActiveRecord::Base
belongs_to :user
belongs_to :video
validates_with VideoValidator
def video_url
self.video = Video.create if video.nil?
self.video.video_url
end
def video_url=(video_url)
self.video = Video.create if video.nil?
self.video.video_url = video_url
# Video url is set via Active Admin, AA will not call save on the video as it does not realise it's changed
self.video.save! if video.present? and video.valid?
end
end
Doing this meant that Active Admin did not need to know about the Video model, and could just operate on the UserVideo model:
f.has_many :user_videos, heading: 'Videos', sortable: :order, allow_destroy: true, new_record: 'New Record' do |v|
v.input :video_url, :hint => (v.object.video.embed_code unless v.object.nil? or v.object.video.nil?)
end
If anyone has an actual solution rather than a work around, I'd love to hear it, but otherwise this is a possible solution for anyone searching for an answer to the same problem.
I have a pretty basic Rails 4 app, and am using Cocoon's nested forms to manage the has_many... :through model association.
class Student < ActiveRecord::Base
has_many :evaluations
has_many :assessments, through: :evaluations
# ... etc
end
class Evaluation < ActiveRecord::Base
belongs_to :student
belongs_to :assessment
# ... etc
end
class Assessment < ActiveRecord::Base
has_many :evaluations
has_many :students, through: :evaluations
accepts_nested_attributes_for :evaluation, reject_if: :all_blank
# ... etc
end
When I use Cocoon in the View, I want to use the New Assessment view to pre-fill all the Student records in order to create a new Evaluation for each one. I don't want to have to do some hacky logic on the controller side to add some new records manually, so how would I structure the incoming request? With Cocoon I see that requests have some number in the space where the id would go (I've replaced these with ?? below).
{"utf8"=>"✓", "authenticity_token"=>"whatever", "assessment"=>{"description"=>"quiz 3", "date(3i)"=>"24", "date(2i)"=>"10", "date(1i)"=>"2015", "assessments_attributes"=>{"??"=>{"student_id"=>"2", "grade" => "A"}, "??"=>{"student_id"=>"1", "grade" => "B"}, "??"=>{"student_id"=>"3", "grade"=>"C"}}, }}, "commit"=>"Create Assessment"}
I see in the Coccoon source code that this is somehow generated but I can't figure out how it works with the Rails engine to make this into a new record without an ID.
What algorithm should I use (or rules should I follow) to fill in the id above to make a new record?
"??"
Never a good sign in your params.
With Cocoon I see that requests have some number in the space where the id would go
That ID is nothing more than the next ID in the fields_for array that Rails creates. It's not your record's id (more explained below).
From your setup, here's what I'd do:
#app/models/student.rb
class Student < ActiveRecord::Base
has_many :evaluations
has_many :assessments, through: :evaluations
end
#app/models/evaluation.rb
class Evaluation < ActiveRecord::Base
belongs_to :student
belongs_to :assessment
end
#app/models/assessment.rb
class Assessment < ActiveRecord::Base
has_many :evaluations
has_many :students, through: :evaluations
accepts_nested_attributes_for :evaluations, reject_if: :all_blank
end
This will allow you to do the following:
#app/controllers/assessments_controller.rb
class AssessmentsController < ApplicationController
def new
#assessment = Assessment.new
#students = Student.all
#students.each do
#assessment.evaluations.build
end
end
end
Allowing you:
#app/views/assessments/new.html.erb
<%= form_for #assessment do |f| %>
<%= f.fields_for :evaluations, #students do |e| %>
<%= e.hidden_field :student_id %>
<%= e.text_field :grade %>
<% end %>
<%= f.submit %>
<% end %>
As far as I can tell, this will provide the functionality you need.
Remember that each evaluation can connect with existing students, meaning that if you pull #students = Student.all, it will populate the fields_for accordingly.
If you wanted to add new students through your form, it's a slightly different ballgame.
Cocoon
You should also be clear about the role of Cocoon.
You seem like an experienced dev so I'll cut to the chase - Cocoon is front-end, what you're asking is back-end.
Specifically, Cocoon is meant to give you the ability to add a number of fields_for associated fields to a form. This was discussed in this Railscast...
Technically, Cocoon is just a way to create new fields_for records for a form. It's only required if you want to dynamically "add" fields (the RailsCast will tell you more).
Thus, if you wanted to just have a "static" array of associative data fields (which is I think what you're asking), you'll be able to use fields_for as submitted in both Max and my answers.
Thanks to #rich-peck I was able to figure out exactly what I wanted to do. I'm leaving his answer as accepted because it was basically how I got to my own. :)
assessments/new.html.haml (just raw, no fancy formatting)
= form_for #assessment do |f|
= f.fields_for :evaluations do |ff|
.meaningless-div
= ff.object.student.name
= ff.hidden_field :student_id, value: ff.object.student_id
= ff.label :comment
= ff.text_field :comment
%br/
assessments_controller.rb
def new
#assessment = Assessment.new
#students = Student.all
#students.each do |student|
#assessment.evaluations.build(student: student)
end
end
As I am learning RoR now, I would like to know a more appropriated (rails) way to achieve that the application only shows associated resources.
Right now I have the following models:
class Account < ActiveRecord::Base
has_many :billing_accounts
has_many :addresses
end
class BillingAccount < ActiveRecord::Base
belongs_to :invoice_address,
class_name: "Address",
foreign_key:"invoice_address_id"
end
class Address < ActiveRecord::Base
has_many :billing_accounts
belongs_to :account
end
In my edit.billing_account I have this form:
= simple_form_for([:account, #billing_account]) do |f|
= f.association :invoice_address
I expected that only the associated address will be shwon, but this shows "all" address records in the database (also from other user accounts).
Users only should be able to see account.addresses and for now I do this with:
= f.association :invoice_address, collection: current_user.account.addresses.all
But I am sure there is better way to do this inside the models. For every form I now use current_user.account.MODEL.all but that is not very DRY I think.
So basically what I want is only to use =f.association :invoice_address and BillingAccount should know it only can show the account.addresses.
Suggestions are welcome. Thanks!
You just need to set default_scope for nested models:
class Address < ActiveRecord::Base
default_scope { where(account_id: current_user.account_id) }
But in this case you should define current_user in models
In your case you should use f.simple_fields_for instead of f.association as described here: https://github.com/plataformatec/simple_form/wiki/Nested-Models
class BillingAccount < ActiveRecord::Base
belongs_to :invoice_address,
class_name: "Address",
foreign_key:"invoice_address_id"
accepts_nested_attributes_for :invoice_address
end
View:
= simple_form_for([:account, #billing_account]) do |f|
= f.simple_fields_for :invoice_address do |f_address|
= f_address.input :street
= f_address.input :zipcode
...
Don't forget to build invoice_address of account in a controller if it is needed. For example:
class BillingAccountController < ApplicationController
def new
#billing_account = BillingAccount.new
#billing_account.build_invoice_address
end
Since you're using has_many you can use the plural version of the model name rather than current_user.account.MODEL.all.
Like this:
current_user.account.addresses
or
current_user.account.billing_accounts
It even works the other way with belongs_to:
#address = Address.last
#address.accounts
Try to add conditions to belongs_to association:
class BillingAccount < ActiveRecord::Base
belongs_to :invoice_address,
->(billing_account) { where "account_id = #{billing_account.account_id}" },
class_name: "Address",
foreign_key:"invoice_address_id"
end
Currently, my app have 3 models,
I want to add new Receipt by specifying the quantity of food required.
class Receipt < ActiveRecord::Base
# columns: id, place
has_many :receipt_foods
has_many :foods, through: :receipt_food
end
class ReceiptFood < ActiveRecord::Base
# columns: id, quantity, receipt_id, food_id
belongs_to :receipt
belongs_to :food
end
class Food < ActiveRecord::Base
# columns: id, name
has_many :receipt_foods
has_many :receipts, through: :receipt_food
end
My problem is how to create the quantity of food in my form,
for example: create 3 different food where 3 quantity each food
I know how to create this in rails console only, don't know how in web using form_for
I have try fields_for but most examples explain create and edit 2 models property only.
Can any one suggest ways or any materials/articles talk about this?
Thanks
I am not sure, but I would do:
= form_for :receipt_food do |f|
= f.number_field :quantity, min: 0
= f.select :food_id, Food.all.map{ |food| [food.name, food.id] }
= f.select :receipt_id, Receipt.all.map{ |receipt| [receipt.place, receipt.id] }
I would add labels to these fields.
Also, I think the convention is to name it: FoodReceipt, it's alphabetical.
Here are my models:
class Lesson < ActiveRecord::Base
belongs_to :topic, :polymorphic => true
validates_presence_of :topic_type, :topic_id
end
class Subject < ActiveRecord::Base
has_many :lessons, :as => :topic
end
class Category < ActiveRecord::Base
has_many :lessons, :as => :topic
end
Now, what I need is a form that will allow the user to create or update Lessons. The questions is, how can I provide a select menu that offers a mix of Subjects and Categories? (To the user, on this particular form, Subjects and Categories are interchangeable, but that's not the case elsewhere.)
Ideally, this would look something like this:
views/lessons/_form.html.haml
= simple_form_for(#lesson) do |f|
= f.input :title
= f.association :topic, :collection => (#subjects + #categories)
That won't work because we'd only be specifying the topic_id, and we need the topic_types as well. But how can we specify those values?
I guess the crux of the problem is that I really want a single select menu that specifies two values corresponding to two different attributes (topic_id and topic_type). Is there any elegant railsy way to do this?
A few notes:
a) Single table inheritance would make this issue go away, but I'd like to avoid this, as Categories and Subjects have their own relationship… I'll spare you the details.
b) I might could pull some javascript shenanigans, yes? But that sounds messy, and if there's a cleaner way to do it, some magic form helper or something, then that's certainly preferable.
c) Though I'm using simple_form, I'm not wedded to it, in case that's complicating matters.
Thanks
If you don't wish to use STI, you can do something similar: create a new model Topic(name:string) which will polymorphically reference Subject or Category.
class Lesson < ActiveRecord::Base
belongs_to :topic
validates_presence_of :topic_id
end
class Topic < ActiveRecord::Base
belongs_to :topicable, :polymorphic => true
end
class Subject < ActiveRecord::Base
has_one :topic, :as => :topicable
has_many :lessons, :through => :topic
accepts_nested_attributes_for :topic
end
class Category < ActiveRecord::Base
has_one :topic, :as => :topicable
has_many :lessons, :through => :topic
accepts_nested_attributes_for :topic
end
In the view where you create a new Subject/Category:
<%= form_for #subject do |subject_form| %>
<%= subject_form.fields_for :topic do |topic_fields| %>
<%= topic_fields.text_field :name %>
<% end %>
<% end %>
After thinking this through, the less dirty implementation IMO would be to hire the JS shenanigans (b):
= f.input_field :topic_type, as: :hidden, class: 'topic_type'
- (#subjects + #categories).each do |topic|
= f.radio_button :topic_id, topic.id, {:'data-type' => topic.class.name, class: 'topic_id'}
With a sprinkle of JS (your needs may vary):
$('input:radio.topic_id').change(function() {
$('input:hidden.topic_type').val($(this).attr('data-type'));
});
Notes:
I use a radio button to select a topic (category or subject) in a list
The class name of each of possible topic is stored in an attribute 'data-type'
When a radio button is selected, the class name is copied to the hidden input via JS
Using: HTML5, jQuery, haml, simple_form