In the rails application I am developing, I want the to give the end users the ability to select an avatar. Nothing unusual here.
Unfortunately, I have found numerous articles, posts and gems about the uploading and selection of images (example: Is there any rails gem for avatar selection? | http://rubygems.org/gems/paperclip ) but what I want is for them to select from an 'Avatar' table in my database. I do not want them to be able to upload anything. The Avatars which they can select from will be created for them.
My thought is that the Avatar table would have two columns: id and url_path.
In their user profile I would have a field I assume to store the avatar_id -- but from there I am stuck --- I want them to see the gallery of avatars and make a selection to populate that field. I am open to a gem -- but most seem more complex than I need.
Any assistance would be appreciated.
Here's some input to push you in the right direction. I don't think you need a gem for this.
You need three models:
class User < ActiveRecord::Base
has_one :avatar, through: :profile_picture
has_one :profile_picture
# change both associations to has many if a user can have multiple avatars
end
class Avatar < ActiveRecord::Base
has_one :profile_picture
# attribute: filename
end
class ProfilePicture < ActiveRecord::Base
belongs_to :user
belongs_to :avatar
# attributes: user_id and avatar_id
end
The profile picture model links users with avatars.
Since the user will only be able to choose from a set of avatars, you simply create the avatars as admin. Just put avatar images in assets/images/avatars and create the avatar records in the database yourself:
# assuming an avatar's filename is avatar.png
Avatar.create(filename: 'avatar.png')
# and make sure you have such an image in assets/images/avatars
Then, assuming you have a page where you render all avatars as, e.g., linked images as preview that users can click on to select one, you could simply link those images to a ProfilePicturesController's create action. You will need the following route
resources :profile_pictures, only: [ :create ]
and the following erb code for your images for the user to choose from:
<% Avatar.all.each do |a| %>
<%= link_to image_tag("assets/images/avatars/#{a.filename}"), profile_pictures_path(avatar_id: a.id), method: :post %>
<% end %>
and the following controller code:
class ProfilePicturesController < ActionController::Base
def create
ProfilePicture.create(
avatar_id: params[:avatar_id],
user_id: current_user.id # or however you retrieve the current user
)
flash[:notice] = 'Avatar updated.' # just a suggestion
redirect_to :back # just a suggestion
end
end
Haven't actually tried any of this, so let me know if it works.
Related
I am using rails_admin gem for the rails api application for the backend admin side.
I am using rails 6 with active_storage and storing attachments on the S3.
On the admin side, I need to display the list of attachments which might be images or files anything.
My question is How to show those in index method, do I need to show images then what to show in pdf/docs, do I need to show the only the link of s3?
currently, it looks like these broken images are the images and other one were files
My model
class Attachment < AttachmentBlock::ApplicationRecord
self.table_name = :attachments
include Wisper::Publisher
has_one_attached :attachment
belongs_to :account, class_name: 'AccountBlock::Account'
has_and_belongs_to_many :orders , class_name: 'BxBlockOrdermanagement::Order'
scope :not_expired, -> {where('is_expired = ?',false)}
end
What should I use here to list the attachment that the user upload?
how to check the attachment type and then if its image then shows the image and if it files then show file url from s3?
thanks.
Yours is a two part question:
To add links to the side menu on rails admin you need to define models so if you wanted an index for all the attachments of type 'pdf' you could use rails STI (single table inheritance) or define a custom default_scope like this:
class ImageAttachment < Attachment
def self.default_scope
where('attachments.attachment LIKE ?', '%png') }
end
end
To customize the row of each individual attachment record you need to defined that behavior for the field on the model rails admin config.
For example you can put this block of code inside your ImageAttachment class.
class ImageAttachment < Attachment
def self.default_scope
where('attachments.attachment LIKE ?', '%png') }
end
rails_admin do
configure :attachment do
view = bindings[:view]
attachment = bindings[:object]
if view
view.content_tag(:img, attachment.attachment.url)
else
''
end
end
list do
field :attachment
end
end
end
As you can see inside the attachment field configuration block you have access to the attachment object and of course all its methods, you can determine the type of attachment and do some special html rendering.
Say, I have a model Activity. And I want to use it as a model for kind of news or event happening on my website such:
a user has created an article
a user has edited an article
a user has deleted a comment
a user has uploaded a picture
There always be a connection to a User. And to something else: Article, News, Comment, Picture, Profile. How can I implement this? I can do this:
Activity
belongs_to :with_activity, polymorphic: true
belongs_to :user
Articles
has_many :activities, as :with_activity
News
has_many :activities, as :with_activity
But when I'm creating an activity, how would I specify all the IDs involved?
For example:
"A user has added a comment for an article". There're 3 entities here. User id is captured via "belongs_to :user". But either Comment ID or Article is captured via "belongs_to :with_activity, polymorphic: true" and not both. Whereas I want both. Or more if needed:
"A user has added a comment for a picture of an article". -- 4 entities, but only IDs of 2 of them are captures. I need to store all their IDs. "polymorphic" allows to store only an ID of a single entity.
How can I get around of that? Should I add "belongs_to" :picture, :article, :comment and so to Activity? Is there a better solution?
Note that I don't want to use a gem for that.
Usually you only need the association to the with_activity. From that you can follow the already existing belongs_to associations (I assume a comment belongs_to an article).
For example: A user has added a comment for an article:
# User creates a comment
comment = user.comments.create(article: article, text: 'Lorem ipsum')
# store the comment creation activity
activity = user.activities.create(with_activity: comment, activity: 'created')
Now you want to know render the activity:
<%= activity.user.name %> # John Do
<%= activity.activity %> # created
<%= activity.with_activity.class.to_s %> # Comment
"<%= activity.with_activity.text %>" # "Lorem ipsum"
on <%= link_to 'Article', user.with_activity.article %> # on <Article Link>
Or for your other example: A user has added a comment for a picture of an article If user_activity is the user's activity than
user_activity.with_comment.picture.acticle
would return the corresponding article.
Sorry for the vague title, but it's a little much to explain in a sentence.
I've got three models, User, Device, and DeviceMessage. Their relationships are fairly simple:
a User has_many :devices,
a Device belongs_to :user,
a Device has_many :device_messages,
and a DeviceMessage belongs_to :device.
Rails provides ways to start playing with these associations quickly, like the ability to get all device messages that belong to a certain user (from any device).
In order to do this, I defined a method in the User model:
class User < ActiveRecord::Base
...
has_many :devices, :as => : owner #Other entities may "own" a device
def device_feed
DeviceMessage.that_belong_to_user(self)
end
end
And I define the called method in the DeviceMessage model:
class DeviceMessage < ActiveRecord::Base
...
belongs_to :device
def self.that_belong_to_user(user)
device_ids = "SELECT owner_id FROM devices WHERE owner_id = :user_id
AND owner_type = \"User\""
where("device_id IN (#{device_ids})", user_id: user.id)
end
end
I define a user page where they can associate a device to their account (the device has a name as well), and upon adding the device to the account, it will add the name to a list of device names in a pane to the left, while showing the user's device feed much like a twitter feed (yes, I followed Michael Hartl's RoR tutorial). At this point it is important to note that I am using helper functions to keep track of the current user so I can display this information when a user visits the root_path while logged in. When visiting the root_path, the controller for the root_path is defined so that:
if user_signed_in?
#device_feed_items = current_user.device_feed.paginate(page: params[:page])
end
And this all works perfectly!
So... what's the issue? When I create a new user via the signup page, and associate the device via the device-association page, I am redirected to the root_path, the device name is correctly displayed in the left pane (which mean the device is correctly associated with the new user), but the device_feed is not displayed.
I've used the Rails console to verify that the device messages should be showing (User.find(2).devices.first.device_messages.first displays the first message associated with the first device that is newly associated with the 2nd user), so I know that I need to reach down into the database to get a fresh rather than cached copy of the current_user, but I'm confused because it seems like that should be happening every time the user.device_feed method is called because of it's use of where() which is a part of the query API...
Any ideas? Thanks in advance for any and all answers.
-MM
I am just wondering why you have the device_feed function. For your feed display could you not just a loop like this one, this is
class Device < ActiveRecord::Base
scope :in_new_message_order, :joins => :device_messages, :order => "created_at DESC"
end
Added a joined scope
class User < ActiveRecord::Base
...
has_many :devices, :as => : owner #Other entities may "own" a device
scope :in_sort_order, order("message_date DESC")
def device_feed
DeviceMessage.that_belong_to_user(self)
end
end
Above I have added a scope to sort your messages
<% user.devices.in_new_message_order.each do |device| %>
<% device.device_messages_in_sort_order.each do |message| %>
<%= ....render out the message %>
<% end %>
<% end %>
I am trying to save to a join table in a habtm relationship, but I am having problems.
From my view, I pass in a group id with:
<%= link_to "Create New User", new_user_url(:group => 1) %>
# User model (user.rb)
class User < ActiveRecord::Base
has_and_belongs_to_many :user_groups
accepts_nested_attributes_for :user_groups
end
# UserGroups model (user_groups.rb)
class UserGroup < ActiveRecord::Base
has_and_belongs_to_many :users
end
# users_controller.rb
def new
#user = User.new(:user_group_ids => params[:group])
end
in the new user view, i have access to the User.user_groups object, however when i submit the form, not only does it not save into my join table (user_groups_users), but the object is no longer there. all the other objects & attributes of my User object are persistent except for the user group.
i just started learning rails, so maybe i am missing something conceptually here, but i have been really struggling with this.
Instead of using accepts_nested_attributes_for, have you considered just adding the user to the group in your controller? That way you don't need to pass user_group_id back and forth.
In users_controller.rb:
def create
#user = User.new params[:user]
#user.user_groups << UserGroup.find(group_id_you_wanted)
end
This way you'll also stop people from doctoring the form and adding themselves to whichever group they wanted.
What does your create method look like in users_controller.rb?
If you're using the fields_for construct in your view, for example:
<% user_form.fields_for :user_groups do |user_groups_form| %>
You should be able to just pass the params[:user] (or whatever it is) to User.new() and it will handle the nested attributes.
Expanding on #jimworm 's answer:
groups_hash = params[:user].delete(:groups_attributes)
group_ids = groups_hash.values.select{|h|h["_destroy"]=="false"}.collect{|h|h["group_id"]}
That way, you've yanked the hash out of the params hash and collected the ids only. Now you can save the user separately, like:
#user.update_attributes(params[:user])
and add/remove his group ids separately in one line:
# The next line will add or remove items associated with those IDs as needed
# (part of the habtm parcel)
#user.group_ids = group_ids
I have 2 equal-access models: Users and Categories
Each of these should have the standard-actions: index, new, create, edit, update and destroy
But where do I integrate the associations, when I want to create an association between this two models?
Do I have to write 2 times nearly the same code:
class UsersController << ApplicationController
# blabla
def addCategory
User.find(params[:id]).categories << Category.find(params[:user_id])
end
end
class CategoriessController << ApplicationController
# blabla
def addUser
Category.find(params[:id]).users << User.find(params[:user_id])
end
end
Or should I create a new Controller, named UsersCategoriesController?
Whats the best practice here? The above example doens't look very DRY.... And a new controller is a little bit too much, I think?
Thanks!
EDIT:
I need to have both of these associations-adding-functions, because f.e.
#on the
show_category_path(1)
# I want to see all assigned users (with possibility to assign new users)
and
#on the
show_user_path(1)
#I want to see all assigned categories (with possibility to assign new categories)
EDIT:
I'm taking about a HBTM relationship.
If you have a situation where you need to do this with has_and_belongs_to_many, you could take the approach you are currently using, or you could build this into your existing update actions.
When you add a habtm relationship, you will get an additional method on your classes...
class User < ActiveRecord::Base
has_and_belongs_to_many :categories
end
With this, you can do this:
user = User.find(params[:id])
user.category_ids = [1,3,4,7,10]
user.save
The categories with those ids will be set. If you name your form fields appropriately, the update can take care of this for you if you want to use checkboxes or multiselect controls.
If you need to add them one at a time, then the methods you've built in your original post are reasonable enough. If you think the repetition you have is a code smell, you are correct - this is why you should use the approach I outlined in my previous answer - an additional model and an additional controller.
You didn't mention if you are using has_and_belongs_to_many or if you are using has_many :through. I recommend has_many :through, which forces you to use an actual model for the join, something like UserCategory or Categorization something like that. Then you just make a new controller to handle creation of that.
You will want to pass the user and category as parameters to the create action of this controller.
Your form...
<% form_tag categorizations_path(:category_id => #category.id), :method => :post do %>
<%=text_field_tag "user_id" %>
<%=submit_tag "Add user" %>
<% end %>
Your controller...
class CategorizationsController < ApplicationController
def create
if Categorization.add_user_to_category(params[:user_id], params[:category_id])
...
end
end
then your categorization class...
class Categorization
belongs_to :user
belongs_to :category
def self.add_user_to_category(user_id, category_id)
# might want to validate that this user and category exist somehow
Categorization.new(:user_id => user_id, :category_id => category_id)
Categorization.save
end
end
The problem comes in when you want to send the users back, but that's not terribly hard - detect where they came from and send them back there. Or put the return page into a hidden field on your form.
Hope that helps.