Rails-Passing an object to a DB method - ruby-on-rails

I'm wondering whether the following is possible in Rails. I'm pretty new to Rails, but it just seems like the type of thing I would expect Rails to make possible...
I am wondering whether a DB method (for example, find) can take an object and know automatically to look for its ID value in the appropriate foreign key column without being given the name of that column.
My specific use case is that I would like to make a generic method that checks whether two models are correlated via a connection-table or not.
(In other words, different tables will be linking different models, and thus will use different column names. I want to be able to pass in something [presumably objects of the relevant classes] and Rails will know that the id of #sample_model of class SampleModel should be searched for in the column sample_model_id.)
So the method would be something like:
def connected? (connection_table, model1, model2)
connection_table.class.find_by(model1, model2)
end
…and the method could know automatically that it's looking for model1.id in the model1_id column and the model2.id in the model2_id column.
Is this (or something like it) possible? If not, is there any other way to accomplish my goal (the generic-connection-checker method)?

This method should work as expected:
def self.find_by(obj1, obj2)
fk_1, fk_2 =
[obj1, obj2].map { |o| self.reflect_on_association(o.class.to_s.split("::").last.underscore.to_sym).foreign_key }
self.where(fk_1 => obj1, fk_2 => obj2).last
end

Related

Rails 4: Always use a Postgres function for a certain model field

I've got a model Location with a field name that often contains diacritics - for example "Rhône-Alpes", "Midi-Pyrénées" and "Île-de-France".
This model gets used in a few different ways in my application, particularly for queries by the name. Examples include:
Location.find_by(name: 'Rhône-Alpes')
Location.find_or_create_by(name: 'Rhône-Alpes', country: 'France')
The arguments to those find methods vary across the application. My question is this: how can I make sure that a certain Postgres function (in this case unaccent()) is called every time the name field is referred to? This means I can search for 'Rhone' and 'Rhône', and get the same results.
Is it best to override the find_by and find_or_create_by methods? If so, what's the best way of doing this?
It's always good to push logic down into the database layer, but if you work on lots of different projects all the time, you might need to perform a find without unaccenting in future, then run into problems when you can't easily debug so you must always be careful when doing so.
Do not override the find_by method, this isn't recommended as you may perform a standard find_by and have unforeseen consequences.
Have you considered sanitising the input before the find_by?
>> ActiveSupport::Inflector.transliterate("Rhône-Alpes")
=> "Rhone-Alpes"
I know you're using find_by and find_or_create_by when querying by name, and you could override those class methods, not exactly sure how to do it the right way, but it should be pretty simple. But why not creating a class method for specifically querying by name?, like a scope!. There you could transform both the given name as a parameter, and the actual name value in the model. Something like this (assuming you have a Ruby method unaccent at your disposal)...
def self.named(name)
self.all.select {|location| unaccent(location.name) == unaccent(name) }
end
Not sure if it is the answer you're looking for, but it is a way to do it, and you leave this logic in the model layer, independently of the DBMS you're using.
Best.
You can create virtual column in DB, like that:
CREATE TABLE customer (id SERIAL, firstname TEXT, lastname TEXT);
-- create virtual column
CREATE FUNCTION fullname(customer) RETURNS text AS $$
SELECT $1.firstname || ' ' || $1.lastname
$$ LANGUAGE SQL;
INSERT INTO customer VALUES (DEFAULT, 'Mark', 'Pennypincher');
SELECT customer.fullname FROM customer;
(stolen from http://momjian.us/main/blogs/pgblog/2013.html#April_1_2013 )
Other options is create view for table and wrap column into the function in this view. If you don't try write into virtual column - view is editable.

How Do I Call An Array Written in My Model

Greeting all
I would like to create an array in my model and then reference it later from a view or a helper. How do I do this?
This is in my Events model. Users can select a lunch type(1,2,3) for the event. Instead of hard-coding sandwich names, which can change, in my view, I thought I would keep the names in one place (model) then reference the name based on the lunch type chosen.
SANDWICHES = { 1 => 'Turkey', 2 => 'Veggie', 3 => 'Roast Beef' }
How do I call this from another script in my app like a view or a helper?
Event.SANDWICHES[1] does not work
event_obj.SANDWICHES[1] does not work
thanks for any help.
What you have there is a constant and you need to access it with Event::SANDWICHES.
What you are describing here is not a Model (in the rails way), it's simply a constant.
If you don't want to create an actual Model with its own database table, you could pull this out into a helper.
Such as fillings_helper.rb
def fillings
%q{Turkey, Veggie, Roast Beef}
end
Then you can use it in your views for a select box by passing in fillings
http://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html#method-i-select
You would need to set up a text field in your Event model to accept a string for fillings.
This isn't necessarily the best way from a database normalisation perspective, but it will do what you're asking for.

Force reload another model's methods in rails?

I have a model that defines methods based off of the entries in another model's table: eg Article and Type. An article habtm types and vice versa.
I define in Article.rb:
Type.all.each do |type|
define_method "#{type.name}?" do
is?(:"#{type.name}")
end
end
This works great! it allows me to ensure that any types in the type db result in the methods associated being created, such as:
article.type?
However, these methods only run when you load the Article model. This introduces certain caveats: for example, in Rails Console, if I create a new Type, its method article.type_name? won't be defined until I reload! everything.
Additionally, the same problem exists in test/rspec: if I create a certain number of types, their associated methods won't exist yet. And in rspec, I don't know how to reload the User model.
Does anyone know a solution here? Perhaps, is there some way to, on creation of a new Type, to reload the Article model's methods? This sounds unlikely.. Any advice or guidance would be great!
I think you'll be better off avoiding reloading the model and changing your api a bit. In Article, are you really opposed to a single point of access through a more generic method?
def type?(type)
return is? type if type.is_a? String # for when type is the Type name already
is? type.name # for when an instance of Type is passed
end
If you're set on having separate methods for each type, perhaps something like this would work in your Type class
after_insert do
block = eval <<-END.gsub(/^ {6}/, '')
Proc.new { is? :#{self.name} }
END
Article.send(:define_method, "#{self.name}?", block)
end

next available record id

#user = User.new
#user.id returns nil but i need to know it before i save. Is it possible ?
YES you can!
I had the same question and investigated the docs.
The ability to solve this question is very related to your database type in fact.
Oracle and Postgresql do have useful functions to easily solve this.
For MySQL(oracle) or SkySQL(open-source) it seems more complicated (but still possible). I would recommend you avoid using these (MySQL/SkySQL) databases if you need advanced database tools.
First you must try to avoid this situation as much as possible in your application design, as it is dangerous to play with IDs before they get saved.
There may be situation where you don't have any other choice:
For instance when two tables are referencing themselves and for security reason you don't allow DELETE or UPDATE on these tables.
When this is the case, you can use the (PostgreSQL, Oracle) database nextval function to generate the next ID number without actually inserting a new record.
Use it in conjunction with the find_by_sql rails method.
To do this with postgreSQL and Rails for instance, choose one of your rails models and add a class method (not an instance method!).
This is possible with the "self" word at the beginning of the method name.
self tells Ruby that this method is usable only by the class, not by its instance variables (the objects created with 'new').
My Rails model:
class MyToy < ActiveRecord::Base
...
def self.my_next_id_sequence
self.find_by_sql "SELECT nextval('my_toys_id_seq') AS my_next_id"
end
end
When you generate a table with a Rails migration, by default Rails automatically creates a column called id and sets it as the primary key's table. To ensure that you don't get any "duplicate primary key error", Rails automatically creates a sequence inside the database and applies it to the id column. For each new record (row) you insert in your table, the database will calculate by itself what will be the next id for your new record.
Rails names this sequence automatically with the table name append with "_id_seq".
The PostgreSQL nextval function must be applied to this sequence as explained here.
Now about find_by_sql, as explained here, it will create an array containing new objects instances of your class. Each of those objects will contain all the columns the SQL statement generates. Those columns will appear in each new object instance under the form of attributes. Even if those attributes don't exist in your class model !
As you wisely realized, our nextval function will only return a single value.
So find_by_sql will create an array containing a single object instance with a single attribute.
To make it easy to read the value of this very attribute, we will name the resulting SQL column with "my_next_id", so our attribute will have the same name.
So that's it. We can use our new method:
my_resulting_array = MyToy.my_next_id_sequence
my_toy_object = my_resulting_array[0]
my_next_id_value = my_toy_object.my_next_id
And use it to solve our dead lock situation :
my_dog = DogModel.create(:name => 'Dogy', :toy_id => my_next_id_value)
a_dog_toy = MyToy.new(:my_dog_id => my_dog.id)
a_dog_toy.id = my_next_id_value
a_dog_toy.save
Be aware that if you don't use your my_next_id_value this id number will be lost forever. (I mean, it won't be used by any record in the future).
The database doesn't wait on you to use it. If somewhere at any time, your application needs to insert a new record in your my_table_example (maybe at the same time as we are playing with my_next_id_sequence), the database will always assign an id number to this new record immediately following the one you generated with my_next_id_sequence, considering that your my_next_id_value is reserved.
This may lead to situations where the records in your my_table_example don't appear to be sorted by the time they were created.
No, you can't get the ID before saving. The ID number comes from the database but the database won't assign the ID until you call save. All this is assuming that you're using ActiveRecord of course.
I had a similar situation. I called the sequence using find_by_sql on my model which returns the model array. I got the id from the first object of the arry. something like below.
Class User < ActiveRecord::Base
set_primary_key 'user_id'
alias user_id= id=
def self.get_sequence_id
self.find_by_sql "select TEST_USER_ID_SEQ.nextval as contact_id from dual"
end
end
and on the class on which you reference the user model,
#users = User.get_sequence_id
user = users[0]
Normally the ID is filled from a database sequence automatically.
In rails you can use the after_create event, which gives you access to the object just after it has been saved (and thus it has the ID). This would cover most cases.
When using Oracle i had the case where I wanted to create the ID ourselves (and not use a sequence), and in this post i provide the details how i did that. In short the code:
# a small patch as proposed by the author of OracleEnhancedAdapter: http://blog.rayapps.com/2008/05/13/activerecord-oracle-enhanced-adapter/#comment-240
# if a ActiveRecord model has a sequence with name "autogenerated", the id will not be filled in from any sequence
ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
alias_method :orig_next_sequence_value, :next_sequence_value
def next_sequence_value(sequence_name)
if sequence_name == 'autogenerated'
# we assume id must have gotten a good value before insert!
id
else
orig_next_sequence_value(sequence_name)
end
end
end
while this solution is specific to Oracle-enhanced, i am assuming the other databases will have a similar method that you could redefine.
So, while it is definitely not advised and you want to be absolutely sure why you would not want to use an id generated by a sequence, if it is needed it is most definitely possible.
It is why I love ruby and Ruby on Rails! :)
In Oracle you can get your current sequence value with this query:
SELECT last_number FROM user_sequences where sequence_name='your_sequence_name';
So in your model class, you can put something like this:
class MyModel < ActiveRecord::Base
self.sequence_name = 'your_sequence_name'
def self.my_next_id_sequence
get_data = self.find_by_sql "SELECT last_number FROM user_sequences where sequence_name='your_sequence_name'"
get_data[0].last_number
end
end
And finally, in controller you can get this value with this:
my_sequence_number = MyModel.my_next_id_sequence
So, there is no need to get your next value by using NEXTVAL and you won't lose you ID.
What you could do is User.max(id). which will return the highest ID in the database, you could then add 1. This is not reliable, although might meet your needs.
Since Rails 5 you can simply call next_sequence_value
Note: For Oracle when self.sequence_name is set, requesting next sequence value creates side effect by incrementing sequence value

Type method interfering with database type column problem

I'm using Ruby on Rails and the paths_of_glory gem
I need to access the types of achievements that a user accomplishes in order to display a picture along with each particular achievement. When I try to access it via #user.achievements.type, I get an error that says that it wants to return "array" (as in achievements is an array) instead of actually returning the elements in the type column of my database.
Since every ruby object has a method called type, my call to access the type column of the database fails. When I try to change the entry in the table, the paths_of_glory gem says it needs a type column in order to function properly.
I'm not quite sure where to go from here in order to access that column in the database. Any suggestions?
Not entirely sure what you're asking here, but maybe this will help.
For the first thing, #user.achievements is an array because you have multiple achievements, and the type method is for individual elements of #user.achievements which is why that won't work just like that. You'll have to do something like this:
#user.achievements.each do |achievement|
# Do stuff here
end
Regarding the type column, type is a reserved column in Rails used specifically for Single Table Inheritance, where multiple Rails models use a single database table. So you can't access it directly. I assume that paths_of_glory uses STI in some manner. You can access the model's class with something like achievement.class, then if you want just the name of it you can try achievement.class.to_s.
#user.achievements.each do |achievement|
model = achievement.class # => MyAwesomeAchievementClass
#image = model.picture # You could write some method in the model like this
end

Resources