How to Associate 5 atmost pictures with a account - ruby-on-rails

I have a model Company which has_many :company_pictures but at most 5 photos. I have to show user all 5 file field for image uploading.
class Company < ActiveRecord::Base
has_many :company_pictures
accepts_nested_attributes_for :company_pictures
end
class CompanyPicture < ActiveRecord::Base
belongs_to :company
has_attached_file :picture, :styles => { :medium => "300x300>"}
end
controller code
def new
#company = Company.new
5.times { #company.company_pictures.build }
end
Now through this in new form I am able to show 5 file field. Now on submit . let say now my form did not get saved because of any other error. now I find only those file field that hold some value with that. i want to make sure that user will always able to attach at most 5 photos whether he is creating a new company or updating a company.

What I usually do in this cases is that before presenting the user with the "new" layout, I create and save the object first in the database with a "draft" status with the objective of getting a primary key and be able to save the pictures (the pictures need a primary key in order to be saved). Then, when the user submits the form, no matter if there are errors or not I always save the pictures and then present the user with the form, the errors (in case there are) and since the images got saved to the database you can display them to the user. If there are no errors just remove the "draft" status from the user and continue your workflow.
Now the question is, what do we do with all those drafts ? Create a rake task that run's every hour and delete all the records in this state.

Related

Guidance on how to set validations correctly with a has_many :through relationship?

I've set up three models: User, List, and UserList -- the latter being the join model between User and List, in a has_many_through relationship.
I'm trying to set up what I think should be fairly vanilla uniqueness constraints -- but it's not quite working. Would appreciate your guidance / advice please!
Technical details
I have 3 models:
class User < ApplicationRecord
has_many :user_lists
has_many :lists, through: :user_lists, dependent: :destroy
End
class List < ApplicationRecord
has_many :user_lists
has_many :users, through: :user_lists, dependent: :destroy
# no duplicate titles in the List table
validates :title, uniqueness: true
End
class UserList < ApplicationRecord
belongs_to :list
belongs_to :user
# a given user can only have one copy of a list item
validates :list_id, uniqueness: { scope: :user_id }
end
As you can see, I'd like List items to be unique, based on their title. In other words, if user Adam adds a List with title "The Dark Knight", then user Beatrice adding a List with title "The Dark Knight" shouldn't actually create a new List record -- it should just create a new / distinct UserList association, pointing to the previously created List item.
(Somewhat tangential, but I also added a unique index on the table since I understand this avoids a race condition)
class AddIndexToUserLists < ActiveRecord::Migration[5.2]
def change
add_index :user_lists, [:user_id, :list_id], unique: true
end
end
Here's where things are going wrong.
As user Adam, I log in, and add a new title, "The Dark Knight", to my list.
Here's the controller action (assume current_user correctly retrieves Adam):
# POST /lists
def create
#list = current_user.lists.find_or_create_by!(list_params)
end
This correctly results in a new List record, and associated UserList record, being created. Hurrah!
As Adam, if I try to add that same title "The Dark Knight", to my list again, nothing happens -- including no errors on the console. Hurrah!
However -- as user Beatrice, if I log in and now try to add "The Dark Knight" to my list, I now get an error in the console:
POST http://localhost:3000/api/v1/lists 422 (Unprocessable Entity)
My debugging and hypothesis
If I remove the uniqueness constraint on List.title, this error disappears, and Beatrice is able to add "The Dark Knight" to her list.
However, List then contains two records, both titled "The Dark Knight", which seems redundant.
As Adam, it seems like perhaps current_user.lists.find_or_create_by!(list_params) in my controller action is finding the existing "The Dark Knight" list associated with my current user, and realising it exists -- thereby not triggering the create action.
Then as Beatrice, it seems that the same controller action is not finding the existing "The Dark Knight" list item associated with my current user -- and therefore it tries to trigger the create action.
However, this create action tries to create a new List item with a title that already exists -- i.e. it falls foul of the List.rb model uniqueness validation.
I'm not sure how to modify that find_or_create_by action, or the model validations, to ensure that for Beatrice, a new UserList record / association is created -- but not a new List record (since that already exists).
It feels like maybe I'm missing something easy here. Or maybe not. Would really appreciate some guidance on how to proceed. Thanks!
I'm 99% certain that what's happening is current_user.lists.find_or_create_by will only search for List records that the user has a UserList entry for. Thus if the List exists but the current user doesn't have an association to it, it will try to create a new list which will conflict with the existing one.
Assuming this is the issue, you need to find the List independently of the user associations: #list = List.find_or_create_by(list_params)
Once you have that list, you can create a UserList record through the associations or the UserList model. If you're looking for brevity, I think you can use current_user.lists << #list to create the UserList, but you should check how this behaves if the user has a UserList for that list already, I'm not sure if it will overwrite your existing data.
So (assuming the << method works appropriately for creating the UserList) your controller action could look like this:
def create
#list = List.find_or_create_by!(list_params)
current_user.lists << #list
end

User creates an Item, deletes his account. undefined method for nil:NilClass

When an User creates an item, attributes such as:
user_name
user_email
user_crawl
and much much more are displayed through out the app.
Because this items are not :dependent => :destroy as soon a user destroys his account
the app brakes.
How can i hold on to that attributes when a user destroys his account ?
Example
:item belongs_to :user
:user has_many :items
An item has:
<%= item.user.id%>
<%= image_tag(item.user.avatar) %>
<%= item.user.crawl %>
The Items will be shown even if the user is not existent. How can i
keep the attributes such as "name" "ID" "email" in the view. e.g.
"Item was created by TestUser", can i somehow keep that "TestUser"
string?
Soft deletion
To avoid that, usually, I won't delete user records, but rather just deactivate user's account with a soft delete that removes user#password and user#activated_at. That way, you're certain your database integrity is not compromised.
Of course, there may be privacy issues with this, so make sure that :
only data absolutely necessary is kept (remove emails, address, phone number, etc)
double check user won't receive any mails or something (removing email is a good way, you may also have an unsubscribed attribute anyway for registered users that don't want communication, so automatically set it for users that want to delete their account).
Proxy model
An other option would be to group most vital data in an other model. You could have, for example :
User.has_one :vcard
User.has_many :items, through: :vcard
Item.belongs_to :vcard
Item.has_one :user, through :vcard
Vcard.belongs_to :user
Vcard.has_many :items
A Vcard would contain user name, avatar and crawl (and of course a user_id since it belongs to user).
Using that, you can entirely delete User, and Item is still related to a Vcard with relevant information. You should probably change avatar to some placeholder to be kind to an user that want to delete his account, though.
This solution is also nice to avoid having conditional validations in your user model.
You seem to be delegating to the User model from Item. If the user no longer exists, naturally this will break. You can specify 'allow_nil' and it will merely return nil:
class Item
delegates :name, :to => :user, :allow_nil => true, :prefix => true
end
If the user does not exists, then then calling item.user_name will return nil.
EDIT
If you want to keep that information, you should not delete the associated database record. You can add a disabled boolean column that deactivate the account, but the record will still exist so that data stays in the database.
Your users may be expecting their data to be purged

Limit number of images - Paperclip

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

Trouble on saving an associated model and then retrieve some data from that

I am using Ruby on Rails 3 and I am trying to retrieve some data from a just saved child model (associated model) in order to store that data in the parent model.
More precisely (in steps) I would like to do:
Save the child model Account of the parent model User
Retrieve the just created Account ID value and save that value in the User model attribute named users_account_id.
... and more explicitly (in values) I would like to have the following scenario after saving the child model Account:
# Account values
Account.id = 222
Account.name = "Test_name"
...
Account.user_id = 111
# User values
User.id = 111
User.users_account_id = 222
I already implemented the first step, but how can I implement the second step?
In order to retrieve the Account ID, I tryed to use an association callback
class User < ActiveRecord::Base
has_one :account, :before_add => :callback_name
validates_associated :account
accepts_nested_attributes_for :account
def callback_name
self.users_account_id = Account.find_by_id(self.id).id
end
end
but I get this error:
Unknown key(s): before_add
This is way overkill. All you need to do is put the user_id in the form of the account that is getting created as a hidden field.
<% form_for(#account) do |f| %>
<%= f.hidden_field :user_id, :value => current_user.id %>
<% end %>
Of course add your other fields that you want for account and you need a current_user object which you will need anyways with your current logic.
I'm going to side step your question a bit, and ask why you need IDs pointing in both directions? I assume you want your User to be related to an Account, and an Account to have one or more Users. The "Rails Way" to do this would be something like the following:
class User < ActiveRecord::Base
belongs_to :account
end
class Account < ActiveRecord::Base
has_many :users
end
In your database the users table will have account_id and the accounts table will not have a user_id of any kind.
This will still allow you to user the association in both directions:
some_user.account # Returns the correct account object
some_account.users # Returns all users for the account
I hope this helps somewhat!

How to call two different create methods at single call in rails 2

I am new to rails and developing a small user application. In my app, I have a user model and a subscription model. I want that when a user creates his/her profile and hits the submit button , another action should be called at the same time known as subscription which enters the data of the user such as profile_id , subscription_id, plan_id and generates a raw state, in the same subscription table. I am using 'AASM' for state management. I know how to insert values in the subscription table using console, which works fine. But , I want to create new subscription as soon as the user registers his new profile. I mean , how shall I call two create methods at single event.
I didn't get it clearly but i guess after_create is all you need.
(assuming user :has_one => :profile, and profile has_many => :subscriptions)
In profile model
after_create :subscribe!
attr_accessor :plan_id, :subscription_id
def subscribe!
self.subscriptions << Subscription.new(:plan_id => plan_id, :subscription_id => subscription_id)
self.save
end

Resources