Dynamic Assets - can it be done?

I'm looking for 3 areas to work as dynamic assets:

image_path, javascript_path, and stylesheet_path

When I say dynamic, I mean that they will be dynamic through controller/models. I have been working through approx. 12 hours of searches to satisfy my answer to this question but am not finding much luck.

The closest things I've found enabling this are use of config for assets (which is normally done for CDNs), or monkey-patching the tag helpers, which still won't give me what I want.

The reason why I want to do this is to provide for themes. As an example, if your rails app contained 20 different themes, a theme would be composed of layouts, images, javascripts, stylesheets, etc. You wouldn't want to have to change your theme through an initializer and stop/restart your app for it to work. I've already done a lot of leg work with getting my themes into the MVC structure.

I just need the ability to change the three paths above using either a custom call to a module, or through the application controller.

Can it be done and if so, how?

Many thanks.

Alpha Blue wrote:

I'm looking for 3 areas to work as dynamic assets:

image_path, javascript_path, and stylesheet_path

When I say dynamic, I mean that they will be dynamic through controller/models.

That only barely helps clarify. Defining something by repetition doesn't work. :slight_smile:

I have been working through approx. 12 hours of searches to satisfy my answer to this question but am not finding much luck.

The closest things I've found enabling this are use of config for assets (which is normally done for CDNs), or monkey-patching the tag helpers, which still won't give me what I want.

No, it probably won't unless you monkey-patch to look up the *current user's* theme each time -- which is doable but potentially costly.

The reason why I want to do this is to provide for themes. As an example, if your rails app contained 20 different themes, a theme would be composed of layouts, images, javascripts, stylesheets, etc. You wouldn't want to have to change your theme through an initializer and stop/restart your app for it to work. I've already done a lot of leg work with getting my themes into the MVC structure.

Is there a reason that you didn't use something like Liquid, which seems to have been designed for exactly this purpose?

I just need the ability to change the three paths above using either a custom call to a module, or through the application controller.

Can it be done and if so, how?

Of course it can be done by monkeypatching as described above. Whether it should be done that way is another question. :slight_smile:

Another possibility: I recently found out that Haml can use Sass as a markup filter. This has some interesting potential for dynamically generated CSS.

Best,

Sounds like a fun and interesting project. I would be interested in finding out your

final solution.

You might consider adding to the config object used in intitializer ala the way

action mailer and actioncontroller use it for UrlWriter. and then having a mixin that

renames the above methods and prepends a different directory for the images, javascript

and style sheets. making some common theme convention that will be assumed under

the themes directory will make the relative addressing of the image path etc more sane.

thats one thought. good luck. good tests will make a difference as you try multiple solutions

and will pay for themselves.

Curtis Schofield wrote:

Thanks guys - I appreciate both of your responses.

Marnen, I did look at liquid previously and did so again this morning. However, while I like what the developers are trying to do with certain features, it's not for me.

And, the nice thing about my system is that there will always be a default theme in place in a set location, for instance:

public/themes/default

That will be the base default theme.

What I have to really consider are quite a few things. First and foremost are asset tags. If the asset tags don't include the current theme, then caching is not going to work properly. There is an interesting bit in the helper though:

ASSETS_DIR = defined?(Rails.public_path) ? Rails.public_path : "public" JAVASCRIPTS_DIR = "#{ASSETS_DIR}/javascripts" STYLESHEETS_DIR = "#{ASSETS_DIR}/stylesheets" JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'].freeze unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES)

So, my first decision is how to dynamically modify the assets_dir. I know that assets can be modified through URLs (CDNs), but again, I'm just now touching into looking through quite a bit of code to make sure I understand what is going on step by step. Then, I'll create some test code to play with.

Alpha Blue wrote:

Thanks guys - I appreciate both of your responses.

Marnen, I did look at liquid previously and did so again this morning. However, while I like what the developers are trying to do with certain features, it's not for me.

[...]

Why not? I've never used it, but it is a widely used solution to the exact problem you're trying to solve.

Best,

Marnen Laibow-Koser wrote:

Alpha Blue wrote:

Thanks guys - I appreciate both of your responses.

Marnen, I did look at liquid previously and did so again this morning. However, while I like what the developers are trying to do with certain features, it's not for me.

[...]

Why not? I've never used it, but it is a widely used solution to the exact problem you're trying to solve.

Best, -- Marnen Laibow-Koser http://www.marnen.org marnen@marnen.org

A few reasons. First, I don't like the markup style they are using when interacting with templates. They also go far above and beyond limiting what I'm eventually going to do and that is implementing hooks within templates. In addition, I really do not try to use other people's plugins (unless it's something very minor). This is mainly because of how I program, personally. I like to be able to adjust/alter and understand exactly what is being developed and make alterations as need be. Going with a plugin on this scale hampers my development as their ideas and mine might differ and I'd end up monkey patching a plugin. That's not for me.

I want to develop my own work. I can certainly learn from others and their work, but that's something entirely different.

Thanks guys - I appreciate both of your responses.

Marnen, I did look at liquid previously and did so again this morning. However, while I like what the developers are trying to do with certain features, it's not for me.

As Marnen said you may want to reconsider - mephisto for example uses liquid for themes and so on and you can change those on the fly

So, my first decision is how to dynamically modify the assets_dir. I know that assets can be modified through URLs (CDNs), but again, I'm just now touching into looking through quite a bit of code to make sure I understand what is going on step by step. Then, I'll create some test code to play with.

I wonder whether you're overthinking things slightly. If you had a helper called current_theme_path that returned the path to the current theme (eg themes/default/ initially, themes/minimalist/ etc...

then you should be able to stuff like style_sheet_tag (current_theme_path + 'blah.css') or image_tag(current_theme_path + 'heading.png')

Fred

Fred

I wonder whether you’re overthinking things slightly. If you had a

helper called current_theme_path that returned the path to the current

theme (eg themes/default/ initially, themes/minimalist/ etc…

then you should be able to stuff like style_sheet_tag

(current_theme_path + ‘blah.css’) or image_tag(current_theme_path +

‘heading.png’)

I think this is very pragmatic.

Frederick Cheung wrote:

Fred, so the issue with the code above is this:

When using such a helper it will display:

link href="/stylesheets/public/themes/default/stylesheets/default.css" media="screen" rel="stylesheet" type="text/css"

.. which won't work.

The issue here is that /stylesheets is being used as the default stylesheets_path through rails. In order to utilize such a helper, I have to first change the stylesheet_path to go to ''.

The same would have to be done for image_path, javascripts_path, etc.

That seems simple enough - you may be able to monkey patch those methods

in the controller level to do ‘./themes/current_user.theme/’

Curtis Schofield wrote:

  That seems simple enough - you may be able to monkey patch those methods in the controller level to do './themes/current_user.theme/'

What bothers me is that you can't simply decide a default path to your assets through a config.

I should be able to set:

config.asset_tag_helper.image_path = '' config.asset_tag_helper.javascripts_path = '' config.asset_tag_helper.stylesheets_path = ''

However, looking through all of the code I just don't see a way to tie into changing those default paths unless I monkey patch. I was hoping there would be another way which is why I opened the topic.

I am able to get it to work by doing the following:

Monkey Patching :: asset_tag_helper.rb

def javascript_path(source)   compute_public_path(source, 'themes', 'js') end

def stylesheet_path(source)   compute_public_path(source, 'themes', 'css') end

def image_path(source)   compute_public_path(source, 'themes') end

...

And adding a helper: (this will of course change)

def current_theme_path(type)   path = @opt_default_theme + "/images/" if type == 'img'   path = @opt_default_theme + "/stylesheets/" if type == 'css'   path = @opt_default_theme + "/javascripts/" if type == 'js'   path = @opt_default_theme + "/swfs/" if type == 'swf'   return path end

...

And finally in my layout:

<%= stylesheet_link_tag(current_theme_path('css') + @opt_theme + '.css') %> <%= javascript_include_tag(current_theme_path('js') + @opt_theme + '.js') %>

Which makes all stylesheets and javascripts dynamically loaded based on options set in the site on the fly.

However, it still doesn't solve the problem of assets..

Fred, so the issue with the code above is this:

When using such a helper it will display:

link href="/stylesheets/public/themes/default/stylesheets/default.css" media="screen" rel="stylesheet" type="text/css"

That sounds like the path you passed didn't have a leading / (and you don't want the public in there).

Fred

Frederick Cheung wrote:

Fred, so the issue with the code above is this:

When using such a helper it will display:

link href="/stylesheets/public/themes/default/stylesheets/default.css" media="screen" rel="stylesheet" type="text/css"

That sounds like the path you passed didn't have a leading / (and you don't want the public in there).

Fred

Fred, as always, you were correct. I removed the monkey patching code and then I fixed the test helper to:

def current_theme_path(type)   path = "/themes/" + @opt_default_theme + "/images/" if type == 'img'   path = "/themes/" + @opt_default_theme + "/stylesheets/" if type == 'css'   path = "/themes/" + @opt_default_theme + "/javascripts/" if type == 'js'   path = "/themes/" + @opt_default_theme + "/swfs/" if type == 'swf'   return path end

...

And finally in my layout:

<%= stylesheet_link_tag(current_theme_path('css')+ @opt_theme +'.css') %> <%= javascript_include_tag(current_theme_path('js')+ @opt_theme +'.js') %>

.. and it works.

I'll work with this bit of code instead and see how it affects asset caching.

I don't understand why you can't use a stylesheet like:

application.css

that handles the general case or "default theme", as you refer to it, and then simply serve up a second stylesheet after that based on preference, as:

<%= stylesheet_link_tag :application %> <%= stylesheet_link_tag @user_theme_stylesheet if @user_theme_stylesheet %>

You can then simply implement a before filter in your controller that sets @user_theme_stylesheet based on user preference and relies on the CSS proximity rule disambiguation to decide what CSS rule to apply.

Have I misunderstood your problem?

Steve Ross wrote:

I don't understand why you can't use a stylesheet like:

application.css

that handles the general case or "default theme", as you refer to it, and then simply serve up a second stylesheet after that based on preference, as:

<%= stylesheet_link_tag :application %> <%= stylesheet_link_tag @user_theme_stylesheet if @user_theme_stylesheet %>

You can then simply implement a before filter in your controller that sets @user_theme_stylesheet based on user preference and relies on the CSS proximity rule disambiguation to decide what CSS rule to apply.

Have I misunderstood your problem?

Yes, it's not that simple. My problem isn't with CSS directly. My problem lays simply in delivering up the paths to the css files, and the js files, and the swf files, and the image files, and the layout files - depending on what theme is set as the default theme for the site.

I no longer have these issues thanks to Fred.

I already have a layouts system implemented where based on any given theme, a user can decide on exactly what layout to use for every single page or grouped by controller type. It's very detailed but it works great.

The only issues I had that were remaining were handling of images, css, js, and swfs, relative to the path of the theme and still have the ability for rails to manage these assets.

I've tested out the new code and it works great. The assets and timestamps are working properly for root path and for the theme path.

So, I'm able to do the following:

+1 for LIQUID and/or Haml and Sass. Interestingly enough there are some changes in the Sass 2.4 source code that might be exactly what you're looking for:

http://nex-3.com/posts/89-powerful-color-manipulation-with-sass

Obviously it's edge at this point, but you can use the haml master branch to check out some of the new features mentioned in the article:

http://github.com/nex3/haml

Chris Eppstein has some nice examples of the Compass Color module here:

http://chriseppstein.github.com/compass-colors/

Hope this helps a little bit.

So, here is the output code and what's displayed. As you can see, I simplified the helper calls and am able to call multiple stylesheets and javascripts and am able to feed image tags with options.

http://gist.github.com/278391

application.html.erb gives you an idea of what's being placed in the head and in the body tags as an example.

application_helper.rb contains the helper methods being called.

display.html shows you what is output in a browser and as you can see the assets are being managed properly.