Single Ruby Value in One Line From a Collection - ruby

I have a collection of objects. There are 3 properties in each object
'id', 'name', 'is_primary'
The collection of objects will usually have anywhere from 1 to 5 objects.
What I want to do is check the collection to see if is_primary is true. If so output the name, or at least return it.
I want to do this in 1 line of code if possible. I am trying to slim up this one line for erb output in rails. Later in the page i'll output them all. I thought I had it, but if I return nil it adds extra space which shifts all the html oddly.
Thanks.

Hmm, this doesn't quite work if no element is_primary...I'm still thinking...
c.detect(&:is_primary).name
Ok, how about:
((a = c.detect(&:is_primary)) && a.name).to_s
As it happens, it is OK in an erb template for the <%= expression to return nil, that just results in an empty string, so for that case you can use:
(a = c.detect(&:is_primary)) && a.name
Update: Responding to the first comment, I do have a test case that I didn't post...
class A; attr_accessor :is_primary, :name, :id; end
t = [A.new, A.new, A.new, (a = A.new; a.name = 'xyz'; a.is_primary = true; a)]
puts (a = t.detect(&:is_primary)) && a.name
puts ((a = [].detect(&:is_primary)) && a.name).to_s

Complementing #DigitalRoss, you can also write:
collection.detect(&:is_primary).try(:name) || "default_if_no_element_or_name"
(well, to be honest I prefer Ick's maybe over Rails' try: c.detect(&:is_primary).maybe.name)
Side note: IMHO a flag that can only be active for a row it's not such a good idea. You may have inconsistent states with more than one being active and you'll have worry about it when updating (transactions, and so on). Try to store the PK reference somewhere else (a parent model? a state model?).
I want to do this in 1 line of code if possible. I am trying to slim up this one line for erb output in rails. Later in the page i'll output them all.
No need for one-liners (funny since I just wrote one): move the code to yous models or helpers as appropriate and keep your views pristine.

Related

rails string substitution or similar solution in controller

I'm building a site with users in all 50 states. We need to display information for each user that is specific to their situation, e.g., the number of events they completed in that state. Each state's view (a partial) displays state-specific information and, therefore, relies upon state-specific calculations in a state-specific model. We'd like to do something similar to this:
##{user.state} = #{user.state.capitalize}.new(current_user)
in the users_controller instead of
#illinois = Illinois.new(current_user) if (#user.state == 'illinois')
.... [and the remaining 49 states]
#wisconsin = Wisconsin.new(current_user) if (#user.state == 'wisconsin')
to trigger the Illinois.rb model and, in turn, drive the view defined in the users_controller by
def user_state_view
#user = current_user
#events = Event.all
#illinois = Illinois.new(current_user) if (#user.state == 'illinois')
end
I'm struggling to find a better way to do this / refactor it. Thanks!
I would avoid dynamically defining instance variables if you can help it. It can be done with instance_variable_set but it's unnecessary. There's no reason you need to define the variable as #illinois instead of just #user_state or something like that. Here is one way to do it.
First make a static list of states:
def states
%{wisconsin arkansas new_york etc}
end
then make a dictionary which maps those states to their classes:
def state_classes
states.reduce({}) do |memo, state|
memo[state] = state.camelize.constantize
memo
end
end
# = { 'illinois' => Illinois, 'wisconsin' => Wisconsin, 'new_york' => NewYork, etc }
It's important that you hard-code a list of state identifiers somewhere, because it's not a good practice to pass arbitrary values to contantize.
Then instantiating the correct class is a breeze:
#user_state = state_classes[#user.state].new(current_user)
there are definitely other ways to do this (for example, it could be added on the model layer instead)

Create an object if one is not found

How do I create an object if one is not found? This is the query I was running:
#event_object = #event_entry.event_objects.find_all_by_plantype('dog')
and I was trying this:
#event_object = EventObject.new unless #event_entry.event_objects.find_all_by_plantype('dog')
but that does not seem to work. I know I'm missing something very simple like normal :( Thanks for any help!!! :)
find_all style methods return an array of matching records. That is an empty array if no matching records are found. And an empty is truthy. Which means:
arr = []
if arr
puts 'arr is considered turthy!' # this line will execute
end
Also, the dynamic finder methods (like find_by_whatever) are officially depreacted So you shouldn't be using them.
You probably want something more like:
#event_object = #event_entry.event_objects.where(plantype: 'dog').first || EventObject.new
But you can also configure the event object better, since you obviously want it to belong to #event_entry.
#event_object = #event_entry.event_objects.where(plantype: 'dog').first
#event_object ||= #event_entry.event_objects.build(plantype: dog)
In this last example, we try to find an existing object by getting an array of matching records and asking for the first item. If there are no items, #event_object will be nil.
Then we use the ||= operator that says "assign the value on the right if this is currently set to a falsy value". And nil is falsy. So if it's nil we can build the object form the association it should belong to. And we can preset it's attributes while we are at it.
Why not use built in query methods like find_or_create_by or find_or_initialize_by
#event_object = #event_entry.event_objects.find_or_create_by(plantype:'dog')
This will find an #event_entry.event_object with plantype = 'dog' if one does not exist it will then create one instead.
find_or_initialize_by is probably more what you want as it will leave #event_object in an unsaved state with just the association and plantype set
#event_object = #event_entry.event_objects.find_or_initialize_by(plantype:'dog')
This assumes you are looking for a single event_object as it will return the first one it finds with plantype = 'dog'. If more than 1 event_object can have the plantype ='dog' within the #event_entry scope then this might not be the best solution but it seems to fit with your description.

Sinatra can't convert Symbol into Integer when making MongoDB query

This is a sort of followup to my other MongoDB question about the torrent indexer.
I'm making an open source torrent indexer (like a mini TPB, in essence), and offer both SQLite and MongoDB for backend, currently.
However, I'm having trouble with the MongoDB part of it. In Sinatra, I get when trying to upload a torrent, or search for one.
In uploading, one needs to tag the torrent — and it fails here. The code for adding tags is as follows:
def add_tag(tag)
if $sqlite
unless tag_exists? tag
$db.execute("insert into #{$tag_table} values ( ? )", tag)
end
id = $db.execute("select oid from #{$tag_table} where tag = ?", tag)
return id[0]
elsif $mongo
unless tag_exists? tag
$tag.insert({:tag => tag})
end
return $tag.find({:tag => tag})[:_id] #this is the line it presumably crashes on
end
end
It reaches line 105 (noted above), and then fails. What's going on? Also, as an FYI this might turn into a few other questions as solutions come in.
Thanks!
EDIT
So instead of returning the tag result with [:_id], I changed the block inside the elsif to:
id = $tag.find({:tag => tag})
puts id.inspect
return id
and still get an error. You can see a demo at http://torrent.hypeno.de and the source at http://github.com/tekknolagi/indexer/
Given that you are doing an insert(), the easiest way to get the id is:
id = $tag.insert({:tag => tag})
id will be a BSON::ObjectId, so you can use appropriate methods depending on the return value you want:
return id # BSON::ObjectId('5017cace1d5710170b000001')
return id.to_s # "5017cace1d5710170b000001"
In your original question you are trying to use the Collection.find() method. This returns a Mongo::Cursor, but you are trying to reference the cursor as a document. You need to iterate over the cursor using each or next, eg:
cursor = $tag.find_one({:tag => tag})
return cursor.next['_id'];
If you want a single document, you should be using Collection.find_one().
For example, you can find and return the _id using:
return $tag.find_one({:tag => tag})['_id']
I think the problem here is [:_id]. I dont know much about Mongo but `$tag.find({:tag => tag}) is probably retutning an array and passing a symbol to the [] array operator is not defined.

OR operators and Ruby where clause

Probably really easy but im having trouble finding documentation online about this
I have two activerecord queries in Ruby that i want to join together via an OR operator
#pro = Project.where(:manager_user_id => current_user.id )
#proa = Project.where(:account_manager => current_user.id)
im new to ruby but tried this myself using ||
#pro = Project.where(:manager_user_id => current_user.id || :account_manager => current_user.id)
this didnt work, So 1. id like to know how to actually do this in Ruby and 2. if that person can also give me a heads up on the boolean syntax in a ruby statement like this altogether.
e.g. AND,OR,XOR...
You can't use the Hash syntax in this case.
Project.where("manager_user_id = ? OR account_manager = ?", current_user.id, current_user.id)
You should take a look at the API documentation and follow conventions, too. In this case for the code that you might send to the where method.
This should work:
#projects = Project.where("manager_user_id = '#{current_user.id}' or account_manager_id = '#{current_user.id}'")
This should be safe since I'm assuming current_user's id value comes from your own app and not from an external source such as form submissions. If you are using form submitted data that you intent to use in your queries you should use placeholders so that Rails creates properly escaped SQL.
# with placeholders
#projects = Project.where(["manager_user_id = ? or account_manager_id = ?", some_value_from_form1, some_value_from_form_2])
When you pass multiple parameters to the where method (the example with placeholders), the first parameter will be treated by Rails as a template for the SQL. The remaining elements in the array will be replaced at runtime by the number of placeholders (?) you use in the first element, which is the template.
Metawhere can do OR operations, plus a lot of other nifty things.

DataMapper first_or_create doesn't work... any ideas why?

Well, I've finally decided that I'm not crazy. So, that leaves DataMapper.
Here's what I'm doing. I have a model Msrun which has 1 Metric.
tmp = Msrun.first_or_create # I'll skip the boring details
tmp.metric = Metric.first_or_create( {msrun_id: tmp.id}, {metric_input_file: #metricsfile} )
p tmp.metric # => #<Metric #metric_input_file=nil #msrun_id=1>
tmp.metric.metric_input_file = #metricsfile
p tmp.metric # => #<Metric #metric_input_file=#<Pathname:/home/ryanmt/Dropbox/coding/rails/metrics_site/spec/tfiles/single_metric.txt> #msrun_id=1>
So, why doesn't this work? I'm reading http://datamapper.org/docs/create_and_destroy and doing what it shows working. This has been terribly arduous. Thanks for any help.
Update:
I still can't figure out what is going on, but to prove I'm not insane...
puts Metric.all # => []
tmp.metric = Metric.first_or_create( {msrun_id: tmp.id}, {metric_input_file: #metricsfile} )
puts Metric.all # => [] #??????????????
tmp.metric.metric_input_file = #metricsfile
p tmp.metric # => #<Metric #metric_input_file=#<Pathname:/home/ryanmt/Dropbox/coding/rails/metrics_site/spec/tfiles/single_metric.txt> #msrun_id=1>
tmp.metric.save
puts Metric.all # => [#<Metric #metric_input_file=#<Pathname:/home/ryanmt/Dropbox/coding/rails/metrics_site/spec/tfiles/single_metric.txt> #msrun_id=1>]
So, not only is first_or_create not delivering on the behavior I expect by reading the source
def first_or_create(conditions = {}, attributes = {})
first(conditions) || create(conditions.merge(attributes))
end
but it is also not even creating.
I'm probably missing something here (more of those boring details might help) but if the metric exists, it's metric_input_file shouldn't be updated, i.e., it's only set when new. If you're after updating then you can do
.first_or_create(msrun_id: tmp.id).update(metric_input_file: #metricsfile)
Or if not hitting the database twice is relevant, then
m = Metric.first_or_new(msrun_id: tmp.id)
[set..save..assign]
But if it's not being set on new models, I don't see what would cause that from the code posted so far, more..?
[UPDATED]
Based on your new code, I'd say this is "a classic case" of a false DM save. I usually add the following line to an initialization section, e.g., application.rb in Rails.
DataMapper::Model.raise_on_save_failure = true
Unfortunately, the exception raised never tells you why (there's a special place in hell for that choice, right next to people who talk in theaters.) But it's typically one of:
a slightly incorrect association definition
a has/belongs_to that isn't "required: false" and isn't set
putting the wrong datatype into a field, e.g., a string into a decimal
a validation failing
If you want to post your model definitions, the problem may be spottable there.
In addition to the answer above, I've seen this call die (like, literally halt all execution) with no error when I was doing a find_or_create that would have created an object that violated the primary key constraint. This is because the datamapper model was not in sync with the actual database schema.

Resources