Allow Active Record Model to Assign Association using String - ruby-on-rails

I have a fairly basic set of models where a 'Project' has one 'Owner'. I'd like to allow users to enter the owner's name when creating a new project. If owner doesn't exist, a new one should be created. Does a good way to do this exist? I've been thinking about using attr_accessor and before_validation however it seems it would conflict with the relationship. Any ideas? Thanks!

Use something different than the name of your relationship... owner_name should be fine. Then write the necessary before_validation method.

I'd use something along the lines of this in your controller:
def update
Project.transaction do
#project.owner = Owner.find_or_create_by_name(params[:project].delete(:owner_name))
#project.attributes = params[:project]
#project.save!
end
end

Related

Rails application with legacy database

I'm building an API that will look for data in a "non-rails-database". It already exists and was not created with the rails project. What is the best approach to use in this case ? Let's say I have a table called User in this database, I would like to be able to do something like this in the controller: User.last or User.find(id). It would be good, because I have to send back to the frontend as a JSON.
Should I use PORO Models ? How would it be ?
Create your models then set table name and primary key explicitly, this helps you call active record methods in a controller
class User < ApplicationRecord
self.primary_key = 'user table primary key'
self.table_name = 'user table name'
end
ref http://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/PrimaryKey/ClassMethods.html
http://api.rubyonrails.org/classes/ActiveRecord/ModelSchema/ClassMethods.html#method-i-table_name-3D
I would personally prefer to have them in PORO. For me, it makes things clear and gives more flexibility.
For an example, I would have a seperate folder called api_models in the app folder and have the classes there.
First, it may feel like you are duplicating the code, but it will give you an object to work with when API needs changes.
E.g
ApiModel::User
...
end
also,
if you wanting be bit more advance and thinking about versioning (if not you should ;) etc, I would recommend a gem like grape, because that handles lot of api related things and all you need to focus is the code.
I would create custom methods that contain custom DB queries that override ActiveRecord methods. Because model classes inherit from ActiveRecord, if you define a method in the model class with the same method name as the ActiveRecord method, then when you call it, the model class will be called. You can still call the ActiveRecord method if you call super
So, assuming you already setup the connection to the DB, you can do something like:
class User < ApplicationRecord
def self.find(id)
sql = "Select * from ... where id = ..."
records_array = ActiveRecord::Base.connection.execute(sql)
end
end
So when you call find it will call the User find, and not the ActiveRecord find. You can also define a custom method so that you don't override the ActiveRecord find:
class User < ApplicationRecord
def self.new_find(id)
sql = "Select * from ... where id = ..."
records_array = ActiveRecord::Base.connection.execute(sql)
end
end
Not sure if its the best approach but its an approach :)

Adding a new column to acts_as_taggable_on tag

Basically I would like to add the ability to vote on tags, so I would like to have a priority column for each different model's tag.
Any ideas about how to do this?
I suspect I need to make a new migration, but I don't know what to make it for. What would the migration be?
Thanks!
As I remember, acts_as_taggable creates a table called tags, so you add a field to that table:
rails g migration add_votes_to_tag votes:integer
and add your logic to vote on tag.
P.S. Not sure if I understood correctly your question.
If you want to extend the regular usage of the tag class, seems to be the case, and create a special case for those tags that needs to be counted you can rely on a hook method from the core named [find_or_create_tags_from_list_with_context][1]
class Company < ActiveRecord::Base
acts_as_taggable_on :markets, :locations
def find_or_create_tags_from_list_with_context(tag_list, context)
if context.to_sym == :markets
MarketTag.find_or_create_all_with_like_by_name(tag_list)
else
super
end
end
end

How to refactor that complex singelton model method for create nested models in Rails?

I have following complex method which I cut off from controller:
def self.create_with_company_and_employer(job_params)
company_attributes = job_params.delete(:company_attributes)
employer_attributes = job_params.delete(:employer_attributes)
new(job_params) do |job|
job.employer = Employer.find_or_create_by_email(employer_attributes)
company_attributes[:admin_id] = job.employer.id if Company.find_by_nip(company_attributes[:nip]).nil?
job.company = Company.find_or_create_by_nip(company_attributes)
Employment.create(employer_id: job.employer.id, company_id: job.company.id)
end
end
I using here two nested_attributes functionality for create company and employer.
Whole code you can find here: https://gist.github.com/2c3b52c35df763b6d9b4
company_attributes[:admin_id] = job.employer.id if Company.find_by_nip(company_attributes[:nip]).nil?
Employment.create(employer_id: job.employer.id, company_id: job.company.id)
Basically I would like to refactor that two lines:
I looked at your gist and i think this is a design issue.
your Employment and Job models seem somewhat redundant, but i don't know what are their actual purpose exactly so i can't really help for now on this matter (i have a hunch that your schema could be remodeled with the employements belonging to the jobs). However, if you really want to, you can use an after_create callback to manage the replication :
class Job < ActiveRecord::Base
after_create :create_corresponding_employment
def create_corresponding_employment
Employment.create( employer_id: employer.id, company_id: company.id )
end
end
this gets you rid of the last line of your method.
the other line you want to get rid of is tricky : you assign an admin_id to your company, but why would you want to do that ? In fact, you're just creating a 'hidden' relation between Company and Employer (a belongs_to one). Why do you need that ? Give more information and i can help.
one more thing: it is not advised to delete keys form the params, or even modify the hash directly. Use a copy.

How to implement a last_modified_by (person) attribute on two unrelated models - Rails

I have a Record model and in order to edit this model, you must be logged in as an instance of Admin. I would like to have a column called last_modified_by which points to the Admin who last modified the Record. In the database, I was thinking it would be good in the records table to add a column that holds the Admin's id; however, the only way I know how to do that is with an association. These two models are not associated with each other so an association doesn't make a lot of sense. Is there any other way I might be able to accomplish this task without resorting to associations? Any advice would be much appreciated!
Hmm, I think the association is a good tool here. You might want to try to hack it somehow but I think nothing you can conjure up will ever be as good as an association via a foreign_key(also so fast). But perhaps you would like to name your association and do something like:
class Record < ActiveRecord::Base
belongs_to :culprit, :class_name => 'Admin', :foreign_key => 'last_modified_by'
end
or give it some more senseful naming?
You could create an Active Record before_save callback. The callback would save the admin's id into the last_modified_column. This would make sure the admin id is saved/updated each time there is a change to the model.
For example, assuming admin is #admin:
class Record < ActiveRecord::Base
before_save :save_last_modified
def save_last_modified
self.last_modified_column = #admin.id
end
As for getting #admin, you could employ a method similar to this, and set #admin = Admin.current (like User.current in the link) somewhere in the Record model.

Rails 3 nested attributes: How can I assign a matching record to the parent model instead of creating a new record every time?

Here's the basic setup:
I have an Order model. An Order has one Address and it accepts_nested_attributes_for :address.
I have a basic order form where I ask a user to input her address. This is handled with nested_fields_for. Everything works great - new addresses are validated and assigned nicely.
However, the problem is that it creates a new Address every time, even if an Address already exists with identical attributes.
I would like to modify the behavior so that if the user-inputted address matches all the attributes for an existing Address, the order assigns the existing Address to itself rather than creating a new one.
The methods I have tried are:
In the controller, try to find an existing Address record with the nested attributes (params[:order][:address_attributes]). If a match exists, delete all the nested attributes and replace them with params[:order][:address_id].
Don't use nested_attributes_for at all and instead override the address= method in the model, then just use the controller to create a new Address based on the parameters and then hand it off to the model.
Both of these solutions seem various degrees of messy. Could somebody please enlighten me on whether this is a controller or model responsibility, and perhaps suggest an elegant way to accomplish this?
Thanks in advance.
Have you tried something like this?
class Order < ActiveRecord::Base
# [..]
before_save :replace_existing_address!
def replace_existing_address!
db_address = Address.where(:city => self.address.city,
:street => self.address.street,
:number => self.address.number).first
self.address = db_address if db_address
end
end
Since I'm asking this as a survey of good ways to do this, I figured I'd offer the solution I'm currently using as well as a basis for comment.
In the Controller:
#new_address = Address.new( params[:order][:address] )
#order.address = new_address
#order.update_attributes( params[:order] )
In the Model:
def address=( address )
return unless address and address.is_a? Address
duplicate_address = Address.where( address_1: address.address_1,
address_2: address.address_2,
[etc. etc.] ).first
if duplicate_address
self.address_id = duplicate_address.id
else
address.save
self.address_id = address.id
end
end
I it's truly a :has_one relationship as you say and not a :has_many, you don't need to explicitly assign the address like you do in your own answer. That's what accepts_nested_attributes is for, after all. This line by itself should work:
#order.update_attributes( params[:order] )
That should create a new address if none exists, and update an existing one.
Your solution may work, but it a) doesn't take advantage of accepts_nested_attributes and b) will leave lots of orphaned addresses in your database.

Categories

Resources