I have a model Post which belongs_to one Section. There are two different Section subclasses and I use STI to implement different behavior for each of them. In the Post form I would like to have a tab for each Section. The tab will let the user either A) Pick from an existing Section using a <select> or B) Let the user create a new Section. I would like to know how to use accepts_nested_attributes_for and fields_for or whatever is required to get this done The Rails Way.
Any advice is greatly appreciated. Thanks.
Assuming the tabs correspond to the two subclasses
class Post
# the two subclasses. Each instance will only be using one or the other
belongs_to :section_foo
belongs_to :section_bar
accepts_nested_attributes_for :section_foo
accepts_nested_attributes_for :section_bar
end
And in the view (probably once per tab)
= form_for #post do |f|
= f.select :section_id, SectionFoo.all # etc
= fields_for #post.build_section_foo do |s|
= s.text_field :bla_bla_bla
That should get you 85% of the way there. You might need some :reject_if bidness on the accepts_* to avoid creating a new section and assigning an old section.
Related
In application user can enter new post which contain title, content of the post and category of post. So creating new post will be through some simple html form with few fields. Now i don't know where to put logic for creating new post for following reasons:
Post(or posts collection) is object which is constructed from different tables, for example.
#posts = User.joins(entries: [{storage: :vote}, :category])
.where("votes.count > ?", 0)
.select("users.username AS username,
storages.id AS storage_id,
storages.title AS title,
storages.content AS content,
votes.count AS votes,
categories.category_name AS category_name")
.order("votes.count DESC")
So when user create new post application must create new entries in different tables:
1.Create new entry in entries table. (id, user_id, category_id)
2. Create new entry in storages table.(id, title, content, entry_id)
3. Create new entry in vote table.(id, count, storage_id)
In situation where post is model i can use something like resources: posts then in posts controller through new and create i can create new post, but what in situation like this where i don't need posts controller nor post model? So, question is: which place is more appropriate to put logic for creating new post? Q1
My solution is to craete Storages controller with resource: storages, :only => [:new, :create] then through new and create of this controller to populate different tables in db? I'm forcing here only because i dont see any point of other CRUD actions here(like showing all or one storage), because i will not use storages individually but in conjunction with other tables. So from views/storages through new.html.erb and create.html.erb i can construct new post? Q2
Another solution is to create Post controller which doesn't have "corresponding" post model as i stated before. Here i'm guessing i can't use restful routes(CRUD) because there is not :id of post? I only can make manually non-restful routes like:
post 'posts/create/:title/:content/:category' => 'posts#create', :as => 'create_post' then from params[:title], params[:content] and params[:category] to populate other tables. Q3
Im new to rails so dont yell please :D
This sound like a call for nested forms, its covered in a screen cast
here.
You use the resources of the top model, in this case Entry.
and drill down to the 3rd model.
Simple sample of what to do is bellow.
Model should look like so,
entry.rb
class Entry < ActiveRecord::Base
has_many :storages, :dependent => :destroy
accepts_nested_attributes_for :storages, :allow_destroy => true
end
storage.rb
class Storage < ActiveRecord::Base
belongs_to :entry
has_many :votes, :dependent => :destroy
accepts_nested_attributes_for :votes, :allow_destroy => true
end
vote.rb
class Vote < ActiveRecord::Base
belongs_to :storage
end
and the form should look like so, in simple_form style
<%= simple_form_for #entry do |f| %>
<%= f.simple_fields_for :storages do |storage_fields| %>
<%= storage_fields_for :votes do |vote_fields| %>
<% end %>
<% end %>
<% end %>
and if you have all the models set up, you shouldn't have to do anything to the controller.
This approach is also nice because you can add multiple storages and votes ajax style(without reloading the page) if needed, which is always nice.
I'd use a form class instead of nested attributes any day, see http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/ for an example of this pattern (Chapter "3. Extract Form Objects")
I've used the pattern often enough to gemify it https://github.com/bbozo/simple_form_class and it's used roughly in this way: https://gist.github.com/bbozo/5036937, if you're interested to use it I'll push some docs
EDIT: reason why I tend to go the form class route most of the time is because nested attributes never failed to bite me in the end, either because strong parameter handling got cumbersome, or validators get too complicated (if persisted? else ...), or persistence logic needs to be extended to support some little knack that resolves into callback hell or recursive saves in the model (before/after save spaghetti troubles)
Here is my setup, followed by an explanation of what I am trying to accomplish.
class Layer < ActiveRecord::Base
has_and_belongs_to_many :components
end
class Component < ActiveRecord::Base
has_and_belongs_to_many :layers
end
class ImageComponent < Component
# I want this table to inherit from the Component table
# I should be able to add image-specific fields to this table
end
class VideoComponent < Component
# I want this table to inherit from the Component table
# I should be able to add video-specific fields to this table
end
What I want to be able to do:
layer.components << ImageComponent.create
layer.components << VideoComponent.create
In practice, I realize that ImageComponent and VideoComponent will actually have to inherit from ActiveRecord::Base. Is there any way to nicely implement model subclassing in Rails?
Right now I have my Component model setup to be polymorphic such that ImageComponent and VideoComponent each has_one :component, as: :componentable. This adds a layer of annoyance and ugliness to my code though:
image_component = ImageComponent.create
component = Component.create
component.componentable = image_component
layer.components << component
I guess a simple way to explain this is that I want to implement a habtm relationship between Layers and Components. I have multiple types of Components (i.e. ImageComponent, VideoComponent) that each have the same base structure but different fields associated with them. Any suggestions on ways this can be accomplished? I feel that I am missing something because my code feels hackish.
The "official" way to achieve this in Rails is to use Single Table Inheritance. Support for STI is built into ActiveRecord: http://api.rubyonrails.org/classes/ActiveRecord/Base.html#class-ActiveRecord::Base-label-Single+table+inheritance
If you want to use Multi Table Inheritance you would have to implement it by yourself...
here the main issue is between the Component and its types and not Layer and Component. i had a similar problem. will explain the solution specific to ur problem.
Store the type(Image/Video) as resource for Component and have a controller for Component and not all the types()
let the model structure be as
Component < ActiveRecord::Base
accepts_nested_attributes_for :resource
belongs_to :resource, :polymorphic => true, :dependent => :destroy
def resource_attributes=(params = {})
self.resource = spec_type.constantize.new unless self.resource
self.resource.attributes = params.select{|k| self.resource.attribute_names.include?(k) || self.resource.class::ACCESSOR.include?(k.to_sym)}
end
#component will be either image or video and not both
Image < ActiveRecord::Base
has_one :component, as :resource
Video < ActiveRecord::Base
has_one :component, as :resource
and a single controller as ComponentsController for CRUD of Component. Since the Component accepts attributes for resource(ie image/video), u can save the component as well as the resource and add normal validations for each resource.
the basic view for adding a Component can be as
= form_for(#component, :url => components_path, :method => :post) do |f|
= fields of Component
= f.fields_for :resource, build_resource('image') do |image|
= fields of resource Image
= f.fields_for :resource, build_resource('video') do |video|
= fields of resource Video
the fields for Image/Video can be added using the helper method
module ComponentsHelper
def build_resource(klass)
klass = "{klass.capitalize}"
object = eval("#{klass}.new")
if #component.resource.class.name == klass
object = #component.resource
end
return object
end
end
since the Component can have only one related resource(image/video), u need to select the the resource type on the view(in my case it was a dropdown list) and depending upon the selected resource show its fields and hide/remove all other resources fields(if image is selected, remove video fields using javascript). When the form is submitted, the method from Component model filters out all the key-value pairs for the intended resource and creates the component and its related resource.
Also
1) keep the field names for each resource unique cause when the form is submitted, the hidden resource(unwanted resources) fields are submitted which overwrite the intended resource fields.
2) the above model structure gives problem for resource attr_accessor only(they are not accessible on rails console). it can be solved as
ACCESSOR = ['accessor1', 'accessor2'] #needed accessors
has_one :component, :as => :resource
attr_accessor *ACCESSOR
See how to implement jobpost functionality that has 3 fixed categoris
i hope this helps.
With STI, you are sharing the same table with several model classes, so if you want subclassed models to have unique fields (database columns), then they need to be represented in that common table. From the comments in your example, it appears that this is what you want.
There is a trick you can do, however, which involves having a string column in the table that each model can use to store custom serialized data. In order to do this, it has to be OK that these data elements aren't indexed, because you won't be able to easily search on them within SQL. Let's say you call this field aux. Put this in the parent model:
require 'ostruct'
serialize :aux, OpenStruct
Now let's say you want the fields called manager and experience in a subclassed model, but none of the other STI models need this field and you won't need to search based on these attributes. So you can do this in the subclassed model:
# gets the value
def manager
return self.aux.manager
end
# sets the value
def manager=(value)
self.aux.manager = value
end
# gets the value
def experience
return self.aux.experience
end
# sets the value
def experience=(value)
self.aux.experience = value
end
In this example, single table inheritance still works fine and you also get custom persistant attributes for subclassed models. This gives you the benefits of sharing code and database resources among several models, but also allows each model to have unique attributes.
I have a very simple model
class Lifestyle < ActiveRecord::Base
attr_accessible :name
has_and_belongs_to_many :profiles
end
that has a has_and_belongs_to_many relationship with Profile
class Profile < ActiveRecord::Base
attr_accessible ...
belongs_to :occupation
has_and_belongs_to_many :lifestyles
accepts_nested_attributes_for :lifestyles
end
I want to use ActiveAdmin to edit the Profile object, but also assign Lifestyles to a profile. It should be similar to dealing with belongs_to :occupation, as this is sorted out automatically by ActiveAdmin to a dropbox with the options pre-filled with available occupations.
I've tried to use the has_many form builder method, but that only got me to show a form to type in the name of the Lifestyle and on submission, it returned an error.
f.object.lifestyles.build
f.has_many :lifestyles do |l|
l.input :name
end
Error I get:
Can't mass-assign protected attributes: lifestyles_attributes
The perfect way for me would be to build several checkboxes, one for each Lifestyle in the DB. Selected means that the lifestyle is connected to the profile, and unselected means to delete the relation.
I'm having great doubts that this is possible using ActiveAdmin and without having to create very complex logic to deal with this. I would really appreciate it if you'd give your opinion and advise me if I should go this way or approach it differently.
After some research, I am ready to answer my own question.
First, I have to say thanks to #Lichtamberg for suggesting the fix. However, that only complicates things (also regarding security, though not an issue in this case), and doesn't help me reach my ideal solution.
Digging more, I found out that this is a very common scenario in Rails, and it's actually explained in Ryan Bates' screencast no #17.
Therefore, in Rails, if you have a has_and_belongs_to_many (short form HABTM) association, you can easily set the ids of the other associated object through this method:
profile.lifestyle_ids = [1,2]
And this obviously works for forms if you've set the attr_accessible for lifestyle_ids:
class Profile < ActiveRecord::Base
attr_accessible :lifestyle_ids
end
In ActiveAdmin, because it uses Formtastic, you can use this method to output the correct fields (in this case checkboxes):
f.input :lifestyles, as: :check_boxes, collection: Lifestyle.all
Also, I have simplified my form view so it's now merely this:
form do |f|
f.inputs # Include the default inputs
f.inputs "Lifestlyes" do # Make a panel that holds inputs for lifestyles
f.input :lifestyles, as: :check_boxes, collection: Lifestyle.all # Use formtastic to output my collection of checkboxes
end
f.actions # Include the default actions
end
Ok, now this rendered perfectly in the view, but if I try and submit my changes, it gives me this database error:
PG::Error: ERROR: null value in column "created_at" violates not-null constraint
: INSERT INTO "lifestyles_profiles" ("profile_id", "lifestyle_id") VALUES (2, 1) RETURNING "id"
I found out that this is due to the fact that Rails 3.2 doesn't automatically update the timestamps for a HABTM association table (because they are extra attributes, and Rails only handles the _id attributes.
There are 2 solutions to fix this:
Either convert the association into a hm:t (has_many, :through =>)
Or remove the timestamps from the table
I'm going to go for 2) because I will never need the timestamps or any extra attributes.
I hope this helps other people having the same problems.
Edit: #cdesrosiers was closest to the solution but I already wrote this answer before I read his. Anyway, this is great nevertheless. I'm learning a lot.
Active Admin creates a thin DSL (Domain-Specific Language) over formtastic, so it's best to look at the formastic doc when you need form customization. There, you'll find that you might be able to use f.input :lifestyles, :as => :check_boxes to modify a has_and_belongs_to_many relationship.
I say "might" because I haven't tried this helper myself for your particular case, but these things have a tendency to just work automagically, so try it out.
Also, you probably won't need accepts_nested_attributes_for :lifestyles unless you actually want to modify the attributes of lifestyles from profiles, which I don't think is particularly useful when using active admin (just modify lifestyles directly).
Add
attr_accessible :lifestyles_attributes
f.e.:
class AccountsController < ApplicationController
attr_accessible :first_name, :last_name
end
I've been trying to switch my Orders model to a polymorphic association with my Product and Service models. However, I have a few questions that I haven't been able to find answers to, even after watching the RailsCast and reading the documentation (so, those suggestions are appreciated, but I need a more concrete answer).
Question:
Is a polymorphic association the best thing to use in this case? Prior to this, I was using a Transaction model that had multiple belongs_to associations and used a custom Parent function to determine which one it was. This was working fine, but someone suggested a polymorphic association may clean things up.
I set up the polymorphic association properly and have been unable to have the transactable_id and transactable_type automatically populated. The code is below. I have side-stepped this by manually putting them in inside the form, but if anyone knows the proper way to do it, that would be great!
How can I access elements with polymorphic associations? For example, in my Cart object (which has_many Transactions and which Transactions belongs_to) I can no longer access things using #cart.transactions.each do |t| ... #t.product.name type coding.
My model associations look like this:
class Order < ActiveRecord::Base
belongs_to :orderable, :polymorphic => true
end
class Product < ActiveRecord::Base
has_many :orders, :as => :orderable
end
My forms used to look like this:
<% form_for [#orderable, #order] do |f| %>
...
<% end %>
And were rendered like this in my Product Show view:
<%= render 'orders/form' %>
Now, I pass a variable for the product.id in the render partial and use it to populate the transactable_id field. But, I feel like that is very messy.
Again, I have read the tutorials and API docs and have been unable to solve this, so any help would be greatly appreciated!!
Answers to your questions:
If your business login implies that multiple models will have related model with the same fields so you should use polymorphic association. (In your case you can use it).
If set up polymorphic association Rails will automatically handle setting *_id and *_type fields depending on associated parent model.
Lets say you have Product with many orders in polymorphic association and you want to define which model order belongs to:
order = Order.first
order.orderable
I want to use fields_for on a subset of records in an association.
I have a Month model, which has_many :payments.
But in my form in my view I only want to have fields_for some of those payments. For example:
- fields_for #month.payments.large
This doesn't work.
Can I pass a set of records to fields_for, rather than the usual symbol (fields_for :payments) approach?
You can add additional association for large payments, for example:
class Month < ActiveRecord::Base
has_many :payments
has_many :large_payments, :class_name => "Payment", :conditions => "value > 1000000"
end
After that you can use fields_for in common way:
- fields_for :large_payments
I think to encapsulate this logic on a model side is a better approach then in the view.
You can, however, use an array of objects without having to create any additional associations. For example, let's say that in your controller you prepared some array of #large_payments, then in the view you can do the following:
<%= f.fields_for :payments, #large_payments do |payment| %> ...
That way if you've got a pretty big form or multiple pages of forms, and you don't want to have to create an additional association for each group that you want to display, you don't have to.