John Sanchez
Joined
Activity
Hi Steve, I see what the problem is. You CSV iterator is built for a has_one or belongs_to relationship.
In a has_one or belongs_to relationship, Active record will pass the object in directly. Which allows you to call something like:
Invoice.first.product
# returns object
However, since you have a has_many
relationship in your Invoice
model, you need to treat the returned value as an array.
For example:
Invoice.first.products
# returns an array of objects
You need to change how your iteration works.
First, you're iterating through invoices, not products.
Change all.each do |product|
to all.each do |invoice|
.
Then, you need to add another iterator because an invoice has_many
products. That means, you need to go through each one of those products.
invoice.products.each do |product|
Next, you can place those products into the CSV file.
For example:
class Invoice < ApplicationRecord
has_many :products, dependent: :destroy
def self.to_csv
CSV.generate(headers: true) do |csv|
csv << attributes = %w{date vendor_name invoice_number title }
all.each do |invoice|
invoice.products.each do |product|
csv << invoice.attributes.merge(product.attributes).values_at(*attributes)
end
end
end
end
Give that a shot and let me know if it works.
Hey Steve, did you add the merge for the title because you weren't seeing the title in your CSV file? What results are you hoping for and what are you receiving?
Hmm I don't think the entry example you mentioned pasted in correctly. Would you be able to try again?
Interesting. Would you please check your rails server logs to see how many "Last Product" lines processed?
In another terminal window, start the rails console via rails console
and run Product
. Please paste in what that says here.
A stack trace is the entire error displayed in your application log. Here's an example of the stack trace that rails provides in the browser: https://i.stack.imgur.com/yrraB.png
I see what the issue is here. title
is not an ActiveRecord Model object. It's an ActiveRecord model attribute for Product
. With that said you won't be able to call #attibutes
on it like: product.title.attributes
. You should already be outputing the title
into your CSV file with your code if you remove the merge.
Since, Invoice
is the only association you have on your Product
model, that's the only other object you can run #attributes
on. For instance: product.invoice.attributes
.
You're most welcome! :)
Would you be able to post the stack trace that you're receiving with that error?
An undefined method would usually mean a couple of things. Here's what's common:
- You have a typo in the association name that you're calling.
- That association doesn't yet exist. For example, one of your products doesn't have a title yet. If that's that case, you'd usually receive an "Undefined method 'title' for NilClass" <-- I'm betting this is that case here.
You may be able to find this error by adding a couple of logger lines right below the all.each do |product|
line.
For example:
def self.to_csv
CSV.generate(headers: true) do |csv|
csv << attributes = %w{date vendor_name invoice_number title }
all.each do |product|
Rails.logger.debug "Last Product: #{product.id}"
Rails.logger.debug "Last Product Title: #{product.title}"
csv << product.attributes.merge(product.title.attributes).values_at(*attributes)
end
end
end
This will iterate over each product and allow you to see the last product in the iteration that caused the error.
By the way, what is the title object association for your product? What attributes? What type of association is it? etc.
Posted in Struggling with an association test
Thought 1:
Sounds like you just need to make sure the role and building are created before the user is. Then, in your model, make sure that the user is associated with a default role and building using something like a before_validation
.
Then, in your test suite, make sure you create that default role and building before running the test.
Thought 2:
By factory, I'm assuming you're using Factory Girl. You could try using a callback such as before(:create)
to create the role and building before the factory is created. Then, use those for the associations.
If the attribute names don't overlap, you could just use a Hash#merge on the attributes before you append them to the CSV.
For example, just change:
all.each do |product|
csv << product.attributes.values_at(*attributes)
end
To:
all.each do |product|
csv << product.attributes.merge(product.vendor.attributes).values_at(*attributes)
end
Where product.vendor.attributes
would be the attributes for your nested model.
Since you're new to Rails, you may ask why this works.
The product
object is an ActiveRecord model object but when you call .attributes
on it, Rails will return the values as a Hash. This allows you to use Ruby and Rails Hash methods on it.
You can confirm the object class by opening your Rails console and running .class
on the objects.
You can merge as many hashes as you need to for this. Just append .merge
to it right before the .values_at
method. If you start doing that, I'd recommend cleaning your code up by moving the merges to a new line and storing them in a variable. Then, you can call .values_at
on that variable.
Posted in Stripe gems: Koudoku or Payola
I agree with Chris and Jack. I've found that integrating Stripe directly with the api is best. One less dependency for your app to rely on.
Fantastic! No payment needed. :)
Now that's it's working, I'd recommend experimenting with a few things to clean the javascript up and make it unobtrusive.
- Try placing the Maps API Src call into the view partial itself. Be sure to place it above the address JS so that it runs first. This way, the maps api only runs on the pages that the map is loaded on.
- Try placing the address js back into the javascript file that you had so it's loaded with the rest of the asset pipline. Then, make sure your initMap() call is after the map HTML element is created.
- Try placing the initMap() call in a document ready function
If those don't work, no worries. You atleast have it working in the view with what you have now and can move forward with other features for your app.
Hmmm ... okay. I've tested your Javascript code because that would be the only issue here...
I've tested and refactored it outside of rails.
This works as a regular HTML file:
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
#map { width: 800px; height: 500px; }
</style>
<script src="https://maps.googleapis.com/maps/api/js?key=[YOUR_KEY_HERE]"></script>
</head>
<body>
<div id="map"></div>
<script>
var map;
// I'm guessing your address object structure here.
var addresses = [
{
latitude: '30.2669444',
longitude: '-97.7427778'
},
{
latitude: '31.2669444',
longitude: '-98.7427778'
},
{
latitude: '29.2669444',
longitude: '-96.7427778'
}
];
function initMap() {
var bounds = new google.maps.LatLngBounds();
var mapOptions = {
zoom: 5,
center: {lat: 30.2669444, lng: -97.7427778},
mapTypeId: google.maps.MapTypeId.ROADMAP
}
var mapDiv = document.getElementById('map')
map = new google.maps.Map(mapDiv, mapOptions);
var n = addresses.length;
for (var i = 0; i < n; i++) {
var lat = addresses[i].latitude;
var lng = addresses[i].longitude;
if (lat == null || lng ==null){
console.log(addresses[i].name + " doesn't have coordinates");
} else {
var address = new google.maps.Marker({
position: {lat: parseFloat(lat), lng: parseFloat(lng)},
title: addresses[i].name,
map: map
});
bounds.extend(address.position);
}
}
map.fitBounds(bounds);
}
// Make sure you call the initMap function
initMap();
</script>
</body>
</html>
You may want to try model something similar in your rails view.
A couple of notes here:
- Make sure you have CSS that sets your height and width of the map element.
- Make sure you actually call your initMap() function.
- You don't need to add 'center' to your mapOptions object but I did it anyway.
- I moved the map api call to the header again. Feel free to do so.
- replace [YOUR_KEY_HERE] with your Google API key
Hmm. I just reordered a few things to load in the right order. I haven't tested your code...
Try this. Any errors in the console?
<div id="map"></div>
<script>
var addresses = <%= raw @addresses.to_json %>;
function initMap() {
var bounds = new google.maps.LatLngBounds();
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 5
});
var n = addresses.length;
for (var i = 0; i < n; i++) {
var lat = addresses[i].latitude;
var lng = addresses[i].longitude;
if (lat == null || lng ==null){
console.log(addresses[i].name + " doesn't have coordinates");
} else {
var address = new google.maps.Marker({
position: {lat: parseFloat(lat), lng: parseFloat(lng)},
title: addresses[i].name,
map: map
});
bounds.extend(address.position);
}
}
map.fitBounds(bounds);
}
</script>
<script src="https://maps.googleapis.com/maps/api/js?key=<%= ENV["GOOGLE_MAPS_API_KEY"] %>"></script>
I don't mean add an event listener. I mean add it to the view file itself. You can organize your JS to be unobtrusive later. You want the code to run after the element is created. Place the function and the function call in a script tag beneath the map html element. Once you've done that, copy and paste the code you have for your view here.
I can either have googlemaps working (if I move the javascript include tag out of the head and to the end of the body tag) in which case, the rest of my javascript doesnt work.
Yeah, don't do that. :)
Leave the JS tag in the header.
Try loading your address.js on the page itself instead. That's right, place it at the bottom of your views file.
I tried removing turbolinks. I commented out all the files that refer to turbolinks and pushed that to production
Leave turbolinks commented out until you figure this out.
I also dont get a map - just a blank white square where the map should be.
That means that the element is rendering correctly. Great!
You probably don't see the map because your javascript in your address.js file is being called before your element is rendered in the DOM.
Make sure your address.js code is after your map element in your view. That way, the javascript code runs after the element has been created.
I intentionally removed those two lines (underscore and gmaps). They were required when I was trying to use gmaps4rails gem but are not required to just use javascript. I couldnt get gmaps for rails gem to work. I found a solution that worked (although I couldnt set a zoom level) to render a map without that gem - so I removed the requirment for those 2 lines.
If you're no longer using it, I would just delete those lines.
In my opinion, if writing the code out is simple, try not to use gems. Everytime you add a gem, you add a dependency that could break and cause a LOT of headace in the future when trying to update rails.
Oh, by the way, your application.js file isn't quite right...
You have:
//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require jquery-fileupload/vendor/jquery.ui.widget
//= require jquery-fileupload/jquery.iframe-transport
//= require jquery-fileupload/jquery.fileupload
//= require bootstrap-sprockets
//= require moment
//= require bootstrap-datetimepicker
//= require pickers
// require underscore
// require gmaps/google
//= require markerclusterer
//= require cocoon
//= require cable
//= require_tree .
Not that you're missing the equal signs on
// require underscore
// require gmaps/google
It should be:
//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require jquery-fileupload/vendor/jquery.ui.widget
//= require jquery-fileupload/jquery.iframe-transport
//= require jquery-fileupload/jquery.fileupload
//= require bootstrap-sprockets
//= require moment
//= require bootstrap-datetimepicker
//= require pickers
//= require underscore
//= require gmaps/google
//= require markerclusterer
//= require cocoon
//= require cable
//= require_tree .
That could be your issue right there...
Don't listen to people like that.
I mean't that you should comment out or remove turbolinks until your JS is working. If your other libraries are working, comment them out and just leave the one you're working on uncommented.
Your StackOverflow post is has become extremely long and in my opinion all over the place. Rather than me trying to figure out where you're at, tell me where you're at.
What's currently working and not working on your production environment app? Is it just Google Maps that's not working?
Posted in Add paywall functionality to app
You're welcome! Good luck! :)
Posted in Add paywall functionality to app
Hi Stan, if you're looking for something simple, I'd recommend just creating a session and using that session as a counter. The following is a pure Ruby implmentation. You'll want to make some adjustments for your Rails app. I haven't tested it within Rails yet but I think you'll get the point.
# Rails has a sessions object that you can use. the next line creates an object to represent it.
session = {}
# You typically have your content in a database and can pull it out as an object. The following will represent that object.
content = {}
# The following represents the values of your content.
content[:title] = 'Title of Content'
content[:content] = 'Display content'
# Create your pay wall session counter
session[:pay_wall] ||= 1
# Run your conditional to check the counter
if session[:pay_wall] >= 5
puts 'Add Pay Wall code here'
elsif session[:pay_wall] >= 1
session[:pay_wall] += 1
puts content[:content]
else
session[:pay_wall] = 1
puts content[:content]
end
# Test the value of your session
puts session[:pay_wall]
Resources you may find interesting on the topic of sessions:
http://www.justinweiss.com/articles/how-rails-sessions-work/
http://api.rubyonrails.org/v5.0.1/classes/ActionDispatch/Integration/Session.html
Hmm ... I'm only briefly able to read your SO post don't have the time right now to go through it all the way.
From what I understand, the problem is that your code works on Development but not in Production? Is that right?
It might be a stupid question, but have you tried disabling turbolinks? Here's a link to how http://blog.flightswithfriends.com/post/53943440505/how-to-disable-turbolinks-in-rails-4 if you haven't tried it.
Sometimes, turbolinks will mess with you like you've mentioned in your post. If you haven't tried it, see if disabling it helps at all.
As I outlined in this post, the js files (except for google maps) don't work in production if I have the javascript include tag in the head. I only arrived at this solution after a long, expensive session on codementor. I can't afford another session to find out how to solve the problems that this solution has created. I also didn't get an explanation as to the fundamental reason why this change is necessary. The rails guides suggest including it there so I don't understand why, for the most part, moving it to the end of the body tag is a solution that gets the js to work in the production environment. This breaks the address js though, so it isn't the right solution
By reading through your JS, the reason that you need to load it at the bottom is because of the "getElementById" in the init()
var map = new google.maps.Map(document.getElementById('map'), {
By loading that at the top, the script might not find the element because it's loading before the element is created.
You could also try wrapping those in a document.onload handler to have it load after the DOM is done loading.