Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fullstack challenge - Jed Stanton #4

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

JStant95
Copy link

@JStant95 JStant95 commented May 20, 2024

My Process

Started with challenge two:

I initially cleaned up the index action, removing any n+1s, string injection and using activerecord methods for sort/offset/limit etc rather than ruby.

Having set that up I moved on to creating the index view. Being mindful of time I just did all the styling inline but if I had more time I would've moved them into a scss file.

I then moved on to pagination for reviews for each product. I then created two new partials one which will render the reviews for each product and another which some very basic pagination (list all the pages of the reviews which is calculated by the total number of reviews for each product which i pass in the data instance variable). I added some javascript to send an AJAX request to a new route + action I created, it'll pass the page the user has clicked and product id. The action will then rerender the reviews partial for that particular product. Improvements I would make to this if I had more time would be to improve the UI/UX of pagination partial, currently just a list of all the page numbers rather than what was specific on the design.

I did not have time to add pagination to the product pages, however if I did I probably would've used the kaminari gem.

I did not have time to add tests here but if I did I would test the following.

  • rspec test for the index action that would data object contained the correct products with the correct associated reviews
  • cucumber test that would test the reviews pagination, that after selecting a different page the reviews display were adjusted correctly
Screen.Recording.2024-05-20.at.15.37.53.mov

Challenge 1:

Added new route for reviews and then a very basic form to create a new review as I did not have a enough time to style it to match design. As it was a standalone page rather than a button on each product on the index page for the product dropdown rather than listing all product which would have made the dropdown v slow I added a search field for the user to type in to populate the dropdown, once the user had typed at least 5 letters this sent an ajax request to a new route that I had added which then sent the results of the query back to the javascript where I populated the dropdown. I also changed the create action to async call the worker i created to make the reviews plus added validations to the review model to ensure valid reviews were being created.

Again here I unfortunately did not the time to added tests, however had I done:

  • rspec test for the create worker to ensure it created a review when called
  • rspec test to the model to test validations
  • rspec test to the search action on product controller to ensure it returned correct data for different params
  • happy path end-to-end cuke test for creating a review
Screen.Recording.2024-05-20.at.15.38.40.mov

I also would've tidied up my commits to make them as atomic as possible if I had more time

Jed Stanton added 7 commits May 20, 2024 14:14
Initially users weren't able to view any products unless shop_id param was
present. In this commit I've also optimised some of the existing queries as
well as removed string injection in sql queries.
In order to allow a user to create a new review, I made the following
changes:
- Add new route to create a review
- Add create review work so we can do this asynchronously
- Added a new form to create a review
- As part of the new review form, I added a new field to allow the user
  to select a product however in order to improve performance the dropdown
  only populates when a user types text into search field
@o0khoiclub0o
Copy link

Are you actually able to run the app without upgrading any dependencies (Ruby, gems, JS packages)? Or did you upgrade some of them but didn't commit?

@o0khoiclub0o
Copy link

Thanks for the demo videos, they are very helpful!

Comment on lines +4 to +6
.field
= form.label :shop_id, 'Shop'
= form.collection_select :shop_id, @shops, :id, :name, prompt: 'Select a Shop'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shop_id field is redundant.

Comment on lines +54 to +83
const searchInput = document.createElement('input');
searchInput.type = 'text';
searchInput.placeholder = 'Search for a product';
searchInput.id = 'product-search';
productSelect.parentNode.insertBefore(searchInput, productSelect);

let timeout = null;

searchInput.addEventListener('input', function () {
clearTimeout(timeout);
timeout = setTimeout(function () {
const query = searchInput.value.trim();
if (query.length > 4) {
fetch(`/products/search?q=${encodeURIComponent(query)}`)
.then(response => response.json())
.then(data => {
productSelect.innerHTML = '<option value="">Select a Product</option>';
data.items.forEach(product => {
const option = document.createElement('option');
option.value = product.id;
option.textContent = product.title;
productSelect.appendChild(option);
});
})
.catch(error => console.error('Error fetching products:', error));
} else {
productSelect.innerHTML = '<option value="">Select a Product</option>';
}
}, 300);
});
Copy link

@o0khoiclub0o o0khoiclub0o May 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm impressed!

products.each do |product|
product_reviews_page_data = params[:product_reviews_page_data] || {}
page = product_reviews_page_data[product.id.to_s]&.to_i || 1
reviews = product.reviews.order(created_at: :desc).limit(REVIEWS_PER_PAGE).offset((page.to_i - 1) * REVIEWS_PER_PAGE)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you sort product reviews by created_at DESC but sort products by created ASC?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought it made sense to show the most recently created review first but with the products I thought most recently added gets tagged on to the last page made sense. However thinking about it now they should probably both be DESC

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem here. I'm just curious about your thoughts.

products_query = products_query.where(shop_id: params[:shop_id]) if params[:shop_id].present?
products_query = products_query.order(created_at: :asc)

products = products_query.includes(:reviews).limit(params[:per_page]).offset(offset)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need .includes(:reviews)?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would there not be a N+1 without it on line 22?

reviews = product.reviews.order(created_at: :desc).limit(REVIEWS_PER_PAGE).offset((page.to_i - 1) * REVIEWS_PER_PAGE)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the difference in generated SQL queries between with and without includes(:reviews)?

@@ -41,4 +65,7 @@ def tags_with_default(params)
default_tags.concat(params[:tags].split(',')).uniq
end

def review_params
params.require(:review).permit(:rating, :body, :reviewer_name, :product_id, :shop_id, :tags)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

review_params[:tags] is not used anywhere

products.each do |product|
product_reviews_page_data = params[:product_reviews_page_data] || {}
page = product_reviews_page_data[product.id.to_s]&.to_i || 1
reviews = product.reviews.order(created_at: :desc).limit(REVIEWS_PER_PAGE).offset((page.to_i - 1) * REVIEWS_PER_PAGE)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: page.to_i is redundant

@JStant95
Copy link
Author

Are you actually able to run the app without upgrading any dependencies (Ruby, gems, JS packages)? Or did you upgrade some of them but didn't commit?

did end up having to upgrade a few gems and js packages but didn't end up committing them

})
.then(response => response.text())
.then(data => {
eval(data);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This AJAX way is not recommended but it works, so still fine to me.

@o0khoiclub0o
Copy link

Are you actually able to run the app without upgrading any dependencies (Ruby, gems, JS packages)? Or did you upgrade some of them but didn't commit?

did end up having to upgrade a few gems and js packages but didn't end up committing them

Thanks as I'm not distracted by a big PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants