How do I set attributes in the Model? - ruby-on-rails

I want to auto generate a hash value from within the model.
The user creates a resume, they then have the option to share it by clicking a share button which auto generates a unique (random hashed string) url tied to that specific resumes view.
class ResumesController < ApplicationController
def share
#resume = Resume.find(params[:id])
#share = Share.new
#share.resume_id = #resume.id
#share.save
redirect_to action: 'index'
end
end
My Share model has two columns, resume_id, which I already set in the controller, andhash_url` which I want to automatically set in the model.
class Share < ActiveRecord::Base
attr_accessible :label, :resume_id, :url
end
My question is, how do I go about creating a unique hash and store it in the hash_url column? Also, I'm assuming before it saves it will have to check the share table to make sure it is not saving a hash that already exists.

You can generate and store a hash before saving the object. Add something like this to your model:
# share.rb
before_validation :generate_hash
private
def generate_hash
self.hash_url = Resume.find(resume_id).content.hash
end
The hash method is a method Ruby provides: http://ruby-doc.org/core-2.1.1/String.html#method-i-hash It returns a hash based on the string's length and content.

before_create
I'm guessing you want to send users to the likes of:
domain.com/resumes/your_secret_hash_url #-> kind of like a uuid?
The way I would do this is to use the before_create callback with SecureRandom. Whilst this won't give you a unique value, you can check it against the form:
#app/models/resume.rb
Class Resume < ActiveRecord::Base
before_create :set_hash
private
def set_hash
self.hash_url = loop do
random_token = SecureRandom.urlsafe_base64(nil, false)
break random_token unless Resume.exists?(token: random_token)
end
end
end
Reference: Best way to create unique token in Rails?
This will give you the ability to set the hash_url on create, and have it be unique

Related

how to permit and validate parameter in ruby?

i'm working on validating parameter passed in ruby controller/model as below.
in the controller, i want to allow only to allow id and name parameter. and i want to pass that id and name parameter to the model class and validate they have acceptable values. i have following , is this a acceptable way to do it in ruby?
controller class
class PersonController
def create
Person.find_By_person_id(check_params[:id], check_params[:name])
end
...
private
def check_params
params.permit(:id, :name)
end
end
model class
class People
class<<self
def find_By_person_id(id, name)
//validate id and name have values
raise ArugmentError unless validate_params
//calls another service to get the person
end
private
def validate_params
return false if id is not integer || id is less than zero || name is empty string
end
end
end
You should consider using Active Record Validations (https://guides.rubyonrails.org/active_record_validations.html) and use check for presence of the :id and :name. You model will look like so:
people.rb
class People < ApplicationRecord
validates :name, presence: true
end
As for the id, Active Record generates a unique running id for any new record to be created, so I don't see any reason for you to need to validate it - unless you are passing another :id from the front end, in which case you should rename it so it won't conflict withe the :id for the record.

Update another column if specific column exist on updation

I have a model which have two columns admin_approved and approval_date. Admin update admin_approved by using activeadmin. I want when admin update this column approval_date also update by current_time.
I cant understand how I do this.Which call_back I use.
#app/models/model.rb
class Model < ActiveRecord::Base
before_update 'self.approval_date = Time.now', if: "admin_approved?"
end
This assumes you have admin_approved (bool) and approval_date (datetime) in your table.
The way it works is to use a string to evaluate whether the admin_approved attribute is "true" before update. If it is, it sets the approval_date to the current time.
Use after_save callback inside your model.
It would be something like this:
after_save do
if admin_approved_changed?
self.approval_date = Time.now
save!
end
end
Or change the condition as you like!
You could set the approval_date before your model instance will be saved. So you save a database write process instead of usage of after_save where you save your instance and in the after_save callback you would save it again.
class MyModel < ActiveRecord::Base
before_save :set_approval_date
# ... your model code ...
private
def set_approval_date
if admin_approved_changed?
self.approval_date = Time.now
end
end
end
May be in your controller:
my_instance = MyModel.find(params[:id])
my_instance.admin_approved = true
my_instance.save

How Do I Pass Additional Parameters in Rails from Controller to Model

I would like to pass an additional field, say item_id into these line of codes (both controller and model):
# transactions_controller.rb (controller)
#transaction = Transaction.new(app_token: params[:token])
# transaction.rb (model)
def app_token=(token)
write_attribute(:app_token, token)
# I want to add a few more lines of code here so that I can manipulate item_id
end
That means, I would like my item_id to be passed from the controller to the model so that I can manipulate it do some customization within the model.
What would be the best way in order to do as such (based on the code above)?
===Updated as of 1-Sep-2014 for further details===
I have an association of cart and transaction in which cart has_many transactions and transaction belongs_to cart; below is the controller;
# transactions_controller.rb (controller)
def new
#transaction = Transaction.new(app_token: params[:token])
end
While the method below is in the model:
# transaction.rb (model)
def app_token=(token)
write_attribute(:app_token, token)
# I want to add a few more lines of code here so that I can manipulate cart.id
end
What I would like to achieve here is to pass in the cart.id into the method of app_token which is located in transaction.rb. Please note that this cart.id is not meant to be saved into the database which I can easily do it via the create method through build, but rather this cart.id is used to be passed into the method to invoke other methods which is located within app_token method which sits in the transaction.rb model. The reason why I am doing this is because, the service which I am communicating with returns a token and I would like to hold the token and perform another method which requires the cart.id to be in.
Thus, I just would like to understand, based on the given format of the controller and model above, what is the most recommended manner to pass in this cart.id into the app_token method which sits in the transaction.rb model which I would want to use for other functions within the method?
Thank you!
I have an association of cart and transaction in which cart has_many transactions and transaction belongs_to cart
Since that's the case and you already have a cart object, in your controller just instantiate the transaction from the cart:
transaction = cart.transactions.build app_token: params[:token]
transaction.save
cart_id will then be available to all the instance methods in the model, so there is no need to extend app_token= with additional logic unrelated to the app_token. Instead, take advantage of ActiveRecord callbacks. For example, you could use a before_save callback to implement your logic:
class Transaction < ActiveRecord::Base
belongs_to :cart
before_save :do_something_with_cart
def do_something_with_cart
# I want to add a few more lines of code here so that I can manipulate cart_id
end
end
If for some reason a callback does not fit your use casae, call the custom method directly in the controller:
transaction = cart.transactions.build app_token: params[:token]
transaction.do_something_with_cart
You don't need to override app_token=
# transactions_controller.rb (controller)
#transaction = Transaction.new(app_token: params[:token], item_id: params[:item_id])
#transaction.save
Attribute
It will mainly depend on whether you have item_id set up as an attribute, either virtual or in the database.
If you have an associative foreign_key set up already, you'll be able to discount what I'm going to write, but in case you haven't, you should consider the following:
#app/models/transaction.rb
class Transaction < ActiveRecord::Base
belongs_to :item # -> expects item_id by default
end
If you don't have an association set up (and hence no attributes), you'll want to use attr_accessor to create a virtual attribute:
#app/models/transaction.rb
class Transaction < ActiveRecord::Base
attr_accessor :item_id
end
Params
Passing attributes in Rails 4 is actually the least of your concerns - you can pass as many attributes through your routes as you wish. The problems occur when you try and match the items with your db objects (hence my recommendation above)
If you want to pass the item_id attribute, you'll just have to ensure it's set in your view. This is either done with your routes, or by passing it in your form:
#config/routes.rb
resources :items
resources :transactions #-> domain.com/items/:item_id/transactions/new
end
This would allow you to pass the item_id you wish (which will load in your controllers as params[:item_id]. You can also use the following:
#app/views/transactions/new.html.erb
<%= form_for #transaction do |f| %>
<%= f.text_field :item_id %>
<%= f.text_field :token %>
<%= f.submit %>
<% end %>
This will give you the ability to send the two different attributes to your controller, which can then populate as follows:
#app/controllers/transactions_controller.rb
class TransactionsController < ApplicationController
def new
#transaction = Transaction.new
end
def create
#transaction = Transaction.new transaction_params
#transaction.save
end
private
def transaction_params
params.require(:transaction).permit(:item_id, :token)
end
end
It must be noted the form method will only be viable if you have the attribute defined in your model - either in the database, or virtual (with attr_accessor)

Rails: passing params hash to model

I have a user-to-user messaging system. I'm trying to pass an array of user ids to a ConversationUser (join table) model which would then create multiple conversation_users from each individual user.id. The two fields in ConversationUser are conversation_id and user_id. I'm able to initialize a single conversation user because the new conversation_id is being passed along to the model, but for some reason, the hash of user ids is not getting to my model. I'm getting a Validation failed: User can't be blank
My conversation/new view for capturing the user_ids:
<%= check_box_tag "conversation_user[recipient][]", user.id %> <%= user.name %><br />
I know this is working because part of my params that I'm receiving back are:
"conversation_user"=>{"recipient"=>["9", "10"]}
The essentials of my Rails 4 controller & strong params:
class ConversationsController < ApplicationController
def new
#user = User.find(params[:user_id])
#conversation = #user.conversation_users.build
#conversation.build_conversation.messages.build
end
def create
#conv = Conversation.create!
#conversation = #conv.conversation_users.create!(conversation_user_params)
end
def conversation_user_params
params.require(:conversation_user).permit(recipient: [])
end
The essentials of my ConversationUser model:
class ConversationUser < ActiveRecord::Base
attr_accessor :recipient
before_create :acquire_conversation
validates :user_id, :conversation_id, presence: true
def acquire_conversation
unless recipient.blank?
recipient.each do |u|
ConversationUser.create(user_id: u, conversation: conversation)
end
end
end
end
I think the problem is somewhere in my controller's conversation_user_params. But it also might be in the model's before_create method. I've been trying to fix this problem for a day now, with lots of debugging with no success. If anyone can be of assistance, I thank you in advance.
The problem is in the model. before_create callback is called before creating a ConversationUser. Let's name this created ConversationUser as CURRENT. So, before creating the CURRENT ConversationUser you loop through recipient ids and create a ConversationUser for each of them. The ConversationUsers that you are creating here are not CURRENT ConversationUser. CURRENT ConversationUser is saved after the callback is executed (after you create other ConversationUsers). But in this case CURRENT ConversationUser doesn't know wich User it belongs to, because you pass user_id parameter to ConversationUsers that you create in before_create callback, but you do not pass it to CURRENT ConversationUser when it is created (when original create! method is executed).
To solve this problem you can override original create! method or not use it at all for creating ConversationUsers by recipient ids. Add a new method to your Conversation model (for example create_conversation_users):
Solution
In the controller:
def create
#conv = Conversation.create!
#conversation = #conv.create_conversation_users!(conversation_user_params[:recipient])
end
In the model:
class Conversation
def create_conversation_users!(recipient_ids)
return if recipient_ids.blank?
recipient_ids.each do |recipient_id|
conversation_users.create!(user_id: recipient_id, conversation: self)
end
end
end
You should also update ConversationUser model:
class ConversationUser < ActiveRecord::Base
validates :user_id, :conversation_id, presence: true
end
The error is in the ConversationUser. before_create callbacks are ran before a record is created in the database BUT after validations are ran. To solve your issue, there's a few things you can do. One of them was answered by Chumakoff. Here's another option you can use.
Remove all the code inside ConversationUser and change conversation_user_params to
def conversation_user_params
params[:conversation_user][recipient].map do |recipient|
{ user_id: recipient }
end
end
What happens is you're passing an array of { user_id: 1 } to create! which is the same as calling multiple create!({ user_id: 1 }).

Ruby ActiveRecord validation based on existing value in database

I know the common ways of validation in Ruby.
My current problem is, I would like the insert to fail if the record exists with boolean set to true. Example
Columns in table are => Name, Address, Has_changed, id
Now if has_changed is set to true in table, I would like to add new entry in the table (in a separate call) which will have name, Address (new one), has_changed(set to false) and id. I would not like to update existing entry as I want to keep history for the record.
Is there a way to have such a validation by using Ruby?
Thanks in advance.
I think its better use "new" and "create" actions in your controller
class RecordsController < BaseController
def new
#record = Record.find(params[:id])
end
def create
Record.create(params[:record])
end
end
Your form just contains adress
In your model make a before_create hook
class Record < ActiveRecord::Base
default_value_for :has_changed, false
before_create :check_changed
...
private
def check_changed
changed_record = Record.where(name: name, has_changed: false).first
if changed_record && (changed_record.address != address)
changed_record.has_changed = true
changed_record.save
end
end
end
And add a validation for address to avoid duplication

Resources