Right now I have a Rails 3 model for storing profile data. One of the columns in that database table contains an image url if the user chooses to display a profile picture (this also integrates with the Facebook Graph API to store the user's profile picture url if they login with Facebook but that's irrelevant). The problem I am having is that when the image column is nil, I need a way to set it to a default path on my server. Please note that I cannot use a default value in a migration or model here. My thought was to use an after_find but the following is not working:
In Profile Model:
def after_find
if self.image.nil?
self.image = "/assets/generic_profile_image.png"
end
end
In view (HAML):
.profile_pic
= image_tag #user.profile.image
The Profile model is linked to a User model via a has_one association. Right now instead of dynamically turning the image attribute into "/assets/generic_profile_image.png", it seems to do nothing leaving me with the following generated code on my page:
<div class='profile_pic'>
<img alt="Assets" src="/assets/" />
</div>
Any suggestions on how to fix this issue would be greatly appreciated!
P.S. A conditional in the view is out of the question as the profile image is shown in way too many places!
Just make the condition in the model and reference it in the view.
class User
delegate :image, :to => :profile, :prefix => true, :allow_nil => true
def picture_url
if profile_image.present?
profile_image
else
"/assets/generic_profile_image.png"
end
end
end
I like this approach because you won't have to run a sql query when you want to change the default picture.
I added the delegate to prevent breaking the law of demeter.
Of course you've already guessed the view code:
.profile_pic
= image_tag #user.picture_url
My guess is your after_find callback ins't actually getting called. You need to define it this way:
class Profile < ActiveRecord::Base
after_find :update_image
def update_image
if self.image.nil?
self.image = "/assets/generic_profile_image.png"
end
end
end
Now everything should work fine.
Related
My model has a has_many_attached :photos.
The first time this model is created, it has 0 photos. If I run photos.attached? I get false.
When the user uploads some files to photos, I need to do some actions, but only the first time. I tried using before_update :if photos.attached?. But I need to know if the user is updating photos specifically.
Is there a way to know if the user is trying to update photos? Or is there a simple way to do this?
There is the dirty? method that you can use
class Post < ApplicationRecord
has_many_attached :photos
before_update :do_whatever, if: -> { photos.dirty? }
def do_whatever
# doing something
end
end
You might also be able to try before_update :do_whatever, if: -> { photos_changed? }
Check callbacks (after_create, after_save, etc) in rails models and options "on: :create" and "update".
Rails has not added the ability to add validations to file attachments. See Issue #31656
Try this approach:
Validate for mime-types:
<%= f.file_field :photos, accept: "image/png,image/gif,image/jpeg', multiple: true %>
You still need to add model validations. See this post Validating the Content-Type of Active Storage Attachments
on how to use a custom validator.
To limit your action to be executed only once, you could add a boolean field to your model and set it to true after your action terminates successfully. Use a before_update as Antarr Byrd suggested to check after each photo is attached to your model.
I've been following the Getting Started rails tutorial and am now trying some custom functionality.
I have 2 models, Person and Hangout. A Person can have many Hangouts. When creating a Hangout, a Person has to be selected and associated with the new Hangout. I'm running into issues however when I call my create action. This fires before the validate_presence_of for person.
Am I going about this the wrong way? Seems like I shouldn't have to create a custom before_create validation to make sure that a Hangout was created with a Person.
#hangout_controller
def create
#person = Person.find(params[:hangout][:person_id])
#hangout = #person.hangouts.create(hangout_params)
#hangout.save
redirect_to hangouts_path(#hangout)
end
#hangout.rb
class Hangout < ActiveRecord::Base
belongs_to :person
validates_presence_of :person
end
#person.rb
class Person < ActiveRecord::Base
has_many :hangouts
validates :first_name, presence: true
validates :met_location, presence: true
validates :last_contacted, presence: true
def full_name
"#{first_name} #{last_name}"
end
end
Create action fires before the validate_presence_of for person
I think you are confused about rails MVC. Your form contains a url and when you submit your form your form params are send to your controller action according to the routes you have defined in routes.rb Your controller action, in this case create action, interacts with model this is very it checks for your validations and if all the validations are passed your object is saved in databse so even though in your app the control is first passed to your controller but your object is saved only once if all the validations are passed.
Now lets comeback to your code. There are couple of things you are doing wrong
a. You don't need to associate your person separately:
In your create action you have this line:
#person = Person.find(params[:hangout][:person_id])
You don't need to do this because your person_id is already coming from your form and it'll automatically associate your hangout with person.
b. You are calling create method instead of build:
When you call .association.create method it does two things for you it first initialize your object, in your case your hangout and if all the validations are passed it saves it. If all the validations are not passed it simply rollback your query.
If you'll use .association.build it'll only initialize your object with the params coming from your form
c. Validation errors won't show:
As explained above, since you are calling create method instead of build your validation error won't show up.
Fix
Your create method should look like this:
def create
#hangout = Hangout.new(hangout_params) # since your person_id is coming from form it'll automatically associate your new hangout with person
if #hangout.save
redirect_to hangouts_path(#hangout)
else
render "new" # this will show up validation errors in your form if your hangout is not saved in database
end
end
private
def hangout_params
params.require(:hangout).permit(:person_id, :other_attributes)
end
You are confused with the controller and model responsibilities.
Let me try to explain what I think is confusing you:
First try this in your rails console:
Hangout.create
It shouldn't let you because you are not passing a Person object to the create method. So, we confirm that the validation is working fine. That validation means that before creating a Hangout, make sure that there is a person attribute. All this is at the model level, nothing about controllers yet!
Let's go to the controllers part. When the create action of the controller 'is fired', that controller doesn't know what you are trying to do at all. It doesn't run any validations. It is just an action, that if you want, can call the Hangout model to create one of those.
I believe that when you say 'it fires' you are saying that the create action of the HangoutController is called first than the create method on the Hangout model. And that is completely fine. The validations run at the model level.
Nested Attributes
I think you'll be better using accepts_nested_attributes_for - we've achieved functionality you're seeking before by using validation on the nested model (although you'll be able to get away with using reject_if: :all_blank):
#app/models/person.rb
Class Person < ActiveRecord::Base
has_many :hangouts
accepts_nested_attributes_for :hangouts, reject_if: :all_blank
end
#app/models/hangout.rb
Class Hangout < ActiveRecord::Base
belongs_to :person
end
This will give you the ability to call the reject_if: :all_blank method -
Passing :all_blank instead of a Proc will create a proc that will
reject a record where all the attributes are blank excluding any value
for _destroy.
--
This means you'll be able to create the following:
#config/routes.rb
resources :people do
resources :hangouts # -> domain.com/people/:people_id/hangouts/new
end
#app/controllers/hangouts_controller.rb
Class HangoutsController < ApplicationController
def new
#person = Person.find params[:people_id]
#hangout = #person.hangouts.build
end
def create
#person = Person.find params[:people_id]
#person.update(hangout_attributes)
end
private
def hangout_attributes
params.require(:person).permit(hangouts_attributes: [:hangout, :attributes])
end
end
Although I've not tested the above, I believe this is the way you should handle it. This will basically save the Hangout associated object for a particular Person - allowing you to reject if the Hangout associated object is blank
The views would be as follows:
#app/views/hangouts/new.html.erb
<%= form_for [#person, #hangout] do |f| %>
<%= f.fields_for :hangouts do |h| %>
<%= h.text_field :name %>
<% end %>
<%= f.submit %>
<% end %>
I've just started to learn Ruby and Ruby on Rails, and this is actually the first time I really have to ask a question on SO, is really making me mad.
I'm programming a REST api where I need to receive an image url and store it in my db.
For this, I've done a model called ImageSet that uses carrierwave to store the uploaded images like this:
class ImageSet < ActiveRecord::Base
has_one :template
mount_uploader :icon1, Icon1Uploader
mount_uploader :icon2, Icon2Uploader
def icon1=(url)
super(url)
self.remote_icon1_url = url
end
def icon2=(url)
super(url)
self.remote_icon2_url = url
end
end
This icon1 and icon2 are both received as urls, hence the setter override, and they can't be null.
My uploader classes are creating some versions with a whitelist of extensions and a override of full_name.
Then, I have this template class that receives nested attributes for ImageSet.
class Template < ActiveRecord::Base
belongs_to :image_set
accepts_nested_attributes_for :image_set
(other stuff)
def image_set
super || build_image_set
end
end
This model has a image_set_id that can't be null.
Considering a simple request, like a post with a json:
{
"template":
{
"image_set_attributes":
{
"icon1": "http....",
"icon2": "http...."
}
}
}
It gives always : ImageSet can't be blank.
I can access temp.image_set from the console,if temp is a Template, and I can set values there too, like, temp.image_set.icon = 'http...' but I can't seem to figure out why is it breaking there.
It should create the image_set, set its attributes a save it for the template class, which would assign its id to the respective column in its own model-
My controller is doing:
(...)
def create
#template = Template.create(params)
if #template
render status: 200
else
render status: 422
end
end
private
def params
params.require(:template).permit(image_set_attributes: [:id, :icon1, :icon2])
end
(...)
Hope you can give me tip on this one.
Thanks!
accepts_nested_attributes doesn't work as expected with belongs_to.
Don't use accepts_nested_attributes_for with belongs_to
Does accepts_nested_attributes_for work with belongs_to?
It can be tricked into working in certain circumstances, but you're better off changing your application elsewhere to do things the "rails way."
Templates validate_presence_of :image_set, right? If so, the problem is that this means ImageSets must always be created before their Templates, but accepts_nested_attributes thinks Template is the parent, and is trying to save the parent first.
The simplest thing you can do is switch the relationship, so Template has_one :image_set, and ImageSet belongs_to :template. Otherwise you're going to have to write some rather odd controller logic to deal with the params as expected.
I have a simple Rails app that has a model that is contained in several places. I allow the updating of the model from several different controllers in Rails 2.3.8. In my model I have code that allows for the name and the description to be validated. If they are blank Active_Scaffold should be populating the div with an error message. This works in the page that is linked directly to the User's controller but if I include the user anywhere else it will only show up on the User's controller page and not on the page that they are currently on. Shouldn't Acitive_Scaffold magically redirect to the correct place?
Code:
class User < ActiveRecord::Base
validates_presence_of :name, :description, :allow_blank => false
def create_from_params(params)
#name = params[:name]
#description = params[:description]
end
As you can see nothing spectacularly hard.
EDIT: Sorry I left out the rest of the method signature
I suspect the active_scaffold views only check for errors on the model it represents. Add this error check code to any view but the user view.
<%= error_messages_for :user %>
I have a form that contains the data to create a Product object. The Product has a relationship with an Image like so:
has_one :image
In my view I have a file input like so:
<%= f.file_field :image_file %>
In my Product model I have:
def image_file=(input_data)
self.image = Image.new({:image_file => input_data})
end
The image model knows how to deal with the input_data and has validation rules for it.
The problem is that if the validation fails, my product gets created without an image.
How do I make the validation errors ripple back to the Product, so that the Product does not get created at all and so I can show the errors on the form?
As I'm new to Rails, if I'm doing it all wrong please tell me so.
You can use validates_associated for that
I ended up doing it by creating a custom validator:
validate :associated_image
This is the method:
def associated_image
if image && !image.valid?
image.errors.each { |attr, msg| errors.add(:image_file, msg)}
end
end
Thanks to Gareth for pointing me to validates_associated, but the problem with it is that you lose the actual error messages from the associated object.