Here's my model hierarchy:
Page
has_many :media (media can be TextMedium, ImageMedium, VideoMedium... (STI))
each media has_one :attachment (ImageAttachment, VideoAttachment... (different tables))
I want to have a form to create a page with:
an ImageMedium (not required)
a VideoMedium (not required)
other attributes...
An ImageMedium validates the presence of its attachment.
Same for a VideoMedium.
But the page don't validates the presence of an ImageMedium or VideoMedium media.
My form looks something like this ("pseudo code"):
form_for page
fields_for :image_media
fields_for :attachement
image_field
fields_for :video_media
fields_for :attachement
video_field
PROBLEM:
if I fill in the form completely, it works great, and I can create the page with its media and attachments.
but if I don't fill in the video field for instance, I would still like my page to be created (because the video media is not required by the page). BUT it won't be, because the video media requires the presence of its attachment, so it's invalid, so the page is invalid...
QUESTION:
How can I ignore the fact that the video media validation fails and still create the page?
EDIT:
i found an answer:
class Page < ActiveRecord::Base
accepts_nested_attributes_for :video_media, reject_if: ->(attributes) { attributes["attachment_attributes"]["video"].blank? }
end
If you have a better one, please share.
I found an answer:
class Page < ActiveRecord::Base
accepts_nested_attributes_for :video_media, reject_if: ->(attributes) { attributes["attachment_attributes"]["video"].blank? }
end
If you have a better one, please share.
Related
I have models in my Rails 5 app for User, Proposal and Potential.
Users create Proposals which they themselves and others can then create comments on.
The associations between models are:
User
has_many :proposals, dependent: :destroy
has_many :potentials
Proposal
belongs_to :user
has_many :potentials, inverse_of: :proposal
accepts_nested_attributes_for :potentials, reject_if: :all_blank, allow_destroy: true
Potential
belongs_to :proposal, inverse_of: :potentials
belongs_to :user
In my routes file, I have two resources for potentials. I'm not sure if I've gone off piste with this bit- I cant find an example of how to do this otherwise. I have both:
resources :potentials
as well as:
resources :proposals do
resources :potentials
The reason I have done this is so that when the user is creating a proposal, they can use a nested fields form to add potential attributes. When another user sees that proposal, they get a new form to add a :potential set of attributes (they don't do it via the proposal form).
In my potentials view folder, I have views for new which renders form for as well as potential_fields_for which is incorporated in my proposals form (only the proposal creator can use the nested fields).
The new/render form has:
<%= simple_form_for [ #proposal, #potential ] do |f| %>
f
The proposal form has:
<%= f.simple_fields_for :potentials do |f| %>
<%= f.error_notification %>
<%= render 'potentials/potential_fields', f: f %>
<% end %>
<%= link_to_add_association 'Add another novel aspect', f, :potentials, partial: 'potentials/potential_fields' %>
</div>
In my proposals controller, I'm trying to find a way to exclude 3rd party created :potentials from the fields displayed in the proposal form.
def edit
#proposal.potentials_build unless #proposal.potentials || #proposal.potential.user_id != current_user.id
I don't want the proposal creator to be able to edit those fields from the proposal form but even if I don't touch them, the user id on the 3rd party potential gets updated to the proposal creator id when I update the proposal form (without updating those specific 3rd party potential attributes).
I tried to change the edit action in the proposals controller be excluding potentials created by a user that is not the current user id. Only the proposal creator can edit the proposal, so I expect that this will exclude instances of proposal.potential that have a user id other than the proposal.user_id.
That doesnt work.
Is there a way that I can limit the proposal#edit action to only those potential instances that are not created by the proposal creator?
TARYN'S SUGGESTION
I tried to adopt Taryn's thoughts about this.
In my proposal.rb, I made 2 scopes:
scope :owner_potentials, ->{ where(user_id: potential.user_id ) }
scope :third_party_potentials, ->{ where(user_id: != potential.user_id) }
I am not confident that this is the correct way to write a scope. Although I cant find a reference to how to write them, on previous occasions when I have tried to learn how to compose them, I have received advice to set them out in this format:
scope :owner_potentials, ->(user){ where(user_id: potential.user_id ) }
scope :third_party_potentials, ->(user){ where(user_id: != potential.user_id) }
I tried this way as well, but I get the same error as I do if I don't include "(user)". I don't understand what that inclusion does or should do.
In my proposal controller, edit action, I have then tried each of the following:
# #proposal.potentials_build unless #proposal.potentials || #proposal.potential.user_id != current_user.id
# #proposal.owner_potentials.build unless #proposal.owner_potentials
##potentials = #proposal.owner_potentials || #proposal.owner_potentials.build
The first one was my original attempt. It didnt work and prompted me to write this post.
The second one is the way I think it should be written.
The third is just incorporating Taryn's idea directly to see if that's how it should be written (although I think that was more of general way of describing what to do).
None of these work. In the case of the 2nd version of this attempt, I get an error that says:
NoMethodError at /proposals/17/edit
undefined method `owner_potentials' for #<Proposal:0x007f84f6ca1700>
I think the reason why this isnt working is that the scope is run on the class as a table, not the specific instance to be edited. I think this post explains that.
Proposals have many potentials, so the idea is to check all of the potentials belonging to the specific proposal to see whether any of those potentials have the user id that is the same as the user id on the proposal instance. How can I do that?
I cant write:
Proposal.owner_potentials.build unless Proposal.owner_potentials
in the proposal controller edit action because the set_proposal method is picking out the correct proposal for edit to apply on.
Can I use a scope in a controller edit action where there is a has_many relationship that is being tested by the scope?
NEXT ATTEMPT
My next thought is to try to define the scopes in the Potential model so that the scope can run on the class.
I tried:
scope :owner_potentials, ->{ where('user_id == potential.proposal.user_id' ) }
scope :third_party_potentials, ->{ where('user_id != potential.proposal.user_id') }
I spent hours on codementor trying to learn scopes and my takeaway from that session is the syntax i used above is incorrect - but the way I was shown to write them (which is at the top of the post, gives an error with the undefined variable). I don't know how to figure out how to learn to write a scope.
Anyway- next I tried changing the edit action in my Proposal controller to:
#proposal.potentials.owner_potentials.build unless #proposal.potentials.owner_potentials
Now, I get no errors when I save this and try it, but when I try to edit the proposal, I can still edit potentials that have been created by third parties. I don't understand whether Im not writing the scope effectively, or if this solution isnt going to work for another reason.
I would consider adding a custom scoped association eg owners_potentials/third_party_potentials that limits potentials to those created by the owner/ by somebody other than the owner. You can then use these scopes whenever you need them.
eg
has_many :potentials, inverse_of: :proposal
has_many :owner_potentials, ->{ where("potentials.user_id = proposals.creator_id") }
has_many :third_party_potentials, ->{ where("potentials.user_id != proposals.creator_id") }
Then you could do something like:
def edit
#potentials = #proposal.owner_potentials || #proposal.owner_potentials.build
Then in the form, be specific about using the one you've specified:
<%= f.simple_fields_for #potentials do |f| %>
Note: code has not been tested or sanity-checked, it's here to give an idea of the kind of thing you can do - getting it to actually work is left as an exercise for the reader ;)
I have been trying to wrap my mind on a polymorphic structure for my app for a while now, but I can't get it, and I don't know how to solve it.
I have a Supplier model, and a supplier's profile can by a Company OR a Person as listed below.
Supplier Model
class Supplier < ActiveRecord::Base
belongs_to :profile, :polymorphic => true
accepts_nested_attributes_for :personable, :allow_destroy => true, :reject_if => :all_blank
end
Company Model
class Company < ActiveRecord::Base
has_one :supplier, as: :profile
end
Person Model
class Person < ActiveRecord::Base
has_one :supplier, as: :profile
end
On the Model level (on console) my associations works fine, but I have to those between the two model up front (supplier.profile = Company.new, for example).
What I want is, when a user get to the NEW Supplier action he can select on the form if the Supplier is a Person or a Company, and then something must be done to show the correct fields for. And back on the Create action, handle everything.
I have read many stackoverflow questions and watch Ryan Bates cast #197 and #394.
I'm a Rails enthusiastic, not a programmer.
If someone can point me out to the right direction, I would be thankful.
Regards,
David
This largely depends on the variety of fields you need, and how much data is shared. But if they are very different, I would actually have two separate divs or section on the page that a radio button toggles showing one & hiding the other with javascript. Can do this pretty easily with jquery at the bottom of the view, given it's a default in rails:
<input type="radio" name="supplier_type" value="person" />
<input type="radio" name="supplier_type" value="company" />
<script>
$('input[name="supplier_type"]').on('change', function () {
if ($(this).val() === "person") {
$('.person-form').show();
$('.company-form').hide();
}
else {
$('.person-form').hide();
$('.company-form').show();
}
});
</script>
Then inside each of those divs have an entirely separate form that post to different actions. If the data is different enough to require two forms, then it's worth having two different controllers for this.
Now if it's just a couple fields different, or an additional field for one or something. I would have the radio button same as above inside the form_for. Again attach some javascript to show or hide the values. In the controller, use a check against the radio button value to get the params you need:
supplier_params = params.slice :list, :of, :generic, :params
if params[:supplier_type] == "company"
supplier_params.merge! params.slice :company_name
elsif params[:supplier_type] == "person"
supplier_params.merge! params.slice :person_name
end
Supplier.new supplier_params
I saw a feature in an app that I'd like to be able to implement. The app has several resources - photos, articles etc.. In the nav bar next to the photos and articles tabs there were two buttons - organization and personal. When one clicks on the organization button if they then click on the photos or articles, they get a list of all photos and articles that belong to the members of their organization. If they clicked on the personal button and after that they click on photos or articles, they get lists of only their personal photos and articles, omitting the resources that belong to the other members of their organization. So I wonder how this state is kept between requests.
I imagine that one way would be to constantly pass a variable between the views and the controller and based on that variable to list a particular resource. Or maybe save the state in the session (though I suppose this should be done as a last resort). Another way would be to use a decorator like draper, but I am kind of confused about the specifics of implementing this. I would be very grateful if somebody points me to an article or to a tutorial that shows how to implement such a feature or just provides an overview of the steps.
To be clear, one again: there are links to index different resources, but based on a parameter the index action of the respective controller returns different results. Something like:
def index
if params[:type] == 'organization'
#photos = Organization.find_by(id: params[:organization][:id]).photos
else
#photos = User.find_by(id: params[:user][:id]).photos
end
end
The question is - how do I pass the type parameter - hard code it in the path helpers and have different views with different values for that parameter or is there a better way?
This is the relationship of my models:
class User < ActiveRecord::Base
belongs_to :organization,
inverse_of: :members
has_many :photos,
inverse_of: :owner,
foreign_key: :owner_id,
dependent: :destroy
...
end
class Photo < ActiveRecord::Base
belongs_to :owner,
class_name: User,
inverse_of: :photos
belongs_to :organization,
inverse_of: :photos
...
end
class Organization < ActiveRecord::Base
has_many :members,
class_name: User,
inverse_of: :organization,
dependent: :destroy
has_many :photos,
inverse_of: :organization,
dependent: :destroy
...
end
By Reloading the Page with URL Parameters
I had a similar issue and there's a couple different ways to pass parameters depending on what you want. I actually just went with passing them through the url. If you append ?type=organization to your url, you will get an additional key-value pair in your params, 'type' => 'organization' and you can just use that. If you want to go to this URL through a link, you can just do link_to photos_path + "?type=organization". I ended up using javascript since I wanted something other than a link. Whatever your html element is, give it the attribute onclick=changeParam() and then define that function in your javascript.
function changeParam() {
window.location.search = "?type=organization"
}
That will automatically reload the same page with that parameter. If you want more than one, append ¶m2=value2 to what you have so far.
Through AJAX
You can also use AJAX to pass parameters if you don't want a full refresh of the page. If you know how to use AJAX already, just use the data option:
$.ajax({
type: "GET",
url: "/photos",
data: { type : organization, param2 : value2 }
})
In this case, your params are sent though data. If you want to know more about AJAX and how to use that, let me know and I will update my answer with more details. It's pretty confusing in Rails and there isn't good documentation, so I'll add what I know if you want.
it can be solved on many different ways, but for example, if I understood you, one of them is that the resources photos, articles etc.. have one field which can be 'organization' or 'personal'. After that by clicking on this in application, you can filter resources (articles, photos,...) by that field.
Everything depends on situation, maybe another solution will be to create totally separated module where you gonna store things like organization, personal etc. This is better if you want to extend latter that and next to organization and personal add something else.
As I said, everything depends on situation and project.
additional:
ok, from your example I can see that by clicking on, for example, user link, you will have user id. Therefore, you can easily show all photos of that user:
# params[id] - user id which is got by clicking
user = User.find(params[id])
#photos = user.photos
I have User model with the following association:
class User < ActiveRecord
has_many :pictures, :as => :imageable
accepts_nested_attributes :pictures
end
My Picture model looks like this:
class Picture < ActiveRecord
belongs_to :imageable, :polymorphic => true
has_attached_file :image
end
Now, I want to user to be able to upload maximum 5 images. And he will select 1 image as his avatar. Now, user can upload images but I don't know how to limit the maximum number of pictures. One more thing, user needs to be able to change his avatar image. How can I achieve this?
In my view, I use input file with name user[picture_attributes][0][image] in order to allow user to change the first picture but it keeps inserting new pictures into database instead of replacing the first picture.
Please help me on this.
Thanks in advance
For the first part of the problem you have, i would suggest you use rails built-in counter_cache method.
Your picture model would thus become:
class Picture < ActiveRecord::Base
belongs_to :imageable, :polymorphic => true, counter_cache: true
has_attached_file :image
end
Also you would need to add a column called pictures_count to the User model.
This way in your controller you could check if the count is upto 5 records and therefore inform them that they have uploaded the maximum allowed.
if #user.pictures.size == 5 #sorry no more uploads
For the second part of the problem. Is the form action pointed to the new/create action or to your update action. If pointed to the new action a new record would be created but if pointed to the update action then it should change the first image record as you expect.
#charinten For the second part of the problem, some suggestions:
You could try making the id of the pictures accessible in the user model. This way when you try to point to the image to use as an avatar, rails uses that id to update the record. If you try to update the record without pointing rails to that id, it would assume you are trying to create a new record.
Also rather than using user[picture_attributes][0][image] you could in your user profile. Find a specific image and point to that image on the edit action.
Hope this helps
Have a nested form, the relationship is like so
class Inspection < ActiveRecord::Base
has_many :inspection_components
accepts_nested_attributes_for :inspection_components
class InspectionComponent < ActiveRecord::Base
belongs_to :inspection
I have a custom validate method in Inspection which depends on attributes entered for InspectionComponent. How can I validate - InspectionComponent attributes are not saved or available in validation for Inspection.
Thanks!
EDIT: To make things a bit more clear, here's an example of what I'm trying to do.
Inspection has an attribute status.
InspectionComponent also has an attribute status.
The Inspection edit form has nested InspectionComponents and one can update each model's status' on this form. #inspection.status should only be able to be marked 'complete' if all #inspection_component.status == 'complete'.
Therefore, when validating #inspection, I must be able to see what the user entered for #inspection_component.status.
Obviously I have access to the params of both instances in the controller however in the model, where validation should occur, I don't see a way of making this happen.
Hopefully that is clear, thanks.
Ok, a new answer in case the other one I posted is useful to someone else. Specifically for your issue, I think you need something like this:
class Inspection < ActiveRecord::Base
has_many :inspection_components
accepts_nested_attributes_for :inspection_components
# we only care about validating the components being complete
# if this inspection is complete. Otherwise we don't care.
validate :all_components_complete, :if => :complete
def complete
self.status == 'complete'
end
def all_components_complete
self.inspection_components.each do |component|
# as soon as you find an incomplete component, this inspection
# is INVALID!
Errors.add("field","msg") if component.status != 'complete'
end
end
end
class InspectionComponent < ActiveRecord::Base
belongs_to :inspection
end
You want to use validates_associated.
probably something like:
validates_associated :inspection_components
Do a search on it and look up the api. There are some useful options for this method as well.