Rails efficient way to save non-duplicates? - ruby-on-rails

I have a type I want to save to the database, say, email addresses, and I want to save them in the most efficient way possible. I also want to associate posts with email addresses, so if it exists, I want to assign the post to that email address. Should I do a search first and then go based on that result, or is there a more efficient way? for ex, if it doesn't exist, I just want it to insert, I don't want to then to have to insert it after a search; that takes 2 calls where 1 would do.

I believe you should use something like find_or_create.
Does post and email are separated models and post has_one email?
Edit
When creating a post, you should do something like
# posts controller
#email = Email.find_or_initialize_by_address(params[:email])
#post.email = #email #or whatever you do to associate them

Related

How do associations affect the way we create objects in the controller?

I understand that associations are important because this gives us a way of linking two objects together so they have a 'relation' and we can query through both of them. For example,
A landing page belongs to a blog user & a blog user can have many landing pages
We obviously go into the models and apply the correct methods 'has_many' and 'belongs_to'. We also create a migration and add the foreign key to the 'belongs_to' model. This being the 'landing page'.
My problem:
When creating a landing page, it is possible to choose a blog user. This obviously passes the blog user ID into the params. I want to save this ID into the foreign key field in the landing page model.
Is this possible without doing:
def create
#landing_page = #blog_user.landing_pages.build(landing_pages_params)
end
Why do you have to go through a blog user? Another example:
def new
#landing_page = #blog_user.landing_pages.new
end
What is the purpose of doing it this way? Surely passing the ID into the field is enough without going through the blog_user?
Sure, you could do something like
#landing_page = LandingPages.new(blog_user_id: X)
Obviously, you need to know the ID of the BlogUser record. You'll then be able to access the newly associated BlogUser as you'd expect
#landing_page.blog_user #=> BlogUser(id: X)

Rails 5 access profile data anywhere in session without querying database each time

I've a user profile (with name, logo, about_me) which is created after user creation(using Devise). Profile table uses user_id as Primary key.
Now I want that whenever the user creates/updates a post, while filling in form some details are taken from profile, so profile data or #profile be available in post form as I cannot expose my model in form.
To set post.myname attribute in create and #update I'm doing this:
#myprofile = Profile.find_by_user_id(current_user)
write_attribute(:myname, #myprofile.name)
I read from various sources but what's the best solution of the 4 given and if anyone can back with easy code as I do not want to do something extensive? Thanks in advance.
1)Form Hidden fields - Like get the profile data as above in hash in #edit and then pass through form and access fields in #update but that way we will pass each field separately. Can one #myprofile be passed?
2)Session - I feel if profile data is stored in a session and someone updates profile then updated data won't be available in that session.So not sure if it is plausible.
3)Caching - easy way to do that?
4)polymorphic profile---tried it but I didnot get relevant example. I was stuck with what to put as profileable id and type and how to use them in the code.
If your Profile and User models have a one-to-one relationship with each other, the simplest solution is to remove the Profile model altogether and move its fields into the User model.
Devise already queries the database to obtain the current_user object. So, your example would like this:
write_attribute(:myname, current_user.name)
Which wouldn't hit the database (after Devise has retrieved the current_user object).
If you're forced to keep the Profile model, in looking at your four scenarios ...
You could use a session variable. Something like:
session[:profile_name] ||= #myprofile.name
This would go in a controller action.
The trick here is that you will want to redefine the each relevant session variable if the profile gets updated. And because you don't have access to the session in the model, you'd be best to perform that action in the controller. So, not pretty, but it could work.
You could also use low-level caching, and save the profile relationship on the user. In general, you could have a method like this in your user model:
def profile_cached
Rails.cache.fetch(['Profile', profile.id]) do
profile
end
end
Here, too, you will have to know when to expire the cache. The benefit of this approach is that you can put this code in the model, which means you can hook its expiration in a callback.
Read more about this in Caching with Rails.
I would avoid hidden fields and I'm not sure how a polymorphic relationship would solve you not hitting the database. So, #2 and #3 are options, but if you can combine the two models into one, that should simplify it.

Different update / edit methods available to different users

I have a model Post, which is submitted and graded by different Users. The submitter and grader are identified by submitter_id and grader_id in Post model. Note that an user is both a submitter himself and a grader to others.
I want to make sure that the submitter can only edit the content of the Post but not the grade. Likewise, the grader can only edit the grade but not the content.
Is multiple edit methods the way to go? How should I accomplish this otherwise?
You can have a role column in your users table, and the role can be either submitter or grader. Not sure what you are using for authentication, but in case you are using devise, you can access the currently logged in user with current_user helper (in case you are using something else, figure this part out, or add a new helper).
Now in your update method, you can do something like this:
# Controller
# scope post to current user, so that a user cannot edit someone else's post. A crude way to achieve this is post = Post.find(params[:id])
post = current_user.posts.find(params[:id])
post.content = params[:content] if post.submitter?(current_user.id)
post.grade = params[:grade] if post.grader?(current_user.id)
post.save!
# Model - Post.rb
def submitter?(user_id)
self.submitter_id == user_id
end
def grader?(user_id)
self.grader_id == user_id
end
The advantage of keeping those methods in the model is that in case you permission logic changes (who is submitter, or a grader), you need to change it at a single location. DRY.
You can modify the above approach to show error messages, and do other similar stuff. In case you are looking for more granular authorization control, you can look into cancan gem:
https://github.com/ryanb/cancan
Your post model should only be concerned with persisting data. Better to use plain old ruby objects to encapsulate the higher order behavior of grading and submitting. Consider using service objects or form objects.
Each service or form object can then include ActiveModel::Model(rails > v4) to get its own validations.
See more about service and form objects here: http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/
If you only have one submit action and one grade action, its probably ok to keep in one controller. But if you start having multiple actions that are related to submitted, and multiple actions that are related to grading, this sounds like they would make great resources controllers on their own.

Issue and clarification needed with attr_accessible

There is so much written about the security threat of attr_accessible that I am beginning to wonder if I should even have any attributes in it. Here is the issue. I have a Message model which has the following:
attr_accessible :body,:sender_id,:recipient_id
I do not have the update or edit action in my messages_controller. With the new and create action I am able to create a new message and send it to a recipient. Only users who have logged in and meet certain conditions can message each other. I do that with the help of a before_filter and the conditions work fine. The message is stored and can be viewed by the sender and the recipient. Perfect!
The question I have is that since :body,:sender_id,:recipient_id are included in attr_accessible, can a malicious user somehow change the :body,:sender_id,:recipient_id of the original message? Should I just add these attributes to attr_readonly as well so they cannot be modified once saved?
This question has been haunting me for practically all my models.
can a malicious user somehow change the :body,:sender_id,:recipient_id
of the original message?
This would depend on other things rather than attr_accesible. attr_accesible will only filter which fields are allowed to be updated using mass assignment. Since you say you don't have any update action, then no, there is now way a user can edit a message since you always create a new Message through you create action.
But there is something you need to care about. What is sender_id? If you do have users in your app and they send messages to each others, then sender_id should not be an accessible field, since this will allow users to send messages on behalf of other users. You probably want to keep that field off the attr_accessible list and do something like this:
m = Message.new params[:message] # body and recipient_id
m.sender_id = current_user.id # this is not mass assignment
m.save
.....
Well, it depends on how your are creating your model's instance. If you use:
FooModel.create(params[:foo])
then yes, your are not secure because a logged in user may pass additional parameters to the request even if you don't provide explicitly form fields for those attributes.
So, for your case, anyone posting to your "create" action with sender_id, recipient_id (values in the request) will be able to change them unless you take care about this assignments in your action.

Logic for sending emails to particular User in Rails

This is more like a conceptual question because I have the feeling that I am doing this wrong:
I have to send emails to some Users that satisfy some conditions. What I have right now is the following:
1) A task that basically does this:
users = User.includes(:aptitudes).where({:role => ['Boy','boy','kid'], :aptitudes => {:name =>'Good'} })
users.each do |user|
MyMailer.report(user).deliver
end
2) In the report method of MyMailer, I have several things:
def report(user)
#user = user
#value = #user.value
#travels = #user.travels.where(:end_at)
#rewards = #user.rewards
# More logic depending of values of User.
...
end
What happens is that in the Mailer itself sometimes a particular property or attribute of the object #user does not satisfy for the email to be sent. I am wondering where am I supposed to make sure that all the Users that I pass to the Mailers will be sent an email ? Shall I create a new method in the rake to do those checkings? Or how would you do that?
Generally speaking, you can do as you wish as long as the name is revealing enough of the intentions.
If you gave me this MyMailer object to use, without knowing the implementation, I would expect it to send a report when requested and not filter out of its own whim... eventually deal with other problems (fail to send, delays, retries etc.) but when I give it a user I want that user to be emailed.
So, to answer your question, yes I would either filter out all the users before calling the Mailer, or define a method in the Mailer called something like report_only_those_users that will take care of the filtering.
By the way, you can pass a list of emails to ActionMailer (section 2.3.4), without having to loop and send one at a time.

Resources